From 3a30fa7e557cf14422f22e0c7963d4d6b7c76804 Mon Sep 17 00:00:00 2001 From: "Curt J. Sampson" Date: Mon, 25 Mar 2019 22:36:24 +0900 Subject: [PATCH] If local branch has a tracking branch, fast-forward to tracking branch head This is almost invariably the desired behaviour and makes this a lot more convenient to use when others are also committing to the release branch. We fail if we can't fast-forward; we could consider making this a default option rather than the only behaviour. This has pushed our hacked-together testing framework about as far as it can conveniently go (or probably beyond). Any further significant work on this probably wants a test framework rewrite, say, in Python. And boy oh boy did we learn about `git update-ref`. (There's a comment in the code.) Reviewed-by: Nishant Rodrigues --- README.md | 26 ++++++++++-- bin/git-commit-filetree | 30 +++++++++++++- t/40-commit.t | 89 ++++++++++++++++++++++++++++++++++++++--- 3 files changed, 135 insertions(+), 10 deletions(-) diff --git a/README.md b/README.md index 6c0ed77..35dd221 100644 --- a/README.md +++ b/README.md @@ -41,9 +41,6 @@ Missing Features The following features are still missing from this version of the program. -* Deal with cases when the local branch to which we're committing is - behind its remote tracking branch. Normally we'd want the local branch - to be fast-forwarded to match the remote before doing our commit. * Update the reflog after doing the commit. * Add the ability to specify a commit message. (This should allow a token to substitute the current HEAD commit at @@ -66,6 +63,24 @@ The second method will allow you to use `git` options before the `commit-filetree` command, such as `-C`, `--git-dir`, `--work-tree`, and so on. +#### Fast-forwarding to Tracking Branch Head + +If the release branch to which you're committing has a tracking branch, +the local branch will first be fast-forwarded to the head of that +tracking branch before the commit is made. This is almost invariably +the desired behaviour: if someone else has released new versions on +the release branch you want to continue that history rather than +diverging. (I.e., you want the `fetch`/`git-commit-filetree`/`push` +sequence to Just Work without having to do any other manipulation of +the release branch.) + +If your release branch has diverged from its tracking branch, +`git-commit-filetree` will currently print an error and refuse to +commit, and you will need to manually resolve the divergence. If you +wish to be able to commit on a divergent local branch, you can modify +the script not to generate this error. If there's sufficient demand, a +command-line option could be added to toggle this behaviour. + ### Usage with Windows The standard Git installation for Windows includes the Bash shell, and @@ -86,6 +101,11 @@ systems. However, you should be able to use other TAP test harnesses instead. If you have any difficulty, please feel free to contact the authors for help. +As a side note, this hacked-together testing framework has probably +been taken about as far as it reasonably can. If much more functionality +needs to be added (to the program or the test framework), the framework +probably wants a rewrite in a better language like Python. + Authors and History ------------------- diff --git a/bin/git-commit-filetree b/bin/git-commit-filetree index 9812836..030dcce 100755 --- a/bin/git-commit-filetree +++ b/bin/git-commit-filetree @@ -3,7 +3,7 @@ # git-commit-filetree - commit an arbitrary tree of files # to a branch in a git repository. # -# Version: cjsnjr.2019-03-23.0 +# Version: cjsnjr.2019-03-28.0 # # Please find the latest version of this (with tests and copying rights) at: # https://github.com/cynic-net/git-commit-filetree @@ -29,6 +29,28 @@ err() { exit $exitcode } +fast_forward_to_tracking() { + declare -g branch ref tracking + # If local ahead of tracking; carry on. + git merge-base --is-ancestor "$tracking" "$ref" && return 0 + # If local can fast-foward to tracking; do it. + git merge-base --is-ancestor "$ref" "$tracking" && { + # Remember, update-ref must be given a full ref name; just `mybr` + # will create or update that name at the top of the "refs tree," + # not `refs/heads/mybr`. To help catch this kind of error, we use + # the third argument (required current branch value) where short + # names such as `mybr` are expanded to `refs/heads/mybr`. + git update-ref "$ref" "$tracking" "$branch" \ + -m 'commit-filetree: fast-forward' + return 0 + } + # Local diverged. + err 3 "Branch $branch has diverged from tracking $tracking." \ + $'\n''You must fix this manually before using git-commit-filetree.' + # We should probably extend this explanation with suggestions + # on how to fix the problem. +} + branch="$1"; shift || true path="$1"; shift || true [ -z "$path" -o -n "$1" ] \ @@ -43,6 +65,12 @@ git diff-index --quiet HEAD \ ref=refs/heads/$(echo $branch | sed -e 's,^refs/heads/,,') git show-ref -q --verify $ref || err 128 "Invalid ref: $ref" +# Find and fast-forward the tracking ref, if we have one +tracking=$(\ + git rev-parse --symbolic-full-name "$branch@{upstream}" 2>/dev/null \ + || true) +[[ $tracking ]] && fast_forward_to_tracking + source_sha=$(git show --quiet --pretty='format:%h') # Switch to an index separate from repo working copy. diff --git a/t/40-commit.t b/t/40-commit.t index 4f4547c..c1d184e 100644 --- a/t/40-commit.t +++ b/t/40-commit.t @@ -2,7 +2,7 @@ . t/test-lib.sh set -e -o 'pipefail' -echo "1..9" +echo "1..11" branch=testbr repo=tmp/test/repo @@ -83,12 +83,12 @@ start_test 'Commit data correct' make_test_repo $git branch $branch assert_branch '737b0f439051 refs/heads/master' master -assert_branch '737b0f439051 refs/heads/testbr' +assert_branch "737b0f439051 refs/heads/$branch" echo bar > $files/one echo bar > $files/subdir/two $git commit-filetree $branch $files -assert_branch '003e5987f385 refs/heads/testbr' +assert_branch "003e5987f385 refs/heads/$branch" end_test @@ -102,7 +102,7 @@ $git branch $branch echo bar > $files/one echo bar > $files/subdir/two $git commit-filetree refs/heads/$branch $files -assert_branch '003e5987f385 refs/heads/testbr' +assert_branch "003e5987f385 refs/heads/$branch" end_test @@ -116,7 +116,7 @@ $git branch $branch echo bar > $files/one echo bar > $files/subdir/two (cd $repo && ../../../bin/git-commit-filetree $branch ../files) -assert_branch '003e5987f385 refs/heads/testbr' +assert_branch "003e5987f385 refs/heads/$branch" end_test @@ -130,7 +130,7 @@ echo bar > $files/subdir/two $git commit-filetree $branch $files $git commit-filetree $branch $files $git commit-filetree $branch $files -assert_branch '003e5987f385 refs/heads/testbr' +assert_branch "003e5987f385 refs/heads/$branch" end_test ##### 9 @@ -146,3 +146,80 @@ test_equal \ 737b0f4 testbr@{1}: branch: Created from master' \ "$($git reflog --no-decorate $branch)" end_test + +##### fast-foward test support + +make_test_repo_with_two_branches() { + make_test_repo + + # Test branch to which we commit + $git branch $branch + assert_branch "737b0f439051 refs/heads/$branch" + touch $files/one; $git commit-filetree $branch $files + assert_branch "8a4cf12bef5f refs/heads/$branch" + + # Test tracking branch with an additional commit + $git branch $branch-tracking $branch + assert_branch "8a4cf12bef5f refs/heads/$branch-tracking" $branch-tracking + touch $files/two; $git commit-filetree $branch-tracking $files + assert_branch "fcb13b95f172 refs/heads/$branch-tracking" $branch-tracking +} + +# If you want to view the commit graph in a test, add the following. +view_commit_graph() { + $git log --all --graph --pretty=oneline --abbrev=12 +} + +##### 10 + +start_test 'Fast-forward commit branch' +make_test_repo_with_two_branches + +# Make test branch track test-tracking branch, but it's one commit behind. +$git >/dev/null branch --set-upstream-to=$branch-tracking $branch +assert_branch "8a4cf12bef5f refs/heads/$branch" + +touch $files/three +test_equal 0 "$($git commit-filetree 2>&1 $branch $files; echo $?)" +# Parent commit is head of tracking branch. +expected_log=" +ef65bb4d9108 978307200 +fcb13b95f172 978307200 +8a4cf12bef5f 978307200" +test_equal "$expected_log" \ + "$(echo; $git log -3 --abbrev=12 --pretty='format:%h %ct' $branch)" + +# We can add more commits to commit branch when already ahead of tracking +touch $files/four +test_equal 0 "$($git commit-filetree 2>&1 $branch $files; echo $?)" +expected_log=" +191c80c3688d 978307200 +ef65bb4d9108 978307200 +fcb13b95f172 978307200 +8a4cf12bef5f 978307200" +test_equal "$expected_log" \ + "$(echo; $git log -4 --abbrev=12 --pretty='format:%h %ct' $branch)" + +end_test + +##### 11 + +start_test 'Cannot fast-forward commit branch' +make_test_repo_with_two_branches + +# Add another commit to local branch that's not on tracking branch. +touch $files/commit-from-elsewhere +$git commit-filetree $branch $files +assert_branch "58cce3125df7 refs/heads/$branch" + +# Make test branch track test-tracking branch, but 1/1 ahead/behind +$git >/dev/null branch --set-upstream-to=$branch-tracking $branch + +touch $files/three +exitcode="$($git commit-filetree $branch $files >/dev/null 2>&1; echo $?)" +test_equal 3 "$exitcode" +message=$($git commit-filetree 2>&1 $branch $files || true) +expected='Branch testbr has diverged from tracking refs/heads/testbr-tracking' +[[ $message =~ $expected ]] || fail_test "bad message: $message" + +end_test