Skip to content

Instantly share code, notes, and snippets.

@drbrain
Forked from robmiller/git-cleanup-repo
Last active December 3, 2023 08:47
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save drbrain/57ac1be661d3d9a83e8064cd4221ae8b to your computer and use it in GitHub Desktop.
Save drbrain/57ac1be661d3d9a83e8064cd4221ae8b to your computer and use it in GitHub Desktop.
A script for cleaning up Git repositories. It deletes branches that are fully merged into the origin default branch, prunes obsolete remote tracking branches, and as an added bonus will replicate these changes on the remote.
#!/usr/bin/env nu
# Adapted from original by Yorick Sijsling
# Adapted from bash version by Rob Miller <rob@bigfish.co.uk>
#
# Nushell version
#
# See history for a less-capable bash version
#
# Compared to the fork point this version has additional features:
# * The default branch is automatically discovered (also in prior bash version)
# * Returns you to the branch you started in
# * The default upstream is "origin" but you may override it
# * The branches to keep are stored in the local git config
# Delete local (and optionally remote) merged branches
#
# Set cleanup-repo.keep locally to a space-separated list of branch patterns to keep
export def "git cleanup-repo" [
upstream: string = "origin" # Upstream remote repository
] {
let current_branch = current_branch
let default_branch = get_default_branch $"refs/remotes/($upstream)/HEAD"
let keep = get_keep
# Switch to the default branch
switch_branch $default_branch
# Make sure we're working with the most up-to-date version of the default
# branch.
run-external "git" "fetch"
# Prune obsolete remote tracking branches. These are branches that we
# once tracked, but have since been deleted on the remote.
run-external "git" "remote" "prune" $upstream
# Delete local branches that have been fully merged into the default branch
list_merged $upstream $default_branch $keep
| each {|branch|
delete_local $branch
}
# Again with remote branches
let merged_on_remote = list_merged --remote $upstream $default_branch $keep
if not ( $merged_on_remote | is-empty ) {
print "The following remote branches are fully merged and will be removed:"
$merged_on_remote
| each {||
print $"\t($in)"
}
print ""
if ( input --suppress-output "Continue (y/N)? " | str trim ) == "y" {
$merged_on_remote
| each {|branch|
delete_remote $upstream $branch
}
}
}
switch_branch $current_branch
}
# The current branch name
def current_branch [] {
run-external --redirect-stdout "git" "branch" "--show-current"
| into string
| str trim
}
# Delete a local branch
def delete_local [
branch: string # Branch to delete
] {
run-external "git" "branch" "--delete" $branch
}
# Delete a remote branch
def delete_remote [
upstream: string # Repository to delete from
branch: string # branch to delete
] {
run-external "git" "push" "--quiet" "--delete" $upstream $branch
}
# Get the default branch
def get_default_branch [
upstream: string # Repository to get the upstream branch name from
] {
let args = [
"--short"
$upstream
]
run-external --redirect-stdout "git" "symbolic-ref" $args
| str trim
| path basename
}
# Get the local set of branches to always keep
def get_keep [] {
let keep = run-external --redirect-stdout "git" "config" "--local" "--get" "cleanup-repo.keep"
if ( $keep | is-empty ) {
return []
}
$keep
| str trim
| split column " "
| get column1
}
# List all the branches that have been merged fully into the default branch.
#
# We use the remote default branch here, just in case our local default branch is out of date.
def list_merged [
--remote # List remote branches (local default)
upstream: string # Upstream repository
branch: string # Default branch
keep: list<string> # Patterns to keep. The default branch and HEAD will be added
] {
mut args = [
"--list"
"--merged" $"($upstream)/($branch)"
]
if $remote {
$args = ( $args | append [
"--format" "%(refname:lstrip=3)"
"--remote"
])
} else {
$args = ( $args | append [
"--format" "%(refname:lstrip=2)"
])
}
let args = $args
let keep = (
$keep
| append [
"HEAD",
$branch,
]
)
run-external --redirect-stdout "git" "branch" $args
| lines
| filter {|branch|
$keep
| all {|pattern|
$branch !~ $'\A($pattern)\z'
}
}
}
# Switch to a different branch
def switch_branch [
branch: string
] {
let args = [
"--quiet"
"--no-guess"
$branch
]
run-external "git" "switch" $args
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment