Why "git fetch origin branch:branch" works only on a non-current branch?
Asked Answered
I

2

19

While working on a feature branch, I use this Git command to update my "develop" branch to the latest state, immediately before merging my feature branch with the "develop":

git fetch origin develop:develop

This works, i.e. the local "develop" points at the same commit as "origin/develop" and is in the latest state with origin.

Somehow, though, this command fails when the "develop" branch is checked out:

fatal: Refusing to fetch into current branch refs/heads/develop of non-bare repository
fatal: The remote end hung up unexpectedly

It would help me understand Git better, if I knew why it happens so.

Immuno answered 13/3, 2015 at 9:24 Comment(0)
C
25

The error message comes from builtin/fetch.c#check_not_current_branch().
That function goes all the way back to commit 8ee5d73, Oct. 2008, git 1.6.0.4.
(see also "Git refusing to fetch into current branch").

The comment is instructive:

Some confusing tutorials suggested that it would be a good idea to fetch into the current branch with something like this:

git fetch origin master:master

(or even worse: the same command line with "pull" instead of "fetch").
While it might make sense to store what you want to pull, it typically is plain wrong when the current branch is "master".
This should only be allowed when (an incorrect) "git pull origin master:master" tries to work around by giving --update-head-ok to underlying "git fetch", and otherwise we should refuse it, but somewhere along the lines we lost that behavior.

The check for the current branch is now only performed in non-bare repositories, which is an improvement from the original behaviour.

Considering that the function check_not_current_branch() is called with:

if (!update_head_ok)
        check_not_current_branch(ref_map);

That means a git fetch -u origin develop:develop should work.

-u
--update-head-ok

By default git fetch refuses to update the head which corresponds to the current branch. This flag disables the check.
This is purely for the internal use for git pull to communicate with git fetch, and unless you are implementing your own Porcelain you are not supposed to use it.

Even though you are not supposed to use that option, it does answer your initial requirement, making “git fetch origin branch:branch” work on a current branch.


Regarding the origin of this patch, follow the discussion there.

While it might make sense to store what you want to pull

That is the fetch part: it stores the remote history from the updated origin/master.
But that is especially broken when the current local branch is also master.
As mentioned in this answer:

I think "git fetch url side:master" when master is the current branch and we have omitted --update-head-ok is broken.
The test fails on current master.

It would also fail to update the working directory and would leave the index as if you're removing everything.


See "git pull with refspec" as an example.
torek shows an example where:

suppose that I run git fetch and it brings in two new commits that I will label C and D.
C's parent is A, and D's is the node just before B:

                C
               /
   ...--o--o--A   <-- master
               \
                o--B   <-- develop
                 \
                  D

The output from this git fetch will list this as:

  aaaaaaa..ccccccc  master     -> origin/master
+ bbbbbbb...ddddddd develop    -> origin/develop  (forced update)

That forced update might be what you want if your current branch is not develop.
But if you are on develop when you type git fetch origin develop:develop, and if the fetch was allowed to update HEAD, ... then your current index would reflect D, and no longer B.
So a git diff done in your working tree would show differences between your files and D, not your previous HEAD B.

That is bad, because your initial git checkout develop created a working tree identical to B HEAD files.
Even if your git status was clean (no modification of any kind), if git fetch origin develop:develop updated HEAD (forcing an update from B to D), git status would now report differences where there were none before the fetch.

That is why, by default git fetch refuses to update the head which corresponds to the current branch.


Note: a bug in Git 2.29 also triggers a similar error message.

When "git commit-graph"(man) detects the same commit recorded more than once while it is merging the layers, it used to die.
The code now ignores all but one of them and continues, fixed in Git 2.30 (Q1 2021).

See commit 85102ac, commit 150f115 (09 Oct 2020) by Derrick Stolee (derrickstolee).
(Merged by Junio C Hamano -- gitster -- in commit 307a53d, 02 Nov 2020)

commit-graph: ignore duplicates when merging layers

Reported-by: Thomas Braun
Helped-by: Taylor Blau
Co-authored-by: Jeff King
Signed-off-by: Derrick Stolee

Thomas reported that a "git fetch"(man) command was failing with an error saying "unexpected duplicate commit id".

$ git fetch origin +refs/head/abcd:refs/remotes/origin/abcd fatal: unexpected duplicate commit id 31a13139875bc5f49ddcbd42b4b4d3dc18c16576

The root cause is that they had fetch.writeCommitGraph enabled which generates commit-graph chains, and this instance was merging two layers that both contained the same commit ID.

The initial assumption is that Git would not write a commit ID into a commit-graph layer if it already exists in a lower commit-graph layer.
Somehow, this specific case did get into that situation, leading to this error.

While unexpected, this isn't actually invalid (as long as the two layers agree on the metadata for the commit). When we parse a commit that does not have a graph_pos in the commit_graph_data_slab, we use binary search in the commit-graph layers to find the commit and set graph_pos.
That position is never used again in this case. However, when we parse a commit from the commit-graph file, we load its parents from the commit-graph and assign graph_pos at that point.
If those parents were already parsed from the commit-graph, then nothing needs to be done. Otherwise, this graph_pos is a valid position in the commit-graph so we can parse the parents, when necessary.

Thus, this die() is too aggressive. The easiest thing to do would be to ignore the duplicates.

