Web lists-archives.com

[PATCH/RFC] git-post: the opposite of git-cherry-pick




Add a new command that can be used to copy an arbitrary commit
to a branch that is not checked out.

Signed-off-by: Johannes Sixt <j6t@xxxxxxxx>
---
 I have been using this command since years, but got around to
 write a man page and tests only now. I hope the man page makes
 sense. I don't have the tool chain to build it, though, so
 any hints for improvement are welcome.

 .gitignore                        |  1 +
 Documentation/git-cherry-pick.txt |  3 +-
 Documentation/git-post.txt        | 57 +++++++++++++++++++++++++++++
 Makefile                          |  1 +
 command-list.txt                  |  1 +
 git-post.sh                       | 60 ++++++++++++++++++++++++++++++
 t/t3514-post.sh                   | 77 +++++++++++++++++++++++++++++++++++++++
 7 files changed, 199 insertions(+), 1 deletion(-)
 create mode 100644 Documentation/git-post.txt
 create mode 100755 git-post.sh
 create mode 100755 t/t3514-post.sh

diff --git a/.gitignore b/.gitignore
index 833ef3b0b7..a16263249a 100644
--- a/.gitignore
+++ b/.gitignore
@@ -106,6 +106,7 @@
 /git-pack-refs
 /git-parse-remote
 /git-patch-id
+/git-post
 /git-prune
 /git-prune-packed
 /git-pull
diff --git a/Documentation/git-cherry-pick.txt b/Documentation/git-cherry-pick.txt
index d35d771fc8..6b34f4d994 100644
--- a/Documentation/git-cherry-pick.txt
+++ b/Documentation/git-cherry-pick.txt
@@ -226,7 +226,8 @@ context lines.
 
 SEE ALSO
 --------
-linkgit:git-revert[1]
+linkgit:git-revert[1],
+linkgit:git-post[1]
 
 GIT
 ---
diff --git a/Documentation/git-post.txt b/Documentation/git-post.txt
new file mode 100644
index 0000000000..e835e62be3
--- /dev/null
+++ b/Documentation/git-post.txt
@@ -0,0 +1,57 @@
+git-post(1)
+===========
+
+NAME
+----
+git-post - Apply a commit on top of a branch that is not checked out
+
+SYNOPSIS
+--------
+[verse]
+'git post' dest-branch [source-rev]
+
+DESCRIPTION
+-----------
+
+Applies the changes made by 'source-rev' (or, if not given, `HEAD`)
+on top of the branch 'dest-branch' and records a new commit.
+'dest-branch' is advanced to point to the new commit.
+The operation that this command performs can be regarded as
+the opposite of cherry-picking.
+
+EXAMPLES
+--------
+
+Assume, while working on a topic, you find and fix an unrelated bug.
+Now:
+
+------------
+$ git commit                                   <1>
+$ git post master                              <2>
+$ git show | git apply -R && git reset HEAD^   <3>
+------------
+
+<1> create a commit with the fix on the current branch
+<2> copy the fix onto the branch where it ought to be
+<3> revert current topic branch to the unfixed state;
+can also be done with `git reset --keep HEAD^` if there are no
+unstaged changes in files that are modified by the fix
+
+Oftentimes, switching branches triggers a rebuild of a code base.
+With the sequence above the branch switch can be avoided.
+That said, it is good practice to test the bug fix on the
+destination branch eventually.
+
+BUGS
+----
+
+The change can be applied on `dest-branch` only if there is
+no textual conflict.
+
+SEE ALSO
+--------
+linkgit:git-cherry-pick[1].
+
+GIT
+---
+Part of the linkgit:git[1] suite
diff --git a/Makefile b/Makefile
index b143e4eea3..1753bf176f 100644
--- a/Makefile
+++ b/Makefile
@@ -551,6 +551,7 @@ SCRIPT_SH += git-merge-octopus.sh
 SCRIPT_SH += git-merge-one-file.sh
 SCRIPT_SH += git-merge-resolve.sh
 SCRIPT_SH += git-mergetool.sh
+SCRIPT_SH += git-post.sh
 SCRIPT_SH += git-quiltimport.sh
 SCRIPT_SH += git-rebase.sh
 SCRIPT_SH += git-remote-testgit.sh
diff --git a/command-list.txt b/command-list.txt
index a1fad28fd8..bc95b424d0 100644
--- a/command-list.txt
+++ b/command-list.txt
@@ -98,6 +98,7 @@ git-pack-redundant                      plumbinginterrogators
 git-pack-refs                           ancillarymanipulators
 git-parse-remote                        synchelpers
 git-patch-id                            purehelpers
