git rebase, keeping track of 'local' and 'remote'
Asked Answered
O

4

206

When doing a git rebase, I often have difficulty working out what is happening with the 'local' and 'remote' when resolving conflicts. I sometimes have the impression that they swap sides from one commit to the next.

This is probably (definitely) because I still don't properly understand the paradigm.

When rebasing, who is 'local' and who is 'remote'?

(I use P4Merge for resolving conflicts.)

Optimal answered 16/6, 2010 at 7:43 Comment(6)
Is it possible that reading this would help you? The rest of the tutorial is very helpful as well....Paste
Would #2959943 help? (not for the 'git svn' part, just for the 'git rebase' part)Grekin
@VonC, yes, that's exactly it. If you want to copy the relevant bit of your answer over here, I'll tick it (I really will this time, I promise!)Optimal
all right... I'll bite;) Relevant extracts posted.Grekin
I had exactly same problem and same feelings (When rebasing, who is 'local' and who is 'remote'?). Even I use P4Merge :-DAlvy
I'm hoping someone would open a pull request on the git project to name the branch what it is actually called!Bently
G
292

TL;DR;

To summarize (As Benubird comments), when:

git checkout A
git rebase   B    # rebase A on top of B
  • local is B (rebase onto),
  • remote is A

And:

git checkout A
git merge    B    # merge B into A
  • local is A (merge into),
  • remote is B

A rebase switches ours (current branch before rebase starts) and theirs (the branch on top of which you want to rebase).


kutschkem points out that, in a GUI mergetool context:

  • local references the partially rebased commits: "ours" (the upstream branch)
  • remote refers to the incoming changes: "theirs" - the current branch before the rebase.

See illustrations in the last part of this answer.


Inversion when rebase

The confusion might be related to the inversion of ours and theirs during a rebase.
(relevant extracts)

git rebase man page:

Note that a rebase merge works by replaying each commit from the working branch on top of the <upstream> branch.

Because of this, when a merge conflict happens:

  • the side reported as 'ours' is the so-far rebased series, starting with <upstream>,
  • and 'theirs' is the working branch. In other words, the sides are swapped.

Inversion illustrated

On a merge

x--x--x--x--x(*) <- current branch B ('*'=HEAD)
    \
     \
      \--y--y--y <- other branch to merge

, we don't change the current branch 'B', so what we have is still what we were working on (and we merge from another branch)

x--x--x--x--x---------o(*)  MERGE, still on branch B
    \       ^        /
     \     ours     /
      \            /
       --y--y--y--/  
               ^
              their

On a rebase:

But on a rebase, we switch side because the first thing a rebase does is to checkout the upstream branch! (to replay the current commits on top of it)

x--x--x--x--x(*) <- current branch B
    \
     \
      \--y--y--y <- upstream branch

A git rebase upstream will first change HEAD of B to the upstream branch HEAD (hence the switch of 'ours' and 'theirs' compared to the previous "current" working branch.)

x--x--x--x--x <- former "current" branch, new "theirs"
    \
     \
      \--y--y--y(*) <- upstream branch with B reset on it,  
                       new "ours", to replay x's on it

, and then the rebase will replay 'their' commits on the new 'our' B branch:

x--x..x..x..x <- old "theirs" commits, now "ghosts", available through reflogs
    \
     \
      \--y--y--y--x'--x'--x'(*) <-  branch B with HEAD updated ("ours")
               ^
               |
        upstream branch

Note: the "upstream" notion is the referential set of data (a all repo or, like here, a branch, which can be a local branch) from which data are read or to which new data are added/created.


'local' and 'remote' vs. 'mine' and 'theirs'

Pandawood adds in the comments:

For me, the question still remains, which is "local" and who is "remote" (since the terms "ours" and "theirs" are not used when rebasing in git, referring to them just seems to make an answer more confusing).

GUI git mergetool

kutschkem adds, and rightly so:

When resolving conflicts, git will say something like:

local: modified file and remote: modified file. 

I am quite sure the question aims at the definition of local and remote at this point. At that point, it seems to me from my experience that:

  • local references the partially rebased commits: "ours" (the upstream branch)
  • remote refers to the incoming changes: "theirs" - the current branch before the rebase.

git mergetool does indeed mention 'local' and 'remote':

Merging:
f.txt

Normal merge conflict for 'f.txt':
  {local}: modified file
  {remote}: modified file
Hit return to start merge resolution tool (kdiff3):

For instance, KDiff3 would display the merge resolution like so:

kdiff3

And meld would display it too:

Meld diff

