【问题标题】:How to Merge when you get error "Hint: You have divergent branches and need to specify how to reconcile them."How to Merge when you get error \"Hint: You have divergent branches and need to specify how to reconcile them.\"
【发布时间】:2022-12-27 18:02:00
【问题描述】:

I'm working with 1 other developer who has created a branch that needs to be merged with master.

I get this error when attempting to Pull in Git in Visual Studio Community (not Visual Studio Code) to a Bitbucket repo

If I attempt to Push it says "unable to push because your local branch is behind the remote branch".

This is the error:

Hint: You have divergent branches and need to specify how to reconcile them.
Hint: You can do so by running one of the following commands sometime before
Hint: your next pull:
Hint: 
Hint:   git config pull.rebase false  # merge
Hint:   git config pull.rebase true   # rebase
Hint:   git config pull.ff only       # fast-forward only
Hint: 
Hint: You can replace "git config" with "git config --global" to set a default
Hint: preference for all repositories. You can also pass --rebase, --no-rebase,
Hint: or --ff-only on the command line to override the configured default per
Hint: invocation.
Git failed with a fatal error.
Git failed with a fatal error.
Need to specify how to reconcile divergent branches.

I've found various things that discuss this, eg

https://laracasts.com/discuss/channels/code-review/git-pull-error-pulling-without-specifying-how-to-reconcile-divergent-branches-is-discouraged?page=1&replyId=773818

and How can I deal with this Git warning? "Pulling without specifying how to reconcile divergent branches is discouraged"

But none of them explain WHY this is happening and what the actions actually do.

How can I merge in what's in the other branch into master, why is this message coming up and what effect do all the suggestions in the hints have?

Thanks

【问题讨论】:

  • The reason it is happening is that what happens in your sandbox when you merge, rebase or fast-forward is quite different, so Git won't make the decision for you as to which one is best for you. Your task, now, is to understand the different options, and pick which one you want to use by default with you run git pull.

标签: git visual-studio


【解决方案1】:

As is often the case with confusing stuff in Git, there's some history involved.

