washboardalex
washboardalex

Reputation: 89

Create a remote branch of a remote repository

I have started a job as a front end dev and have been instructed to use my predecessor's git workflow for the time being.

He was creating a remote branch, tracking master, for sprints (so sprint-01(remote) tracks master) and then branching again locally (sprint-01(local)) to make changes. The remote sprint-01 deploys to a staging website - no development is done on a local server.

The issue is I do not know how to create a remote branch of a remote depository.

So far I have tried

git remote add -t master sprint-01 <URL>

and it's not showing up in the branch list the way previous sprints have; I get:

master 
remotes/origin/master

when listing branches.

However, when I enter command git remote -v I get:

origin  <URL>(fetch)
origin  <URL>(push)
sprint-01    <URL>(fetch)
sprint-01    <URL>(push)

What I'd like is to get another branch in the usual way but remote:

master remotes/origin/master remotes/origin/sprint-01

I am currently running through in depth git tutorials but we are starting a sprint on Tuesday and would like to be able to join the workflow without issue even if I haven't found the answer in documentation by then. Thank you stack community.

Upvotes: 0

Views: 2081

Answers (1)

torek
torek

Reputation: 489758

TL;DR summary

Commits are shared (by copying) between different Git repositories. Branches are not shared: when you run git fetch, your Git asks some other Git about its branches, and then your Git creates remote-tracking names like origin/master to remember their branches. This has no effect on your branches!

You can also run git push, which has your Git call up their Git and ask them to create or update their branches, which again has no effect on your branches. But, a successful git push has your Git update your Git's remote-tracking names, since your Git's remote-tracking names remember where their Git's branches go.

What you did

You have created a new remote. A remote is a way for your Git to remember the URL of some other Git, and along with that, to remember some or all of their—the other Git's—branches, at least, as of the last time your Git talked with their Git.

By default, git remote add asks your Git to record, under some name, the URL for some other Git. That's all it does by default, so:

git remote add them <url>

just has your Git remember <url> under the name them, so that you can—later—run git fetch them or git push them without having to type in a long URL again.

Adding -t tells your Git that it should not just record the URL, but also change the way that your Git will talk with their Git in the future. Normally, your Git would call up their Git and have them list all their branches, then copy all those names to your remote-tracking names. That is, after:

git remote add sprint-01 <url> -t master

(you can shuffle the -t more towards the front, as you did), the next git fetch call your Git makes to their Git will go like this:

  • YOUR GIT: Hi them, what branches do you have?
  • THEIR GIT: I have master, dev, and feature-123.
  • YOUR GIT: I don't care about dev and feature-123, but give me any master commits you have that I don't.
  • Insert long conversation about hash IDs—now that your Git knows their master hash ID, everything happens by hash ID. Once your Git knows which hash IDs they have that you don't, your Git has them send you all the commits and any necessary files and so on, as well.
  • Last, after your Git has the commits you need, your Git creates your sprint-01/master to remember their master.

Note that nowhere in this process has your Git ever asked their Git to create any branches. Nor, for that matter, has your Git created any branches: at the end of the git fetch conversation above, your Git creates your sprint-01/master. (The full name of this thing is refs/remotes/sprint-01/master.) That's your Git's way of remembering their Git's master. Your Git sticks sprint-01/ in front so as to remember that it came from the remote that you called sprint-01: sprint-01 is the name you're using to remember the URL for their Git.

What you wanted to do

You probably already have a remote, remembering the URL of another Git. That remote's name is origin.

You'd like to have your Git call up their Git and start a rather different conversation:

  • YOUR GIT: Hi origin, I'd like you to create a new branch named sprint-01. Make it point to (some particular commit hash ID).

  • THEIR GIT: OK, done.

  • YOUR GIT: 'kthxbye!

  • Now that your Git has seen that their Git has created sprint-01, and knows which commit hash ID your Git asked them to use, your Git creates, in your repository, the remote-tracking name origin/sprint-01.

The tricky part here is the phrase some particular commit hash ID. Where will you get this hash ID? The only hash ID your Git can use is the hash ID of some commit you have in your own repository.

