git-fetch: Split fetch and merge logic
PATCH v3 1/1 git-fetch: Split fetch and merge logic
It makes git-parse-remote.sh and almost all git-fetch independent of the merge logic.
git-fetch fetches the branches from the remote and saves this information in .git/FETCH_FETCHED, and at the end it generates the file .git/FETCH_HEAD.
The current merge behaviour is unchanged.
---
Hi *,
This version removes the behaviour changes.
Now to rebase this on top of 'jc/fetch' (a branch of 'pu').
Santi
git-fetch.sh | 79 +++++++++++++++++++++++++++++++-------------------- git-parse-remote.sh | 64 ++++++++++++----------------------------- t/t5510-fetch.sh | 16 ++++++++++ 3 files changed, 83 insertions(+), 76 deletions(-)
git-fetch.sh +48 -31
@@ -77,9 +77,9 @@ do shift done +origin=$(get_default_remote) case "$#" in 0) - origin=$(get_default_remote) test -n "$(get_remote_url ${origin})" || die "Where do you want to fetch from today?" set x $origin ; shift ;;
@@ -101,6 +101,7 @@ if test "" = "$append" then : >"$GIT_DIR/FETCH_HEAD" fi +: >"$GIT_DIR/FETCH_FETCHED" # Global that is reused later ls_remote_result=$(git ls-remote $exec "$remote") ||
@@ -112,10 +113,6 @@ append_fetch_head () { remote_name_="$3" remote_nick_="$4" local_name_="$5" - case "$6" in - t) not_for_merge_='not-for-merge' ;; - '') not_for_merge_= ;; - esac # remote-nick is the URL given on the command line (or a shorthand) # remote-name is the $GIT_DIR relative refs/ path we computed
@@ -146,9 +143,7 @@ append_fetch_head () { if git-cat-file commit "$head_" >/dev/null 2>&1 then headc_=$(git-rev-parse --verify "$head_^0") || exit - echo "$headc_ $not_for_merge_ $note_" >>"$GIT_DIR/FETCH_HEAD" - else - echo "$head_ not-for-merge $note_" >>"$GIT_DIR/FETCH_HEAD" + echo "$headc_ $remote_name_:$local_name_ $note_" >>"$GIT_DIR/FETCH_FETCHED" fi update_local_ref "$local_name_" "$head_" "$note_"
@@ -265,7 +260,7 @@ then git-show-ref --exclude-existing=refs/tags/ | while read sha1 name do - echo ".${name}:${name}" + echo "${name}:${name}" done` || exit if test "$#" -gt 1 then
@@ -288,13 +283,6 @@ fetch_main () { # These are relative path from $GIT_DIR, typically starting at refs/ # but may be HEAD - if expr "z$ref" : 'z\.' >/dev/null - then - not_for_merge=t - ref=$(expr "z$ref" : 'z\.\(.*\)') - else - not_for_merge= - fi if expr "z$ref" : 'z+' >/dev/null then single_force=t
@@ -375,7 +363,7 @@ fetch_main () { esac append_fetch_head "$head" "$remote" \ - "$remote_name" "$remote_nick" "$local_name" "$not_for_merge" || exit + "$remote_name" "$remote_nick" "$local_name" || exit done
@@ -418,28 +406,16 @@ fetch_main () { case "$ref" in +$remote_name:*) single_force=t - not_for_merge= - found="$ref" - break ;; - .+$remote_name:*) - single_force=t - not_for_merge=t - found="$ref" - break ;; - .$remote_name:*) - not_for_merge=t found="$ref" break ;; $remote_name:*) - not_for_merge= found="$ref" break ;; esac done local_name=$(expr "z$found" : 'z[^:]*:\(.*\)') append_fetch_head "$sha1" "$remote" \ - "$remote_name" "$remote_nick" "$local_name" \ - "$not_for_merge" || exit + "$remote_name" "$remote_nick" "$local_name" || exit done ) ) || exit ;;
@@ -463,7 +439,7 @@ case "$no_tags$tags" in do git-cat-file -t "$sha1" >/dev/null 2>&1 || continue echo >&2 "Auto-following $name" - echo ".${name}:${name}" + echo "${name}:${name}" done) esac case "$taglist" in
@@ -491,3 +467,44 @@ case "$orig_head" in fi ;; esac + +# Generate $GIT_DIR/FETCH_HEAD +case ",$#,$remote_nick," in +,1,$origin,) + curr_branch=$(git-symbolic-ref -q HEAD | sed -e 's|^refs/heads/||') + merge_branches=$(git-repo-config \ + --get-all "branch.${curr_branch}.merge" | sort -u) + fetch_branches=$(get_remote_default_refs_for_fetch -n $remote_nick | + sed 's/:.*$//g;s/^+//' | sort -u) + test "$merge_branches" && test "$fetch_branches" && + merge_branches=$(echo -e "$merge_branches\n$fetch_branches" | sort | uniq -d) + [ -z "$merge_branches" ] || + test "$(get_data_source $remote_nick)" = branches && merge_first=yes;; +,1,$remote,) merge_branches=HEAD;; +,1,*)merge_first=yes ;; +*) + shift + merge_branches=$(for ref; do + expr "z$ref" : 'z.*:' >/dev/null || ref="${ref}:" + echo "$(expr "z$ref" : 'z\([^:]*\):')"; done);; +esac + +test "$merge_first" = "yes" && +test "$(get_remote_default_refs_for_fetch -t $remote_nick)" != "explicit" && +merge_branches= && merge_first= + +merge_branches=$(canon_refs_list_for_fetch $merge_branches | sed 's/:.*$//g') + +cat "$GIT_DIR"/FETCH_FETCHED | while IFS=' ' read sha1 ref note ; do + remote_branch=$(expr "z$ref" : 'z\([^:]*\):') + for merge_branch in $merge_branches ; do + [ "$merge_branch" == "$remote_branch" ] && + echo "$sha1 $note" && continue 2 + done + if ! test "$merge_first" || test "$merge_first" == "done" ; then + echo "$sha1 not-for-merge $note" + else + echo "$sha1 $note" + merge_first=done + fi +done >> "$GIT_DIR/FETCH_HEAD"
git-parse-remote.sh +19 -45
107 lines not on this page · raw message
t/t5510-fetch.sh +16 -0
@@ -50,6 +50,22 @@ test_expect_success "fetch test" ' test "z$mine" = "z$his" ' +test_expect_success "fetch test fetched" ' + cd "$D" && + cd three && + git fetch && + test -f .git/refs/heads/two && + test -f .git/refs/heads/one && + master_in_two=`cd ../two && git rev-parse master` && + one_in_two=`cd ../two && git rev-parse one` && + { + echo "$master_in_two refs/heads/master:refs/heads/two" + echo "$one_in_two refs/heads/one:refs/heads/one" + } >expected && + cut -f -2 .git/FETCH_FETCHED >actual && + diff expected actual' + + test_expect_success "fetch test for-merge" ' cd "$D" && cd three &&
Signature · 1 line
1.5.0.1.576.g78541
Quoted 8 lines · Santi Béjar
It makes
git-parse-remote.shand almost all git-fetch independent of the merge logic.git-fetch fetches the branches from the remote and saves this information in .git/FETCH_FETCHED, and at the end it generates the file .git/FETCH_HEAD.
The current merge behaviour is unchanged.
I overlooked your earlier huge testcase patch, but I noticed it does not include any test that fetches an annotated tag that points at a commit or a non-commit, nor an unannotated tag that is a tree or a blob. I am too tired to check them right now, but are you sure you are not regressing with them?
It is sometimes handy to be able to say:
$ git fetch git-gui tag spearce-gpg-pub
$ git cat-file -p FETCH_HEADQuoted 8 lines · Junio C Hamano
Santi Béjar <[email protected]> writes:
...Quoted 1 line
The current merge behaviour is unchanged.
I overlooked your earlier huge testcase patch, but I noticed it does not include any test that fetches an annotated tag that points at a commit or a non-commit, nor an unannotated tag that is a tree or a blob.
A couple of other things I think the users of information in FETCH_HEAD rely on need to be checked by the test suite.
* Automated following of tags when a branch head is fetched. When this happens, I think the users (not just git-pull script but end users) expect the branch head to be listed first in the FETCH_HEAD file, so that:
$ git fetch git://some.repo/ $ git show FETCH_HEAD
gives stable and useful result.
* When two refs are listed on the command line explicitly, are they listed in the order given by the user in the resulting FETCH_HEAD file?
$ git fetch git://some.repo/ one two $ git fetch git://some.repo/ two one
Again, "git show FETCH_HEAD" after that expects the first one to show 'one' and the second one 'two'. Replacing 'fetch' with 'pull' in the example would affect the parent order in the resulting Octopus.
Quoted 6 lines · Santi Béjar
It makes
git-parse-remote.shand almost all git-fetch independent of the merge logic.git-fetch fetches the branches from the remote and saves this information in .git/FETCH_FETCHED, and at the end it generates the file .git/FETCH_HEAD.
I might have some more comments after actually applying this and reviewing it with wider contexts, but it looks nice overall.
I am wondering if FETCH_FETCHED is purely for internal use by git-fetch (it appears so), and if so if it is worth trying to do without the temporary file, but that is a minor detail.
Quoted 4 lines
git-fetch.sh | 79 +++++++++++++++++++++++++++++++-------------------- git-parse-remote.sh | 64 ++++++++++++----------------------------- t/t5510-fetch.sh | 16 ++++++++++ 3 files changed, 83 insertions(+), 76 deletions(-)
Loses more lines than it adds (+16 lines to test does not count), which is a very good sign.
Quoted 5 lines
git-fetch.sh +0
-0
git-fetch.sh case "$orig_head" in · line 476
Quoted 10 lines
git-fetch.sh +7
-0@@ -491,3 +467,44 @@ case "$orig_head" in fi ;; esac + +# Generate $GIT_DIR/FETCH_HEAD +case ",$#,$remote_nick," in +,1,$origin,) + curr_branch=$(git-symbolic-ref -q HEAD | sed -e 's|^refs/heads/||') + merge_branches=$(git-repo-config \ + --get-all "branch.${curr_branch}.merge" | sort -u)
Why "sort -u" (instead of erroring out if the repository is misconfigured)?
git-fetch.sh case "$orig_head" in · line 478
Quoted 2 lines
git-fetch.sh +2
-0+ fetch_branches=$(get_remote_default_refs_for_fetch -n $remote_nick | + sed 's/:.*$//g;s/^+//' | sort -u)
GNU sed users do not have problems with this, but I recall that we had to rewrite our sed scripts not to use multiple commands concatenated with ';' for portability, like:
sed -e 's/:.*$//g' -e 's/^+//'
Again why "sort -u"?
git-fetch.sh case "$orig_head" in · line 479
Quoted 1 line
git-fetch.sh +1
-0+ test "$merge_branches" && test "$fetch_branches" &&
We probably would want to be defensive by saying "test -n".
git-fetch.sh case "$orig_head" in · line 480
Quoted 1 line
git-fetch.sh +1
-0+ merge_branches=$(echo -e "$merge_branches\n$fetch_branches" | sort | uniq -d)
I appreciate the cleverness of the intersection. However, is "echo -e" portable? I think we have avoided it so far (we have avoided even "echo -n" which is traditionally much more available).
git-fetch.sh case "$orig_head" in · line 510
Quoted 13 lines
git-fetch.sh +13
-0+cat "$GIT_DIR"/FETCH_FETCHED | while IFS=' ' read sha1 ref note ; do + remote_branch=$(expr "z$ref" : 'z\([^:]*\):') + for merge_branch in $merge_branches ; do + [ "$merge_branch" == "$remote_branch" ] && + echo "$sha1 $note" && continue 2 + done + if ! test "$merge_first" || test "$merge_first" == "done" ; then + echo "$sha1 not-for-merge $note" + else + echo "$sha1 $note" + merge_first=done + fi +done >> "$GIT_DIR/FETCH_HEAD"
You can do:
while ...
do
done < "$GIT_DIR/FETCH_FETCHED"which is easier on the eye.
I often see a buggy shell script that expects assignment in a while loop to survive after the loop finished, when the loop is on the downstream side of a pipe (e.g. the loop is run in a subshell so merge_first after this loop is finished will never be 'done'). You do not use the variable after the loop so your script is not buggy, but avoiding a pipe into while loop is a good habit to get into.
git-parse-remote.sh canon_refs_list_for_fetch () { · line 162
Quoted 11 lines
git-parse-remote.sh +2
-2@@ -196,32 +159,43 @@ canon_refs_list_for_fetch () { config) - canon_refs_list_for_fetch -d "$1" \ - $(git-config --get-all "remote.$1.fetch") ;; + set $(expand_refs_wildcard "$1" \ + $(git-repo-config --get-all "remote.$1.fetch")) ;;
Oops? It is not buggy but it's better to set an example by using git-config consistenty. You have another mention of repo-config above.
git-parse-remote.sh canon_refs_list_for_fetch () { · line 185
Quoted 6 lines
git-parse-remote.sh +2
-2remotes) - canon_refs_list_for_fetch -d "$1" $(sed -ne '/^Pull: */{ + set $(expand_refs_wildcard "$1" $(sed -ne '/^Pull: */{ s///p - }' "$GIT_DIR/remotes/$1") + }' "$GIT_DIR/remotes/$1"))
Hmph. I wonder why the original author did not do '/^Pull: */s///p'...
Quoted 11 lines · Junio C Hamano
Quoted 6 lines · Santi Béjar
It makes
git-parse-remote.shand almost all git-fetch independent of the merge logic.git-fetch fetches the branches from the remote and saves this information in .git/FETCH_FETCHED, and at the end it generates the file .git/FETCH_HEAD.
I might have some more comments after actually applying this and reviewing it with wider contexts, but it looks nice overall.
Thanks.
Quoted 4 lines
I am wondering if
FETCH_FETCHEDis purely for internal use by git-fetch (it appears so), and if so if it is worth trying to do without the temporary file, but that is a minor detail.
Yes, it's purely internal. With "without the temporary file" you mean to put the content in a variable or removing at the end?
Quoted 10 lines
Quoted 10 lines
git-fetch.sh +2
-0@@ -491,3 +467,44 @@ case "$orig_head" in fi ;; esac + +# Generate $GIT_DIR/FETCH_HEAD
Quoted 8 lines
git-fetch.sh case "$orig_head" in · line 476
Quoted 5 lines
git-fetch.sh +5
-0+case ",$#,$remote_nick," in +,1,$origin,) + curr_branch=$(git-symbolic-ref -q HEAD | sed -e 's|^refs/heads/||') + merge_branches=$(git-repo-config \ + --get-all "branch.${curr_branch}.merge" | sort -u)Why "sort -u" (instead of erroring out if the repository is misconfigured)?
It is consistent with the current code and I needed it for the intersection below. The check could be added later.
Quoted 9 lines
git-fetch.sh case "$orig_head" in · line 478
Quoted 3 lines
git-fetch.sh +2
-0+ fetch_branches=$(get_remote_default_refs_for_fetch -n $remote_nick | + sed 's/:.*$//g;s/^+//' | sort -u)GNU sed users do not have problems with this, but I recall that we had to rewrite our sed scripts not to use multiple commands concatenated with ';' for portability, like:
sed -e 's/:.*$//g' -e 's/^+//'
OK.
Quoted 2 lines
Again why "sort -u"?
For the intersection below. Moreover, it make sense because remotes.${remote}.fetch could be of the form:
[remote "origin"]
url=...
fetch= refs/heads/master:refs/remotes/origin/master fetch= refs/heads/*:refs/remotes/origin/*
to have a well defined first refspec. In this case refs/heads/master appears twice.
Quoted 5 lines
git-fetch.sh case "$orig_head" in · line 479
Quoted 2 lines
git-fetch.sh +1
-0+ test "$merge_branches" && test "$fetch_branches" &&We probably would want to be defensive by saying "test -n".
Ok.
Quoted 6 lines
git-fetch.sh case "$orig_head" in · line 480
Quoted 1 line
git-fetch.sh +1
-0+ merge_branches=$(echo -e "$merge_branches\n$fetch_branches" | sort | uniq -d)I appreciate the cleverness of the intersection. However, is "echo -e" portable? I think we have avoided it so far (we have avoided even "echo -n" which is traditionally much more available).
printf '%s\n%s' "$merge_branches" "$fetch_branches"
is OK?
Quoted 23 lines
Quoted 4 lines
git-fetch.sh +3
-0+cat "$GIT_DIR"/FETCH_FETCHED | while IFS=' ' read sha1 ref note ; do + remote_branch=$(expr "z$ref" : 'z\([^:]*\):') + for merge_branch in $merge_branches ; dogit-fetch.sh case "$orig_head" in · line 510
Quoted 10 lines
git-fetch.sh +10
-0+ [ "$merge_branch" == "$remote_branch" ] && + echo "$sha1 $note" && continue 2 + done + if ! test "$merge_first" || test "$merge_first" == "done" ; then + echo "$sha1 not-for-merge $note" + else + echo "$sha1 $note" + merge_first=done + fi +done >> "$GIT_DIR/FETCH_HEAD"You can do:
while ...
do
done < "$GIT_DIR/FETCH_FETCHED"which is easier on the eye.
Quoted 7 lines
I often see a buggy shell script that expects assignment in a while loop to survive after the loop finished, when the loop is on the downstream side of a pipe (e.g. the loop is run in a subshell so
merge_firstafter this loop is finished will never be 'done'). You do not use the variable after the loop so your script is not buggy, but avoiding a pipe into while loop is a good habit to get into.
OK, and thanks for the explanation.
Quoted 13 lines
git-parse-remote.sh canon_refs_list_for_fetch () { · line 162
Quoted 12 lines
git-parse-remote.sh +2
-2@@ -196,32 +159,43 @@ canon_refs_list_for_fetch () { config) - canon_refs_list_for_fetch -d "$1" \ - $(git-config --get-all "remote.$1.fetch") ;; + set $(expand_refs_wildcard "$1" \ + $(git-repo-config --get-all "remote.$1.fetch")) ;;
Quoted 3 lines
Oops? It is not buggy but it's better to set an example by using git-config consistenty. You have another mention of repo-config above.
Yes, of course.
Quoted 9 lines
git-parse-remote.sh canon_refs_list_for_fetch () { · line 185
Quoted 7 lines
git-parse-remote.sh +2
-2remotes) - canon_refs_list_for_fetch -d "$1" $(sed -ne '/^Pull: */{ + set $(expand_refs_wildcard "$1" $(sed -ne '/^Pull: */{ s///p - }' "$GIT_DIR/remotes/$1") + }' "$GIT_DIR/remotes/$1"))Hmph. I wonder why the original author did not do '/^Pull: */s///p'...
I don't know, I'll do.
Santi
Quoted 8 lines · Santi Béjar
Quoted 3 lines · Junio C Hamano
I am wondering if
FETCH_FETCHEDis purely for internal use by git-fetch (it appears so), and if so if it is worth trying to do without the temporary file, but that is a minor detail.Yes, it's purely internal. With "without the temporary file" you mean to put the content in a variable or removing at the end?
If a variable suffices that would be quite nice, but it is not a big deal. As the script does fair amount of computation in subprocess, I suspect it may not be worth trying to use variable, only to get rid of the temporary file.
Quoted 8 lines
Quoted 4 lines
I appreciate the cleverness of the intersection. However, is "echo -e" portable? I think we have avoided it so far (we have avoided even "echo -n" which is traditionally much more available).
printf '%s\n%s' "$merge_branches" "$fetch_branches"
is OK?
Yes. I think printf is what people who rewrote my "echo -n" have done elsewhere.