+git-post                                mainporcelain
 git-prune                               ancillarymanipulators
 git-prune-packed                        plumbingmanipulators
 git-pull                                mainporcelain           remote
diff --git a/git-post.sh b/git-post.sh
new file mode 100755
index 0000000000..6627d69f73
--- /dev/null
+++ b/git-post.sh
@@ -0,0 +1,60 @@
+#!/bin/sh
+# Copyright (c) 2017 Johannes Sixt
+
+SUBDIRECTORY_OK=Yes
+OPTIONS_SPEC="\
+git post dest-branch [source-rev]
+--
+"
+. git-sh-setup
+
+while test $# != 0
+do
+	case "$1" in
+	--)	shift; break;;
+	-*)	usage;;
+	*)	break;;
+	esac
+	shift
+done
+
+dest=$(git rev-parse --verify --symbolic-full-name "$1") || exit
+if test -z "$dest"
+then
+	die "$(gettext "Destination must be a branch tip")"
+fi
+
+shift
+case $# in
+0)	set -- HEAD;;
+1)	: good;;
+*)	usage;;
+esac
+
+# apply change to a temporary index
+tmpidx=$GIT_DIR/index-post-$$
+git read-tree --index-output="$tmpidx" "$dest" || exit
+GIT_INDEX_FILE=$tmpidx
+export GIT_INDEX_FILE
+trap 'rm -f "$tmpidx"' 0 1 2 15
+
+git diff-tree -p --binary -M -C "$1" | git apply --cached || exit
+
+newtree=$(git write-tree) &&
+newrev=$(
+	eval "$(get_author_ident_from_commit "$1")" &&
+	export GIT_AUTHOR_NAME GIT_AUTHOR_EMAIL GIT_AUTHOR_DATE
+	git-cat-file commit "$1" | sed -e '1,/^$/d' |
+	git commit-tree $newtree -p "$dest"
+) || exit
+
+if git check-ref-format "$dest"
+then
+	set_reflog_action post
+	subject=$(git log --no-walk --pretty=%s "$newrev") &&
+	git update-ref -m "$GIT_REFLOG_ACTION: $subject" "$dest" "$newrev" || exit
+fi
+if test -z "$GIT_QUIET"
+then
+	git rev-list -1 --oneline "$newrev"
+fi
diff --git a/t/t3514-post.sh b/t/t3514-post.sh
new file mode 100755
index 0000000000..4d31515c52
--- /dev/null
+++ b/t/t3514-post.sh
@@ -0,0 +1,77 @@
+#!/bin/sh
+
+test_description='test git post
+
+We build this history:
+
+   A--B--C  <-- master, HEAD
+  /
+ O          <-- side-base, side
+
+Then we post B and C on top of branch "side":
+
+   A--B--C  <-- master, HEAD
+  /
+ O          <-- side-base
+  \
+   B*--C*   <-- side
+
+B has a different author, which must be copied to B*.
+'
+
+. ./test-lib.sh
+
+test_expect_success setup '
+
+	printf "a%s\n" 1 2 3 4 >file-a &&
+	printf "b%s\n" 5 6 7 8 >file-b &&
+
+	test_tick &&
+	git add file-a file-b &&
+	git commit -m initial &&
+	git tag side-base &&
+
+	test_tick &&
+	echo "Advance master" >>file-a &&
+	git commit -a -m advance-master &&
+
+	test_tick &&
+	echo "Unrelated fix" >>file-b &&
+	GIT_AUTHOR_NAME="S O Else" git commit -a -m fix-for-b &&
+
+	test_tick &&
+	echo "Another fix" >>file-b &&
+	git commit -a -m another-fix-for-b
+'
+
+test_expect_success 'post two commits on top of side' '
+
+	git branch -f side side-base &&
+	test_tick &&
+	git post side HEAD^ &&
+	test_tick &&
+	git post side &&
+
+	git log --pretty="%at %an %ae %s" HEAD~2.. >expect &&
+	git log --pretty="%at %an %ae %s" side-base..side >actual &&
+
+	test_cmp expect actual &&
+	git cat-file blob side:file-b >actual &&
+	test_cmp file-b actual &&
+
+	git diff --exit-code side-base side -- file-a	# no change
+'
+
+test_expect_success 'post requiring merge resolution fails' '
+
+	git branch -f side side-base &&
+	test_must_fail git post side HEAD
+'
+
+test_expect_success 'cannot post onto arbitrary commit name' '
+
+	git branch -f side side-base &&
+	test_must_fail git post side^0 HEAD^
+'
+
+test_done
-- 
2.14.2.808.g3bc32f2729