2

In a Git repo how can I efficiently remove local tracking branches for deleted remote branches?

If I create a Github repo, then create a feature branch off master, then the Git cli shows this

user@workstation C:\Home\Development\Workspaces\Scratch\GitTest1      
$ git branch -vv                                                            
* feature1 e1f5c4e [origin/feature1] Stuff added on branch feature1         
  master   d7a1ee8 [origin/master] Initial commit                           
                                                                            
user@workstation C:\Home\Development\Workspaces\Scratch\GitTest1      
$ git branch -a                                                             
* feature1                                                                  
  master                                                                    
  remotes/origin/feature1                                                   
  remotes/origin/master                                                     

I can see what remote branches my local branches are tracking and everything is good.

If I delete the feature branch from Github and fetch --prune.

user@workstation C:\Home\Development\Workspaces\Scratch\GitTest1      
$ git fetch --prune                                                         
From https://github.com/Neutrino-Sunset/git-test                            
 - [deleted]         (none)     -> origin/feature1                          

But now git branch -vv still shows the deleted remote branch, even though git branch -a no longer lists it.

user@workstation C:\Home\Development\Workspaces\Scratch\GitTest1      
$ git branch -vv                                                            
* feature1 e1f5c4e [origin/feature1: gone] Stuff added on branch feature1   
  master   d7a1ee8 [origin/master] Initial commit                           
                                                                            
user@workstation C:\Home\Development\Workspaces\Scratch\GitTest1      
$ git branch -a                                                             
* feature1                                                                  
  master                                                                    
  remotes/origin/master          

Because of this the only way I have to keep my local repository synchronised with my remote is to run git branch -vv in one console, git branch -a in another console. Then manually compare which of my local branches that say they are tracking a remote branch are actually tracking a branch that doesn't even exist, and then in a third console manually delete the extraneous branches.

For a large repo it's astonishingly cumbersome, and very easy to make a mistake.

Does Git really not provide a more intelligent way to do this?