Typically, the way you choose the commit hash ID is to look through the commits you have in your repository and choose one of them as the right commit that they should also use as their sprint-01. You might use git log to find this commit, for instance.

You then create your own branch, using that commit hash ID:

git branch sprint-01 <hash>

or:

git checkout -b sprint-01 <hash>

If no commit is suitable because you need a new commit, you first create your own sprint-01 from some existing commit hash ID:

git checkout -b sprint-01 <existing-hash>

and then make for yourself the new commit that is suitable, in the usual way of making any commit.

Then, in any case, you now have, in your Git, a branch named sprint-01 that points to the desired commit hash ID. This may be some existing commit—which they probably already have—or some new commit that you just created, whose parent hash ID is some existing commit that they probably already have. You can now simply run:

git push origin sprint-01

to have your Git call up their Git, offer them new commits if necessary, and then ask their Git to create their sprint-01 branch, pointing to the same commit hash ID that yours uses.

Terminology: or, tracking is a badly-overloaded verb

He was creating a remote branch, tracking master, for sprints (so sprint-01(remote) tracks master) ...

This doesn't quite make sense, as stated.

Git has a problem with its terminology. I've used the phrase remote-tracking name to describe strings like refs/remotes/origin/master (or origin/master for short). A remote-tracking name is a name in your Git repository whose purpose is to remember the hash ID stored under their Git's branch name. Git calls this a remote-tracking branch, but it's not actually a branch, because you can't get on it the way you can a real branch:

$ git checkout origin/master
Note: checking out 'origin/master'.

You are in 'detached HEAD' state. ...

Note the somewhat-scary stuff about "detached HEAD" here. All this really means is: you're not on any branch now. Running git status will show you, among other things, HEAD detached at .... But:

$ git checkout master
Switched to branch 'master'
Your branch is up to date with 'origin/master'.

Now git status will say on branch master.

A branch, then, is something you can get on, by running git checkout branch-name. You can't "get on" a remote-tracking name like origin/master, so we shouldn't call it a branch. The longer phrase "remote-tracking branch" has the word "branch" right there in it, so it's not a good phrase. (But Git uses it, so keep in mind that a "remote-tracking branch" is not a branch even though it has the word "branch" in it!)

Now, a branch—but not a remote-tracking name—can be set to have what modern Git calls an upstream. You can have one, and only one, upstream, for each of your branches. To set the upstream of some existing branch, run:

git branch --set-upstream-to=<upstream> <branch>

Typically, the upstream of the branch master is origin/master. The upstream of your dev or develop would be your origin/dev or origin/develop. The pattern here is clear: usually, you want the upstream of your (local) branch X to be (your) remote-tracking name origin/X.

To make it so that a branch has no upstream, run:

git branch --unset-upstream <branch>

It's never necessary to --unset-upstream, but sometimes you might want to. See Why call git branch --unset-upstream to fixup?

When a branch like master has origin/master set as its upstream, Git says that master is tracking origin/master. Note that this tracking verb applies to the local branch name. You can set the upstream of one of your (local) branches to another local branch, too:

git branch foo                           # create a new local branch named foo
git branch --set-upstream-to=master foo  # set its upstream to master

You cannot set an upstream for a remote-tracking name:

$ git status
On branch master
Your branch is up to date with 'origin/master'.
$ git branch --set-upstream-to=master origin/master
fatal: branch 'origin/master' does not exist

This is correct: branch origin/master does not exist; only remote-tracking name origin/master exists.1 What this means is that a remote-tracking name cannot track anything, in the sense in which the verb track is used for branches! This is true even though it's a remote-tracking name: as a remote-tracking name, it's just automatically updated when you run git fetch, based on what your Git learns from the other Git.

This same verb track gets applied to files, too: some files are tracked and others are untracked. This particular kind of tracking has nothing to do with branches! A file is tracked if and only if it is in Git's index. The index is very important, but has nothing to do with branch names and remote-tracking names, so all I will say here is that you can put files into, or take files out of, the index at any time, which means that whether some file is tracked is something you can control; there are crucial consequences to this when you run git commit because git commit makes new commits from the index, rather than from the work-tree.

Now that we know that tracking, for a branch, means has an upstream set, let's look at this again:

(so sprint-01(remote) tracks master)

