* It ensures the default branch is not deleted (main, master)
* It does not touch the current branch
* It does not touch the branch in a different worktree[2]
* It also works with non-merge repos by deleting the local branches that are gone on the remote
git branch --merged "$(git config init.defaultBranch)" \
| grep -Fv "$(git config init.defaultBranch)" \
| grep -vF '*' \
| grep -vF '+' \
| xargs git branch -d \
&& git fetch \
&& git remote prune origin \
&& git branch -v \
| grep -F '[gone]' \
| grep -vF '*' \
| grep -vF '+' \
| awk '{print $1}' \
| xargs git branch -D
[1]: https://github.com/fphilipe/dotfiles/blob/ba9187d7c895e44c35...I have an alias I use called git default which works like this:
default = !git symbolic-ref refs/remotes/origin/HEAD | sed 's@^refs/remotes/origin/@@'
then it becomes ..."$(git default)"...
This figures out the actual default from the origin.I work at a company that was born and grew during the master->main transition. As a result, we have a 50/50 split of main and master.
No matter what you think about the reason for the transition, any reasonable person must admit that this was a stupid, user hostile, and needlessly complexifying change.
I am a trainer at my company. I literally teach git. And: I have no words.
Every time I decide to NOT explain to a new engineer why it's that way and say, "just learn that some are master, newer ones are main, there's no way to be sure" a little piece of me dies inside.
# remove merged branches (local and remote)
cleanup = "!git branch -vv | grep ': gone]' | awk '{print $1}' | fzf --multi --sync --bind start:select-all | xargs git branch -D; git remote prune origin;"
https://github.com/WickyNilliams/dotfiles/blob/c4154dd9b6980...I've got a few aliases that integrate with fzf like an interactive cherry pick (choose branch, choose 1 or more commits), or a branch selector with a preview panel showing commits to the side. Super useful
The article also mentions that master has changed to main mostly, but some places use develop and other names as their primary branch. For that reason I always use a git config variable to reference such branches. In my global git config it's main. Then I override where necessary in any repo's local config eg here's an update command that updates primary and rebases the current branch on top:
# switch to primary branch, pull, switch back, rebase
update = !"git switch ${1:-$(git config user.primaryBranch)}; git pull; git switch -; git rebase -;"
https://github.com/WickyNilliams/dotfiles/blob/c4154dd9b6980... $(git config user.primaryBranch)
What about using git's own `init.defaultBranch`?I mean, while useless in terms of `git init` because the repo's already init'd, this works:
git config --local init.defaultBranch main
And if you have `init.defaultBranch` set up already globally for `git init` then it all just worksIn any case the main thrust was just to avoid embeddings assumptions about branch names in your scripts :)
git switch my-test-branch
...
git pull origin main:main
git rebase main git fetch
git rebase origin/main [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 statsWhat 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.
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 tools are the best to do the equivalent but for squash-merged branches detections?
Hooking on remote branch deletion is what most people do, under the assumption that you tend to clean out the branches of your PRs after a while. But of course if you don't do that it doesn't work.
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
# ~/.gitconfig
[alias]
gone = ! "git fetch -p && git for-each-ref --format '%(refname:short) %(upstream:track)' | awk '$2 == \"[gone]\" {print $1}' | xargs -r git branch -D"
Then you just `git gone` every once in a while, when you're between features. function Rename-GitBranches {
git branch --list "my-branch-prefix/*" | Out-GridView -Title "Branches to Zoo?" -OutputMode Multiple | % { git branch -m $_.Trim() "zoo/$($_.Trim())" }
}
`Out-GridView` gives a very simple dialog box to (multi) select branch names I want to mark finished.I'm a branch hoarder in a squash merge repo and just prepend a `zoo/` prefix. `zoo/` generally sorts to the bottom of branch lists and I can collapse it as a folder in many UIs. I have found this useful in several ways:
1) It makes `git rebase --interactive` much easier when working with stacked branches by taking advantage of `--update-refs`. Merges do all that work for you by finding their common base/ancestor. Squash merging you have to remember which commits already merged to drop from your branch. With `--update-refs` if I find it trying to update a `zoo/` branch I know I can drop/delete every commit up to that update-ref line and also delete the update-ref.
2) I sometimes do want to find code in intermediate commits that never made it into the squashed version. Maybe I tried an experiment in a commit in a branch, then deleted that experiment in switching directions in a later commit. Squashing removes all evidence of that deleted experiment, but I can still find it if I remember the `zoo/` branch name.
All this extra work for things that merge commits gives you for free/simpler just makes me dislike squash merging repos more.
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 :)
Beyond that, this is just OP learning how `xargs` works.
If nothing else maybe for inspiration
If that's something you're worried about, review the code before running it.
> don't you get anxiety "what if there's an error in tui code and it would mess up my git repo"?
I think you might want to not run untrusted programs in an environment like that, alternatively find a way of start being able to trust the program. Either approaches work, and works best depending on what you're trying to do.
It takes more, not less, time to thoroughly review code you didn't write.
If we're talking receiving random patches where first you have to understand the context, background and so on, then yeah I agree, it'll take longer time probably than what it took for someone to hammer with their fingers. But again, I'm not sure that's how professionals use LLMs right now, vibe-coding is a small hyped world mostly non-programmers seem to engage in.
How can you "check" that which you don't "understand"?
> I'm not sure that's how professionals use LLMs right now
I'm a professional and I can tell you how I use LLMs: I write code with their assistance, they don't write code for me.
The few times I let Claude or Copilot loose, the results were heartbreaking and I spent more time reviewing (and then discarding) the code than what it took me to later write it from scratch.
??? I do understand, since I literally just instructed it, how would I otherwise? I'm not letting the LLM do the design, it's all me still. So the "understand" already exists before the LLM even finished working.
> I'm a professional and I can tell you how I use LLMs: I write code with their assistance, they don't write code for me.
Hey, welcome to the club, me too :) I don't write code, I write English prose, yet nothing is vibe coded, and probably I'll end up being able to spend more time than you thinking about the design and architecture and how it fits in, because actual typing is no longer slowing me down. Yet again, every line is reviewed multiple times.
It's more about the person behind the tools, than the tools themselves I think ultimately. Except for Copilot probably, the times I've tried it I've just not been able to produce code that is even slightly up to my standards. It's a breeze with Codex though (5.2), and kind of hit and miss with Claude Code.
But I do quickly check the output what it does, and especially the commands it runs. Sometimes it throws all code in a single file, so I ask for 'good architecture with abstractions'.
If `gh repo ...` commands get run you can lose everything instantly. You can force push and be left with a single blank commit on both sides. The agent has full control of everything, not just your local data.
Just set up Rclone/restic and get your stuff into a system with some immutability.
Ais are giving you what they get from common patterns, parsing documentation etc. Depending what you're asking this might be an entirely novel combination of commands never run before. And depending on the model/prompt it might solve in a way any human would balk at (push main to origin, delete .git, re-clone from origin. Merged local branches are gone!)
It's like the ai art issues - people struggle with relative proportions and tones and making it look real. Ai has no issues with tones, but will add extra fingers or arms etc that humans rarely struggle with. You have to look for different things, and Ai bugs are definitely more dangerous than (most) human bugs.
(Depends a little, it's pretty easy to tell if a human knows what they're talking about. There's for sure humans who could write super destructive code, but other elements usually make you suspicious and worried about the code before that)
Really, they're just a GUI drawn with Unicode instead of drawing primitives.
Like many restrictions, limiting oneself to just a fixed grid of colored Unicode characters for drawing lends itself to more creative solutions to problems. Some people prefer such UIs, some people don't.
Some people will be like you save two seconds trying to do something simple. You lose more time building the tool than you will use it in your life.
It's not about saving time. It's about eliminating the mental toll from having to context switch(i know it sounds ai, reading so much ai text has gotten to me)
This broke my brain! Woah!
Or mouse / trackpad.
I really haven't seen anything better for making TUIs than Borland's Turbo Vision framework from 35ish years ago.
Peoples definitions will be on a gradient, but its somewhere between CLI (type into a terminal to use) and GUI (use your mouse in a windowing system), TUI runs in your terminal like a CLI but probably supports "graphical widgets" like buttons, bars, hotkeys, panes, etc.
I never heard "TUI" until the last few years, but it may be due to my background being Microsoft-oriented.
One of the only references I can find is the PC Magazine encyclopedia: https://www.pcmag.com/encyclopedia/term/cui
I’m fairly certain this terminology has been around since at least the early aughts.
https://en.wikipedia.org/w/index.php?title=Text-based_user_i...
Norton Commander (or Midnight Commander) is probably the quintessential example of a powerful TUI; it can do things that would be quite hard to replicate as easily in a CLI.
https://books.google.com/ngrams/graph?content=TUI&year_start...
Basically it was never used, then it was heavily used, and then never used, and then in the early 00s it took off again.
That'd explain why you used it, I never did, and now young kids are.
It's not a term I recall hearing at all when I started using computers in the mid-'80s - all that mattered back then was "shiny new GUI, or the clunky old thing?" I really thought it was a retroneologism when I first heard it, maybe twenty years ago.
I have a draft here about one aspect of Magit I enjoy: https://entropicthoughts.com/rebasing-in-magit
Did you open source that one? I was thinking of this exact same thing but wanted to think a little about how to share deps, i.e. if I do quick worktree to try a branch I don't wanna npm i that takes forever.
Also, if you share it with me, there's obviously no expectations, even it's a half backed vibecoded mess.
Nix helps Claude a lot with dependencies, it can add stuff and execute the flake as well.
I will come back to you with project itself.
I’ve been entirely terminal based for 20 years now and those issues have just worn me down. Yet I still love terminal for its simplicity. Rock and a hard place I guess.
That said, I do think it would be awesome if including prompts/history in the repos somehow became a thing. Not only would it help people learn and improve, but it would allow tweaking.
Maybe I'll try using small TUI too.
I personally lean more towards the "let's share cool little productivity tips and tricks with one another" instead of the "in order to share this you have to meet [entirely arbitrary line of novelty/cleverness/originality]."
But each to their own I suppose. I wonder how you learned about using xargs? Maybe a blog-post or article not dissimilar to this one?
Why do people constantly have to be looking for any way to justify their sense of superiority over others? Collaborative attitudes are so much better for all involved.
Saying "If you read X book, you'll realize it's a solved problem" IS the information -- the name of the book you need to read
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... 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 exist2. 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
The only case in which this wouldn't work is when you have a ton of necessary local branches you can't even push to remote, which is a risk and anti-pattern per se.
you mean the… pile of shame?
All those "merged" workflows only work, if you actually merge the branches. It doesn't work with a squash merge workflow.
edit: I delegate this task to a coding agent. I'm really bad at bash commands. yolo!
Unfortunately its name makes it hard to search for and find.
git branch | xargs git branch -d
Don't quote me, that's off the top of my head.
It won't delete unmerged branches by default. The line with the marker for the current branch throws an error but it does no harm. And I just run it with `develop` checked out. If I delete develop by accident I can recreate it from origin/develop.
Sometimes I intentionally delete develop if my develop branch is far behind the feature branch I'm on. If I don't and I have to switch to a really old develop and pull before merging in my feature branch, it creates unnecessary churn on my files and makes my IDE waste time trying to build the obsolete stuff. And depending how obsolete it is and what files have changed, it can be disruptive to the IDE.
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' '!f() { git branch --format '%(refname:short) %(upstream:track,nobracket)' | awk '$2~/^gone$/{print $1}' | xargs git branch -D; }; f' DEFAULT_BRANCH=$(git remote show origin | sed -n '/HEAD branch/s/.*: //p')
git branch --merged "origin/$DEFAULT_BRANCH" \
| grep -vE "^\s*(\*|$DEFAULT_BRANCH)" \
| xargs -r -n 1 git branch -d
This is the version I'd want in my $EMPLOYER's codebase that has a mix of default brancheshttps://gist.github.com/andrewaylett/27c6a33bd2fc8c99eada605...
But actually nowadays I use JJ and don't worry about named branches :).
https://replicated.wiki/blog/partII.html#navigating-the-hist...
Still, many oddities of git are inevitable due to its underlying storage model, so it makes sense to explore other models too.
https://github.com/henrikpersson/git-trash
I use this script with a quick overview to prevent accidentally deleting something important
https://github.com/foriequal0/git-trim
Readme also explains why it's better than a bash-oneliner in some cases.
#!/bin/sh
git branch --merged | egrep -v "(^\*|master|main|dev)" | xargs --no-run-if-empty
git branch -d #!/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. function Remove-GitBranches {
git branch --merged | Out-GridView -Title "Branches to Remove?" -OutputMode Multiple | % { git branch -d $_.Trim() }
}
`Out-GridView` gives you a quick popup dialog with all the branch names that supports easy multi-select. That way you get a quick preview of what you are cleaning up and can skip work in progress branch names that you haven't committed anything to yet.Have a merge workflow which deletes the branch right there.
I have replaced my standard ddg of, "git <the thing i need>" with asking Claude to give me the commands I need to run.
so then it's `git ciaclean` and not bare `ciaclean` which imo is cleaner.
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'https://github.com/tkrajina/git-plus
Disclaimer, I'm the author ^^
[1]: https://github.com/tj/git-extras/blob/main/Commands.md#git-delete-merged-branchesgit branch | lines | where ($it !~ '^*') | each {|br| git branch -D ($br | str trim)} | str trim
hey, gemini, how do I...
(not on my computer right now to check)
git fetch --prune && git branch -vv | awk '/: gone]/{print $1}' | xargs git branch -DI think I probably copied this from Stack Overflow close to a decade ago. Seems like a lot of people have very similar variations.
I see that even the CIA, a federal government office, has not fully used DEI approved, inclusive language yet :-)
I am not sure under what usecases, you will end up with a lot of stale branches. And git fetch -pa should fix it locally
A lot of my developer colleagues don't know how git works, so they have no idea that "I merged the PR" != "I deleted the feature branch". I once had to cleanup a couple repositories that had hundreds of branches spanning back 5+ years.
Nowadays I enforce it as the default project setting.
By default, I don't think so. And even if the branch is deleted, objects can still be there. I think GitLab has a "Clean stale objects" thing you can trigger, I don't seem to recall ever seeing any "Git Maintenance" UI actions on GitHub so not sure how it works there.
> Since most projects now use main instead of master
some delusions to boot
It also has one for squash-merged branches: gbds
Very useful I've been using them for years
git branch -vv | grep ': gone\]' | awk '{print $1}' | xargs -n 1 git branch -D
git branch --merged origin/main --format="%(refname:short)" \ | grep -vE "^(main|develop)$" \ | xargs -r git branch -d
that said... pretty hilarious a dev was just like "uhh yeah ciaclean..." curious what... other aliases they might have?? git branch --format '%(if:equals=gone)%(upstream:track,nobracket)%(then)%(refname:short)%(end)' --omit-empty | xargs --verbose -r git branch -D
It deletes all the branches for which remotes were deleted. GitHub deletes branches after PR was merged. I alias it to delete-mergedSee this email for some references:
https://mail.gnome.org/archives/desktop-devel-list/2019-May/...
Then does simply performing a search on bitkeepers documents for "slave" then automatically imply any particular terminology "came from bitkeeper?"
Did they take it from bitkeeper because they prefer antiquated chattel slavery terminology? Is there any actual documents that show this /intention/?
Or did they take it because "master" without slave is easily recognizable as described above which accurately describes how it's _actually_ implemented in git.
Further git is distributed. Bitkeeper was not.
This is just time wasting silliness.
With git it was basically entirely driven by SJW that felt empowered by people accepting the replica rebrand
I don't really care what the default branch is called tho so I'm willing to play along.
This let them claim huge diff counts and major contributions to DEI and get promos.
Now, the left wing activists have turned it on its head again, and now saying that the term "black" is shameful and racist. It's bizarre how ignorant people are who say the term "blacklist" is racist.
Blocklist makes more sense in most scenarios.
are you sure this is about time/breaking and not "being told how to think"?
Yeah, it's not like 99% of the world has already switched from master to main already (without any major problems) ...
These are the kinda local things that the parent was probably referring to.
> Slavic peoples were considered to be people of an "inferior race" who were unable to assimilate into American society. They were popularly not considered to be "fully white" (and thus fully American), and Slavic peoples' "whiteness" continues to be a debate to this day.
https://en.wikipedia.org/wiki/Anti-Slavic_sentiment#United_S...