48

Situation:

I have a main repo with a main dev branch and lots of "experiment" branches sprouting off from it (e.g., exp1 and exp2). The purpose of these experiment branches is to serve as placeholders for experiments that generate numerical results. I record the branch name (and commit ID) of the experiment branches so I can return to the commits to see precisely the code and history behind the results.

But, now, there are so many experiment branches that it's getting hard to see the main tree. So, I'm rethinking my strategy for keeping placeholders to the code behind each set of results (i.e., each experiment). Obviously I could just save the working dir at each branch, but it would be nice to keep the commit history also.

Possible solution:

One way to deal with this is to move the experiment branches into their own independent repos, each being rooted at the child node of the appropriate node in the commit history of the dev branch. Here's an illustration of what I mean:

enter image description here

Click here for larger version of image (on imgur.com).

So, for example, for branch exp1, I would like to export commits A->B->C to a separate repo rooted at commit A. Then, I can just record the hash of commit P1 so that I know where the exp1 branch descended from.

Question:

How can I do that?

Better question:

On the other hand, I strongly suspect there is a much better strategy for doing what I want to do---namely, unclutter the tree for visual inspection but keep placeholders to prior branches so I can return to them if needed. So, can anyone recommend a strategy for this?

synaptik
  • 8,971
  • 16
  • 71
  • 98
  • 2
    I just keep a separate "archive" remote repo that I push old branches to, then delete them from my local repository. – Ajedi32 Aug 06 '14 at 20:30
  • @Ajedi32 That sounds like a pretty good idea. How could I exclude that remote repo from my git log commands? For example, I usually run `git log --oneline --graph --decorate --all`. How could I modify this git-log command to *not* show a given remote? – synaptik Aug 06 '14 at 20:33
  • Good question. Right now, I don't do that myself so I'm not really sure. If you do find out, be sure to let me know. ;-) – Ajedi32 Aug 06 '14 at 21:04
  • Also, for your proposed solution, you may want to look into [`git replace`](http://git-scm.com/blog/2010/03/17/replace.html) – Ajedi32 Aug 06 '14 at 21:07
  • 2
    Just a question: It sounds as if you keep the exp branches around just in case you need to find the commit later. Why don't you just tag them, instead of keeping the branches? – sleske Aug 07 '14 at 07:12
  • @sleske Maybe that's what I should be doing, actually. – synaptik Aug 07 '14 at 12:26
  • _"But, now, there are so many experiment branches that it's getting hard to see the main tree."_ What do you mean by "see the main tree" exactly? How do you "see" the tree at all? Are you just looking at the `git branch` output and don't want to see all of those extra branches there? – Gabriel Staples Mar 11 '21 at 00:14
  • Related: [Hide but still save a branch with GIT?](https://stackoverflow.com/questions/35435440/hide-but-still-save-a-branch-with-git). – Gabriel Staples Mar 11 '21 at 00:15
  • ^^ If that's what you mean, here's my answer: https://stackoverflow.com/a/66574807/4561887. – Gabriel Staples Mar 11 '21 at 04:19

1 Answers1

42

Here's one alternative: use non-branch references to save the branch-tips before deleting the branch names.

Since these are non-branch references, they won't show up in git branch output, nor in stuff shown by git log --branches and gitk --branches, for instance. However, they will show up in --all listings, and will retain repository objects.

To create or update a non-branch reference, use git update-ref. Choose a name-space within refs/ that you think will not collide with some future use (current uses are refs/heads/ for branches, refs/tags/ for tags, refs/remotes/ for remote branches, refs/notes/ for notes, and refs/stash for the stash). [Edit, July 2022: refs/namespaces/ is now reserved as well, and refs/replace/ is used by git replace. refs/bisect/, refs/rewritten/, and refs/worktree/ are reserved; refs/original/ is reserved if you will use git filter-branch. Gerrit, if you use it, has more reserved names. The list never seems to stop growing, so use care here.]

torek
  • 448,244
  • 59
  • 642
  • 775
  • 4
    I see, so this is a git so-called plumbing command, and it allows you to basically arbitrarily create (update) references. And `heads`, `tags`, `remotes`, `notes`, and `stash` are really just namespaces for references that have a standard recognized meaning in git. Right? – synaptik Aug 07 '14 at 12:34
  • @sleske So, If I have a remote branch named `origin/run/diss-001`, I can run `git update-ref archive/run/diss-001 origin/run/diss-001`. It then produces the file `.git/archive/run/diss-001`, which contains the SHA1 of the remote branch. Is it a problem that my branch name and new ref name have `/` characters in them? – synaptik Aug 11 '14 at 20:05
  • 1
    @synaptik: the slashes are not a problem but the ref should start with `refs/`, so that it shows up in `--all`. Note that even ordinary branches can have slashes in their names, e.g., `git checkout -b bugs/bug1234` to work on bug # 1234. – torek Aug 11 '14 at 20:28
  • 3
    Ah-hah! Thanks. So, this command should be good enough? `git update-ref refs/archive/run/diss-001 origin/run/diss-001` – synaptik Aug 11 '14 at 20:29
  • 2
    Yes. Just look into the future to see if any commands or utilities you'll want to use 5 years from now are also trying to use the `refs/archive/` name-space. :-) (It seems unlikely, but then, 5 years ago, who would have known that `refs/notes/` was going to be used?) – torek Aug 11 '14 at 20:39
  • @torek: thank you for your answer and sorry if I bring this question back from the dead. I was wondering if you could expand/improve it by adding how you can list the branches now in, say, refs/archive/. Also, can you add an example of how to bring a branch back from the archive? – manu3d Mar 09 '16 at 21:40
  • 10
    @manu3d: simply create the branch pointing to the desired commit. Remember that any ordinary branch-name (before or after this kind of "archiving") simply points to some specific commit ID, which we then call "the tip of the branch". Since a ref like, say, `refs/archive/foo` *also* points to some specific commit ID, you can simply do `git checkout -b restored refs/archive/foo` to create new branch `restored` (`refs/heads/restored`) pointing to that commit. What makes it a branch is that its name is in `refs/heads/`. Git will auto-update it once it's the *current* branch. – torek Mar 09 '16 at 22:02
  • Thank you @torek! And how do you list the archived branches once they are in there? And would you like me to edit your answer to include some of the concept from your reply to me? – manu3d Mar 13 '16 at 16:36
  • 5
    If all your "archived branches" are listed under `refs/archive/`, use `git for-each-ref refs/archive` to show them (note that `git for-each-ref` has many arguments, including a formatting directive to control its output). If you chose some other name space(s), you will need something at least slightly different. I'm not sure the answer here needs additional elaboration; people can read the comments too. :-) – torek Mar 13 '16 at 19:33
  • AFAICT using a non-branch ref for these "archived branches", e.g. `ref/archive/heads` (so that other stuff can be archived as well) means that the goodness of tools like show-branches is lost. Or, is there a way to do something like `git show-branches` and have it apply to the union of `refs/heads` and `refs/archive/heads` ? Conversely, is there a a way, e.g. a glob, that can be passed to show-branches that says "everything under `refs/heads` except stuff under `refs/heads/archive`? – Krazy Glew Dec 11 '18 at 20:28
  • 1
    @KrazyGlew: as far as I know there's no front end interface for that. Internally, `git show-branches` has to use the full names anyway, so it should in theory be easy to augment it to take a namespace in which to operate. – torek Dec 11 '18 at 20:35
  • @KrazyGlew You could always make your own custom command that lists your your archived branches alongside the normal `git show-branches` output. – jamesdlin Mar 27 '19 at 05:15
  • 1
    `refs/namespaces/` is used by Git as well and should not be used as a custom refspec. – kagmole Sep 11 '20 at 07:55
  • @torek this is an elegant solution. But it is not clear how to push and fetch these references. I'd really appreciate it if you can give an example. – Dmitry Petrov Sep 13 '20 at 05:24
  • 2
    @DmitryPetrov: they may not be push-able (it's up to the receiving Git what refspecs to allow), but if the reference namespace you're using is `refs/archive/`, just `git fetch --prune "+refs/archive/*:refs/archive/*" for instance, to override all your own `refs/archive/*` names with those from the other Git. Note that this will delete any `refs/archive/*` names you have that they don't, so don't use `-p` if that's bad; or, receive into a different namespace, or qualify the names under `refs/archive/`, or whatever it is you would like. – torek Sep 13 '20 at 07:14
  • @torek, can these new refs/archive references themselves be pushed to the remote, so all my other users can use them if they need to recover the histories from the branches I've deleted? – Dave Jul 22 '22 at 18:54
  • 2
    @Dave: yes, *provided* the receiving Git allows it. Each Git implementation is allowed to place its own restrictions on nonstandard namespaces. For instance GitHub won't let you create `refs/pull/` names: they reserve those to themselves. Gerrit servers reserve `refs/for/`: they let you push there, but those names are "magic" and cause special action and then vanish. – torek Jul 22 '22 at 18:58
  • So, I was permitted to push them ( `git push origin refs/archive/*` ) but in a separate clone, they do not come down via git pull. I also tried `git fetch origin refs/archive/*` but that fails (invalid refspec). Any ideas? – Dave Jul 22 '22 at 19:59
  • 2
    You must `git fetch origin "refs/archive/*:refs/archive/*"`, and as before it depends on cooperation from the server. The *default* fetch refspec (with `git fetch`) is in your Git configuration; run `git config --get-all remote.origin.fetch` to see the one for the remote named `origin`. – torek Jul 22 '22 at 20:06