简体   繁体   中英

Git: How to update the master branch and switch to it in one step?

I'm working on a project that's hosted on GitLab and uses issue/work branches and merge requests to bring that work into the master branch when it's done. Usually I work on issue branches. When it has been merged by GitLab, I need to switch to the current master to do a build, locally.

My workflow is this:

  • Switch to master
  • Pull from remote (--ff-only)
  • Remove stale remote tracking branches
  • Also remove their local tracking branches

There's also a client-side tool that watches the code directory and updates some files (CSS, JavaScript). When it sees a change in the first step (switch to master), I first need to wait for it to finish before going on (to avoid confusion). If there's a difference between the issue branch and the old master, there's a good chance that the difference will disappear when updating master (as that issue branch is now merged).

I'm looking for a way to switch to the already-updated master branch in one step. How can I do that with a git command? I want to bundle up all these actions in a batch file to avoid repeating all those manual steps in TortoiseGit every time.

This question is different from the suggested one in that the local master branch already exists. I'm not switching to a new branch from a remote, but to a branch that already exists and it just behind the remote .

TL;DR

Unless you write your own script (or use a Git alias to run multiple commands and/or scripts), you can't get this down to a single command, but you can get closer. See the long section for many caveats: the biggest one is that it assumes you're not already on master when you do it. If you are, the second step won't work (see the long section for what will).

git fetch -p &&
    git fetch . refs/remotes/origin/master:refs/heads/master &&
    git checkout master

will take care of the first three bullet points—not in the same order—with a single work-tree-updating git checkout step .

(Note that I split this into three lines for posting purposes, but as a Git alias using ! , it's really all one big line.)

Long

There are several approaches, including actual, literal batch files (shell scripts on Unix-like systems, or.BAT files, or whatever) and aliases (as suggested by Joe in a comment ).

There's also a client-side tool that watches the code directory and updates some files...

This is... not necessarily a good idea, let's say. :-)

While git checkout master runs, it's changing various files. Let's say that for some reason, it changes one of several files that the watcher watches, but then it pauses for a few minutes (or seconds, or microseconds, or some unit of time anyway). While it is paused, the watcher tries to combine the multiple files that are now out of sync.

Maybe this is OK and self-correcting when Git un-pauses and finishes the checkout—but it might be better if you could make sure the update only happens when the checkout is done.

That aside, let's take a look at this particular series of commands, and be very concrete about which Git command you're using:

  • Switch to master

I assume this is git checkout master .

Pull from remote (--ff-only)

I assume this is git pull origin master --ff-only or perhaps just git pull --ff-only .

  • Remove stale remote tracking branches

I'll assume for now that this is git fetch --prune . If you are doing something different, you should include that in your question.

  • Also remove their local tracking branches

If I understand what you mean, this requires a script. Note that this is somewhat dangerous: suppose you have your own branch X on which you are doing development. This X is not related to anyone else's X . Then someone creates their own X —using the same name—and sends it to the machine from which you git fetch . You now have origin/X . Then they delete their X (because they're done with it) and delete origin/X . If you now have your script delete your X , because origin/X went away, that would probably be bad.

If you only delete your X when it explicitly has origin/X set as its upstream, this particular case won't occur—but if someone accidentally deletes your origin/X thinking it was their origin/X , the same problem crops up again, and this time that particular protection does not work.

Anyway, with all that aside, let's look at the variant I suggested above.

git fetch -p

This updates all your origin/* names, 1 including origin/master , without affecting any files in your working tree. The -p is short for --prune , so it deletes any origin/* names that no longer have a corresponding branch in the Git over at the URL stored under the name origin .


1 I assume here that you have only one remote , which is named origin . If you have more than one remote, use git fetch origin -p to make sure you're fetching specifically from the one named origin . I also assume you have not configured your Git to be a single-branch clone.


git fetch. refs/remotes/origin/master:refs/heads/master

This rather magic-looking command tells your Git to call itself up. That is, the special name . refers to your own Git repository . We are using this to trick your Git into fast-forwarding your master branch based on your updated origin/master . The final argument is what does this: we say to your Git: OK, my Git, when you talk to that other Git, find out what commit its refs/remotes/origin/master identifies. Then, if that's a fast-forward operation, update my refs/heads/master to match.

Of course, the "other Git" your Git is talking to is itself—so this means fast-forward my master from my origin/master . 2 It's roughly equivalent to:

git checkout master && git merge --ff-only origin/master && git checkout -

except that no actual checking-out occurs: no files in your work-tree change.


2 You might wonder why some of these use origin/master and some use refs/remotes/origin/master . The longer one is just the full spelling of the name. When using git fetch , it's wise to use the full spellings. In fact, in general, in scripts, you might want to use full spellings more often, but specifically git fetch can become confused if the other Git you talk to accidentally has both a branch and a tag with the same name, for instance. So I'm illustrating the full names with git fetch . You'll use it to talk to your own Git, so if you don't mix up your tags and branch names or otherwise create ambiguity, you won't actually need the full names. But it's a good habit with git fetch .


The above fails if you're on your master

The git fetch command will refuse to fetch into whatever branch name you have checked out. So if you are on master , this git fetch. trick will fail.

In a way, this is OK! If you are on your master , what you should do instead is run:

git merge --ff-only origin/master

or anything equivalent. This is what your git pull --ff-only does: first it runs git fetch (without the -p and limited to fetching only the other Git's master ); then it runs git merge --ff-only .

A more complete version

A more complete version of this sequence, then, is to first check: Which branch am I on? To do that, you can use either of two Git commands:

git rev-parse --abbrev-ref HEAD

or:

git symbolic-ref --short HEAD

Both of these will print master if you are currently on your own master branch. The difference between them is what they do if you're on no-branch-at-all: eg, in the middle of a rebase, when you are in "detached HEAD" state. In that case, the second command—the git symbolic-ref one—errors out, while the first one just prints HEAD .

If you'd like to avoid doing any of this when in such a state, use the second command and check for failure. Otherwise, use the first one. I'll illustrate just the first one here:

if test $(git rev-parse --abbrev-rev HEAD) = master; then
    # already on master - use alternative strategy
    git fetch -p && git merge --ff-only refs/remotes/origin/master
else
    # not currently on master: use fancy tricks to update
    git fetch -p &&
        git fetch . refs/remotes/origin/master:refs/heads/master &&
        git checkout master
fi

The above, while untested, should be suitable as a shell script. If you have Git installed, you have the ability to run shell scripts—or you can turn the above into a very long Git alias, using ! and the appropriate set of semicolons.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM