99 comments

[ 5.0 ms ] story [ 79.5 ms ] thread
I currently have a TUI addiction. Each time I want something to be easier, I open claude-code and ask for a TUI. Now I have a git worktree manager where I can add/rebase/delete. As TUI library I use Textual which claude handles quite well, especially as it can test-run quite some Python code.
> Each time I want something to be easier, I open claude-code and ask for a TUI.

WTF has happened to software development, that anyone thinks they need "AI" for that? Any self-respecting half-way decent IDE has had parameterized templates for that since at least the mid-1990s. (They used to be called "Wizards" on Windows, or "Experts" in the Borland tools.)

Select "File", "New", "[Whatever]", answer a few questions in a (series of) dialog(s), click "Finish", and hey presto!, you had a new project, templated according to your preferences, with the UI scaffolding all finished. (Mostly GUI, of course, since that was the hot new thing back then; but could just as easily have been TUI, and I suppose some had that too.)

All done locally on your stonking new 486 266 Mhz / 16 MB. No need to burn half the planet to run some LLM "in the cloud", i.e. on someone else's machine in a datacenter somewhere. Jeebus fuck, does one have to be labelled "old curmudgeon" just for thinking there's something seriously wrong going on here nowadays?

The git plugin in oh-my-zsh has an alias for this: gbda

It also has one for squash-merged branches: gbds

Very useful I've been using them for years

So effectively "I just discovered xargs"? Not to disparage OP but there isn't anything particularly novel here.
Lots of negative sentiment on your comment, but I was going to write the same. Hopefully AI won’t make us forget that good command line tools are designed to be chained together if you want to achieve something that’s perhaps too niche as a use case to make it into a native command. It’s worth learning about swiss army utilities like xargs that make this easy (and fun)
Seriously, this seems like someone in awe of xargs. Maybe its the Bell Labs in me but this is boilerplate stuff.
It's cool that it comes from CIA, and someone who doesn't know about xargs may just learn something new. What is not to like?
> It's cool that it comes from CIA

What nauseous sentiment. I recommend "The CIA as Organized Crime: How Illegal Operations Corrupt America and the World" by Douglas Valentine, ISBN 978-0997287011. One of the most evil organizations ever to have existed.

(comment deleted)
I've had this command as 'git drop-merged' for a few years now (put as a script in your path named git-drop-merged:

  #!/bin/sh
  git branch --merged | egrep -v "(^\*|master|main|dev)" | xargs --no-run-if-empty
  git branch -d
I use

    #!/bin/sh
    
    git checkout main
    git fetch --prune
    git branch | grep -v main | xargs --no-run-if-empty git branch -D
    git pull
Save that next to your git binary, call it whatever you want. It's destructive on purpose.
(comment deleted)
I use this alias:

    prune-local = "!git fetch -p && for branch in $(git branch -vv | awk '/: gone]/{if ($1!=\"\*\") print $1}'); do git branch -d $branch; done"
1. Fetch the latest from my remote, removing any remote tracking branches that no longer exist

2. Enumerate local branches, selecting each that has been marked as no longer having a remote version (ignoring the current branch)

3. Delete the local branch safely

I have something similar, but open fzf to select the branches to delete [1].

    function fcleanb -d "fzf git select branches to delete where the upstream has disappeared"
        set -l branches_to_delete (
            git for-each-ref --sort=committerdate --format='%(refname:lstrip=2) %(upstream:track)' refs/heads/ | \
            egrep '\[gone\]$' | grep -v "master" | \
            awk '{print $1}' | $_FZF_BINARY --multi --exit-0 \
        )

        for branch in $branches_to_delete
            git branch -D "$branch"
        end
    end
[1]: https://github.com/jo-m/dotfiles/blob/29d4cab4ba6a18dc44dcf9...
I've had essentially that - if a bit fancier to accept an optional argument as well as handle common "mainline" branch names - aliased as `git lint` for a while:

    [alias]
        lint = !git branch --merged ${1-} | grep -v -E -e '^[*]?[ ]*(main|master|[0-9]+[.]([0-9]+|x)-stable)$' -e '^[*][ ]+' | xargs -r -n 1 git branch --delete
so:

    git pull --prune && git lint
sits very high in my history stats
The main issue with `git branch --merged` is that if the repo enforces squash merges, it obviously won't work, because SHA of squash-merged commit in main != SHA of the original branch HEAD.

What tools are the best to do the equivalent but for squash-merged branches detections?

Note: this problem is harder than it seems to do safely, because e.g. I can have a branch `foo` locally that was squash-merged on remote, but before it happened, I might have added a few more commits locally and forgot to push. So naively deleting `foo` locally may make me lose data.

