I have a local git repo with two remotes: upstream
, the original main repo, and origin
, my GitHub fork of it.
I want to create new branches based off of upstream/master
, push them to origin
for PRs, and periodically pull in new changes from upstream/master
.
Is there a way to set up my branches so that this happens by default? Ie:
$ git checkout -b my-new-branch --some-other-flags
$ git maybe some other command
# branch 'my-new-branch' points to 'upstream/master' and is checked out
# make changes, git commit
$ git push # pushes to origin/my-new-branch
$ git pull # pulls from upstream/master
The following seems to work:
git config push.default current && git config remote.pushdefault origin
Then, create branches with git checkout -b new-branch upstream/master
.
git push
pushes to origin/my-branch
, git pull
pulls from upstream/master
.
For branches based on other local branches instead of upstream/master, things seem a bit trickier. I could git config branch.autoSetupMerge always
, but then branches would pull from the local branch they started from, not upstream/master
. Or I could set the upstream to upstream/master
explicitly with -u
when creating the branch. I'm not sure which would be more appropriate though.
Another annoyance is that when I checkout a branch with changes, git sometimes tells me:
Your branch is ahead of 'upstream/master' by 7 commits. (use "git push" to publish your local commits)
But A) it's fine that I'm ahead of my upstream, I'm waiting to merge these changes in a PR, and B) more importantly, git push
will push to new-branch
at origin , not master
at upstream.
This doesn't always happen though, so I think there's some other variable I'm missing here.
Is there a way to set up my branches so that this happens by default?
No: you get only one "upstream" or @{u}
setting per branch (you can have a branch with no upstream set, if you like, but your other option is one upstream). git fetch
fetches from the the remote in this upstream and git merge
merges with the branch named in this upstream (and as usual, git pull
essentially equals fetch + merge); git push
pushes ... well, now things get complicated.
Is there a way to set up my repository so that this happens by default?
Yes, but with a flaw. How big this flaw is depends on your usage and needs. Using (and configuring) this flaw is extra-complicated. Let's run through the items.
Each local branch can have only one upstream, but:
origin
or upstream
; the other is a merge like refs/heads/master
. These get combined to make origin/master
, for instance. So we're already out of luck with having the default upstream be both origin/master
and anything /my-new-branch
, whether or not the anything
is origin
.
But:
This means you can make some remote, let's call it tri
for triangular, fetch from the URL you have for origin
and push to the one you have for upstream
. If branch B has tri
as its remote, and tri
fetches from the same URL as origin
but pushes to the same URL as upstream
, then you will, in effect, fetch from origin
and push to upstream
.
Your Git will be a little confused, though, as to what refs/remotes/tri/master
means . If a push of the form:
git push tri somebranch:master
succeeds, your Git now thinks that refs/remotes/tri/master
has the same hash you just pushed. Your Git thinks: Well, sure! The guy I called up under tri
says he took it! Once you run git fetch tri
you'll get some other hash and your Git will fix this up to remember what's on the same URL as origin
again. Your Git thinks: Well, that's funny, the guy I called up under tri
says he has reset his master
. Oh well, that's the other Git for you, resetting his master
all the time...
Furthermore, when you run git push tri
or git push
with tri
implied, but no refspec arguments on the command line ... well, this comes from the git push
documentation :
If
git push [<repository>]
without any<refspec>
argument is set to update some ref at the destination with<src>
withremote.<repository>.push
configuration variable,:<dst>
part can be omitted—such a push will update a ref that<src>
normally updates without any<refspec>
on the command line. Otherwise, missing:<dst>
means to update the same ref as the<src>
.
This means you can set a special remote.tri.push
configuration, so that git push tri
without naming a source, or git push tri somebranch
without naming :<dst>
, you can make somebranch
map to somebranch
here, even if the one-upstream-setting you are allowed for somebranch
says master
.
@{u}
. @{u}
has two parts, a remote and a merge . @{u}
can name a special remote that has two URLs: one for fetching, and a second for pushing. remote. remote .pushdefault
remote. remote .pushdefault
that forces the push :dst
part to go to a different name than the fetch's merge setting. (Let's call this pushtarget for the last point.) specialremote/pushtarget
will become wrong until you run git fetch specialremote
. This will make things confusing, as if they weren't already confusing because of all of the above. If you ask me whether doing the above is a good idea , I will say: definitely not .
The new solution for a triangular workflow (pull from one branch, push to another) would be:
git config --global merge.autosetupmerge=true # pull from origin/master
git config push.default current # push to origin/feature1
git switch -c feature1 origin/master
With Git 2.37 (Q3 2022), " git -c branch.autosetupmerge=simple branch $A $B
" ( man ) " will set the $B
as $A
's upstream only when $A
and $B
shares the same name, and " git -c push.default=simple
" on branch $A
would push to update the branch $A at the remote $B
came from.
Also more places use the sole remote, if exists, before defaulting to ' origin
'.
See commit 05d5775 , commit 8a649be , commit bdaf1df (29 Apr 2022) by Tao Klerks ( TaoK
) .
(Merged by Junio C Hamano -- gitster
-- in commit f49c478 , 26 May 2022)
branch
: newautosetupmerge
option 'simple
' for matching branchesSigned-off-by: Tao Klerks
With the default
push.default
option, "simple
", beginners are protected from accidentally pushing to the "wrong" branch in centralized workflows: if the remote tracking branch they would push to does not have the same name as the local branch, and they try to do a "defaultpush
", they get an error and explanation with options.There is a particular centralized workflow where this often happens: a user branches to a new local topic branch from an existing remote branch, eg with "
checkout -b feature1 origin/master
".Problem:
With the default
branch.autosetupmerge
configuration (value "true
"), git will automatically addorigin/master
as the upstream tracking branch.When the user pushes with a default "
git push
" ( man ) , with the intention of pushing their (new) topic branch to the remote, they get an error, and (amongst other things) a suggestion to run "git push origin HEAD
".If they follow this suggestion the push succeeds, but on subsequent default pushes they continue to get an error - so eventually they figure out to add "
-u
" to change the tracking branch, or they spelunk thepush.default
config doc as proposed and set it to "current
", or some GUI tooling does one or the other of these things for them.When one of their coworkers later works on the same topic branch, they don't get any of that "weirdness".
They just "git checkout feature1
" ( man ) and everything works exactly as they expect, with the shared remote branch set up as remote tracking branch, and push and pull working out of the box.The "stable state" for this way of working is that local branches have the same-name remote tracking branch (
origin/feature1
in this example), and multiple people can work on that remote feature branch at the same time, trusting "git pull
" ( man ) to merge or rebase as required for them to be able to push their interim changes to that same feature branch on that same remote.(merging from the upstream "
master
" branch, and merging back to it, are separate more involved processes in this flow).Problem (bis):
There is a problem in this flow/way of working, however, which is that the first user, when they first branched from
origin/master
, ended up with the "wrong" remote tracking branch (different from the stable state).For a while, before they pushed (and maybe longer, if they don't use
-u
/--set-upstream
), their "git pull
" was not getting other users' changes to the feature branch - it was getting any changes from the remote "master
" branch instead (a completely different class of changes!)An experienced Git user might say well yeah, that's what it means to have the remote tracking branch set to
origin/master
!" - but the original user above didn't ask to have the remotemaster
branch added as remote tracking branch - that just happened automatically when they branched their feature branch.
They didn't necessarily even notice or understand the meaning of the "set up to track 'origin/master
'" message when they created the branch - especially if they are using a GUI.Looking at how to fix this, you might think "OK, so disable auto setup of remote tracking - set
branch.autosetupmerge
to false" - but that will inconvenience the second user in this story - the one who just wanted to start working on the topic branch.
The first and second users swap roles at different points in time of course - they should both have a sane configuration that does the right thing in both situations.Make this "branches have the same name locally as on the remote" workflow less painful / more obvious by introducing a new
branch.autosetupmerge
option called "simple
", to match the same-name "push.default
" option that makes similar assumptions.This new option automatically sets up tracking in a subset of the current default situations: when the original ref is a remote tracking branch and has the same branch name on the remote (as the new local branch name).
Update the error displayed when the 'push.default=simple' configuration rejects a mismatching-upstream-name default push, to offer this new
branch.autosetupmerge
option that will prevent this class of error.With this new configuration, in the example situation above, the first user does not get
origin/master
set up as the tracking branch for the new local branch.
If they "git pull
" in their new local-only branch, they get an error explaining there is no upstream branch - which makes sense and is helpful.
If they "git push
", they get an error explaining how to push and suggesting they specify--set-upstream
- which is exactly the right thing to do for them.This new option is likely not appropriate for users intentionally implementing a " triangular workflow " with a shared upstream tracking branch, that they "
git pull
" in and a private feature branch that they push/force-push to just for remote safe-keeping until they are ready to push up to the shared branch explicitly/separately.
Such users are likely to prefer keeping the current defaultmerge.autosetupmerge=true
behavior, and change theirpush.default
to "current
".
git config
now includes in its man page :
branch.autoSetupMerge
simple
-- automatic setup is done only when the starting point is a remote-tracking branch and the new branch has the same name as the remote branch.
This option defaults to true.
git branch
now includes in its man page :
-t / --track[=(direct|inherit)]
The
branch.autoSetupMerge
configuration variable specifies howgit switch
,git checkout
andgit branch
should behave when neither--track
nor--no-track
are specified:
- The default option,
true
, behaves as though--track=direct
were given whenever the start-point is a remote-tracking branch.false
behaves as if--no-track
were given.always
behaves as though--track=direct
were given.inherit
behaves as though--track=inherit
were given.simple
behaves as though--track=direct
were given only when the start-point is a remote-tracking branch and the new branch has the same name as the remote branch.
Once you create the branch, you'll need to push it upstream and set it so your local
can keep track of the remote
.
You can do this as follows (assuming you're already in the master branch):
// Create and checkout the new branch
git checkout -b <your_branch_nane>
// Push new branch upstream and set it to track remote branch
git push -u origin <your_branch_name>
This will push your new branch upstream; the -u
argument sets it to track the remote branch. The -u
argument is just a shortcut for the --set-upstream
argument.
You can read more about working with remote branches .
There are a couple of ways to approach this. I think what you probably want is to adopt a workflow that looks like this:
Start working on a new branch based on the upstream master:
git checkout -b my-new-branch upstream/master
Make your changes, and configure the branch to push to your remote repository:
git push -u origin my-new-branch
(Subsequently you can just run git push
by itself and changes will be sent to your repository)
When you want to bring in new changes from upstream:
git remote update git rebase upstream/master
This allows you to periodically bring in new changes from upstream while preserving a linear history (rather than one littered with merge commits), which simplifies things in the event that you want to submit changes back to upstream.
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.