0

git clone and git clone --mirror are quite different:

A standard git clone may be used as a workspace. The set of branches known to the origin are available to be checked out and worked on.

A mirror is more like a backup. You cannot use it directly as a workspace.

Now suppose you clone a repository that has already been cloned as per this question: Git cloning a repository that is already a clone

The resulting repository (clone2) has only the branches that have been used in the first clone (clone1). However, clone1 still has knowledge of the branches at the origin. Is there a way to add a branch known to clone1 to clone2 without setting the (original) origin as a remote?

In case that isn't clear we have:

repo1:

  • branch1
  • branch2
  • branch3
  • branch4

clone1 = git clone repo1.git:

  • branch1 - checked out
  • branch2 - previously checked out
  • branch3 - not checked out but can be at any time
  • branch4 - not checked out but can be at any time

clone2 = git clone clone1.git:

  • branch1 - checked out
  • branch2 - not checked out but can be at any time

clone2 appears to have no knowledge of branch3 or branch4 so cannot check them out. How do we get that information from clone1?

Actually there are two questions here:

  • how do we get information about just one branch from clone1 to clone2?
  • how do we get information about all possible branches from clone1 to clone2?

I believe that branch3 and branch4 are available to clone1 while repo1 is offline.

There are several use cases for this:

  • repo1 is unavailable and we wish to recreate it (bonus points if you know a way to recreate repo1 from clone1)
  • repo1 is temporarily unavailable and we wish to work on a different branch getting that information from someone who has already cloned repo1.
  • testing some changes that involve repo hacking before pushing them to a shared repo (repo1).

I believe it should make no difference but I am testing this using local files rather than URLs. So clone2 is actually made via git clone /local/path/.git

Update

There is a complication I failed to notice initially and report: git branch -r on clone2 should list the branches on clone1 as origin/branch3 and origin/branch4 as suggested in answers. However, for this particular repo it doesn't. I don't know why.

Things that might be special about this repo include:

  • use of .git/refs/replace

pulling the replacements with git pull origin 'refs/replace/*:refs/replace/*' makes no difference.

Any other suggestions?

I have identified what is probably the significant difference between repos for which git branch -r works and the one for which it doesn't.

clone1 and clone2 should both list the remotes in .git/packed-refs with lines like (/path/to/clone1/.git/packed-refs):

2c3c761fbac82556c2178cb28a4e728360093e67 refs/remotes/origin/branch1

For some reason clone2 on the affected repository does not have all the entries in .git/packed-refs that it should.

I checked and some of commit Ids to which the packed-ref file (in clone2) refers exists in the cloned repositories packed-ref (clone2) but some don't. We seem to have both lost and gained branches!

If I experimentally copy the packed-ref file from clone1 to clone2 the branches appear under

git branch -r
they can be checked out but result in a detached head state.

Here is the git config for the 'affected' repo.

>cat .git/config
[core]
    repositoryformatversion = 0
    filemode = true
    bare = false
    logallrefupdates = true