Same for VimDiff, which displays:

Invoke Vimdiff as a mergetool with git mergetool -t gvimdiff. Recent versions of Git invoke Vimdiff with the following window layout:

+--------------------------------+
| LOCAL  |     BASE     | REMOTE |
+--------------------------------+
|             MERGED             |
+--------------------------------+
  • LOCAL:
    A temporary file containing the contents of the file on the current branch.
  • BASE:
    A temporary file containing the common base for the merge.
  • REMOTE:
    A temporary file containing the contents of the file to be merged.
  • MERGED:
    The file containing the conflict markers.

Git has performed as much automatic conflict resolution as possible and the state of this file is a combination of both LOCAL and REMOTE with conflict markers surrounding anything that Git could not resolve itself.
The mergetool should write the result of the resolution to this file.

Grekin answered 16/6, 2010 at 9:37 Comment(16)
For me, the question still remains, which is "local" and who is "remote" (since the terms "ours" and "theirs" are not used when rebasing in git, referring to them just seems to make an answer more confusing). The question is "who is local & who is remote" - so an answer surely requires mentioning the words "local" and "remote"Mick
@PandaWood: "local" is "current branch" (which becomes "theirs"), "remote" is "upstream branch" (which becomes "ours").Grekin
No, "local" is "ours" (so the upstream branch) and "remote" is "theirs" - the current branch. That's the inversion you describe.Summary
@Summary I would disagree with that, and I have edited my answer to provide some arguments supporting my previous comment.Grekin
@Grekin the reason i said that is this: When resolving conflicts, git will say something like: local: modified file and remote: modified file. I am quite sure the question aims at the definition of local and remote at this point. At that point, it seems to me from my experience that local references the partially rebased commits and remote refers to the incoming changes. Potentially the message git outputs there is just misleading?Summary
@Summary I now see your (valid) point, and I have edited the answer accordingly.Grekin
@Grekin very good edit, i was indeed refering to (and i think the question meant the same thing) git mergetool. That being said, i like your answer, this whole inversion thing is important to understand why git mergetool calls the branches as it does. I definitely did not before i read it.Summary
@Summary thank you for pointing out what I originally missed in that question: three years later, I am still learning from writing/editing the answer :)Grekin
So, to summarize: when you git checkout A; git rebase B local is B, remote is A. All I needed to know...Multiple
@Multiple indeed. I have added your summary on top of this answer ;)Grekin
I think all the confusion stems from the still hanging notions of centralized VCM - in DVCS, like git, it does not really make sense to speak all the time of "local" and "remote" as everything can very well be local only. git workflow mostly consists of continuously rebasing=rewriting the commits on your dev branch over stable - I guess ours and theirs were introduced to dispel the confusion - but they didn't quite.Stultz
I find this answer confusing as there is not always an "upstream branch". You can rebase one local branch onto an other.Footpound
@TorKlingberg When you rebase one local branch onto another, said "local branch" is your upstream branch during a rebase. There is amways an upstream branch when it comes to merging and rebasing.Grekin
git is such a clusterfk of usability. this makes no sense: when you git checkout A; git rebase B local is B, remote is A. If I checkout A then I am currently looking at the files as they exist on A, how is that in any way the remote ? (I'm not saying Benubird is wrong; I'm saying git has a stupid UX)Grueling
@Grueling Once the rebase starts, you are no longer looking at A, but HEAD becomes a commit of B (replayed) on top of A. "ours" or "local" is where HEAD is (as shown in https://mcmap.net/q/12202/-what-is-the-precise-meaning-of-quot-ours-quot-and-quot-theirs-quot-in-git)Grekin
@Grekin sure; my (ranting) point is it shouldn't take reading documentation, looking at diagrams, and having to browse StackOverflow. If only the command gave clear, unambiguous feedback. For example, instead of local/remote/theirs/ours/mine/yours, just show {branch A} and {branch B} or similar.Grueling
E
51

The bottom line

git rebase

  • LOCAL = the base you're rebasing onto
  • REMOTE = the commits you're moving up on top

git merge

  • LOCAL = the original branch you're merging into
  • REMOTE = the other branch whose commits you're merging in

In other words, LOCAL is always the original, and REMOTE is always the guy whose commits weren't there before, because they're being merged in or rebased on top

Prove it!

Certainly. Don't take my word for it! Here's an easy experiment you can do to see for yourself.

First, make sure you have git mergetool configured properly. (If you didn't, you probably wouldn't be reading this question anyway.) Then find a directory to work in.

Set up your repository:

md LocalRemoteTest
cd LocalRemoteTest

Create an initial commit (with an empty file):

git init
notepad file.txt  (use the text editor of your choice)
  (save the file as an empty file)
git add -A
git commit -m "Initial commit."

Create a commit on a branch that isn't master:

git checkout -b notmaster
notepad file.txt
  (add the text: notmaster)
  (save and exit)
git commit -a -m "Add notmaster text."

Create a commit on the master branch:

git checkout master
notepad file.txt
  (add the text: master)
  (save and exit)
git commit -a -m "Add master text."

gitk --all

At this point your repository should look like this:

Repository with a base commit and two one-commit branches

Now for the rebase test:

git checkout notmaster
git rebase master
  (you'll get a conflict message)
git mergetool
  LOCAL: master
  REMOTE: notmaster

Now the merge test. Close your mergetool without saving any changes, and then cancel the rebase:

git rebase --abort

Then:

git checkout master
git merge notmaster
git mergetool
  LOCAL: master
  REMOTE: notmaster
git reset --hard  (cancels the merge)

Your results should be the same as what's shown up top.

Equality answered 2/5, 2014 at 18:41 Comment(1)
+1. That clarifies the local/remote aspects I struggled with in my own answer above (which is more about the inversion of ours vs theirs anyway)Grekin
T
3

I too, was confused for a long time, often making the wrong decision and had to start over.

I think I have come to the realization that my confusion was because I pictured a rebase differently than what many draw it. Here are two drawings that commonly are used to describe a rebase:

  --1--2--3--4--5
     \
      6--7--8

and then

  --1--2--3--4--5--6--7--8

And of course that is one way to draw it, but my feeling of what is happening with a rebase is this:

  --1--2--3--4--5
                 \
                  6--7--8

Which of course is exactly the same. But from an "ours/theirs" perspective it is different. In the second case it feels as if "we" are still "on" the branch ("6--7--8") and we want to pick up the changes from the "master". So in this world "ours" is still the "branch". And this is what confused me.

But in the first "world view", which I suppose is Git's view, we move to the master (the commit we want to rebase onto) and from there we pick each of the commits on the branch in turn and apply them. So "ours" becomes the "master", initially 5. After 6 has been applied successfully, "ours" is the 6, but actually the 6' that is "on" the master:

  --1--2--3--4--5--6'
     \
      6--7--8

And then we go on for the same with the "7".

So in a merge you "are on" the 8 and combine the two into a new commit, but in a rebase you move to the 5 and try to apply the diffs in the commits on the branch as new commits there.

So the "true" picture of the end result of a rebase should really be:

  --1--2--3--4--5--6'--7'--8'
     \
      6--7--8

And after the rebase you are on 8'. And so is you branch (I think!). And this could be visualized (in my mind) as:

  --1--2--3--4--5
     \           \
      6--7--8     6'--7'--8'
Tolerant answered 18/9, 2020 at 8:2 Comment(6)
The simple rule is "ours" or "local" is where HEAD is. And during a rebase, HEAD is at 6', then 7', then 8'.Grekin
"Because in that case the 6 only gets a new parent and we need no new commits?": 7 also gets a new parent: 6'. 7 becomes therefore 7', because its parent has changes. Same for 8.Grekin
@Grekin Do you mean that in the case of fast-forward we still get 6', 7' and 8'? Or do you mean, as my last picture tries to show, that if we can't fast-forward the whole sequence generates new commits?Tolerant
If your answer is about rebase, there is no "fast-forward": a rebase would always trigger new commits because their parent are changing.Grekin
Yes, you are right. I'll remove that comment. I was thinking of pull with rebase, isn't that the same thing? Or maybe that is to long a discussion/tutorial to have in comments ;-)Tolerant
A pull with rebase (which can be set by default: https://mcmap.net/q/12278/-can-quot-git-pull-quot-automatically-stash-and-pop-pending-changes) is a fetch + git rebase origin/master (assuming master here). If there is any new commits in origin/master, there won't be any fast-forward for the rebase, since each local commit is replayed (new parent) on top of origin/master. If there is no new commits... the rebase is a no-op (it does nothing, not even a fast-forward).Grekin
B
1

I didn't get your problem exactly but I think the following diagram resolves your issue. (Rebase : Remote Repository ---> Workspace)

https://static.mcmap.net/file/mcmap/ZG-Ab5ovKR_xbRyAbw2vb1-lXVYlKmMva3/images/2008/git-transport.png

Source: My Git Workflow

Bezel answered 16/6, 2010 at 7:50 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.