How can I archive git branches?
Asked Answered
V

16

423

I have some old branches in my git repository that are no longer under active development. I would like to archive the branches so that they don't show up by default when running git branch -l -r. I don't want to delete them, because I want to keep the history. How can I do this?

I know that it's possible to create a ref outside of refs/heads. For example, refs/archive/old_branch. Are there any consequences of doing that?

Vanillin answered 20/8, 2009 at 15:49 Comment(1)
git-rm does not delete resources from the repository, it only removes them from the index kernel.org/pub/software/scm/git/docs/git-rm.html You can easily restore these resources using git checkout [rev] fileJenette
J
565

I believe the proper way to do this is to tag the branch. If you delete the branch after you have tagged it then you've effectively kept the branch around but it won't clutter your branch list.

If you need to go back to the branch just check out the tag. It will effectively restore the branch from the tag.

To archive and delete the branch:

git tag archive/<branchname> <branchname>
git branch -d <branchname>

To restore the branch some time later:

git checkout -b <branchname> archive/<branchname>

The history of the branch will be preserved exactly as it was when you tagged it.

Joyce answered 21/8, 2009 at 3:14 Comment(17)
Is there any reason not to use an object-tag in this case ? Being able to see who archived the branch and when could be interesting.Lannylanolin
@GrégoryJoseph what's that?Loathly
@Lohoris i might have been using the wrong terminology, but if memory serves right, I meant whatever git tag -a produces.Lannylanolin
@GrégoryJoseph: That's a so-called "annotated tag". And yes, using that can make a lot of sense, I'd say.Simla
@Steve More recently you can just use git checkout branchname and it will try to link it up with a branch on your remotes if possible.Arsis
small note, you probably want branch -D since it's likely not to be fully merged if you are archiving it in this wayHuron
Does this work if the branches I want to tag and archive are remote? Are there any extra steps I need to take?Boatright
$ git branch -l -r ... origin/fmd_desa ... $ git tag archive/fmd_desa fmd_desa fatal: Failed to resolve 'fmd_desa' as a valid ref. $ git tag archive/fmd_desa origin/fmd_desa $ git branch -D fmd_desa error: branch 'fmd_desa' not found. $ git branch -D origin/fmd_desa error: branch 'origin/fmd_desa' not found. What I'm doing wrong?Sublimation
More general, and more meaningful imho git tag [-a -m "some description maybe"] archive/<any_name_you_like> <branchname or commit>. I did this when I was working on a branch and it was decided I had to take another course. I continue on the same branch, but with major changes. The work I did is now stored in that archive tag with a good name and description.Creamcups
If you want to list all tags just use: git tag; details about tag show's command: git show archive/<branchname>Specify
2 important commands missing: git push --tags (newly created archive tag pushed to server). git push origin :<branchname> (delete <branchname> from server).Scavenger
Hi @Vanillin , This answer explains how Branches can be Tagged . Git does not support archiving, users can rely on the Commit comments, or tags. See explanation by Ethan Brown on this answer: https://mcmap.net/q/13389/-how-to-get-git-log-display-name-of-deleted-branchesHenry
I guess to list the archived branches one uses git tag --listLazes
The tutorial referenced by @guyaloni can now be found here hereCheops
I've taken this and put into a bash function in my bash_profile for easy usage: gta() { git tag archive/$1 $1; git branch -D $1; }, can then be used like so: gta my-branch and it'll tag it and delete it in one.Jinnyjinrikisha
I've been archiving my branches like that (often versions of them with archive/<branch>-v<num>) and it works very well. Such a simple technique: just a tag namespace. And with a tag namespace you are also able to move them somewhere else. Very nice.Radiant
Thanks for this! To reduce the amount of typing it can be turned into a custom git command. Here's how to create git retire <branch_name> (note git archive already exists and does something else), on Windows: gist.github.com/c0d1f1ed/b2ac82525a20d2ceb7ae3254bcf31ec8Shaving
L
184

Jeremy's answer is correct in principle, but IMHO the commands he specifies are not quite right.

Here's how to archive a branch to a tag without having to checkout the branch (and, therefore, without having to checkout to another branch before you can delete that branch):

> git tag archive/<branchname> <branchname>
> git push origin archive/<branchname>
> git branch -D <branchname>

And here's how to restore a branch:

> git checkout -b <branchname> archive/<branchname>
Labiate answered 27/11, 2010 at 16:41 Comment(1)
git push origin archive/<branchname> to push the tagIstic
E
55

Extending Steve's answer to reflect the changes on the remote, I did

