Sorry if this is a noob question, but I've been searching for an hour+ and can't get a clear answer.
What I'd like to do is this:
- Checkout a remote branch
git checkout origin/feature - Make a local branch based off that branch
git checkout -b feature_my_tweak - So now I have a local copy of
featurecalledfeature_my_tweakthat I can play around with - Make some changes and
commit -m "added my tweak"so now my localfeature_my_tweakhas diverged fromfeature - Now do a
git push --set-upstream origin feature_my_tweakto push my local branch to the remote server - Now a workmate pushes a change to
featureon the remote server
At this point, I would like to be able to git pull and have git see that there was a change to feature and pull it (fetch/merge) into my local feature_my_tweak branch. Is there a way to do that automatically? Or do I have to do a manually git merge feature to get all the changes from that branch to my local feature_my_tweaks branch? I guess I thought there was a way to have one branch track another.
Another part of this is that I'd still like to be able to make local changes to feature_my_tweaks and still be able to git push and have that only push up to the remote origin/feature_my_tweak branch and not pollute the origin/feature branch.
Is there a way to do all that? From what I can read, it seems that if any branch_A is tracking a remote branch_B, any pushes will go to branch_B, which is what I don't want.
I hope that makes sense.
TL;DR
The short version of the answer boils down to no, but it doesn't matter, don't worry about it and just do it manually; it's easy. If you have one particular branch that you do this with often, write a script or a Git alias to do it.
Medium
To do this manually, for your branch B where you want to know if there's new stuff on
origin/feature, vs your own branchfeature_my_tweakswith upstream set toorigin/feature_my_tweaks, just run:This prints out two numbers. The number on the left is the number of commits that you have on
feature_my_tweaksthat are not part of yourorigin/feature. The number on the right is the number of commits that are on yourorigin/featurethat are not on yourfeature_my_tweaks.If the second number is not zero, and you want to, you can now run
git checkout feature_my_tweaks; git rebase origin/featureorgit checkout feature_my_tweaks; git merge origin/feature.Whether and when to use
git rebaseis up to you, but after rebasing, you will need togit push --force origin feature_my_tweaks(orgit push --force-with-lease origin feature_my_tweaks) to get the Git over atoriginto discard the old commits that yourgit rebasediscarded when it copied your commits to new-and-improved commits.Long
Probably the biggest problem here is terminology. What does the word tracking mean to you? Git uses this one word in at least three different ways,1 so at most one of them can match up with the one you're thinking of. (Possibly none of them match exactly, depending on what you're thinking happens here.)
Regardless of which word(s) you use, here is the right way to think about this:
What matters are commits. Each commit has a unique hash ID. Every Git everywhere—every clone of this repository—uses that hash ID for that commit. If you have a commit with that hash ID, it's the same commit. If you don't, you don't have that commit.
Names in Git take a bunch of forms. Each name holds one hash ID—just one!
The three forms that you use are:
Branch names:
master,develop,featureorfeature/one, and so on. These are yours, to do with as you will. (You'll probably want yours to match up with other peoples' names, though, at least at various times.)Tag names:
v2.1and so on. While yours are yours, your Git will try to share them with other Git repositories: if your Git sees that theirs has av2.1and you don't, your Git is likely to copy theirs to yours.Remote-tracking names:
origin/master,origin/feature, and so on. Git calls these remote-tracking branch names and some people shorten that to remote-tracking branch, but they're not quite like branch names. Your Git slaves these to some other Git's branch names. You have your Git call up their Git and get any new commits from them. Then your Git updates your remote-tracking names so that they match theirs.The remote-tracking name is built by sticking the remote name (in this case
origin) in front of their branch name, with a slash to keep them apart. That's why yourorigin/master"tracks" theirmaster, updated every time you rungit fetch origin.2All these names work similarly. They all have long forms:
refs/heads/masteris the full spelling ofmaster, for instance, vsrefs/remotes/origin/masterfororigin/master. That's how Git knows which one is which kind of name. Git normally then shortens the name for display to you, taking off therefs/heads/part for branch names, or therefs/remotes/part for remote-tracking names.Note that
git fetchis as close as there is to the opposite ofgit push. It might seem like these should bepushandpull, but due to a historical accident3 it'spushandfetch. Pull just means: Run fetch, then run a second Git command,git mergeby default, to merge with the current branch's upstream.Fetch and push are very similar, but with two key differences:
Fetch gets things. You tell your Git: call up the Git whose URL you have stored under the name
origin(or whatever remote name you use here). Your Git calls up a server at that URL, which must answer as a Git repository. That server then tells you about its branch names and their commits, and your Git checks to see if you have those commits. If not, your Git asks their Git for those commits, and their parent commits if you don't have them, and the parents' parents if needed, and so on. They give you all the commits they have, that you don't, that your Git needs to complete these.Having gotten the commits,
git fetchthen updates your remote-tracking names by renaming their branches.Push sends things. As before, you have your Git call up another Git, by a remote name like
origin. Then your Git gives them commits, rather than getting commits from them. But here, things are slightly different:The commit(s) your Git offers are the tip commits of any branches you are pushing. (If you are just pushing one branch, that's just one commit.) If they don't have those commits, your Git must offer the parents of these tip commits. (Most commits have just one parent, so that's one more commit.) If they don't have those, your Git must offer more parents, and so on. Through this process, your Git finds all the commits their Git needs to have a complete picture of the tip commit you're sending: all of its history.
When you were fetching, they offered all their branches. The difference: you're only pushing whichever branches you specified.
After your Git has sent any commits you have, that they don't, that they need to complete the tip commit(s) for the branch(es) you're pushing, your Git sends a polite request for them to set their branch name(s).
When you were fetching, your Git set your remote-tracking names. You're now asking them to set their branch names.
To summarize these key points:
You can have
git pushask them to set a different name than the one you use to select the commit(s) to send. For instance:sends the tip commit of your
test-xyzbranch (and its parents and other ancestors if/as needed), but asks them to set or create their branch namenew-feature.So:
This is entirely wrong, at least by default. (There is a setting for
push.defaultthat does make that happen, though.)There is a lot more to know here, in particular, what this
git rev-list --left-right --countis doing and what it means for commits to be "on"—or more precisely, reachable from—a branch name, but I've kind of run out of time and space in this answer, at this point.1In particular, files can be tracked or untracked, some names are remote-tracking names, and a branch with an upstream set is said to be tracking its upstream. The verb (in its present participle form) becomes an adjective, modifying name or file, in the first two cases.
2There are ways to run a limited
git fetchthat doesn't update all your remote-tracking names. Usinggit pullsometimes does this, for instance. I prefer to separate mygit fetchand my other commands, rather than using thegit pullfetch-then-run-another-Git-command command.3It's common after fetching to want to integrate what you fetched, so at first,
git fetchwas a back-end "plumbing" command, not meant for users to run, andgit pullrangit fetch, thengit merge. This was before remotes existed as a concept, and hence before remote-tracking names existed either, so there wasn't any reason for users to rungit fetch.Over time, Git grew remotes and remote-tracking names, and now the difference between
pull—fetch and combine—and justfetchwithout combining (or at least, without doing it yet) became both useful and important. But the namepullwas already in use to mean fetch and combine, hence this particular historical accident.4That is, this is the default in Git 2.0 and later. You can change it with
push.default, and Git versions older than 2.0 default tomatching.When using
matching, your Git asks their Git about their branch names, matches up your matching branch names, and defaults to pushing from your matching names to their matching names.If you change this setting to
upstream, your Git asks their Git to set their branch based on the upstream setting of your branch, which is what you were assuming in your question.The modern default setting is
simple, which requires that the upstream be set to a branch of the same name, so thatgit pushon your end just fails right away if your upstream is set to a different name of their side. However, you can override this easily by typing ingit push origin branch, which means the same thing asgit push origin branch:branch: ask their Git to use the same branch name as you are using in your Git.