410

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?

observer
  • 2,925
  • 1
  • 19
  • 38
dan-manges
  • 5,097
  • 4
  • 19
  • 16
  • git-rm does not delete resources from the repository, it only removes them from the index http://www.kernel.org/pub/software/scm/git/docs/git-rm.html You can easily restore these resources using `git checkout [rev] file` – Dana the Sane Aug 20 '09 at 15:58

15 Answers15

543

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.

Tim Bellis
  • 1,607
  • 3
  • 14
  • 24
Jeremy Wall
  • 23,907
  • 5
  • 55
  • 73
  • 9
    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. – Grégory Joseph May 22 '12 at 10:17
  • 1
    @GrégoryJoseph what's that? – o0'. Sep 19 '13 at 14:52
  • 1
    @Lohoris i might have been using the wrong terminology, but if memory serves right, I meant whatever git tag -a produces. – Grégory Joseph Nov 25 '13 at 19:55
  • 11
    @GrégoryJoseph: That's a so-called "annotated tag". And yes, using that can make a lot of sense, I'd say. – onnodb Dec 13 '13 at 15:09
  • @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. – U2EF1 Dec 15 '13 at 21:10
  • 30
    small note, you probably want `branch -D` since it's likely not to be fully merged if you are archiving it in this way – Arkadiy Kukarkin Mar 21 '14 at 16:08
  • 2
    Does this work if the branches I want to tag and archive are remote? Are there any extra steps I need to take? – Felix Eve Aug 09 '16 at 00:43
  • `$ 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? – Saiyine Dec 01 '16 at 11:38
  • 7
    More general, and more meaningful imho `git tag [-a -m "some description maybe"] archive/ `. 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. – eigil Mar 14 '19 at 13:42
  • If you want to list all tags just use: git tag; details about tag show's command: git show archive/ – arrowman Jun 17 '19 at 05:50
  • 11
    2 important commands missing: `git push --tags` (newly created archive tag pushed to server). `git push origin :` (delete from server). – jerrymouse Jul 02 '19 at 10:53
  • Hi @dan-manges , 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://stackoverflow.com/a/12114948/6345724 – dank8 Nov 17 '19 at 08:51
  • I guess to list the archived branches one uses `git tag --list` – Dr_Zaszuś Jan 17 '20 at 09:57
  • 1
    The tutorial referenced by @guyaloni can now be found here [here](https://www.aaronwest.net/blog/git-workflows-archiving-old-branches/) – tobylaroni Oct 05 '21 at 20:23
  • 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. – fredrivett Dec 27 '22 at 12:54
181

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 branch -D <branchname>

And here's how to restore a branch:

> git checkout -b <branchname> archive/<branchname>
merlin2011
  • 71,677
  • 44
  • 195
  • 329
Steve
  • 2,396
  • 2
  • 15
  • 16
49

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

 git tag archive/<branchname> <branchname>
 git branch -D <branchname>
 git branch -d -r origin/<branchname>
 git push --tags
 git push origin :<branchname>

To restore from the remote, see this question.

Community
  • 1
  • 1
Liam
  • 1,471
  • 1
  • 15
  • 25
  • 2
    The last line can also be `git push origin --delete ` for Git v1.7.0+. Source: https://git-scm.com/book/en/v2/Git-Internals-The-Refspec – jslatane Jan 13 '23 at 17:56
  • 1
    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. – isherwood Feb 16 '23 at 15:10
42

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.

snipsnipsnip
  • 2,268
  • 2
  • 33
  • 34
  • 6
    Excellent, apart from everyone else on this thread, you actually answered the question. – tzrlk Feb 03 '17 at 00:52
  • 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? – Devin Rhode Feb 06 '21 at 23:48
  • 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 :) – Devin Rhode Feb 06 '21 at 23:53
  • 1
    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! – Devin Rhode Feb 06 '21 at 23:55
  • 1
    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 :) – snipsnipsnip Feb 08 '21 at 02:54
  • 1
    This [perl implementation (git-attic)](https://github.com/ap-miscellanea/git-attic/blob/master/git-attic#L7) has `list`, `save`, `rm`, `fetch`, and `push` – Devin Rhode Aug 27 '21 at 18:32
  • 1
    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: https://gist.github.com/devinrhode2/6e370ddfe824b1f49515f56e6314a434 – Devin Rhode Jun 23 '22 at 20:00
  • @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? – Ti Strga Mar 15 '23 at 19:45
  • 1
    Adding --format=refname seems like a GREAT solution. I'm not sure if I do have weird settings locally. – Devin Rhode Mar 17 '23 at 15:35
  • Cool use of non-standard refs! – Guildenstern Apr 28 '23 at 08:45
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
August Lilleaas
  • 54,010
  • 13
  • 102
  • 111
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

Alexey
  • 9,197
  • 5
  • 64
  • 76
  • 5
    With newer versions of git (as suggested [here](https://stackoverflow.com/a/7534289/431033)), this alias gives completion: `!git tag archive/$1 $1 && git branch -D` – Lack Jul 27 '17 at 21:33
  • 2
    For the curious ones, `git archive` is for, like, creating .tar archives, according to my 5 seconds skimming the docs page. – Devin Rhode Oct 11 '20 at 01:33
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.

Pitr
  • 79
  • 1
  • 1
  • 6
    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. – Bart May 23 '16 at 20:17
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.

Community
  • 1
  • 1
Schwern
  • 153,029
  • 25
  • 195
  • 336
  • 3
    But what about the clutter? Perhaps if there were a way to hide the old branches under 10 cubic yards of dirt. – bvj Mar 06 '18 at 20:52
  • 4
    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. – Neil Mayhew Mar 30 '19 at 00:18
  • 2
    @bvj 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. – Neil Mayhew Mar 30 '19 at 00:19
  • @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. – Schwern Mar 30 '19 at 02:42
  • @Schwern 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. – Neil Mayhew Mar 30 '19 at 15:15
  • @Schwern 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. – Devin Rhode Oct 11 '20 at 03:32
  • Squash merging+archiving merged branches can help create a fractal history. My comment was getting too large so I wrote a little gist here: https://gist.github.com/devinrhode2/830813710b2089ae358ef0667e24fb82 – Devin Rhode Oct 11 '20 at 03:46
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
Marco Sulla
  • 15,299
  • 14
  • 65
  • 100
Sridhar Sarnobat
  • 25,183
  • 12
  • 93
  • 106
3

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.

Bill Hoag
  • 677
  • 4
  • 16
  • 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! :) – davmos Apr 01 '21 at 16:36
  • “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`). – Guildenstern Apr 28 '23 at 11:03
3

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

Henke
  • 4,445
  • 3
  • 31
  • 44
  • 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/ remotes/origin/` or similar. Then the only other commands needed are `git push origin --tags` and `git push origin -d ` (Avoids potentially expensive checkout operations) – public satanic void Jul 01 '22 at 14:37
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.

Banezaka
  • 21
  • 3
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>
ya.teck
  • 2,060
  • 28
  • 34
  • 5
    If nothing is pointing the the SHA ref they could get purged so if braches are deleted some commits could be left dangling thus removd – Simson Nov 10 '22 at 01:45
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.

Trent
  • 11
  • 1
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} 
Wolvverine
  • 29
  • 3