简体   繁体   中英

“git fetch” remote refs of remotes

I would like to be able to move all work-in-progress branches from repository to repository and be able to delete the old ones without losing any commits. Is there a way I can fetch the remote branches from a remote repository?

# A: Create a repo with two branches
> mkdir a ; cd a ; git init
> echo initial > file_master ; git add file_master ; git commit -am "initial"
> git checkout -b mybranch
> echo test > file_mybranch ; git add file_mybranch ; git commit -am "test"
> git checkout master

# B: Clone
> git clone . ../b ; cd ../b
> git branch -a
* master
  remotes/origin/HEAD -> origin/master
  remotes/origin/master
  remotes/origin/mybranch
# if I wanted, I could delete a and checkout remotes/origin/mybranch

# C: Clone of clone, but it's missing mybranch
> git clone . ../c ; cd ../c
> git branch -a
* master
  remotes/origin/HEAD -> origin/master
  remotes/origin/master
# no remotes/origin/mybranch - it's a remote of origin
# if I delete a and b I lose mybranch for good

>git ls-remote
7505fa4ec342ff85095737d27df0a5062c53d4d5        HEAD
7505fa4ec342ff85095737d27df0a5062c53d4d5        refs/heads/master
7505fa4ec342ff85095737d27df0a5062c53d4d5        refs/remotes/origin/HEAD
7505fa4ec342ff85095737d27df0a5062c53d4d5        refs/remotes/origin/master
dfd057c18baa4227905999b32bffdecf639185f7        refs/remotes/origin/mybranch
# there it is, but I don't have it locally

Just to reiterate, in repository B I can safely rm -rf ../a , then git checkout remotes/origin/mybranch and I'll have file_mybranch . What I want is to have this commit in repository C . My question is: how can I get the branch remotes/origin/mybranch from B into C along with the commits it points to? Lets say I've already deleted A .

There is clearly a serious Git Naming Problem here because this problem trips people up all the time . The fundamental issue is that the word "branch" is confusing. It doesn't mean what you think it does! See also What exactly do we mean by "branch"?

A clone is a deliberately imperfect copy

When you clone a repository using git clone <url> or git clone <path> or similar, you don't get an exact copy of the upstream repository. You get, instead, a copy with some deliberate changes:

  • The new copy has a remote , usually named origin , set to hold the URL of the original.
  • The new copy takes all the branches from the original, but none of its remote-tracking branches . Despite the name "remote-tracking branch", these things are not branches! Git could call them "elephants" and it would probably be less confusing. :-) Let's call them RTBs here, just so that they do not have the word "branch" in them.
  • The new copy starts out, in fact, with no branches at all , but then immediately does the equivalent of git checkout somebranch . The name git clone gives to this git checkout command depends on several somewhat tricky things.
  • The new clone does start out with a bunch of RTBs. These are not branches, remember. They're these funny RTB things. RTBs are names that start with the same string as the remote, and then have a slash.

The specific set of RTBs you get depends on what branches there are in the repository you're cloning. Let's say that the repository you're cloning has three actual branches, named master , develop , and mybranch . This means you get, in your clone-imperfect-copy, three of these RTB thingies (which aren't branches), named origin/master , origin/develop , and origin/mybranch .

The git checkout command will create branches automatically

Let's say you have a repository that has no branches at all, like one of the ones made by git clone , and you pick a branch name out of a hat-full of possible names: master , develop , and mybranch . For the sake of realism, let's use the one Git usually picks, master . You convince your git clone command to pass this name to git checkout :

git checkout master

Now, the thing is, there is no master branch because there are no branches at all. So Git can't possibly check it out. But—this is the first tricky part—your Git can scan through all your RTBs, looking for one whose name resembles master . And there it is: origin/master smells enough like master , that your Git goes and creates a new branch in your copy, called master , using origin/master as the hash ID for this new branch.

That's the final step of git clone : it checks out some branch, and winds up creating that branch in the process. You can choose which branch, using -b , but you only get one name: you can have your Git run git checkout master , or you can have your Git run git checkout mybranch , but you cannot have it run both. You only get one!

If you don't give a -b argument, your Git picks one out of the hat-full of branch names it saw on the other Git. It usually picks master , but there are rules by which the other Git gives your Git a hint which name to pick. The most important rule is that your Git asks the other Git which branch it has checked-out (or more precisely, inspects its HEAD setting).

There are ways to make a more-perfect clone, but then you can't use it

If you use git clone --mirror , you will get a perfect copy. The problem is that this perfect copy has no work-tree: it's what Git calls a "bare" clone.

The reason for this is that such a clone is set up to be a slavish copy all the time : every time you run git fetch in the mirror clone, you update it to exactly match whatever is in the other repository. This means you throw away any work being done on any branch, and slavishly match the other repository's branches again. To make sure you don't lose work this way, Git prevents you from doing work in the exact-copy.

The solution is to put up with the imperfections

When you clone, you get that imperfect copy, and your Git checks out one branch, copying it from the RTBs your Git made by renaming all of their branches.

You can now run git checkout on each of those remaining RTBs—all but the one that git clone already ran git checkout on, that is. The easiest thing is to just run it on all the names: it doesn't hurt to run git checkout master again, after all.

To extract all the names, you can use git for-each-ref (meant for scripting) or git branch -r (not so much). You will have to massage the names a bit to strip off the origin/ part, and run git checkout .

(Or, you can simply not bother to check them out until you need them. Don't re-clone the imperfect copy: re-clone the original—assuming you still have and can reach it.)

If you still have the copy, but not the original, and want a second copy that's exactly like the first copy—down to having the same branches and the same RTBs—you need to use one or more tricks, because Git is not set up to do that. One trick is to make a mirror clone anyway:

$ git clone --mirror url-of-copy mirror-clone-dir

Now you have an exact duplicate of the copy, except that your duplicate itself is a bare clone. You can now convert it from a bare clone to a non-bare clone (see, eg, How do I convert a bare git repository into a normal one (in-place)? ), but you should also update its remote.origin.fetch setting:

$ cd mirror-clone-dir
$ git config --get remote.origin.fetch
+refs/*:refs/*
$ git config remote.origin.fetch '+refs/heads/*:refs/remotes/origin/*'

(and then the rest of the conversion). Or, you can start with a normal clone, but then run one more command:

$ git clone <url-of-copy> new-clone-dir
[the usual messages]
$ cd new-clone-dir
$ git fetch origin '+refs/remotes/origin/*:refs/remotes/origin/*'

Note that this last step will replace all your existing remote-tracking branches ("RTBs") with the copy's remote-tracking branches. In other words, you lose the memory of the copy 's branches, replacing your Git's "memory of what I saw in url-of-copy" with "memory of what I got from url-of-copy that they had listed as their memory of what they saw in their original."

You literally cannot get both, at least not using these RTBs: either you have your branches and remember their branches, or you have your branches and remember their memory of someone else's branches. You do have some additional alternatives, though:

  • If the real original still lives, add it as second remote, and fetch from it:

     $ git remote add real-source <url> $ git fetch real-source 

    Now you have not just origin/master but also real-source/master .

  • If not, add the copy as a second remote—there's no harm in having two names for the same repository—and then use the same git fetch trick to populate the additional set of RTBs from the copy:

     $ git remote add r2 <url-of-copy> $ git fetch r2 '+refs/remotes/origin/*:refs/remotes/r2/*' 

    Now you have two remotes: origin/master remembers " master as seen on origin, renamed to origin/master " and r2/master remembers "origin/master as seen on r2 (same URL as origin), renamed to r2/master ".

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.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM