Let's say I've got 2 branches, master and codyDev. Before I begin making changes on codyDev, I do the following in order to make sure I'm starting from the latest master commit on codyDev:
git checkout master
git pull
git checkout codyDev
if changes, git merge master
Is there a shortcut for the first 3 steps, so I don't have to leave my codyDev branch? I tend to find out that the master has not been updated and the checkout/pull/checkout was 3 unnecessary commands.
Note that I did Google to try to find an answer to my question, but there seems to be a wide range of potential and somewhat complicated solutions. The one that seemed most appealing was something to the affect of git fetch origin master:master
, but the explanations I read were not super clear because the examples were a bit more complicated. I also found myself wondering "why a fetch instead of a pull" (i.e. git pull origin master:master
)?
You can update your codyDev
branch with the latest changes from master
without actually changing branches:
git checkout codyDev # ignore if already on codyDev
git fetch origin # updates tracking branches, including origin/master
git merge origin/master # merge latest master into codyDev
The trick here is that to merge the latest master
into codyDev
you don't actually need to update your local master
branch. Instead, you can just update the remote tracking branch origin/master
(via git fetch
) and then merge it instead.
You can also directly update the local master
branch using git fetch
without actually checking out the local master
branch:
# from branch codyDev
git fetch origin master:master
However, this will only work if the merge is a fast-forward of the local master
branch. If the merge be not a fast-forward, then this will not work, because a working directory is required in case merge conflicts arise.
Read here for more information.
You could add an alias to your ~/.gitconfig
.
[alias]
start = !git checkout master && git pull origin master && git checkout -b
Or run this to add it from the command line :
git config --global alias.start '!git checkout master && git pull origin master && git checkout'
Also, it might not be clear from the code above, but make sure there is a space after -b
.
You could also omit the -b
and pass it in so you have the option of operating on an existing branch. In that case, you would have :
[alias]
start = !git checkout master && git pull origin master && git checkout
And then to call it would be
git start -b newBranch
Or
git start existingBranch
All "perfect" solutions require something at least a little tricky, or Git's new (since version 2.5) git add-worktree
feature. Tim Biegeleisen's method is the simplest method, and maybe the best, but not exactly the same as your current workflow, though perhaps your current workflow is not ideal anyway.
There are several keys here:
First, git pull
is itself just a shortcut. It "means" (slightly roughly) git fetch && git $something
, where $something
is either merge
or rebase
. Which one you get—merge vs rebase—depends on how you have configured your Git repository and branches.
The git fetch
step is where your Git actually picks up new commits from origin
, if there are any to pick up.
Second, git merge
may do something called a fast-forward merge. This is kind of a misnomer, because a fast-forward is not actually a merge at all. And, while git rebase
just does a rebase, if you have no commits of your own, the effect is the same as a fast-forward.
Third, you need to understand how Git manages your commit graph and branch labels. This is going to tie together the fetch and the not-quite-merge fast-forward.
Fourth (and perhaps the most complicated part), git fetch
does something interesting using what Git calls refspecs. This is why git fetch origin master:master
is interesting, and also answers the question:
"why a fetch instead of a pull" (i.e.
git pull origin master:master
)?
(in fact there is no such thing, because git pull
bypasses the fetch refspecs when it runs git fetch
for you).
Let's start with the third item first, because it's actually quite fundamental. If you already know all this, feel free to skip forward...
Git's commit graph is a Directed Acyclic Graph or DAG. This actually means a lot, but for simple usage purposes we can boil it down to this: each commit has an ID—the big ugly SHA-1 hashes like 7452b4b5786778d5d87f5c90a94fab8936502e20
—and each commit records its parent commit ID(s), which, in effect, means each commit "points back" to its parent(s). Most commits have just one parent, but merge commits have two (or more but we'll stick with two). There's also at least one commit—the first commit you (or whoever) made—that has no parent: it can't have had a parent, because it was the first commit.
This means we can draw the commit graph. If we put older commits on the left and newer ones on the right, we get something like this:
o <- o <- o <-- master
This graph has no merges at all and just three commits: the first one ever, then the second (which points back to the first), and finally the third (which points back to the second). Each commit is kind of boring so we have a little round o
, and each has one arrow pointing left, to its parent.
I also threw in the branch name, master
. It points to the last commit on the branch—the third commit in the graph. This last commit is the tip of the branch, and a branch name is really just a pointer to the tip-most commit.
What this means is that if we add a new commit, the branch name moves, so that it still points to the tip-most (newest) commit. This new commit points back to where the branch-name used to point:
o <- o <- o <- o <-- master
All the inner arrows are kind of boring as well (we know they point leftward, to older commits) so I like to draw these graphs in a more compact way:
o--o--o--o <-- master
This leaves a lot more room for more complicated graphs that branch:
o--o--o <-- master
\
o--o <-- dev
and then maybe merge again (here dev
is "merged into" master
, i.e., someone did git checkout master && git merge dev
):
o--o--o---o <-- master
\ /
o--o <-- dev
They might also keep going:
o--o--o---o <-- master
\ /
o--o--o <-- dev
(After someone merged dev
into master
, someone—maybe the same someone, maybe someone else—made another commit on dev
.)
We already mentioned that the branch name, like master
or dev
, points to the tip-most commit on that branch. But earlier commits are also on the branch, and once we have a merge commit—a commit with at least two parents: two left-pointing arrows—this causes a bunch more commits to suddenly be on the branch. That is, now a bunch of commits are on both branches.
Let's look at this graph again, but use single uppercase letters for each commit, instead of just an o
:
A--B--D---F <-- master
\ /
C--E--G <-- dev
Commit F
is the merge commit. Note how it has two leftward links, one to D
and one to E
. (The arrow to E
points down-and-left, and the arrow from C
to B
points up-and-left, but they still point left, i.e., to some earlier commit.) Commits C
and E
are on branch dev
, but they are also on master
. Commit A
is on master
, but it is also on dev
.
In Git, a commit can be on more than one branch.
The more general term here is "reachable": we look at commits in terms of how (and whether) we can reach them while following the one-way arrows that go from newer commits to their older parent commits. When we reach a merge, which has two parents (or more, but again we'll stick with two), we take both paths simultaneously.
A branch name simply points to the tip commit of the branch. But more than one name can point to some commit. In the graph above, master
points to F
and dev
points to G
. If we check out branch master
, then create a new branch feature
, this new branch will also point to F
:
A--B--D---F <-- master, feature
\ /
C--E--G <-- dev
If we now make a new commit H
, branch feature
needs to change to point to H
, while H
needs to point back to F
. That is, we need to get this new picture (or some variation on it—there are always many ways to draw each graph):
H <-- feature
/
A--B--D---F <-- master
\ /
C--E--G <-- dev
How does Git know to move feature
and not master
?
Both of them point to commit F
. If all Git knew is that F
was the current commit, Git wouldn't know which branch name to change. Clearly Git must know which branch we're actually on. And it does: that's what the file HEAD
is for. The HEAD
file contains the name of the current branch.
HEAD
contains the name of the branch, and the branch name contains the tip commit ID. This is what it means to be "on a branch" in the first place: if you're on branch master
, HEAD
has the name master
in it. If you're on dev
, HEAD
has the name dev
. If you're on feature
, HEAD
has feature
in it. Whatever branch you're on, that's what's in HEAD
.1
The git checkout
command changes the branch name stored in HEAD
. (It does more as well, but that's how it changes which branch you are on.) The git commit
command, once it writes a new commit, stores the new commit's ID in the current branch, read from HEAD
. And, the new commit's parent ID is usually whatever commit HEAD
points to, or rather, whatever commit the branch-that-HEAD
-points-to, points to.2 That is, HEAD
points (indirectly) to the current, branch-tip, commit, and the new commit then points to that as its parent and git commit
updates the branch name and now the new commit is the new branch-tip.
1A "detached HEAD", which sounds scary, just means that HEAD
has the raw hash ID of a commit in it instead of a branch name.
2I say "usually" because git commit --amend
works by setting the new commit's parent to the current commit's parent, instead of to the current commit itself. In effect, instead of adding a new commit to the chain, it shoulders the current commit aside. The new commit is still the end of the chain, but now the new current commit's parent linkage bypasses the "amended" commit, making it seem to be gone.
Merging itself can be complicated and messy, but the act of making a merge commit is extremely straightforward. Git makes a new merge commit in exactly the same way as it makes a regular (non-merge) commit, with the exception that the new commit has two (or more) parent IDs stored in it.
The first parent ID is the same parent as always: the current branch's tip-most commit, i.e., the HEAD
commit.
The second parent ID is the commit you just merged: the tip of the other branch.
This is how Git made commit F
in our example graph. We ran git checkout master
first, so that HEAD
said master
and master
said D
. Then we ran git merge dev
, and dev
said E
:
A--B--D <-- master
\
C--E <-- dev
Git did the stuff it needs to do, to come up with the files to stick into the new merge commit. This "stuff" consists first of finding the merge base: the commit where the two branches join up. That's commit B
. Git figured out how to merge the changes on master
, from B
to D
, with the changes on dev
, from B
to E
. Then Git made the new commit F
. F
's first parent is D
and F
's second parent is E
, and once F
was all made, Git wrote its ID into master
:
A--B--D---F <-- master
\ /
C--E <-- dev
Git had to make a real merge here because of commit D
. Let's make a new, different repository and make some commits:
A--B <-- master
\
C--D <-- dev
Now suppose we git checkout master
here, and run git merge dev
. The tip of dev
is D
and the tip of master
is B
. The merge base is where the branches first join up, which is commit B
again.
Notice anything special about B
?
It's the tip of master
. The current commit, B
, is also the merge base. There are no changes from B
to B
: B
is already itself. There are changes from B
to D
, on dev
, but master
has no changes of its own.
What this means is that Git can "slide the branch label forward". Instead of having master
point to B
, Git can just slide the label forward to D
. (This goes against the direction of the arrows, i.e., forward in time instead of backward, but it is easy for Git to find, since we told Git to look at dev
, which points to D
. Git just has to notice that the current commit, B
, is already an ancestor of D
.)
So now Git does a fast-forward label move:
A--B
\
C--D <-- dev, master
and now master
points to commit D
. The graph no longer even needs the kink in it—we only had to draw it this way to leave room for master
to point to commit B
. Now we have a simple A<-B<-C<-D
chain with both labels pointing to D
.
This is what a fast-forward is: the act of "sliding a branch label forward, against the direction of the parent arrows." Note that it cannot be done if there's a commit in the way:
o--*--o <-- br1
\
o--o <-- br2
You can't "slide br1
forward" to point to the tip of br2
. You would first have to slide it back to the merge base *
commit before you can slide it forward. If you move br1
to point to the same commit as br2
, the final commit along the top line is no longer reachable from br1
, and it stops being on branch br1
. If there is no other label pointing to that tip commit (or to a commit that points to it), it gets "lost".
git fetch
and git push
The fetch and push operations both do the same kinds of things, so we can cover both of them here.
In both cases, you have your Git call up another Git, usually over ssh://
or https://
, i.e., over the Internet-phone. Your Git and their Git then have a little talk, where yours and theirs find out which commits you have that they don't, or vice versa. (Which one depends on whether you're fetching or pushing.)
Then, after the two Gits have agreed about which commits they both know about—using their SHA-1 hash IDs, which are always the same on both sides3—the "sending" Git sends over the commits and other objects needed, and the "receiving" Git saves those away in its repository. Then, at the end of all of this, whichever end is sending commits has also sent some <branch-name, tip-commit> pairs. When you are git fetch
-ing from origin
, origin
's Git sends your Git these name/ID pairs. (When you are pushing, your Git sends the pairs, and this is where the symmetry between fetch and push is deliberately broken.)
Let's look exclusively at the git fetch
case now. In normal, standard setups,4 their Git sends you all their branch tips and all the commits and other objects your Git needs to use those. Your Git then renames the branch tips: instead of "branch master
", your Git calls it "remote-tracking branch origin/master
". (Here I'm assuming the remote is named origin
.) Instead of dev
, your Git uses origin/dev
. In fact, there's a slightly longer, more precise form for this: it's actually +refs/heads/*:refs/remotes/origin/*
. But the key take-away here is that there is this renaming that occurs, from branch
to origin/branch
, so that their branches become your remote-tracking branches.
In any case, when your Git updates your remote-tracking branches, it does a "forced update" if needed. That is, when it's updating your origin/master
, your Git first checks whether you have origin/master
at all. If not, it just creates it, pointing to the same tip commit we just got from them. If so, your Git checks: is this a fast-forward? If it's a fast-forward operation, your Git updates your origin/master
using a fast-forward. If not, your Git says: well, we'll just forget some commit(s) we used to have on origin/master
, and take their master
and call it origin/master
anyway. We'll just re-set our origin/master
as a "forced update":
Receiving objects: 100% (992/992), 370.76 KiB | 0 bytes/s, done.
Resolving deltas: 100% (775/775), completed with 142 local objects.
From [url]
e0c1cea..9194226 maint -> origin/maint
6ebdac1..35f6318 master -> origin/master
f2ff484..15890ea next -> origin/next
+ 7d82ce0...eb0e753 pu -> origin/pu (forced update)
fa7f92e..2d15542 todo -> origin/todo
That's what the "forced update" means: that the update was not, in fact, a fast-forward operation.
3This almost magical trick is the key to distributing repositories. Both Git and Mercurial use this kind of universal hash ID to make it all go. The details are beyond the scope of this article.
4"Mirror" repositories do this differently, but all through refspecs.
These updates are controlled by refspecs. A refspec is, in its second-simplest form,5 just a pair of branch-names with a colon between them: master:master
, or master:origin/master
. The name on the left is the source, and the name on the right is the destination.
For fetch
, the source is the remote Git, and the destination is your own repository. (For push
, this gets reversed, of course.)
A refspec can start with a leading plus sign, as in +master:origin/master
. This leading plus sign is the "force flag". More precisely, it's a per-refspec force flag: you can set it, or leave it out, of each refspec (vs the global --force
option, which sets it for every refspec on the command line).
If you leave out the force flag, Git will only do fast-forwards.
This is the key to using git fetch
to update your own branches. A fast-forward update does not lose commits. A forced update could lose commits. So if you run git fetch
with a pair of branch names, like master:master
, your Git will only update your master
to match the remote's master
if that update is a fast-forward.
There's a serious complication here though. What if it's not a fast-forward? What if the upstream Git users have "rewound" the branch, the way the Git repository for Git rewinds the pu
(pickup) branch all the time? (Note the "forced update" above.) If your Git fetches a bunch of commits, but then sees that the update is not a fast forward, your Git simply doesn't update your labels, unless you've set the force flag.
This is why Git uses remote-tracking branches. Since the remote-tracking branch origin/master
is not just master
, it's safe to update origin/master
, even as a forced update. Either you have your own branches that hang on to commits that the upstream is trying to retract, or you don't. If you do have those commits on branches of your own, your Git still has those commits, because your branches are unaffected. If you don't, Git assumes you don't care about the retraction: your remote-tracking branches are going to track the remote.
(If you run git fetch
twice, with a few minutes in between, and you get some update and then it gets retracted ... well, if you hadn't run that first git fetch
just then, you never would have seen the retracted commits. This means they can't be that important. If you saw them and wanted to hang on to them, you would have made a branch.)
There's one more complication here though: Git normally won't let git fetch
—or git push
, for that matter—update the current branch (the one whose name is stored in HEAD
).6 The reason for that has to do with something we skipped over, above: when you git checkout
a branch, Git does not just write to HEAD
. It also fills your index and work-tree from the tip commit of that branch. If git fetch
were to change the tip commit, with no notice, your index and work-tree would get out of sync. Worse, if you have some changes in progress, your index would fail to catch up with the new commits.
5The simplest form is without the colon, e.g., git push master
. What this means is more complicated, though, because it means different things in git fetch
and git push
. For git fetch
, it means "nothing to update locally", except that since Git version 1.8.4, Git still does "opportunistic updates" of remote-tracking branches. For git push
, this simplest-refspec form means "use the upstream name", which is usually the same name as the local branch.
6That is, if there is a work-tree. If the repository is "bare" (has no work-tree), git push
will let the current branch get updated.
I have run out of energy :-) but let's see if we can summarize:
git checkout master
: switches the index and work-tree to master
and writes master
into HEAD
git pull
: fetches from the current branch's (master's) remote and then runs git merge
:
fetch
step brings over their master
, which becomes your origin/master
.7
merge
step either really-merges, or fast-forwards, your master
to bring in commits you now have on your origin/master
.git checkout codyDev
: switches the index and work-tree to codyDev
and writes codyDev
into HEAD
git merge master
: does a real or fast-forward merge of your master
, which (due to the earlier update) is now merged with, or fast-forwarded-to, the master
on the remote (presumably origin
).Instead of doing all this, you can, as in Tim Biegeleisen's answer, just run git fetch
or git fetch origin
—which brings over all their branches and updates your origin/master
remote-tracking branch—and then git merge origin/master
.
What's different is now obvious: this does not update your master
. Do you need it to? There is one other difference, not quite as obvious: the git merge
command will make a default commit message that says "merge remote-tracking branch origin/master" instead of one that says "merge branch master". Do you care? (You can edit the message to remove the remote-tracking adjective and the origin/
part, if you do care.)
7Assuming your Git is 1.8.4 or newer. Otherwise the opportunistic update gets skipped. The pull
script still manages to merge the right commit, but your origin/master
drifts further and further behind. (If you have an ancient Git, upgrade to a modern Git.)
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With