Skip to content

Commit

Permalink
If local branch has a tracking branch, fast-forward to tracking branc…
Browse files Browse the repository at this point in the history
…h 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 <nishantjr@gmail.com>
  • Loading branch information
0cjs committed Mar 30, 2019
1 parent 4c6ba93 commit 3a30fa7
Show file tree
Hide file tree
Showing 3 changed files with 135 additions and 10 deletions.
26 changes: 23 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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
Expand All @@ -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
-------------------
Expand Down
30 changes: 29 additions & 1 deletion bin/git-commit-filetree
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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" ] \
Expand All @@ -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.
Expand Down
89 changes: 83 additions & 6 deletions t/40-commit.t
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
. t/test-lib.sh
set -e -o 'pipefail'

echo "1..9"
echo "1..11"

branch=testbr
repo=tmp/test/repo
Expand Down Expand Up @@ -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

Expand All @@ -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

Expand All @@ -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

Expand All @@ -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
Expand All @@ -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

0 comments on commit 3a30fa7

Please sign in to comment.