[remote "origin"]
    url = /path/to/clone1/.git
    fetch = +refs/heads/*:refs/remotes/origin/*
[branch "develop"]
    remote = origin
    merge = refs/heads/develop

The [https://git.wiki.kernel.org/index.php|standard instructions] for grabbing all remote tracked branches work even for the broken repo:

git clone --mirror original-repo.git /path/cloned-directory/.git          
cd /path/cloned-directory
git config --bool core.bare false
git checkout anybranch

so there are several workarounds even for a broken repo.

Bruce Adams
  • 4,953
  • 4
  • 48
  • 111
  • I hope I understood the question correctly. – Tuncay Göncüoğlu Jul 11 '17 at 12:24
  • I should probably split this into 2 questions. The original question I asked was answered correctly but was the wrong question as I have a strangely afflicted repo. The strangely afflicted repo probably deserves a separate question so we can close this one. Any mods (or wannabees) care to comment? – Bruce Adams Jul 11 '17 at 15:10

4 Answers4

2

It seems to me that you're looking at the very different typical use cases for clones with --mirror and those without, and letting that lead you to think they are fundamentally different. Actually they're just commonly-used special cases of a more general thing.

That's mostly an aside, but I think if you study git concepts with an eye on really understanding the above statement, then the rest of this may be more clear as well.

So: in clone1, the "knowledge" of the other branches is in the form of remote branch refs (refs/remotes/origin/branch3, ...) The branches that are checked out have, additionally, "local" branch refs (refs/heads/branch1, ...). The default refspec used in a clone (whether for a mirror or otherwise) is set to fetch refs/heads/*. (The difference is that a mirror maps them locally as refs/heads/* while a "regular" clone maps them to refs/remotes/origin/* by default.)

You could set the refspec in clone2 - either through settings, or in the arguments to a specific fetch or pull - to read the refs/remotes/origin/* refs from clone1. But there are some issues to think about.

First, if you're going to map both the local and remote refs from clone1, then you need to give them different namespaces in clone2. That is, refs/heads/master in clone1 is distinct from refs/remotes/origin/master in clone1 and they might refer to different commits at any given time; so they can't both map to the same name in clone2.

Second, clone2s knowledge of - for example - branch3, is rather indirect at this point. "The last time I spoke to clone1, it told me that the last time it spoke to repo1, branch3 was at commit XYZ." It probably makes more sense to get knowledge of branch3 "from the horse's mouth". You'd do that by adding repo1 as a second remote on clone2.

Whether you go about it by adding repo1 as an origin, or by using non-default refspecs to copy the information from clone1, ultimately in clone2 you'll have multiple remote refs corresponding to some branch names (e.g. refs/remotes/origin/branch3 and refs/remotes/repo1/branch3). This means it may not always be clear which branch should be treated as "upstream" of the local refs/heads/branch3. You manage this through configuration, and/or through arguments to specific push and fetch commands telling them what you intend as the upstream in that instance.

Translating all of that into specific commands really depends on what you're trying to accomplish; there are just too many possibilities to list them all out and explain when you'd use any given one of them. If you need that level of detail, I'd suggest that the documentation for git config, git fetch, git push, and maybe git pull would be the places to start.

Mark Adelsberger
  • 42,148
  • 4
  • 35
  • 52
  • You have answered my question correctly. Unfortunately it seems I asked the wrong question. I tried git branch -r to list remote branches and the branches didn't show up for the repo I am using. I tried for a different project and they do. So there is something odd about this particular repo which is preventing many of the branches from showing up. I don't currently have any idea what that might be. – Bruce Adams Jul 11 '17 at 14:28
  • Do you mean they don't show up in `clone1`? In that case the default fetch refspec is the most likely thing. For example, if the clone was made with `--single-branch` (or with `--depth=n` - which by default activates single branch mode) then the `remote.origin.fetch` setting would specify that only a particular branch is fetched by default – Mark Adelsberger Jul 11 '17 at 14:51
  • No. They don't show up in clone2. Both clone1 and clone2 were made with just --clone. --single-branch and --depth were not used (though I suppose the later might apply automaticallt in some circumstances?) – Bruce Adams Jul 11 '17 at 15:06
  • Ok, so, again, in `clone2` the default refspec would've copied *only the local branches* as seen in `refs/heads` of its origin (which, as I understand you, is `clone1`). This is expected context for my answer – Mark Adelsberger Jul 11 '17 at 15:13
  • In my other 'standard' repo. git branch -r reports the remote branches. clone doesn't copy the local branches to .git/refs/heads but they are listed in .git/packed-refs as available (as are remote branches and tags). The 'affected' repo which prompted me to ask this question has the tags and just some of the remotes and not even local branches in .git/packed-refs. I can add a remote pointing to repo1 but if I subsequently remove that remote (as an experiment) even the checked-out 'HEAD' branch becomes a detatched head. If I do that for clone2 of a 'standard' repo the current branch remains. – Bruce Adams Jul 11 '17 at 16:12
  • Accepting the answer though I still don't know what prevents the 'affected' repo from being cloned properly. – Bruce Adams Jul 12 '17 at 11:29
  • Let's see... I think I'm a few comments behind here. So one thing to understand is that a ref represented in a file (like `refs/heads/master`) is the same as a ref represented in the `packed-refs` file. The `packed-refs` file (and packfiles containing objects) are just an alternate representation for more efficient handling. When I say "in `refs/heads`" I'm referring to the ref namespace `refs/heads`, not necessarily the directory - though admittedly that's a little confusing... – Mark Adelsberger Jul 12 '17 at 13:08
  • As for what gets copied into `clone2` - again, my understanding is that you cloned it *from* `clone1`, right? So if `clone1` only has the remote branch ref, then by default that won't be copied to `clone2`. Maybe I'm misunderstanding, but it seems to me that you're treating a local branch as "basically the same thing" as the corresponding remote branch, and it's not. When you clone (without `--mirror`), by default *only* remote branch refs are created (from the remote's local branches), and local branches (which are a different thing) are created only when you request them (which can ... – Mark Adelsberger Jul 12 '17 at 13:10
  • ...sometimes be by asking to check them out, due to convenience shorthand behavior built into git). – Mark Adelsberger Jul 12 '17 at 13:11
0

Git can have multiple remote connections. It is not limited to a single main repository like some other similar systems. You could add repo1 as another remote repository with:

git remote add repo1 /path/to/your/original/complete/repository

Then you can get your branches as you would normally:

git fetch repo1 branch3
git fetch repo1 branch4

which you later can checkout to work on if you want:

git checkout branch4
Tuncay Göncüoğlu
  • 1,699
  • 17
  • 21
  • That works for the case when repo1 is online but not for the case where repo1 is offline. Otherwise why not create clone2 from repo1 in the first place? – Bruce Adams Jul 11 '17 at 14:06
  • well, if branch3 and 4 only exist in repo1, then you have to reach it some way or another. that could be online, or, you could push them to a new repository on a flash drive perhaps, and then pull from there. With gits multi-remote topology the things you can do are practically endless. – Tuncay Göncüoğlu Jul 12 '17 at 14:48
0

First off : you will only have a view of repo1 as it was on your last git fetch or git pull.

All the branches present in repo1 will be stored under origin in clone1.

You can list these branches with : git branch -r.

LeGEC
  • 46,477
  • 5
  • 57
  • 104
0

This should be a comment but I need formatting I cannot do in a comment (and have gotten wordy again too :-) ).

Besides Mark Adelsberger's answer (which is correct and upvoted and you should read it), there is, I think, a key to understanding this that is revealed by this particular bit of your phrasing:

branch3 - not checked out [on clone1] but can be at any time

The key is that in this state, branch3 does not exist.

This is something I have said in other answers, and will repeat: a remote-tracking branch is not a branch. It's a remote-tracking branch name: a name, like origin/branch3, that is a shortened form of a full-name that begins with refs/remotes/ (as compared to a branch name like branch3 that is a shortened form of a full-name that begins with refs/heads/).

(In fact, "a branch" is not always "a branch", in Git, due to the Git authors' habits of re-using the same words with different meanings—which, to be fair, also happens all the time in English, which is one way we get puns. See also What exactly do we mean by "branch"? In this particular case, what we mean by "a branch" is a branch name, i.e., a reference whose full name starts with refs/heads/.)

When you ask git checkout to check out a branch that does not exist, such as branch3, Git will scan through all of your remote-tracking branch names. If there is exactly one that "resembles" branch3 correctly, git checkout will create one at that time, using the remote-tracking branch name to derive the new branch's initial hash-ID value.

For a branch name to exist in a repository, that repository must have a reference whose full name starts with refs/heads/. Whatever appears after the second slash is the branch's name. To list all references in some repository, run git for-each-ref, whose default output is a list of every reference, its hash ID, and the type of Git object identified by that hash.

When you make a clone, you tell your Git—the one making the clone—how to manipulate the references it gets from the other Git. This is the key to what --mirror does: it says use their references to make my references, with no changes at all. Normal clones don't do that, because once you do do that, you have a problem if and when you re-fetch from that other Git: you have become totally enslaved by it; any references you changed in your own copy, you replace your values with their values and return to being a pure copy.

torek
  • 448,244
  • 59
  • 642
  • 775