# Create a tag named similarly to the branch but with the 'archive/' prefix
# Point it to the latest commit of the branch
git tag archive/<branchname> <branchname>

# Delete the branch locally
git branch -D <branchname>

# Delete the local reference of remote branch ("forget" the remote branch)
git branch -d -r origin/<branchname>

# Push local tags to remote
git push --tags

# Delete the remote branch
git push -d origin <branchname>
# or, similarly, 'git push origin :<branchname>'

To restore from the remote, see this question.

Elated answered 14/2, 2017 at 17:48 Comment(2)
The last line can also be git push origin --delete <branchname> for Git v1.7.0+. Source: git-scm.com/book/en/v2/Git-Internals-The-RefspecKevinkevina
This answer would be better with some explanation of the commands. At a glance they appear to be deleting the remote branch twice. I doubt that's true, but I'd have to research each command to know.Landsturm
H
44

Yes, you can create a ref with some non-standard prefix using git update-ref. e.g.

  • Archive the branch: git update-ref refs/archive/old-topic topic && git branch -D topic
  • Restore the branch (if needed): git branch topic refs/archive/old-topic

Refs with non-standard prefix (here refs/archive) won't show up on usual git branch, git log nor git tag. Still, you can list them with git for-each-ref.

I'm using following aliases:

[alias]
    add-archive = "!git update-ref refs/archive/$(date '+%Y%m%d-%s')"
    list-archive = for-each-ref --sort=-authordate --format='%(refname) %(objectname:short) %(contents:subject)' refs/archive/
    rem = !git add-archive
    lsrem = !git list-archive

