RMK
RMK

Reputation: 1171

What is the difference between these two git fetch commands?

Command 1: git fetch origin refs/pull/ID/merge:BRANCHNAME

Command 2: git fetch origin pull/ID/head:BRANCHNAME

Both are used to checkout a PR as a local branch.

I am using a Continuous Integration tool that internally uses the command 1 to checkout the PR, but in some cases its failing with error message fatal: Couldn't find remote ref refs/pull/11/merge .

Please help me to understand the reason of error

Edit 1:

My actual problem is , my CI is unable to get the code of PR ,if PR is from a fork and has conflicts.

If the same PR with no conflicts is working fine.

Branch level PRs working fine , with or without conflicts .

This is what my CI is doing to get the code of PR.

for PR from fork with no conflicts.

git clone --no-checkout https://mygithubenterprise.com/org/repo .
git cat-file -e c14714a5e9a33bd64f94f6af8ebc81fe64223c38
git fetch -t https://mygithubenterprise.com/org/repo refs/pull/173/merge
git reset --hard c14714a5e9a33bd64f94f6af8ebc81fe64223c38 --
git branch -M refs/pull/173/merge
git reset --hard c14714a5e9a33bd64f94f6af8ebc81fe64223c38 --
# done checking out code

for PR from fork with conflicts.

git clone --no-checkout https://mygithubenterprise.com/org/repo .
git cat-file -e 59ca091ee41a0b1170955378c324d2036ff5406c
git fetch -t https://mygithubenterprise.com/org/repo refs/pull/171/merge
fatal: Couldn't find remote ref refs/pull/171/merge

How can I avoid this problem, I want checkout to PR even if it has conflicts.

Upvotes: 0

Views: 403

Answers (1)

torek
torek

Reputation: 487755

Command 1 asks for the fully-qualified name refs/pull/ID/merge, and command-2 asks for the not-fully-qualified named pull/ID/head. If the two names both ended in head or both ended in merge, there would be no practical difference between them, since—as documented in gitrevisions (click on the link to see the six steps), if Git cannot find .git/pull/11/head (assuming the ID is 11 and the suffix is head) it will next look for .git/refs/pull/11/head, and .git/pull/ should never exist.

But the names aren't the same. One asks for refs/pull/11/head and the other asks for refs/pull/11/merge. You must look for the pull request under the name given to it. Whoever created the pull request gave that request its name; you must use the same name.

(For myself, if I am going to use a name like this, I always use the fully-qualified form, i.e., refs/pull/ID/suffix, rather than the version with refs/ stripped off. This way Git stops at step 1 of the six gitrevisions resolution steps, with no chance of bad things happening if the unqualified name starts with a directory name like info or logs.)


Note: according to some of the discussion entries in https://gist.github.com/piscisaureus/3342247, the way GitHub uses the refs/pull namespace is to put the original request itself under ID/head, and the result of merging (assuming the merge works) in ID/merge. It's not entirely clear to me precisely how GitHub uses the head and merge names, but I can make some good guesses based on the discussion.

Re "edit 1"

It appears to be the case that, when invoking the pull request machinery on GitHub, if the pull request merges cleanly1 as far as Git is concerned, GitHub makes a merge commit of the merged result, and labels that with the ID/merge name. If it does not merge cleanly, GitHub discards the attempted merge and does not create the corresponding ID/merge name.

In other words, a pull request is simply formed by attaching the label ID/pull to some specific commit that someone has pushed to GitHub. For instance, some pull-request user—let's call him R for Request–might push three commits that append to some existing main branch (this is the view in R's repository):

...--o--o             <-- main-branch
         \
          o--o--o     <-- R

The main-branch may or may not have additional commits appended to it, e.g., may, but is not required to, become (this is the view from the GitHub repository):

...--o--o-------o     <-- main-branch
         \
          o--o--o     <-- R

before, or even after, R does his push to create this branch R.

At this time, the user in question clicks on "make a pull request". When he does so he provides the name of the branch he would like some other person—let's call this other person the Manager, as this other person manages the main branch—to merge R's commit(s) into.

GitHub now attempts the merge, which it does by running git merge --no-ff.2 This forces a real merge commit even if a fast-forward merge is possible. If the real merge succeeds (see footnote 1 again), this creates a new merge commit:

...--o--o---------o     <-- main-branch
         \         \
          \         o   <-- [one name goes here]
           \       /
            o--o--o     <-- R, [another name goes here]

If the merge fails, GitHub discards the merge attempt, which leaves us with:

...--o--o-------o     <-- main-branch
         \
          o--o--o     <-- R, [another name goes here]

Note that the only difference between these is the presence of the final merge commit, and the extra name.

The two names are refs/pull/ID/head, which points to the same commit as R, and refs/pull/ID/merge, which points to the merge commit. The second, merge-suffixed name, is created if and only if the automatic merge succeeds.

What this means is that you can use the presence or absence of the merge-suffixed name to tell whether GitHub created the merge automatically. If they did, it also tells you which commits were merged, as these are the two parents of the corresponding commit. That's not sufficient to identify the actual branch names, because the graph could look like this:

...--o--o---------o     <-- main-branch, second-name, third-name
         \         \
          \         o   <-- refs/pull/11/merge
           \       /
            o--o--o     <-- R, fifth-name, refs/pull/11/head

While refs/pull/11/merge^1 points to the same commit as main-branch, it also points to the same commit as does branch name second-name and branch name third-name. So it's not at all clear how Manager is supposed to know, at least from this information alone, which branch is the target of the pull request, i.e., which branch is supposed to wind up pointing to this same merge commit.

Of course, as noted in footnote 2, GitHub can be told to make squash "merges" instead of real merges. I have not seen GitHub itself ever make fast-forward merges but I do not know that it will not (I use GitHub only lightly myself). It seems, though, that GitHub always makes a real merge at the time of the pull request, even if Manager will come along later and make a squash-merge from the request.

For more, including the disposition of the two refs/pull name-space labels after the Manager has merged or closed the pull request, see the discussion from which I assembled the above.


1All this means is that Git itself found no conflicts. The resulting merge is not necessarily correct. If automated tests are available (they are on some systems that integrate with GitHub), those automated tests can detect at least some mis-merges, where Git thinks things are OK and yet things are not actually OK. The reliability of these automated tests depends on their thoroughness and the skills of whoever wrote them, of course.

2When actually adding a merge to a branch based on someone's pull request, the person using the GitHub interface—the Manager, in our example here—can choose to make a squash "merge", which is a single commit whose content is the same as the earlier successful automatic merge, but whose parent is just one parent:

...--o--o---------o--o  <-- main-branch
         \         \
          \         o   <-- refs/pull/11/merge
           \       /
            o--o--o     <-- R, refs/pull/11/head

where the last commit on main-branch is identical in terms of snapshot as the commit to which refs/pull/11/merge points, but is not a merge commit and is not connected to the three commits ending in the one to which refs/pull/11/head points.

Upvotes: 1

Related Questions