harperska
harperska

Reputation: 707

Make new git branch track same remote branch

Is there a way to have a new branch automatically track the same remote as the existing branch it was created from?

Say I have a local branch foo that tracks a remote origin/bar. I can explicitly have a new branch baz based on foo also track origin/bar by doing:

git checkout foo
git checkout -b baz origin/bar 

But is there a generic way to create the branch baz from foo and have it track the same remote as foo regardless of what that remote happens to be?

Upvotes: 2

Views: 1736

Answers (4)

VonC
VonC

Reputation: 1329032

This is now (2022, 3 years later) supported.

With Git 2.35 (Q1 2022), git -c branch.autosetupmerge=inherit branch new old makes new to have the same upstream as the old branch, instead of marking "old" itself as its upstream.

Same for:

git switch --track=inherit -c new old

See commit 44f14a9, commit d311566, commit a3f40ec (20 Dec 2021) by Josh Steadmon (steadmon).
(Merged by Junio C Hamano -- gitster -- in commit 0669bdf, 10 Jan 2022)

branch: add flags and config to inherit tracking

Signed-off-by: Josh Steadmon

It can be helpful when creating a new branch to use the existing tracking configuration from the branch point.
However, there is currently not a method to automatically do so.

Teach git-{branch,checkout,switch} an "inherit" argument to the "--track" option.
When this is set, creating a new branch will cause the tracking configuration to default to the configuration of the branch point, if set.

For example, if branch "main" tracks "origin/main", and we run git checkout --track=inherit -b feature main(man), then branch "feature" will track "origin/main".

Thus:

This is particularly useful when creating branches across many submodules, such as with git submodule foreach ...(man) (or if running with a patch such as this one, which we use at $job), as it avoids having to manually set tracking info for each submodule.

Since we've added an argument to "--track", also add "--track=direct" as another way to explicitly get the original "--track" behavior ("--track" without an argument still works as well).

Finally, teach branch.autoSetupMerge a new "inherit" option.
When this is set, "--track=inherit" becomes the default behavior.


git config now includes in its man page:

local branch or remote-tracking branch; inherit -- if the starting point has a tracking configuration, it is copied to the new

git branch now includes in its man page:

'git branch' [--track [direct|inherit] | --no-track] [-f] <branchname> [<start-point>]

git branch now includes in its man page:

The exact upstream branch is chosen depending on the optional argument:

  • --track or --track direct means to use the start-point branch itself as the upstream;
  • --track inherit means to copy the upstream configuration of the start-point branch.

--track direct is the default when the start point is a remote-tracking branch.

git branch now includes in its man page:

start-point is either a local or remote-tracking branch.

Set it to inherit if you want to copy the tracking configuration from the branch point.

See git pull and git config for additional discussion on how the branch.<name>.remote and branch.<name>.merge options are used.

git checkout now includes in its man page:

--track [direct|inherit]

git switch now includes in its man page:

--track [direct|inherit]

Upvotes: 3

jthill
jthill

Reputation: 60555

To copy all of one branch's settings to another:

git config --local --get-regexp ^branch\\.$one \
| sed "s,^,git config ,;s,$one,$another," \
# | sh -x

To do it for just the remote and merge settings you could get more specific,

git config --local --get-regexp "^branch\\.$one\\.(remote|merge)" 

I tend to do these things by constructing command lists as above, others might prefer

git config --local --get-regexp "^branch\\.$one\\.(remote|merge)" \
| while read config value; do
        git config ${config/.$one./.$another.} "$value"
  done

Upvotes: 0

torek
torek

Reputation: 489828

But is there a generic way to create the branch baz from foo and have it track the same remote as foo regardless of what that remote happens to be?

Yes, but it's a little complicated. Git being what it is, there are about twelve dozen1 different ways to do this, but we'll just pick one here.