Neutrino
  • 8,496
  • 4
  • 57
  • 83
  • 2
    `feature1` is not a *remote* branch (whatever you might mean by that). You could call it a "local tracking branch" but I wouldn't: I would just call it a "local branch", with an upstream set. (The upstream setting can be bogus, as this one is due to the remote-tracking `origin/feature1` being gone.) Git won't automatically delete your (local) branches because they're *yours*. You might still be doing work with them. In general, don't create branches when you're not doing work in them, and remove them when you're done doing work in them, and they won't accumulate. That said... – torek May 05 '21 at 12:56
  • ... see [VonC's answer here](https://stackoverflow.com/a/48820687/1256452) to a more or less duplicate question. – torek May 05 '21 at 12:57
  • I'm not sure which instance of the quote `feature1` you are referring to. I know that Git won't automatically delete local branches, I'm asking whether there is some way that I can do that efficiently based on whether the remoate branch they are tracking is gone. – Neutrino May 10 '21 at 10:25
  • I've seen VonC's answer. None of those command lines work in a Windows command line, and I'd never be able to remember them anyway. I'm looking for a solution that works and is efficient. – Neutrino May 10 '21 at 10:26
  • 1
    I was probably triggered by the phrase "deleted remote branches". :-) Anyway, while I don't use Windows, I understand that all the tools (bash, grep, awk, etc.) are available *as* stand-alone Windows commands these days. When I was forced to use Windows in the late 00's I used Cygwin to get a useful command line. – torek May 10 '21 at 10:37
  • No offense but I wouldn't count having to install a new programming environment as an efficient solution. – Neutrino May 10 '21 at 10:44
  • 1
    @Neutrino No cygwin needed. All those Linux commands are available directly in your CMD (no need to even start a bash session). Provided you add to your `%PATH%` the right folders (https://stackoverflow.com/a/63835684/6309) – VonC May 10 '21 at 11:38
  • There's still no way I can remember a command line like that, nor ever want to. It would literally be easier to write a new console program to do it. I'm looking for a usable solution, and to be usable I've go to be able to remember it and understand how it works. I guess I'm just dumb but these paragraph sized invocations of multiple command line programs just aren't a usable tool for me. Lifes too short to waste brainpower on that. – Neutrino May 10 '21 at 12:15

1 Answers1

1

I agree with torek's comment and would like to elaborate on the why.

Conceptually, let's start with a more generalized question:

In a Git repo how can I efficiently remove local branches that I no longer need?

Because everyone's workflow might be different, there isn't a good catch-all for determining which you branches you no longer need. The standard answer that Git supports is:

  1. If a branch is fully merged into another branch (typically a permanent shared branch such as main, master, or develop), then you no longer need it. This is because there is no information on that branch that isn't already stored elsewhere (except for the branch name). You can re-create the branch in the future if you decide you need it again.

When I say that Git supports deleting branches that are fully merged, by this I mean when you delete a branch you can specify -d to perform the delete if the branch is fully merged (into either your currently checked out branch or another specified branch), or you can use -D to delete the branch regardless.

There are other reasons you may no longer need a branch, that don't meet the criteria in #1, some of which may be:

  1. Throwaway branches for testing or playing around.
  2. Potential code that was rejected and will never be used. (For example if you tried something 3 different ways on different branch names and moved forward with the best implementation. If you're certain of your pick, the other 2 branches can be deleted now, and the "good" branch can be deleted once it's fully merged.)
  3. Branches with code that is fully merged but with different commit IDs due to rebasing or amending. This might happen if your PR tool has an option called "Semi-linear merge" (Azure DevOps) or "Rebase, merge" (Bitbucket) which will potentially change the commit IDs on the fly at PR completion, meaning your local branch will have different IDs. Another way it could happen is if someone else rebased or amended a commit on your branch, perhaps to make a minor tweak before completing your PR. (I regularly do this when my co-workers ask me to PR their stuff and set the PR to auto-complete. If I see some minor issues I might comment it in the PR, but then just fix it for them, force-push it out and then approve it. Now they'll certainly need to use -D instead of -d when they delete their branch.)

Now to your specific question (paraphrased):

In a Git repo how can I efficiently remove local branches for deleted remote branches?

One obvious way would be to use the method you suggested, but with the word "gone" next to it. This should catch most of the branches you can delete in #1 and #4, but it's still not perfect. It doesn't catch the case where you added a commit to your local branch and forgot to push it out before the PR was completed! If you aren't worried about that possibility, then your approach should work, and can probably be automated. (See VonC's answer for some ideas.)

If you don't want to risk deleting extra commits on your branches that were never pushed, then the one thing Git supports is the one thing that you know with certainty is safe, which is deleting fully merged branches. So given that, you could safely iterate through every one of your local branches, use the -d argument against a permanent branch such as develop or main, and sleep well at night. The issue with that is that you'll probably still have many more branches leftover that you can delete.

If you have a rebase on-the-fly workflow, then you can also use this algorithm:

  1. Select a branch to compare with (where most of your PRs would go, e.g. main)
  2. For each local branch X,
  3. Create a temp branch T from X.
  4. Rebase T onto main
  5. Diff T and main. If they are identical, you can delete branch X.

Now for the remainder of your branches, you'll just have to look at them manually. I suggest listing your local branches a few times per day and keeping them purged all the time, since you'll know which PRs have recently been completed. If you wait too long it gets messy. (Sort of like an Email Inbox, or perhaps more analogous is the Email Drafts Folder. I think I have over 50 drafts in mine, and there might be 1 or 2 I don't want to delete for some reason, and therefore they all will stay until I dedicate time to purging them.)

TTT
  • 22,611
  • 8
  • 63
  • 69
  • 1
    You might need to prune the tags too, or one of your deleted local branch might still be accessible through a tag: https://stackoverflow.com/a/49215190/6309 – VonC May 06 '21 at 06:54
  • @VonC Agreed! You just reminded me of a [related question](https://stackoverflow.com/q/66212614/184546) about *detecting* out of sync tags before performing a sync. That may even provide additional insight into determining which branches can be removed. – TTT May 06 '21 at 07:25
  • I assume that a local branch that tracks a remote that has been merged can be deleted using `branch -d `. So is there a way to do that for all branches perhaps with a `dry-run` option to list the branches that would be deleted before doing it for real? – Neutrino May 10 '21 at 10:31
  • @Neutrino if the commits haven't been modified then yes, you can use `-d` instead of `-D`. My reason #4 lists possible reasons a merged and deleted branch would need `-D`. (Those reasons may not apply to your workflow.) But for your dry-run, can't you just filter on ": gone"? In Git Bash something like `git branch -vv | grep ": gone"` or in Windows `git branch -vv | find ": gone"` – TTT May 10 '21 at 14:03
  • `git branch -vv | find "gone: "` is nice, thanks for that. But if I wanted to deleted the result of that command I'd still need to write another script or program of some sort, and additionally that wouldn't even work using `branch -d` if anyone has rebased or done anything else to cause git to rewrite history. I greatly appreciate all the input but I can't help thinking that it seems to me the answer to the original question is: no there's no efficient way to do this using git. – Neutrino May 11 '21 at 08:25
  • @Neutrino you could certainly script out the bulk of the deletes, but I agree, there isn't a good way to (easily) catch them *all*. I tried to convey that in the answer with these statements: "there isn't a good catch-all for determining which you branches you no longer need." And, "Now for the remainder of your branches, you'll just have to look at them manually. I suggest listing your local branches a few times per day and keeping them purged all the time" – TTT May 11 '21 at 13:59
  • @Neutrino if I were in your shoes, I'd start with "gone" branches, and purge those. (For a one time bulk delete you could just grab the list of branches and paste them into a delete command.) Then I'd dedicate a couple of hours to look at each branch and purge the ones I don't need. And moving forward I'd try to keep up with it. I used to have over a hundred local branches which took me a while to go through and purge. Now I keep it under 10 most of the time, which is much easier to manage. – TTT May 11 '21 at 14:06