From 024d34cb0813e098c5c0e4ccdacb6a461d34cb6b Mon Sep 17 00:00:00 2001 From: Linus Torvalds Date: Wed, 22 Jan 2014 12:32:30 -0800 Subject: [PATCH 1/6] request-pull: more strictly match local/remote branches The current 'request-pull' will try to find matching commit on the given remote, and rewrite the "please pull" line to match that remote ref. That may be very helpful if your local tree doesn't match the layout of the remote branches, but for the common case it's been a recurring disaster, when "request-pull" is done against a delayed remote update, and it rewrites the target branch randomly to some other branch name that happens to have the same expected SHA1 (or more commonly, leaves it blank). To avoid that recurring problem, this changes "git request-pull" so that it matches the ref name to be pulled against the *local* repository, and then warns if the remote repository does not have that exact same branch or tag name and content. This means that git request-pull will never rewrite the ref-name you gave it. If the local branch name is "xyzzy", that is the only branch name that request-pull will ask the other side to fetch. If the remote has that branch under a different name, that's your problem and git request-pull will not try to fix it up (but git request-pull will warn about the fact that no exact matching branch is found, and you can edit the end result to then have the remote name you want if it doesn't match your local one). The new "find local ref" code will also complain loudly if you give an ambiguous refname (eg you have both a tag and a branch with that same name, and you don't specify "heads/name" or "tags/name"). Signed-off-by: Linus Torvalds Signed-off-by: Junio C Hamano --- git-request-pull.sh | 110 +++++++++++++++++--------------------------- 1 file changed, 43 insertions(+), 67 deletions(-) diff --git a/git-request-pull.sh b/git-request-pull.sh index fe21d5db63..659a412155 100755 --- a/git-request-pull.sh +++ b/git-request-pull.sh @@ -35,20 +35,7 @@ do shift done -base=$1 url=$2 head=${3-HEAD} status=0 branch_name= - -headref=$(git symbolic-ref -q "$head") -if git show-ref -q --verify "$headref" -then - branch_name=${headref#refs/heads/} - if test "z$branch_name" = "z$headref" || - ! git config "branch.$branch_name.description" >/dev/null - then - branch_name= - fi -fi - -tag_name=$(git describe --exact "$head^0" 2>/dev/null) +base=$1 url=$2 status=0 test -n "$base" && test -n "$url" || usage @@ -58,55 +45,68 @@ then die "fatal: Not a valid revision: $base" fi +# +# $3 must be a symbolic ref, a unique ref, or +# a SHA object expression +# +head=$(git symbolic-ref -q "${3-HEAD}") +head=${head:-$(git show-ref "${3-HEAD}" | cut -d' ' -f2)} +head=${head:-$(git rev-parse --quiet --verify "$3")} + +# None of the above? Bad. +test -z "$head" && die "fatal: Not a valid revision: $3" + +# This also verifies that the resulting head is unique: +# "git show-ref" could have shown multiple matching refs.. headrev=$(git rev-parse --verify --quiet "$head"^0) -if test -z "$headrev" +test -z "$headrev" && die "fatal: Ambiguous revision: $3" + +# Was it a branch with a description? +branch_name=${head#refs/heads/} +if test "z$branch_name" = "z$headref" || + ! git config "branch.$branch_name.description" >/dev/null then - die "fatal: Not a valid revision: $head" + branch_name= fi +prettyhead=${head#refs/} +prettyhead=${prettyhead#heads/} + merge_base=$(git merge-base $baserev $headrev) || die "fatal: No commits in common between $base and $head" -# $head is the token given from the command line, and $tag_name, if -# exists, is the tag we are going to show the commit information for. -# If that tag exists at the remote and it points at the commit, use it. -# Otherwise, if a branch with the same name as $head exists at the remote -# and their values match, use that instead. +# $head is the refname from the command line. +# If a ref with the same name as $head exists at the remote +# and their values match, use that. # # Otherwise find a random ref that matches $headrev. find_matching_ref=' - sub abbr { - my $ref = shift; - if ($ref =~ s|^refs/heads/|| || $ref =~ s|^refs/tags/|tags/|) { - return $ref; - } else { - return $ref; - } - } - - my ($tagged, $branch, $found); + my ($exact,$found); while () { - my ($sha1, $ref, $deref) = /^(\S+)\s+(\S+?)(\^\{\})?$/; + my ($sha1, $ref, $deref) = /^(\S+)\s+([^^]+)(\S*)$/; next unless ($sha1 eq $ARGV[1]); - $found = abbr($ref); - if ($deref && $ref eq "tags/$ARGV[2]") { - $tagged = $found; - last; + if ($ref eq $ARGV[0]) { + $exact = $ref; } - if ($ref =~ m|/\Q$ARGV[0]\E$|) { - $exact = $found; + if ($sha1 eq $ARGV[0]) { + $found = $sha1; } } - if ($tagged) { - print "$tagged\n"; - } elsif ($exact) { + if ($exact) { print "$exact\n"; } elsif ($found) { print "$found\n"; } ' -ref=$(git ls-remote "$url" | @@PERL@@ -e "$find_matching_ref" "$head" "$headrev" "$tag_name") +ref=$(git ls-remote "$url" | @@PERL@@ -e "$find_matching_ref" "$head" "$headrev") + +if test -z "$ref" +then + echo "warn: No match for $prettyhead found at $url" >&2 + echo "warn: Are you sure you pushed '$prettyhead' there?" >&2 + status=1 +fi url=$(git ls-remote --get-url "$url") @@ -116,7 +116,7 @@ git show -s --format='The following changes since commit %H: are available in the git repository at: ' $merge_base && -echo " $url${ref+ $ref}" && +echo " $url $prettyhead" && git show -s --format=' for you to fetch changes up to %H: @@ -129,34 +129,10 @@ then echo "(from the branch description for $branch_name local branch)" echo git config "branch.$branch_name.description" -fi && - -if test -n "$tag_name" -then - if test -z "$ref" || test "$ref" != "tags/$tag_name" - then - echo >&2 "warn: You locally have $tag_name but it does not (yet)" - echo >&2 "warn: appear to be at $url" - echo >&2 "warn: Do you want to push it there, perhaps?" - fi - git cat-file tag "$tag_name" | - sed -n -e '1,/^$/d' -e '/^-----BEGIN PGP /q' -e p - echo -fi && - -if test -n "$branch_name" || test -n "$tag_name" -then echo "----------------------------------------------------------------" fi && git shortlog ^$baserev $headrev && git diff -M --stat --summary $patch $merge_base..$headrev || status=1 -if test -z "$ref" -then - echo "warn: No branch of $url is at:" >&2 - git show -s --format='warn: %h: %s' $headrev >&2 - echo "warn: Are you sure you pushed '$head' there?" >&2 - status=1 -fi exit $status From dc2eacc58c1118ad68b9d6973033a60b6ed9be1f Mon Sep 17 00:00:00 2001 From: Linus Torvalds Date: Wed, 22 Jan 2014 15:23:48 -0800 Subject: [PATCH 2/6] request-pull: allow "local:remote" to specify names on both ends This allows a user to say that a local branch has a different name on the remote server, using the same syntax that "git push" uses to create that situation. Signed-off-by: Linus Torvalds Signed-off-by: Junio C Hamano --- git-request-pull.sh | 50 ++++++++++++++++++++++++++------------------- 1 file changed, 29 insertions(+), 21 deletions(-) diff --git a/git-request-pull.sh b/git-request-pull.sh index 659a412155..c8ab0e9120 100755 --- a/git-request-pull.sh +++ b/git-request-pull.sh @@ -47,19 +47,23 @@ fi # # $3 must be a symbolic ref, a unique ref, or -# a SHA object expression +# a SHA object expression. It can also be of +# the format 'local-name:remote-name'. # -head=$(git symbolic-ref -q "${3-HEAD}") -head=${head:-$(git show-ref "${3-HEAD}" | cut -d' ' -f2)} -head=${head:-$(git rev-parse --quiet --verify "$3")} +local=${3%:*} +local=${local:-HEAD} +remote=${3#*:} +head=$(git symbolic-ref -q "$local") +head=${head:-$(git show-ref --heads --tags "$local" | cut -d' ' -f2)} +head=${head:-$(git rev-parse --quiet --verify "$local")} # None of the above? Bad. -test -z "$head" && die "fatal: Not a valid revision: $3" +test -z "$head" && die "fatal: Not a valid revision: $local" # This also verifies that the resulting head is unique: # "git show-ref" could have shown multiple matching refs.. headrev=$(git rev-parse --verify --quiet "$head"^0) -test -z "$headrev" && die "fatal: Ambiguous revision: $3" +test -z "$headrev" && die "fatal: Ambiguous revision: $local" # Was it a branch with a description? branch_name=${head#refs/heads/} @@ -69,9 +73,6 @@ then branch_name= fi -prettyhead=${head#refs/} -prettyhead=${prettyhead#heads/} - merge_base=$(git merge-base $baserev $headrev) || die "fatal: No commits in common between $base and $head" @@ -81,30 +82,37 @@ die "fatal: No commits in common between $base and $head" # # Otherwise find a random ref that matches $headrev. find_matching_ref=' - my ($exact,$found); + my ($head,$headrev) = (@ARGV); + my ($found); + while () { + chomp; my ($sha1, $ref, $deref) = /^(\S+)\s+([^^]+)(\S*)$/; - next unless ($sha1 eq $ARGV[1]); - if ($ref eq $ARGV[0]) { - $exact = $ref; + my ($pattern); + next unless ($sha1 eq $headrev); + + $pattern="/$head\$"; + if ($ref eq $head) { + $found = $ref; } - if ($sha1 eq $ARGV[0]) { + if ($ref =~ /$pattern/) { + $found = $ref; + } + if ($sha1 eq $head) { $found = $sha1; } } - if ($exact) { - print "$exact\n"; - } elsif ($found) { + if ($found) { print "$found\n"; } ' -ref=$(git ls-remote "$url" | @@PERL@@ -e "$find_matching_ref" "$head" "$headrev") +ref=$(git ls-remote "$url" | @@PERL@@ -e "$find_matching_ref" "${remote:-HEAD}" "$headrev") if test -z "$ref" then - echo "warn: No match for $prettyhead found at $url" >&2 - echo "warn: Are you sure you pushed '$prettyhead' there?" >&2 + echo "warn: No match for commit $headrev found at $url" >&2 + echo "warn: Are you sure you pushed '${remote:-HEAD}' there?" >&2 status=1 fi @@ -116,7 +124,7 @@ git show -s --format='The following changes since commit %H: are available in the git repository at: ' $merge_base && -echo " $url $prettyhead" && +echo " $url $remote" && git show -s --format=' for you to fetch changes up to %H: From 4b14ec878a206e87520378b9e1e43aef469bc646 Mon Sep 17 00:00:00 2001 From: Junio C Hamano Date: Wed, 29 Jan 2014 15:08:05 -0800 Subject: [PATCH 3/6] request-pull: pick up tag message as before The previous two steps were meant to stop updating the explicit refname the user gave to the command to a different ref that points at it. Most notably, we no longer substitute a branch name the user used with a name of the tag that points at the commit at the tip of the branch (it still can be done with "local-branch:remote-tag"). However, they also lost the code that included the message in a tag when the user _did_ ask the tag to be pulled. Resurrect it. Signed-off-by: Junio C Hamano --- git-request-pull.sh | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/git-request-pull.sh b/git-request-pull.sh index c8ab0e9120..93b41357ab 100755 --- a/git-request-pull.sh +++ b/git-request-pull.sh @@ -132,6 +132,14 @@ for you to fetch changes up to %H: ----------------------------------------------------------------' $headrev && +if test $(git cat-file -t "$head") = tag +then + git cat-file tag "$head" | + sed -n -e '1,/^$/d' -e '/^-----BEGIN PGP /q' -e p + echo + echo "----------------------------------------------------------------" +fi && + if test -n "$branch_name" then echo "(from the branch description for $branch_name local branch)" From 28ad685f70f93133f2c872e4b1ea2a8f87eaebb5 Mon Sep 17 00:00:00 2001 From: Junio C Hamano Date: Thu, 23 Jan 2014 14:23:43 -0800 Subject: [PATCH 4/6] request-pull: test updates This illustrates behaviour changes that result from the recent change by Linus. Most show good changes, but there may be some usability regressions: - The command continues to fail when the user forgot to push out before running the command, but the wording of the message has been slightly changed. - The command no longer guesses when asked to request the commit at the HEAD be pulled after pushing it to a branch 'for-upstream', even when that branch points at the correct commit. The user must ask the command with the new "master:for-upstream" syntax. The new behaviour needs to be documented in any case, but we need to agree what the new behaviour should be before doing so first. Signed-off-by: Junio C Hamano --- t/t5150-request-pull.sh | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/t/t5150-request-pull.sh b/t/t5150-request-pull.sh index 1afa0d5c44..262205729d 100755 --- a/t/t5150-request-pull.sh +++ b/t/t5150-request-pull.sh @@ -86,7 +86,7 @@ test_expect_success 'setup: two scripts for reading pull requests' ' s/[-0-9]\{10\} [:0-9]\{8\} [-+][0-9]\{4\}/DATE/g s/ [^ ].*/ SUBJECT/g s/ [^ ].* (DATE)/ SUBJECT (DATE)/g - s/for-upstream/BRANCH/g + s|tags/full|BRANCH|g s/mnemonic.txt/FILENAME/g s/^version [0-9]/VERSION/ /^ FILENAME | *[0-9]* [-+]*\$/ b diffstat @@ -127,7 +127,7 @@ test_expect_success 'pull request when forgot to push' ' test_must_fail git request-pull initial "$downstream_url" \ 2>../err ) && - grep "No branch of.*is at:\$" err && + grep "No match for commit .*" err && grep "Are you sure you pushed" err ' @@ -141,7 +141,7 @@ test_expect_success 'pull request after push' ' git checkout initial && git merge --ff-only master && git push origin master:for-upstream && - git request-pull initial origin >../request + git request-pull initial origin master:for-upstream >../request ) && sed -nf read-request.sed digest && cat digest && @@ -160,7 +160,7 @@ test_expect_success 'pull request after push' ' ' -test_expect_success 'request names an appropriate branch' ' +test_expect_success 'request asks HEAD to be pulled' ' rm -fr downstream.git && git init --bare downstream.git && @@ -179,7 +179,7 @@ test_expect_success 'request names an appropriate branch' ' read repository && read branch } ../request + git push origin tags/full && + git request-pull initial "$downstream_url" tags/full >../request ) && request.fuzzy && test_i18ncmp expect request.fuzzy @@ -229,7 +229,7 @@ test_expect_success 'request-pull ignores OPTIONS_KEEPDASHDASH poison' ' git checkout initial && git merge --ff-only master && git push origin master:for-upstream && - git request-pull -- initial "$downstream_url" >../request + git request-pull -- initial "$downstream_url" master:for-upstream >../request ) ' From 5aae66bd998429959d89c5bedf0bd8175a2b3f33 Mon Sep 17 00:00:00 2001 From: Junio C Hamano Date: Tue, 25 Feb 2014 13:44:46 -0800 Subject: [PATCH 5/6] request-pull: resurrect "pretty refname" feature When asking to fetch/pull a branch whose name is B or a tag whose name is T, we used to show the command to run as: git pull $URL B git pull $URL tags/T even when B and T were spelled in a more qualified way in order to disambiguate, e.g. heads/B or refs/tags/T, but the recent update lost this feature. Resurrect it. Signed-off-by: Junio C Hamano --- git-request-pull.sh | 4 +++- t/t5150-request-pull.sh | 8 +++++++- 2 files changed, 10 insertions(+), 2 deletions(-) diff --git a/git-request-pull.sh b/git-request-pull.sh index 93b41357ab..b67513a2e7 100755 --- a/git-request-pull.sh +++ b/git-request-pull.sh @@ -53,6 +53,8 @@ fi local=${3%:*} local=${local:-HEAD} remote=${3#*:} +pretty_remote=${remote#refs/} +pretty_remote=${pretty_remote#heads/} head=$(git symbolic-ref -q "$local") head=${head:-$(git show-ref --heads --tags "$local" | cut -d' ' -f2)} head=${head:-$(git rev-parse --quiet --verify "$local")} @@ -124,7 +126,7 @@ git show -s --format='The following changes since commit %H: are available in the git repository at: ' $merge_base && -echo " $url $remote" && +echo " $url $pretty_remote" && git show -s --format=' for you to fetch changes up to %H: diff --git a/t/t5150-request-pull.sh b/t/t5150-request-pull.sh index 262205729d..75d6b3843a 100755 --- a/t/t5150-request-pull.sh +++ b/t/t5150-request-pull.sh @@ -216,8 +216,14 @@ test_expect_success 'pull request format' ' git request-pull initial "$downstream_url" tags/full >../request ) && request.fuzzy && - test_i18ncmp expect request.fuzzy + test_i18ncmp expect request.fuzzy && + ( + cd local && + git request-pull initial "$downstream_url" tags/full:refs/tags/full + ) >request && + sed -nf fuzz.sed request.fuzzy && + test_i18ncmp expect request.fuzzy ' test_expect_success 'request-pull ignores OPTIONS_KEEPDASHDASH poison' ' From ec445074e0ac4d6003acd21c512c43c8fdb8be86 Mon Sep 17 00:00:00 2001 From: Junio C Hamano Date: Wed, 12 Mar 2014 11:04:11 -0700 Subject: [PATCH 6/6] request-pull: documentation updates The original description talked only about what it does. Instead, start it with the purpose of the command, i.e. what it is used for, and then mention what it does to achieve that goal. Clarify what , and means in the context of the overall purpose of the command. Describe the extended syntax of parameter that is used when the local branch name is different from the branch name at the repository the changes are published. Helped-by: Eric Sunshine Signed-off-by: Junio C Hamano --- Documentation/git-request-pull.txt | 55 ++++++++++++++++++++++++++---- 1 file changed, 49 insertions(+), 6 deletions(-) diff --git a/Documentation/git-request-pull.txt b/Documentation/git-request-pull.txt index b99681ce85..283577b0b6 100644 --- a/Documentation/git-request-pull.txt +++ b/Documentation/git-request-pull.txt @@ -13,22 +13,65 @@ SYNOPSIS DESCRIPTION ----------- -Summarizes the changes between two commits to the standard output, and includes -the given URL in the generated summary. +Generate a request asking your upstream project to pull changes into +their tree. The request, printed to the standard output, summarizes +the changes and indicates from where they can be pulled. + +The upstream project is expected to have the commit named by +`` and the output asks it to integrate the changes you made +since that commit, up to the commit named by ``, by visiting +the repository named by ``. + OPTIONS ------- -p:: - Show patch text + Include patch text in the output. :: - Commit to start at. + Commit to start at. This names a commit that is already in + the upstream history. :: - URL to include in the summary. + The repository URL to be pulled from. :: - Commit to end at; defaults to HEAD. + Commit to end at (defaults to HEAD). This names the commit + at the tip of the history you are asking to be pulled. ++ +When the repository named by `` has the commit at a tip of a +ref that is different from the ref you have locally, you can use the +`:` syntax, to have its local name, a colon `:`, and +its remote name. + + +EXAMPLE +------- + +Imagine that you built your work on your `master` branch on top of +the `v1.0` release, and want it to be integrated to the project. +First you push that change to your public repository for others to +see: + + git push https://git.ko.xz/project master + +Then, you run this command: + + git request-pull v1.0 https://git.ko.xz/project master + +which will produce a request to the upstream, summarizing the +changes between the `v1.0` release and your `master`, to pull it +from your public repository. + +If you pushed your change to a branch whose name is different from +the one you have locally, e.g. + + git push https://git.ko.xz/project master:for-linus + +then you can ask that to be pulled with + + git request-pull v1.0 https://git.ko.xz/project master:for-linus + GIT ---