Also, you may want to configure remotes like push = +refs/archive/*:refs/archive/* to push archived branches automatically (or just specify on push like git push origin refs/archive/*:refs/archive/* for one-shot ).

Another way is to writing down SHA1 somewhere before deleting branch, but it has limitations. Commits without any ref will be GC'd after 3 months (or a couple of weeks without reflog), let alone manual git gc --prune. Commits pointed by refs are safe from GC.

Edit: Found a perl implementation of the same idea by @ap: git-attic

Edit^2: Found a blog post where Gitster himself using the same technique. He named it git hold.

Hartsell answered 7/12, 2016 at 2:55 Comment(10)
Excellent, apart from everyone else on this thread, you actually answered the question.Fichte
I think this is good strategy when you want to "soft delete" something, and aren't concerned with others being reasonably able to discover something. Perhaps, you would delete it, but it's not your branch to delete. I'm curious how you can search all code, including these archived refs?Postpositive
I think most people (sans git ninja's) will want either an alias for pushing refs to the remote, or simply bake that into the add-archive command. Alias could be 'backup-archives' which will just git push origin refs/archive/*:refs/archive/* and then fetch-archives, but I'm not sure exactly what command that should be. Maybe there's a use for deleting a ref locally + remotely too. Also, I'm not sure what git rem alias means, perhaps remote, perhaps remove, maybe a more descriptive name is fitting, or just a note in the answer here :)Postpositive
Last but not least, I'm not sure what params should be passed to these alias commands. I love the approach, but would love some more detail!Postpositive
Thanks for the comment. Adding more aliases to operate on archive (git grep-archive, git fetch-archive, git push-archive?) sounds like a nice idea. I'll update the answer along with some synopsis. rem stands for remember for me, but you'll rename it as you like anyway. It's just a shortcut :)Hartsell
This perl implementation (git-attic) has list, save, rm, fetch, and pushPostpositive
I believe seconds is not very useful/important for the timestamp, therefore makes the ref name feel cluttered to me. It does ensure some unique-ness, though. Here is a version I wrote here: gist.github.com/devinrhode2/6e370ddfe824b1f49515f56e6314a434Postpositive
@DevinRhode The REFS_BRANCH variable in that script ends up containing multiple branch names as well as branch status characters like \+ and *. Do you have local settings that make branch not emit that output like --format=refname or similar? And is it meant to work when multiple branches are printed from looking up one sha?Mechanotherapy
Adding --format=refname seems like a GREAT solution. I'm not sure if I do have weird settings locally.Postpositive
Cool use of non-standard refs!Radiant
A
20

You could archive the branches in another repository. Not quite as elegant, but I'd say it's a viable alternative.

git push git://yourthing.com/myproject-archive-branches.git yourbranch
git branch -d yourbranch
Ankle answered 20/8, 2009 at 15:55 Comment(3)
You can create git-bundle instead of separate repository.Gudgeon
Forks are really great. @JakubNarębski what is git-bundle?Postpositive
@DevinGRhode : git bundle is a way to create a file that contains selected portion of the project history, that can be used to fetch or clone from. It was originally intended, AFAIK, for "sneakernet" transport.Gudgeon
C
15

Here is an alias for that:

arc    = "! f() { git tag archive/$1 $1 && git branch -D $1;}; f"

Add it like this:

git config --global alias.arc '! f() { git tag archive/$1 $1 && git branch -D $1;}; f'

Bear in mind there is git archive command already so you cannot use archive as an alias name.

Also you can define alias to view the list of the 'archived' branches:

arcl   = "! f() { git tag | grep '^archive/';}; f"

about adding aliases

Curate answered 2/9, 2015 at 13:15 Comment(2)
With newer versions of git (as suggested here), this alias gives completion: !git tag archive/$1 $1 && git branch -DSutton
For the curious ones, git archive is for, like, creating .tar archives, according to my 5 seconds skimming the docs page.Postpositive
E
7

I am using following aliases to hide archived branches:

[alias]
    br = branch --no-merge master # show only branches not merged into master
    bra = branch                  # show all branches

So git br to show actively developed branches and git bra to show all branches including "archived" ones.

Elemental answered 18/4, 2013 at 14:58 Comment(1)
Whether a branch has been merged into master has nothing to do with its archive state. For instance, in my dev team we have a few branches which were created specifically to test stuff. We want to keep those branches in our archive, but we definitely don't want to merge them into master.Let
R
6

I would not archive branches. Put another way, branches archive themselves. What you want is to ensure the information relevant to archeologists can be found by reliable means. Reliable in that they aid daily development and doesn't add an extra step to the process of getting work done. That is, I don't believe people will remember to add a tag once they're done with a branch.

Here's two simple steps that will greatly help archeology and development.

  1. Link each task branch with an associated issue in the issue tracker using a simple naming convention.
  2. Always use git merge --no-ff to merge task branches; you want that merge commit and history bubble, even for just one commit.

That's it. Why? Because as a code archeologist, rarely do I start with wanting to know what work was done on a branch. Far more often it's why in all the screaming nine hells is the code written this way?! I need to change code, but it has some odd features, and I need to puzzle them out to avoid breaking something important.

The next step is git blame to find the associated commits and then hope the log message is explanatory. If I need to dig deeper, I'll find out if the work was done in a branch and read the branch as a whole (along with its commentary in the issue tracker).

Let's say git blame points at commit XYZ. I open up a Git history browser (gitk, GitX, git log --decorate --graph, etc...), find commit XYZ and see...

AA - BB - CC - DD - EE - FF - GG - II ...
     \                       /
      QQ - UU - XYZ - JJ - MM

There's my branch! I know QQ, UU, XYZ, JJ and MM are all part of the same branch and I should look at their log messages for details. I know GG will be a merge commit and have the name of the branch which hopefully is associated with an issue in the tracker.

If, for some reason, I want to find an old branch I can run git log and search for the branch name in the merge commit. It is fast enough even on very large repositories.

That is what I mean when I say that branches archives themselves.

Tagging every branch adds unnecessary work to getting things done (a critical process which should be ruthlessly streamlined), gums up the tag list (not speaking of performance, but human readability) with hundreds of tags that are only very occasionally useful, and isn't even very useful for archeology.

Relation answered 5/2, 2015 at 19:19 Comment(11)
But what about the clutter? Perhaps if there were a way to hide the old branches under 10 cubic yards of dirt.Jun
This is useful, but it isn't applicable to unmerged branches. Sometimes an experiment was done on a branch and you want to keep the content in case some of it becomes useful later.Cloudless
@Jun I think this answer is suggesting you should always delete merged branches, because you can always get back to them via the merge commit. I agree with this.Cloudless
@NeilMayhew Yes, I have about 10 unmerged branches like that open myself. Each are associated with an open task so I can remember what it was I was doing. I'll either do something with them, or they become so out of date they're no longer relevant and I'll delete them. I worked on a project absolutely drowning in "I might need it later" branches so we could hardly see what we were doing. It was really an excuse for some devs not to have to clean up after themselves. A bit of leeway is fine, but don't let it get out of control.Relation
@Relation I agree. I've been on projects like that too. I think converting the branches to tags is a good way to get rid of the clutter, because the list of tags is always going to grow whereas the list of branches shouldn't (because it represents the amount of work that's ongoing). Using namespacing for tags makes the list more manageable, but packrat tendencies definitely need to be resisted. A developer should retain the commits on their own machine unless there's a good chance someone else will use them eventually.Cloudless
@Relation You are really getting at the heart of the issue. Sure, this question is asking how to archive branches. It's better than just deleting, IMO. I am happy with my team enforcing FF merges, although this may change once we are using a monorepo. I really like @Pitr's answer, which is to create a new command, which simply uses git branch --no-merge master. With his approach, you necessarily have to think about deleting or archiving. Ultimately, I wish git was better at representing the fractal nature of code history.Postpositive
Squash merging+archiving merged branches can help create a fractal history. My comment was getting too large so I wrote a little gist here: gist.github.com/devinrhode2/830813710b2089ae358ef0667e24fb82Postpositive
Good critique and alternative approach. I still prefer the tagging approach though since (1) I often have several versions (iterations) of a branch and (2) not all project use an issue tracker (3) I don't use the branch name in the merge commit message. If I'm thorough enough I will have tagged the merged-in commit (might be a squash) so that I can see where it came from. One drawback remains though: it's extra manual work! Hopefully though I will be able to automate it further.Radiant
@Radiant 2) The lack of an issue tracker has far-reaching consequences and will compromise many best practices. So much so that issue trackers are now ubiquitous, even in small, single person projects; lacking one is a social issue, not a technical issue. Rather than adopting compromised practices which assume there is no issue tracker, it is better to assume there is an issue tracker and hold alternatives in reserve. 3) You can choose how you write your commit messages. 1) You merge one and the rest can be thrown away. If an iteration needs to be preserved, make it a new active branch.Relation
@Relation I've made drivey-by contributions to such a project. I wasn't speaking in hypotheticals. As a passer-by I can't demand an issue tracker of them.Radiant
@Radiant Of course, it happens. And when that happens adopt the necessary work-around practices. Your comment reads like you always use practices which assume there is no issue tracker, did I misunderstand?Relation
C
4

I sometimes archive branches as follows:

  1. Generate patch files, e.g., format-patch <branchName> <firstHash>^..<lastHash> (get firstHash and lastHash using git log <branchName>.
  2. Move the generated patch files to directory on a file server.
  3. Delete the branch, e.g., git branch -D <branchName>

"Apply" the patch when you need to use the branch again; however, applying the patch files (see git am) can be challenging depending on the state of the target branch. On the plus side, this approach has the benefit of allowing the branch's commits to be garbage-collected and saving space in your repo.

Callida answered 19/12, 2018 at 21:49 Comment(2)
Had to be patient & scroll all the way to the bottom to find the most useful answer for me & gladly give it a long overdue first upvote! :)Frequentative
“can be challenging depending on the state of the target branch.” Hmm. The commit message of the patch should say what commit it was based on (the parent of firstHash).Radiant
G
4

Step 0. Check to make sure that the working tree is clean so as to not lose any work:

git status

Step 1. From the root of the local repository, check out the branch and then tag it to be archived:

git checkout <branch_name> && git tag archive/<branch_name>

Step 2. Upload tags to remote and be sure to be in another branch before continuing, for example main:

git push origin --tags && git checkout main

Step 3. Delete the branch from the local and remote repositories:

git branch -D <branch_name> && git push origin -d <branch_name>

where you should replace <branch_name> with the name of the branch to archive, and origin with the name of the remote repository if other than origin.

Comments:

  • Before and after step 1 you may want to run git tag to notice the added tag.

  • Before and after step 3 you may want to watch https://github.com/<github-username>/<github-repository-name>/branches and/or run git branch -a to notice how the branch was deleted.

  • To restore a branch:

git checkout -b <branch_name> archive/<branch_name>

followed by

git push --set-upstream origin <branch_name>

References:
https://gist.github.com/zkiraly/c378a1a43d8be9c9a8f9
https://dev.to/clsource/archiving-git-branches-3k70

Germann answered 27/12, 2020 at 17:5 Comment(1)
Late to the party but just for completeness' sake: You can tag a branch that you're not currently on, by doing git tag archive/<branch_name> remotes/origin/<branch_name> or similar. Then the only other commands needed are git push origin --tags and git push origin -d <branch_name> (Avoids potentially expensive checkout operations)Cosmopolitan
B
3

My approach is to rename all branches I don't care about with a "trash_" prefix, then use:

git branch | grep -v trash

(with a shell key binding)

To retain the coloring of the active branch, one would need:

git branch --color=always | grep --color=never --invert-match trash
Bribe answered 20/9, 2016 at 20:20 Comment(2)
If renaming branches, you might as well put them in a namespace "archive/"Botelho
I think this is much more simple. PS: my prefix is zzz_Donte
G
1

You can use a script that will archive the branch for you

archbranch

It creates a tag for you with the prefix archive/ and then deletes the branch. Make sure you understand what it does before you use it =)


Usage - $/your/location/of/script/archbranch [branchname] [defaultbranch]

If you want to run the script without writing the location to it add it to your path

Then you can call it by

$ archbranch [branchname] [defaultbranch]

The [defaultbranch] is the branch that it will go to when the archiving is done. There are some issues with the color coding but other than that it works. I've been using it in projects for a long time.

Gayomart answered 25/6, 2013 at 13:17 Comment(2)
Per Stack Overflow Help, you need to disclose your affiliation with your product.Faker
Oh, sorry, didn't know. I am the author of the script.Gayomart
P
1

In Git a branch is just a pointer to some commit. So removing a branch will only remove a pointer, it won't remove the associated code even if the branch was never merged. So that "archiving" a local branch is as simple as remembering the pointer name (commit hash). You can find this information in ./git/refs/heads directory. Alternatively you can simply copy the branch-to-commit map into a text file like follows.

git branch -v > /path/to/backup/branches.txt

Once a branch has been removed you can restore it locally with the following command.

git branch <branch_name> <commit_hash>
Poncho answered 20/8, 2021 at 3:56 Comment(1)
If nothing is pointing the the SHA ref they could get purged so if braches are deleted some commits could be left dangling thus removdConcussion
T
1

To archive branches older than n months or years, run this bash script.

#!/bin/bash

# Average days in a month - 30.436875
# Hours in a day 24
# Minutes in an hour 60
# Seconds in a minute 60
months_in_year=12
days_in_month=30.436875
hours_in_day=24
minutes_in_hour=60
seconds_in_minute=60

# Input is x months or x years
# $1 an integer
# $2 a time metric

# Name of the script, follows through simlinks
script_name="$(basename "$(test -L "$0" && readlink "$0" || echo "$0")")"

if [ $# -le 1 ]; then
    echo "Usage: ./${script_name} [1-9] [month(s)/year(s)]"
    exit 1
fi

time_period=$1
time_metric=$2

if [[ ${time_metric} =~ "month" ]]; then minimum_branch_age_in_seconds=$( echo "scale=4; $time_period * $days_in_month * $hours_in_day * $minutes_in_hour * $seconds_in_minute" | bc); fi
if [[ ${time_metric} =~ "year" ]]; then minimum_branch_age_in_seconds=$( echo "scale=4; $time_period * $months_in_year * $days_in_month * $hours_in_day * $minutes_in_hour * $seconds_in_minute" | bc); fi

echo "minimum_branch_age: $1 $2"
echo "minimum_branch_age_in_seconds: ${minimum_branch_age_in_seconds%.*}"

git for-each-ref refs/remotes | while read commit type ref;do
    current=$(date +%s)
    headcd=$(git log -1 --pretty=%cd --date=format:%s ${commit})
    age_in_seconds=$((current-headcd))
    if [[ ${age_in_seconds} -ge ${minimum_branch_age_in_seconds%.*} ]];then
        branch=$(echo $ref | sed 's=refs/remotes/origin/==g')
        age_in_months=$( echo "scale=4; $age_in_seconds / $days_in_month / $hours_in_day / $minutes_in_hour / $seconds_in_minute" | bc)
        echo "archiving $branch - age in seconds - $age_in_seconds - age in months - $age_in_months "
        git tag archive/${branch} ${branch}
        git push -d origin ${branch}
        git branch -D ${branch}
        echo "Unarchive with: git checkout -b ${branch} archive/${branch}"
    fi
done

Thanks to Jeremy for the meat to this stew.

Teressaterete answered 13/4, 2022 at 23:38 Comment(0)
M
0

With secure situation if you are on other branch locally:

git tag archive/{branchname}  origin/{branchname} 
git tag archive/{branchname}   {branchname} 
git branch -D {branchname} 
git branch -d -r origin/{branchname} 
git push --tags
git push origin :{branchname} 
Mink answered 17/7, 2023 at 17:45 Comment(2)
can you clarify what do you mean by "secure situation", please?Nosy
Accidental deletion of branch before archiving.Mink
K
0

Here is an alias for archiving all old branches except 10 newest ones.

[alias]
  archiveoldbranches = "!git branch --sort=-committerdate | tail -n +10 | awk '{$1=$1};1' | xargs -L1 -I {} sh -c 'git tag archive/{} {}; git branch -D {}'"
Keek answered 28/9, 2023 at 14:1 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.