Reputation: 5650
I try to find a way to checkout to the latest commit in the current branch of a repository. I think this is trivial, I just can't come up with the right incarnation of commands.
To describe my problem a bit more in detail. This is what I want to achive. I want to automatically update the working directory of a git repository with the latest version from server but keeping whatever branch the repository is on right now and I don't care about local changes.
So first I run git fetch to get all commits from server. Then I would do git checkout HEAD to advance to the latest commit or even git chekout -f HEAD since I don't care about local changes. Unfortunately this tells me:
Your branch is behind 'origin/master' by 1 commit, and can be fast-forwarded. (use "git pull" to update your local branch)
But I do not want a pull since that would merge with local changes imho. A git reset --hard also does not work since it would not advance to the latest commit. And a git chechout master also does not work since I don't know if this repository is on branch master or a different one.
So I think the command I am looking for would be something like this:
git checkout -f `git show_lastest_commit_on_current_branch`
P.s.:
While writing this I think I came up with a solution. First run a git reset --hard to throw away any local changes and then I can safely run git pull since there is nothing more which could create a conflict. Is this the right thing to do?
Upvotes: 1
Views: 4918
Reputation: 488183
I probably would not do this in the first place. I'm not sure what your setup is, but in general, if there's anything that git reset --hard
would obliterate, no automated script should be doing that at all. If you want to have automated deployment, the deployment area probably should not be using Git this way in the first place. (That is, “don't use Git at all” or “use Git, but treat the entire deployed branch as ‘build artifacts’ that are not source-controlled” would be the rule here.)
That said, if I had to do this, I'd probably go for the method ElpieKay outlined, or something equivalent, such as git fetch && git reset --hard @{u}
.
Note that if you want to get the name of the current branch, there are two ways to do this in a shell (sh or bash) script:
branch=$(git symbolic-ref HEAD) || exit
branch=${branch#refs/heads/}
Now $branch
is, e.g., master
or deploy
or whatever. If the repository is in "detached HEAD" mode, the call to git symbolic-ref HEAD
printed:
fatal: ref HEAD is not a symbolic ref
to standard error, and then the || exit
clause made the script quit. (Adjust behavior as desired by changing the script; add -q
to suppress Git's message to stderr.)
Alternatively:
ref=$(git rev-parse --symbolic --abbrev-ref HEAD)
Now $ref
is, e.g., master
or deploy
or whatever, unless the repository is in "detached HEAD" mode, in which case $ref
is the literal string HEAD
. (This is why I used the name ref
rather than branch
.)
@{u}
The @{u}
syntax is short for @{upstream}
and is described in the gitrevisions
documentation. In General, any place where Git needs a particular commit ID, you may spell it with any number of revision specifications. A branch name like master
simply translates to the tip commit on that branch. Adding @{upstream}
directs Git to:
branch.$branch.remote
, e.g., branch.master.remote
is probably origin
branch.$branch.merge
, e.g., branch.master.merge
is probably refs/heads/master
remote.remote.fetch
, e.g., remote.origin.fetch
is probably +refs/heads/*:refs/remotes/origin/*
refs/remotes/origin/master
is the full spelling for origin/master
Thus, git reset --hard @{u}
"means" git reset --hard origin/master
if you're on branch master
and your local master
is set to track remote-tracking branch origin/master
.1 Note that because @{u}
looks up the current branch, it fails immediately if you are in "detached HEAD" mode: a detached HEAD is not a named branch2 and hence cannot be tracking anything.
1There are too many occurrences of the words "branch" and "track" in this, but that's how Git spells it out: a local branch (by name, such as master
) is allowed to track one other branch. The other branch that it tracks is usually a remote-tracking branch such as origin/master
. So:
master
is a branch (or more precisely, a branch name);master
-the-name tracks something;refs/remotes/origin/master
, or origin/master
for short; andorigin/master
is a remote-tracking branch.When the local branch named B is tracking a remote-tracking branch RB, the remote in question, and/or that branch on that remote, is what we (and Git) call the upstream.
2In fact, a detached HEAD behaves just like a branch, except that it has no name—or, for some purposes, it has the name HEAD
. You cannot set branch.HEAD.remote
and branch.HEAD.merge
, though.
git fetch origin
and git fetch origin master
Assume we've set $branch
as above. Let's also set up $remote
from branch.$branch.remote
:
remote=$(git config --get branch.$branch.remote) || \
die "branch $branch has no remote"
(where die
does the obvious thing).
We are interested in the difference between these two alternatives:
git fetch $remote $branch && git reset --hard FETCH_HEAD # alternative 1
git fetch && git reset --hard @{u} # alternative 2
If your Git is not older than version 1.8.4, there is only one difference here: adding $remote $branch
—let's say this is origin master
for discussion purposes—tells your Git to bring over only the updates that go into your origin/master
, rather than updating all of your remote-tracking branches.
That is, git fetch origin
gets you all the updates from remote origin
, while git fetch origin master
gets you only the updates to their master
. If there are other updates to other branches, you skip them for now. (You may have to pay the cost to get them later, and/or any such costs are typically tiny enough not to matter anyway.)
If your Git version is very old, though, there is one more difference: specifically, Git versions predating 1.8.4 fail to update origin/master
, putting the new information only in the special FETCH_HEAD
file. (The FETCH_HEAD
file is mainly meant for the git pull
script to use. It records, for git pull
's purposes, everything that git fetch
brought over. Remember that git pull
simply runs git fetch
first, then runs git merge
unless you direct it to run git rebase
instead. The merge-or-rebase step uses the information saved in FETCH_HEAD
to know how to do the merge-or-rebase.)
Which one should you use? That's up to you. Alternative 1 avoids fetching extra branches but requires setting up variables $branch
and $remote
. This in turn forces you to decide up front what to do if you're not on any branch (if you're in detached HEAD mode). Alternative 2 is easier to write, and simply fails automatically if you're in detached HEAD mode. Alternative 2 updates all your remote-tracking branches (both good and bad, considering the extra work in fetching) and papers over the difference between Git 1.7 (pre-1.8.4) and Git 2.10 (post-1.8.3). Papering over this difference is either neutral or good.
The @{upstream}
syntax itself was new in Git version 1.7. As far as I know, even the oldest-and-worst Linux distributions come with Git version 1.7.1 now, so there is probably no need to worry about someone still using 1.5 or 1.6.
reset
handles upstream rebasesAny attempt to use git pull
, which runs git merge
, can cause you trouble if your upstream does a rebase, or strips out commits. This is because your remote-tracking branch (origin/master
or whatever) is moving in a way that does not simply add new commits. When that happens, Git will generally consider commits on your branch (your master
) to be commits that you made, that they never had. The git merge
step will merge "your" commits with their latest, effectively resurrecting commits your upstream thought they had sent down the memory hole.
If your upstream never does this, it is not a problem.
Upvotes: 3