Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How do I configure Git to automatically pull from origin on branch change?

When I switch branches, I'd like to always pull the latest changes from origin if local is clean and behind its origin counterpart. The local branch is a tracking branch of its remote branch, and fetch works as intended.

  • Can Git be configured to do this automatically?

  • Is there a repository-wide setting to do this for all branches? (Our branches are frequently created and deleted as company policy is one branch per task which is PR'd and merged when reviewed)


EDIT:

For context, I use Visual Studio 2017 and mostly its user interface to perform Git commands. There's no problem keeping Git up-to-date. There are a few Git commands that I need to do using the command-line, such as renaming a branch, or hidden repo settings.

When I say the "local is clean", yes that refers to the worktree from git status, and in VS where there are no outgoing commits or locally modified files. VS has a very nice UI which shows you this information in real-time.

By

(Our branches are frequently created and deleted as company policy is one branch per task which is PR'd and merged when reviewed)

This was supposed to be an off-hand way of saying "I really don't want to set this setting or hook up for every single branch", but the explanation is:

We create our own local branches split off of a master branch to fulfil a task. In VS, this is "Create local branch from..." which performs a checkout of the branch with the desired name. Under the hood, I'm sure this is equivalent to git checkout -b fillcatnip master. fillcatnip is an untracked local branch until pushed to the remote, but it'll be pushed as a new branch fillcatnip rather than to the original master. At that point, it becomes a tracking branch and can be given a pull request (PR) to be merged back into master when the task has been completed. BUT if master has moved on and conflicted before the PR can be fulfilled, then the branch cannot be merged until the conflicts have been resolved. By the time this happens, I may have already begun another task. Hence, if I save the task I'm currently on (commit & push, stash, whatever), then switch back to the conflicted branch, I'd like that branch's remote copy and not my own local copy, as somebody may have pushed changes to it while it's been awaiting merging.

like image 898
Slate Avatar asked Feb 16 '18 15:02

Slate


2 Answers

The answers to the two questions are "not exactly" and "no", but also "normally there's no need", and in fact, the question itself does not make a lot of sense as asked. The first and largest problem lies in the phrase "if local is clean"—what, precisely, does this mean?

If "clean" refers to what git status reports:

On branch master
Your branch is up-to-date with 'origin/master'.

nothing to commit, working tree clean

then "clean" refers to the work-tree, and sometimes perhaps to the index, as compared to each other and/or the HEAD commit.

Unless you use git worktree add to create auxiliary work-trees, there's only one work-tree (and one corresponding index) for any given Git repository. (If you do use git worktree add, each added work-tree is on its own separate branch. That might in fact be the answer you are really looking for, here.)

Let's note here as well that a branch name like master or feature denotes one particular commit, which Git calls the tip commit of that branch. There are other meanings for the word "branch": see What exactly do we mean by "branch"? But when you have a branch checked out, what this implies is that you have one particular commit checked out as well. That commit's contents fill the index and the work-tree.

The command that switches branches—or to a "detached HEAD"—is git checkout. It accomplishes the branch-switching (or HEAD-detaching) by writing, into the index and the work-tree, whatever is needed to achieve the switch from the commit you had checked-out before, to the commit you will have checked-out now. If the work-tree was clean before the git checkout, this is always possible, and the work-tree will be clean after the git checkout as well.

If the work-tree is not clean before the git checkout, it's sometimes, but not always, still possible to switch branches: see Checkout another branch when there are uncommitted changes on the current branch. In that case, the work-tree will generally also be not-clean after the git checkout has switched branches. The not-clean-ness will be because the checkout, when switching branches, did not have to update the index and work-tree entries for the files that did not match the commit that you had checked out just before you ran git checkout branchname.

Checking if the work-tree is clean

With the above out of the way, note that git-sh-setup, a script that is available to other Git scripts, includes a function named require_clean_work_tree. You can examine this code, or simply use it, to determine whether a work-tree is clean.

You can combine this with phd's answer about using a post-checkout hook to do the check and, if appropriate, run git merge or git fetch && git merge. (I recommend avoiding git pull entirely: break it down into its two separate steps like this. Your life will be happier afterward.)

Why I suspect you should not need this at all

(Our branches are frequently created and deleted as company policy is one branch per task which is PR'd and merged when reviewed)

As a general rule, if you're not going to work on a named branch (make new commits on it), you should not create it in the first place. Having not created it, you never need to worry about updating it either.

Suppose, for instance, that you have a repository with one remote named origin, and on origin, there are branches for various tasks. You want to inspect origin/task/feedkittens occasionally, but work on the (single) task task/fillcatnip.

In this case, to inspect things, go ahead and use the "detached HEAD" mode:

git checkout origin/task/feedkittens

To work on things, use git checkout to create task/fillcatnip from origin/task/fillcatnip:

git checkout task/fillcatnip

This works because git checkout will, if you ask it to check out a branch that doesn't exist, search for an origin/ branch that otherwise has the same name. If so, it will create the local branch such that the branch tip commit is the same as the remote-tracking name's commit. At the same time, it will set up the local branch to track (have as its upstream) the remote-tracking name.

By creating the local branch name, you can now create new commits. These will make the local branch

By avoiding creating task/feedkittens, you can just run git fetch and git checkout origin/task/feedkittens to see the latest. You will be in "detached HEAD" mode, so it's usually a bad idea to create new commits now, but this means there's no need to drag around a local branch.

When the branch named task/feedkittens is deleted on origin, you can have your own remote-tracking name get deleted automatically during git fetch by setting:

git config fetch.prune true

As soon as you git checkout some other commit or name, you stop being detached-HEAD on the origin/task/feedkittens commit that no longer has a name.

If you want to work on (as in "add commits to") multiple different branches simultaneously, consider using git worktree add, as long as your Git is at least version 2.5. Each added work-tree has its own private index, but all the added work-trees share the single underlying repository. If your Git is too old, consider just making separate clones for each branch you want to work on at once.

like image 104
torek Avatar answered Oct 22 '22 23:10

torek


You need a git hook — in your case it's post-checkout hook. In the hook you call git pull.

Something like this:

#!/bin/sh

prev_HEAD="$1"
new_HEAD="$2"
new_branch="$3"

if [ "$new_branch" = 1 ]; then
    git pull origin "$new_HEAD"
fi

exit 0
like image 42
phd Avatar answered Oct 22 '22 23:10

phd