If sprint-01 is a local branch, it can track another name like master, but if sprint-01 is a remote-tracking name, it cannot track any other name.

If you mean that, when you log on to the remote and cd to that repository, that repository has a (local) branch named master, well, that repository's (local) sprint-01 can certainly track any other name inside that repository—but that repository's branches are theirs. Their Git does not control the branch names in your repository, and any upstream set there is not even visible to your Git! So this statement—that origin's sprint-01 is set up on origin so that it tracks origin's master—could be true, but would be totally irrelevant to your Git.


1A branch is, technically, any reference whose full name starts with refs/heads/. A remote-tracking name is any reference whose full name starts with refs/remotes/. Git normally strips off the refs/heads/ and refs/remotes/ parts for display, though sometimes—in git branch -a output, for instance—it only strips refs/ from the remote-tracking names. A few Git commands, such as git for-each-ref, display the full reference name by default; generally it's only the so-called plumbing commands that keep the whole name.


Conclusion

When dealing with Git, always remember:

  • A repository holds commits. Each commit holds files, so in the end, the repository also holds files, but it does it a commit at a time. Each commit has a unique hash ID.

  • There's almost always more than one repository.

  • When connecting two repositories to each other, they share commits by hash ID. The sending Git and receiving Git converse and exchange hash IDs, to see who has what and who needs what. Then the sender sends commits—not files, but whole commits—to the receiver. Now the sender and receiver have the same commits, by hash ID.

  • If you're using git push, you're the sender, and they are the receiver. At the end, your Git either politely asks their Git a question: If you are willing, please set some of your branch and/or tag names to some particular commit hash IDs? or it commands them, as in git push --force: Set these branch and/or tag names to these hash IDs! The hash IDs come from your Git, and correspond to the commits that your Git has just shared if necessary (if they didn't have them already).

    They can refuse! A polite request will be refused if they would lose some commits by setting a branch name, for instance. A command is obeyed or refused depending on controls and permissions—the default is to always obey, but web hosting services always offer controls these days.

    If they obey the request or command, your Git will update your remote-tracking names. (Your Git doesn't know about any other names they've updated or not-updated, so your Git only updates your remote-tracking names for branch names you had them set.)

  • If you're using git fetch, your Git is the receiver. In most cases, you get all their branch names, and your Git then updates all your remote-tracking names. You can deliberately limit the names your Git asks their Git about, though. For instance:

    git fetch origin master
    

    has your Git call up their Git and ask them only about their master. You get any new commits they have that you don't, and then your Git only updates your own origin/master—you didn't pick any other branches to look at, so your Git doesn't.

    You can also set single-branch mode on any remote. That's what git remote add does when used with -t. In this mode, your git fetch automatically only asks about the one branch you listed. This is rarely very useful: it's mostly to deal with huge, highly active repositories where you're only looking at, or contributing to, a small part (one branch) so you don't care about all the activity in most of the remote. You can do this for small and less-active repositories, but unless you have a 9600 baud internet connection, it's not going to save you enough time to bother with.

  • git pull just means run git fetch, then run a second Git command. The git fetch is where all the remote-repository get-me-their-commits happens. This also updated your origin/master or whatever. The second Git command is useful because git fetch doesn't touch your branches. Usually, after you've fetched their new master commits, you'd also like to update your master.

    One of several problems here, though, is how you want to update your master. Do you want to update it with git merge, or do you want to update it with git rebase? If you know, for certain, in advance, which command you want to use, you can run git pull: git pull --rebase will run git rebase and git pull without --rebase will run git merge, as the second command. Then you'll be done, and one git pull is more convenient than a git fetch followed by a second Git command.

    But if you don't know for sure whether you want git merge or git rebase or perhaps something else entirely, then git pull is a trap. What I like to do is run git fetch, then inspect what I just got, and only then decide what to do. The git pull command makes you decide what you will do, before you can see what you got. So I generally avoid it—but that's me, in my situation, which may differ from you in yours.

    (In very old versions of Git, back in the 1.5 and 1.6 days, git pull had a well deserved reputation for accidentally destroying your work, too. I lost some work to git pull bugs myself.)

Upvotes: 3

Related Questions