First, let's look at all the important parts of this:

  • There are your own local branch names, foo and baz in your example.

  • There are remote-tracking names such as origin/bar (full name refs/remotes/origin/bar).

  • There's the commit hash ID to which each branch name or remote-tracking name points.

  • And, there's an optional upstream setting for any (local) branch name. Typically the upstream of some local branch is a remote-tracking name (Git calls this a "remote-tracking branch") using the same base name but prefixed with a remote name. For instance, master typically has origin/master set as its upstream. However, as in your example, the upstream of a branch need not match: the upstream of foo can be origin/bar.

    (It's also possible to set a local branch as the upstream of another local branch; this behaves the way you would expect. Internally this is implemented by setting the remote half of the two-part upstream setting to ., which means your own repository. But we get to ignore these details here.)

As fphillipe mentioned, we have git branch --set-upstream-to, which can set the upstream of any branch, at any time. If there is already some upstream set, this replaces it. If there was no upstream set, now there is.

We also have two different ways to create a new branch name:

  • git branch name [start-point] creates the new (local) branch named name. The initial hash ID stored in this name is given by start-point; if you omit start-point, the initial hash ID is whatever git rev-parse HEAD produces.

  • git checkout -b name [start-point] creates the new (local) branch named name and then does a git checkout of that name, all in one. It's essentially equivalent to running git branch followed by git checkout.


1"Ew, gross" đŸ˜€


The goal

In this case, you want to create a local branch named baz, set its hash to match that of foo, and set its upstream to foo's upstream, which is origin/bar. That is, after creating baz you would like:

git rev-parse baz

to produce the same output as:

git rev-parse foo

—that means the same commit is the tip commit of each branch—but you want:

git rev-parse --symbolic-full-name baz@{upstream}

to produce refs/remotes/origin/bar.

Both of the creation commands can, but don't always, also set the upstream of the newly created branch. You can use the --track argument to force them to set it, and you can use the --no-track argument to force them not to set it. The --track flag takes the remote-tracking name to use, so if you first figure out that the upstream of foo is origin/bar, you can write:

git checkout -b baz origin/bar

(or the same with git branch if you don't want to also check out the new branch). But this has two bugs:

  • It requires that you manually find the upstream of foo. You want to automate this.
  • Perhaps more important, it creates baz pointing to the same commit as origin/bar. If foo points to a different commit, that violates one of the desires.

Hence the job must be done in several parts. First, you will create the branch (and maybe switch to it if you like), using the local name foo to set the starting-point. Both git branch and git checkout will not set the upstream of the new branch in this case, at least by default, but you can be totally explicit by using --no-track, or you can turn foo into a raw hash ID. Usually all of this is unnecessary,2 but if you wanted to go for raw hash ID, here's a shell fragment to do it:

name=baz
start=foo

hash=$(git rev-parse ${start}^{commit}) || exit

If git rev-parse fails to parse foo to a raw hash ID, or foo does not identify a commit, git rev-parse will produce an error message to stderr and exit nonzero, and the || exit will have your shell fragment quit at this point.

Then:

git branch $name $hash || exit

will try to create the branch name $name, pointing to the desired hash.

We also need to find the upstream name, for which git rev-parse is again the command to use:

upstream=$(git rev-parse --symbolic-full-name ${start}@{upstream}) || exit

As before, we have our shell fragment quit if git rev-parse has failed, letting git rev-parse print the appropriate error. Now that we have the upstream, we can set it using git branch --set-upstream-to:

git branch --set-upstream-to $name $upstream

2At the command line, you just run a command and observe: if it did what you want, fine, if not, you correct as you go. From a script, however, it's often hard to see what did or might happen. It tends to be best to use something that has as few side-effects as possible. So we break down each Git command into direct plumbing commands when possible. I don't carry this to its extreme, in this example, as that gets too annoying, but I will show the git rev-parse operations.


The code

Putting the pieces together in the right order, plus a little bit of argument checking and use of -e to avoid all the || exits, gets us a full (but completely untested) shell script:

#! /bin/sh -e

usage() {
    echo "usage: $0 branch-name start-point"
}

case "$#" in
2) ;;
*) usage 1>&2; exit 1;;
esac

name="$1"
start="$2"
hash=$(git rev-parse ${start}^{commit})
upstream=$(git rev-parse --symbolic-full-name ${start}@{upstream})
git branch $name $hash
git branch --set-upstream-to $name $upstream

After testing this, write it as an executable shell script somewhere in your $PATH, calling it (e.g.) git-branch-with-upstream, and then:

git branch-with-upstream baz foo

will invoke the script, which will create baz pointing to the same commit as foo and having foo's upstream as baz's upstream.

You now know how to write new Git commands!

Upvotes: 3

fphilipe
fphilipe

Reputation: 10054

Have a look at --set-upstream-to of git branch:

git branch (--set-upstream-to=<upstream> | -u <upstream>) [<branchname>]

From the docs:

-u <upstream>, --set-upstream-to=<upstream>

Set up <branchname>'s tracking information so <upstream> is
considered <branchname>'s upstream branch. If no <branchname> is
specified, then it defaults to the current branch.

Upvotes: 0

Related Questions