Neutrino
Neutrino

Reputation: 9594

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

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?

Upvotes: 2

Views: 253

Answers (1)

TTT
TTT

Reputation: 28934

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.)

Upvotes: 1

Related Questions