I have the same issue. Changes get pushed to gerrit and rebased on the server. This is what I have, though not perfected yet.

  prunable = "!f() { \
  : git log ; \
  target=\"$1\"; \
  [ -z \"$target\" ] && target=$(git for-each-ref --format=\"%(refname:short)\" --count=1 refs/remotes/m/); \
  if [ -z \"$target\" ]; then echo \"No remote branches found in refs/remotes/m/\"; return 1; fi; \
  echo \"# git branch --merged shows merged if same commit ID only\" ;\
  echo \"# if rebased, git cherry can show branch HEAD is merged\"  ;\
  echo \"# git log grep will check latest commit subject only.  if amended, this status won't be accurate\" ;\
  echo \"# Comparing against $target...\"; \
  echo \"# git branch --merged:\"; \
  git branch --merged $target  ;\
  echo \" ,- git cherry\" ; \
  echo \" |  ,- git log grep latest message\"; \
  for branch in $(git for-each-ref --format='%(refname:short)' refs/heads/); do \
   if git cherry \"$target\" \"$branch\" | tail -n 1 | grep -q \"^-\"; then \
    cr=""; \
   else \
    cr=""; \
   fi ; \
   c=$(git rev-parse --short $branch) ; \
   subject=$(git log -1 --format=%s \"$branch\" | sed 's/[][(){}.^$\*+?|\\/]/\\\\&/g') ; \
   if git log --grep=\"^$subject$\" --oneline \"$target\" | grep -q .; then \
    printf \"$cr  $c %-20s $subject\\n\"  $branch; \
   else \
    printf \"$cr  \\033[0;33m$c \\033[0;32m%-20s\\033[0m $subject\\n\"  $branch; \
   fi; \
  done; \
  }; f"
(some emojis missing in above. see gist) https://gist.github.com/lawm/8087252b4372759b2fe3b4052bf7e45...

It prints the results of 3 methods:

1. git branch --merged

2. git cherry

3. grep upstream git log for a commit with the same commit subject

Has some caveats, like if upstream's commit was amended or the actual code change is different, it can have a false positive, or if there are multiple commits on your local branch, only the top commit is checked

Mine's this-ish (nushell, but easily bashified or pwshd) for finding all merged, including squashed:

    let t = "origin/dev"; git for-each-ref refs/heads/ --format="%(refname:short)" | lines | where {|b| $b !~ 'dev' and (git merge-tree --write-tree $t $b | lines | first) == (git rev-parse $"($t)^{tree}") }
Does a 3-way in-mem merge against (in my case) dev. If there's code in the branch that isn't in the target it won't show up.

Pipe right to deletion if brave, or to a choice-thingy if prudent :)

I recently revised my script to rely on (1) no commits in the last 30 days and (2) branch not found on origin. This is obviously not perfect, but it's good enough for me and just in case, my script prompts to confirm before deleting each branch, although most of the time I just blindly hit yes.

To avoid losing any work, I have a habit of never keeping branches local-only for long. Additionally this relies on https://docs.github.com/en/repositories/configuring-branches...

What if you first attempted rebases of your branches? Then you would detect empty branches
Unfortunately doesn't work if the project you work on squashes everything :(
"ciaclean" is a nice touch.

I assume CIA stands for Clean It All.

Missed opportunity to call it `git ciao`
If you squash your PR before merging, then this alternative worked really well for me:

  git fetch --prune && git branch -vv | awk '/: gone]/{print $1}' | xargs git branch -D
(comment deleted)
I've had this in my ~/.bash_aliases for awhile:

  alias git-wipe-merged-branches='git branch --merged | grep -v \* | xargs git branch -D'
Trying to remember where I got that one, as I had commented the following version out:

  alias git-wipe-all-branches='git for-each-ref --format '%(refname:short)' refs/heads | grep -v master | xargs git branch -D'
We all have something similar, it seems! I stole mine from https://stackoverflow.com/questions/7726949/remove-tracking-....

I also set mine up to run on `git checkout master` so that I don't really have to think about it too hard -- it just runs automagically. `gcm` has now become muscle memory for me.

  alias gcm=$'git checkout master || git checkout main && git pull && git remote prune origin && git branch -vv | grep \': gone]\'|  grep -v "\*" | awk \'{ print $1; }\' | xargs -r git branch -D'
IIRC, you can do git branch -D $(git branch) and git will refuse to delete your current branch. Kind of the lazy way. I never work off of master/main, and usually when I need to look at them I checkout the remote branches instead.
Wait, why would the update for the silly master->main change be swapping the excluded regex instead of just excluding both?
You probably want git-dmb (dmb = delete merged branches) for a safe and more comprehensive way of dealing with this.
(comment deleted)