Skip to content

Commit 3a30fa7

Browse files
committed
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 <[email protected]>
1 parent 4c6ba93 commit 3a30fa7

File tree

3 files changed

+135
-10
lines changed

3 files changed

+135
-10
lines changed

README.md

+23-3
Original file line numberDiff line numberDiff line change
@@ -41,9 +41,6 @@ Missing Features
4141
The following features are still missing from this version of the
4242
program.
4343

44-
* Deal with cases when the local branch to which we're committing is
45-
behind its remote tracking branch. Normally we'd want the local branch
46-
to be fast-forwarded to match the remote before doing our commit.
4744
* Update the reflog after doing the commit.
4845
* Add the ability to specify a commit message.
4946
(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
6663
`commit-filetree` command, such as `-C`, `--git-dir`, `--work-tree`,
6764
and so on.
6865

66+
#### Fast-forwarding to Tracking Branch Head
67+
68+
If the release branch to which you're committing has a tracking branch,
69+
the local branch will first be fast-forwarded to the head of that
70+
tracking branch before the commit is made. This is almost invariably
71+
the desired behaviour: if someone else has released new versions on
72+
the release branch you want to continue that history rather than
73+
diverging. (I.e., you want the `fetch`/`git-commit-filetree`/`push`
74+
sequence to Just Work without having to do any other manipulation of
75+
the release branch.)
76+
77+
If your release branch has diverged from its tracking branch,
78+
`git-commit-filetree` will currently print an error and refuse to
79+
commit, and you will need to manually resolve the divergence. If you
80+
wish to be able to commit on a divergent local branch, you can modify
81+
the script not to generate this error. If there's sufficient demand, a
82+
command-line option could be added to toggle this behaviour.
83+
6984
### Usage with Windows
7085

7186
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
86101
instead. If you have any difficulty, please feel free to contact the
87102
authors for help.
88103

104+
As a side note, this hacked-together testing framework has probably
105+
been taken about as far as it reasonably can. If much more functionality
106+
needs to be added (to the program or the test framework), the framework
107+
probably wants a rewrite in a better language like Python.
108+
89109

90110
Authors and History
91111
-------------------

bin/git-commit-filetree

+29-1
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
# git-commit-filetree - commit an arbitrary tree of files
44
# to a branch in a git repository.
55
#
6-
# Version: cjsnjr.2019-03-23.0
6+
# Version: cjsnjr.2019-03-28.0
77
#
88
# Please find the latest version of this (with tests and copying rights) at:
99
# https://github.com/cynic-net/git-commit-filetree
@@ -29,6 +29,28 @@ err() {
2929
exit $exitcode
3030
}
3131

32+
fast_forward_to_tracking() {
33+
declare -g branch ref tracking
34+
# If local ahead of tracking; carry on.
35+
git merge-base --is-ancestor "$tracking" "$ref" && return 0
36+
# If local can fast-foward to tracking; do it.
37+
git merge-base --is-ancestor "$ref" "$tracking" && {
38+
# Remember, update-ref must be given a full ref name; just `mybr`
39+
# will create or update that name at the top of the "refs tree,"
40+
# not `refs/heads/mybr`. To help catch this kind of error, we use
41+
# the third argument (required current branch value) where short
42+
# names such as `mybr` are expanded to `refs/heads/mybr`.
43+
git update-ref "$ref" "$tracking" "$branch" \
44+
-m 'commit-filetree: fast-forward'
45+
return 0
46+
}
47+
# Local diverged.
48+
err 3 "Branch $branch has diverged from tracking $tracking." \
49+
$'\n''You must fix this manually before using git-commit-filetree.'
50+
# We should probably extend this explanation with suggestions
51+
# on how to fix the problem.
52+
}
53+
3254
branch="$1"; shift || true
3355
path="$1"; shift || true
3456
[ -z "$path" -o -n "$1" ] \
@@ -43,6 +65,12 @@ git diff-index --quiet HEAD \
4365
ref=refs/heads/$(echo $branch | sed -e 's,^refs/heads/,,')
4466
git show-ref -q --verify $ref || err 128 "Invalid ref: $ref"
4567

68+
# Find and fast-forward the tracking ref, if we have one
69+
tracking=$(\
70+
git rev-parse --symbolic-full-name "$branch@{upstream}" 2>/dev/null \
71+
|| true)
72+
[[ $tracking ]] && fast_forward_to_tracking
73+
4674
source_sha=$(git show --quiet --pretty='format:%h')
4775

4876
# Switch to an index separate from repo working copy.

t/40-commit.t

+83-6
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
. t/test-lib.sh
33
set -e -o 'pipefail'
44

5-
echo "1..9"
5+
echo "1..11"
66

77
branch=testbr
88
repo=tmp/test/repo
@@ -83,12 +83,12 @@ start_test 'Commit data correct'
8383
make_test_repo
8484
$git branch $branch
8585
assert_branch '737b0f439051 refs/heads/master' master
86-
assert_branch '737b0f439051 refs/heads/testbr'
86+
assert_branch "737b0f439051 refs/heads/$branch"
8787

8888
echo bar > $files/one
8989
echo bar > $files/subdir/two
9090
$git commit-filetree $branch $files
91-
assert_branch '003e5987f385 refs/heads/testbr'
91+
assert_branch "003e5987f385 refs/heads/$branch"
9292

9393
end_test
9494

@@ -102,7 +102,7 @@ $git branch $branch
102102
echo bar > $files/one
103103
echo bar > $files/subdir/two
104104
$git commit-filetree refs/heads/$branch $files
105-
assert_branch '003e5987f385 refs/heads/testbr'
105+
assert_branch "003e5987f385 refs/heads/$branch"
106106

107107
end_test
108108

@@ -116,7 +116,7 @@ $git branch $branch
116116
echo bar > $files/one
117117
echo bar > $files/subdir/two
118118
(cd $repo && ../../../bin/git-commit-filetree $branch ../files)
119-
assert_branch '003e5987f385 refs/heads/testbr'
119+
assert_branch "003e5987f385 refs/heads/$branch"
120120

121121
end_test
122122

@@ -130,7 +130,7 @@ echo bar > $files/subdir/two
130130
$git commit-filetree $branch $files
131131
$git commit-filetree $branch $files
132132
$git commit-filetree $branch $files
133-
assert_branch '003e5987f385 refs/heads/testbr'
133+
assert_branch "003e5987f385 refs/heads/$branch"
134134
end_test
135135

136136
##### 9
@@ -146,3 +146,80 @@ test_equal \
146146
737b0f4 testbr@{1}: branch: Created from master' \
147147
"$($git reflog --no-decorate $branch)"
148148
end_test
149+
150+
##### fast-foward test support
151+
152+
make_test_repo_with_two_branches() {
153+
make_test_repo
154+
155+
# Test branch to which we commit
156+
$git branch $branch
157+
assert_branch "737b0f439051 refs/heads/$branch"
158+
touch $files/one; $git commit-filetree $branch $files
159+
assert_branch "8a4cf12bef5f refs/heads/$branch"
160+
161+
# Test tracking branch with an additional commit
162+
$git branch $branch-tracking $branch
163+
assert_branch "8a4cf12bef5f refs/heads/$branch-tracking" $branch-tracking
164+
touch $files/two; $git commit-filetree $branch-tracking $files
165+
assert_branch "fcb13b95f172 refs/heads/$branch-tracking" $branch-tracking
166+
}
167+
168+
# If you want to view the commit graph in a test, add the following.
169+
view_commit_graph() {
170+
$git log --all --graph --pretty=oneline --abbrev=12
171+
}
172+
173+
##### 10
174+
175+
start_test 'Fast-forward commit branch'
176+
make_test_repo_with_two_branches
177+
178+
# Make test branch track test-tracking branch, but it's one commit behind.
179+
$git >/dev/null branch --set-upstream-to=$branch-tracking $branch
180+
assert_branch "8a4cf12bef5f refs/heads/$branch"
181+
182+
touch $files/three
183+
test_equal 0 "$($git commit-filetree 2>&1 $branch $files; echo $?)"
184+
# Parent commit is head of tracking branch.
185+
expected_log="
186+
ef65bb4d9108 978307200
187+
fcb13b95f172 978307200
188+
8a4cf12bef5f 978307200"
189+
test_equal "$expected_log" \
190+
"$(echo; $git log -3 --abbrev=12 --pretty='format:%h %ct' $branch)"
191+
192+
# We can add more commits to commit branch when already ahead of tracking
193+
touch $files/four
194+
test_equal 0 "$($git commit-filetree 2>&1 $branch $files; echo $?)"
195+
expected_log="
196+
191c80c3688d 978307200
197+
ef65bb4d9108 978307200
198+
fcb13b95f172 978307200
199+
8a4cf12bef5f 978307200"
200+
test_equal "$expected_log" \
201+
"$(echo; $git log -4 --abbrev=12 --pretty='format:%h %ct' $branch)"
202+
203+
end_test
204+
205+
##### 11
206+
207+
start_test 'Cannot fast-forward commit branch'
208+
make_test_repo_with_two_branches
209+
210+
# Add another commit to local branch that's not on tracking branch.
211+
touch $files/commit-from-elsewhere
212+
$git commit-filetree $branch $files
213+
assert_branch "58cce3125df7 refs/heads/$branch"
214+
215+
# Make test branch track test-tracking branch, but 1/1 ahead/behind
216+
$git >/dev/null branch --set-upstream-to=$branch-tracking $branch
217+
218+
touch $files/three
219+
exitcode="$($git commit-filetree $branch $files >/dev/null 2>&1; echo $?)"
220+
test_equal 3 "$exitcode"
221+
message=$($git commit-filetree 2>&1 $branch $files || true)
222+
expected='Branch testbr has diverged from tracking refs/heads/testbr-tracking'
223+
[[ $message =~ $expected ]] || fail_test "bad message: $message"
224+
225+
end_test

0 commit comments

Comments
 (0)