The first thing to know is that git pull does too much stuff. Well, for some people (me), it does too much; others like that it does this much; but in fact, it does two jobs, each of which has its own separate Git command:

  1. git pull runs git fetch. Most, but not all, of the arguments you givetogit pull are passed directly to git fetch. So git pull meansrun git fetchand git pull origin somebranch meansrun git fetch origin somebranch.

  2. Assuming the first step succeeds, git pull runs a second Git command.

    The reason to have a step 2 at all is simple enough: git fetchobtains new commitsfrom some other Git repository, stuffing those new commits into your own repository where you now have access to them. But then itstops. Youhave access tothe new commits, butnothing is actually usingthe new commits yet. Tousethe new commits, you need a second step.

    Initially, that second step was always git merge. The git merge command is pretty big and complicated but it has a meaning that's pretty simple to describe:Mergemeanscombine work.Git will attempt to take work you have done, if you have done any, and work they have done, if they have done any, and combine the work, using simple and stupid automated rules. These rules have no clue as to how or why you did the work, or what anything you changedmeans. They just work based on "lines" in diffs.

    There are, however, four possibilities here:

    • Perhaps you did no work and they did no work. You got no new commits. There's literally nothing to do, and git merge does nothing.

    • Perhaps you did some work and they did nothing; you got no new commits; there's nothing to do and git merge does nothing again.

    • Perhaps you did no work and they did do some work. You got some new commits. Combining your lack-of-work with their actual work is easy and git merge will take a shortcut if you allow it.

    • Perhaps you and they both did work. You have new commitsandyou got new commits from them, and git merge has to use its simple-and-stupid rules to combine the work. Git cannot take any shortcuts here and you will get a full-blown merge.

    The shortcut that Gitmaybe able to take is to simply check out their latest commit while dragging your branch name forward. The git merge command calls this afast-forward merge, although there's no actualmerginginvolved. This kind of not-really-a-merge is trivial, and normally extremely safe: the only thing thatcango wrong is if their latest commit doesn't actually function properly. (In that case, you can go back to the older version that does.) So a "fast forward" merge is particularly friendly: there's no complicated line-by-line merging rules that can go awry. Many people like this kind of "merge".

    Sometimes the shortcut is not possible, and sometimes some people don'twantGit to take the shortcut (for reasons we won't cover here to keep this answer short, or short for me anyway). There is a way to tell git mergedo not take the shortcut, even if you can.

    So, for git merge alone, that gives us three possibilities:

    • nothing to do (and git merge is always willing to do nothing);
    • fast-forward is possible, but maybe Git shouldn't do it; and
    • fast-forward is not possible, which means this merge isn't trivial.

    The git merge command has options to tell it what to do in all but the "nothing to do" case:

    • (no flags): do a fast-forward if possible, and if not, attempt a real merge.
    • --ff-only: do a fast-forward if that's possible. If not, give an error stating that fast-forward is not possible; do not attempt a merge.
    • --no-ff: even if a fast-forward is possible, don't use the shortcut: attempt a full merge in every case (except of course the "nothing to do" case).

    The git pull command accepts all of these flags and will pass them on to git merge, should you choose to have git pull run git merge as its step 2.

    But wait, there's more

    Not everyone wants Git to do merges. Suppose you have made one or two new commits, which we'll call I and J, and your git fetch from origin brings in two new commits thattheymade since you started, which we will call K and L. That gives you a set of commits that, if you were to draw them, might look like this:

              I--J   <-- your-branch
             /
    ...--G--H   <-- main
             
              K--L   <-- origin/main
    

    You canfast-forwardyour main to match their origin/main:

              I--J   <-- your-branch
             /
    ...--G--H
             
              K--L   <-- main, origin/main
    

    And, whether or not you do that, you canmergeyour commit J with their commit L to produce a newmerge commitM:

              I--J
             /    
    ...--G--H      M   <-- your-branch (HEAD)
                 /
              K--L   <-- origin/main
    

    But some people prefer torebasetheir commits—in this case I and J—so that they comeaftercommit L, so that the picture now looks like this:

              I--J   [abandoned]
             /
    ...--G--H--K--L   <-- origin/main
                   
                    I'-J'  <-- your-branch
    

    Here, we havecopiedcommits I and J to new-and-improved commits I' and J'. These commits make the samechangesto L that I-J made to H, but these commits have different big-ugly-hash-IDs and look like you made themafterthe origin guys made their K-L commits.

    The git pull command can do this kind of rebasing:

    git switch your-branch
    git pull --rebase origin main
    

    does this all in one shot, by running git fetch to get their commits, then running git rebase with the right arguments to make Git copy I-J to I'-J' as shown above. Once the rebase is done—remember that, like git merge, it may have merge conflicts that you have to solve first—Git will move the branch name your-branch to select the last copied commit: J' in this example.

    Not very long after git pull was written, this --rebase was added to it. And since many people want this sort of thing to happenautomatically, git pull gained the ability todefaultto using --rebase. You configured your branch to do this (by setting branch.<em>branch</em>.rebase to true) and git pull would do a rebase for you. (Note that the commit on which your rebase occurs now depends on two things: theupstreamsetting of the branch, and some of the arguments you can pass to git pull. I've kept things explicit in this example so that we do not have to worry about smaller details, but in practice, you do.)

    This brings us to 2006 or 2008 or so

    At this point in Git's development, we have:

    • git fetch: obtains new commits from somewhere else (an "upstream" or origin repository for instance), often updating origin/* style remote-tracking names;
    • git merge: does nothing, or a fast-forward, or a true merge, of some specified commit or the branch's upstream;
    • git rebase: copies some set of existing commits to new-and-improved commits, using a specified commit or the branch's upstream, then abandons the original commits in favor of the copies; and
    • git pull: using the branch's upstream or explicit arguments, run git fetch and then run either git merge or git rebase.

    Because git merge can take --ff-only or --no-ff arguments, git pull must be able to pass these to git mergeifwe're using git merge.

    As time goes on, more options start appearing, such as auto-stashing, rebase's "fork point", and so on. Also, it turns out that many peoplewantrebasing to betheir defaultfor git pull, so Git acquires a new configuration option, branch.autoSetupRebase. When set to remote or always, this does what many of these folks want (though there are actually four settings today; I don't remember if it had four back then and have not bothered to check).

    Time continues marching on and we reach the 2020s

    By now—some time between 2020 and 2022—it has become clear that git pull does thewrong thingfor many, maybe even most, people who are new to Git. My personal recommendation has been toavoidgit pull. Just don't use it: run git fetch first, then look at what git fetch said. Then, if git fetch did a lot, maybe use git log next. Andthen, once you're sure whether you want git merge with whatever options, or git rebase also with whatever options,run that command.If you use this option, you are in full control. You dictate what happens, rather than getting some surprise from Git.I like this option: it's simple! You do need to run at least two commands, of course. But youget torun additional commandsbetweenthose two, and that can be useful.

    Still, if a git pull brings in new commits thatcanbe merged under git merge --ff-only, that often turns out to be what I want: do that fast-forward, or else stop and let me look around and decide whether I want a rebase, a merge, or whatever else I might want.1And that often turns out to be what others want as well, and now git pull, run with no arguments at all, can be told do that directly:

    git config --global pull.ff only
    

    achieves this.

    Meanwhile, the other two git config --global commands in the hint you show in your question make the second command be merge or rebase. Sonow, in 2022, it's easy to tell git pull to do whatIwould want it to do. Furthermore, it seems that the Git maintainers have come around to my point of view: that git pullwithoutsome forethought isbad, and newbies should not use it. So they've set up git pull so that it nowrequiresthat you pick one of these three options, if you want to run it with no arguments.2

    So, you need to pick one.Theolddefaultwasgit config pull.rebase false, but that was a bad default. I do not recommend it. Idorecommend git config pull.ff only(though I still don't actually use it due to 15+ years of habits).


    1One real-world example: I encounter some bug that's a problem for me. I make a change to the code that I know is wrong, but letsmegetmywork done. I commit this horrible hack. I then wait for the upstream to make changes. They do and I bring in the new commits. If they'vefixedthe bug, I want todropmy fix, not merge or rebase it. If theyhave notfixed the bug, I want to rebase my hack (which may or may not need some tweaking). The "have they fixed the bug" test requires something git pull cannot test on its own.

    2Note that running git pullwitharguments is not supposed to generate this kind of complaint. I still don't run it much, so I'm not quite sure what the bug was, but in the first round or two of implementation of the new feature, there was a bug where git pull would complain inappropriately. I believe it is fixed in 2.35 and am almost positive it's fixed in 2.36, which should be out any time now.

【讨论】:

  • You had a lot to say about it, and you clearly know more than me on this topic. But I'm still left wondering why Visual Studio isn't configured by default to work and merge changes (I thought that was what versioning software did.) And I personally am not using a command line. I'm just using the IDE. Does this mean Microsoft created versioning control within VS that is just unusable?
  • @JonathanWood: I haven't used VS, but I know people who have used it for many years: it predates Git, or at least has versions that were never intended to work with (and did not work with) Git. So its Git interface had to be bolted on later, and that's a recipe for problems. Git's approach to version control is unusual (as compared to most other VCSes), so anyone thinking along conventional lines back in the day when Visual Studio was conceived would not have anticipated the kinds of curves Git would throw at you.
  • what do you mean " simply check out their latest commit while dragging your branch name forward" ?
  • @CatherineIvanova: Abranch name, in Git, points to a single commit (see the drawings above in the "but wait, there's more" section). We run git checkout &lt;name&gt; or git switch &lt;name&gt; and we get the commit to which the name points. But now look at the first and second drawings: in the first one, main points to H, and in the second, main points to L just like origin/main. How do we make the firstbecomethe second? The way Git does it is to check out thecommitL (as found via name origin/main), then make thenamemain point to L.
  • Meanwhile, commit L isforward fromH: we move right-and-down from H to K, then right-only from K to L. So the name main simply "moved forward", or was "dragged forward", by this particular operation. It's a checkout-and-move-name operation, and Git calls this a "fast forward merge" even though there's no actualmerging.
【解决方案2】:

I had this issue using Visual Studio - the following command solved it for me

   git config pull.rebase false

【讨论】:

  • where did you run that command?
  • worked for me thanks. :) @ChrisHDog in terminal
  • hint: git config pull.rebase false # merge
【解决方案3】:

I ran into the same issue. What solved it for me is the running the following line of codes:

git config pull.rebase false
git pull
git commit -m "my commit message"
git push

Then, to check if everything is now working, run "git status" again.

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2022-12-02
    • 1970-01-01
    • 2022-12-27
    • 2022-12-27
    • 2022-12-27
    • 2017-12-25
    • 1970-01-01
    • 2022-09-14
    相关资源
    最近更新 更多