If we only ignore the duplicates, then we will produce a commit-graph that has identical commit IDs listed in adjacent positions. This excess data will never be removed from the commit-graph, which could cascade into significantly bloated file sizes.

Thankfully, we can collapse the list to erase the duplicate commit pointers. This allows us to get the end result we want without extra memory costs and minimal CPU time.

The root cause is due to disabling core.commitGraph, which prevents parsing commits from the lower layers during a 'git commit-graph write --split(man)' command.
Since we use the 'graph_pos' value to determine whether a commit is in a lower layer, we never discover that those commits are already in the commit-graph chain and add them to the top layer. This layer is then merged down, creating duplicates.

The test added in t5324-split-commit-graph.sh fails without this change. However, we still have not completely removed the need for this duplicate check. That will come in a follow-up change.

And:

commit-graph: don't write commit-graph when disabled

Reported-by: Thomas Braun
Helped-by: Jeff King
Helped-by: Taylor Blau
Signed-off-by: Derrick Stolee

The core.commitGraph config setting can be set to 'false' to prevent parsing commits from the commit-graph file(s). This causes an issue when trying to write with "--split" which needs to distinguish between commits that are in the existing commit-graph layers and commits that are not.
The existing mechanism uses parse_commit() and follows by checking if there is a 'graph_pos' that shows the commit was parsed from the commit-graph file.

When core.commitGraph=false, we do not parse the commits from the commit-graph and 'graph_pos' indicates that no commits are in the existing file.
The --split logic moves forward creating a new layer on top that holds all reachable commits, then possibly merges down into those layers, resulting in duplicate commits. The previous change makes that merging process more robust to such a situation in case it happens in the written commit-graph data.

The easy answer here is to avoid writing a commit-graph if reading the commit-graph is disabled. Since the resulting commit-graph will would not be read by subsequent Git processes. This is more natural than forcing core.commitGraph to be true for the 'write' process.

git commit-graph now includes in its man page:

Write a commit-graph file based on the commits found in packfiles. If the config option core.commitGraph is disabled, then this command will output a warning, then return success without writing a commit-graph file.

Cannonball answered 14/9, 2015 at 9:33 Comment(7)
Could you make clearer why git prevents git fetch origin master:master by default? I feel like the key to understanding this may be in this quote, but the meaning is unclear to me: "While it might make sense to store what you want to pull, it typically is plain wrong when the current branch is 'master'." What does the author mean by "storing what you want to pull"?Capreolate
@Capreolate I had to go back to the original thread for this patch: spinics.net/lists/git/msg82242.html. I have updated the answer.Cannonball
@Cannonball , I understand that they set the default behaviour of git fetch not to update HEAD. But why ? I saw this discussion but could not understand the reason why ?Overtake
@BreakingBenjamin I think (re-reading my answer 3 years later) that the last sentence is key: "It would also fail to update the working directory and would leave the index as if you're removing everything." In a non-bare repo, where there is an index, that is not what you want!Cannonball
@Cannonball , Srry. I still did not get you. If index is clean , then why not allow git fetch to update the current branch?Overtake
@BreakingBenjamin OK, I understand the context of your question: https://mcmap.net/q/12801/-git-pull-with-refspec. In that answer, torek described a case where git fetch origin develop:develop bring a new commit D instead of the current HEAD commit B. If develop was the current branch, that means all of a sudden, index would reflect B, and not B. Since a fetch does not update the working tree, a diff done in said working tree suddenly would show difference (against the new updated HEAD D) that were not there before the fetch. Hence the policy of disallowing fetch on the current branch.Cannonball
@BreakingBenjamin I have edited my answer to illustrate why a git fetch does not update HEAD if the branch fetched is the current branch. Let me know if that answer your comment question.Cannonball
D
0

git fetch only fetches data from the remote repo

  1. it does not update your local branches, even the're set up to track remote ones
  2. (because of 1) it can't fetch remote branch into local one. It is just not what git fetch do

According to man:

git fetch [ < options > ] [ < repository > [ < refspec > … ] ]

You can run this like git fetch origin develop and it will only update your remote branch refernece origin/develop

In order to update your local branch you can do this in one way of the following:

  1. explicitly specify what remote branch should be pulled to what local branch: git pull origin develop:develop
  2. if develop branch is checked out now and it is set up to track origin/develop you may just run git pull and it will figure out what to pull

update

More from man:

When git fetch is run with explicit branches and/or tags to fetch on the command line, e.g. git fetch origin master, the s given on the command line determine what are to be fetched (e.g. master in the example, which is a short-hand for master:, which in turn means "fetch the master branch but I do not explicitly say what remote-tracking branch to update with it from the command line"), and the example command will fetch only the master branch. The remote..fetch values determine which remote-tracking branch, if any, is updated. When used in this way, the remote..fetch values do not have any effect in deciding what gets fetched (i.e. the values are not used as refspecs when the command-line lists refspecs); they are only used to decide where the refs that are fetched are stored by acting as a mapping.

It is possible to update local branches, but I still don't understand why it is impossible to do while you're on the branch you want to update.

Downstage answered 13/3, 2015 at 12:10 Comment(2)
"it can't fetch remote branch into local one" - but it does! See my updated question. It works, on condition that the local "develop" is NOT checked out.Immuno
@MaDa, yep, you're right. I missed this...now I am confused too:)Downstage

© 2022 - 2024 — McMap. All rights reserved.