是否有一种简单的方法可以删除所有远程对等分支不再存在的跟踪分支?
例子:
分支机构(本地和远程)
主人原始/主起源/bug-fix-a起源/bug-fix-b起源/bug-fix-c
在本地,我只有一个主分支。现在我需要处理bug-fix-a,所以我检查它,处理它,并将更改推到远程。接下来,我对bug-fix-b做同样的操作。
分支机构(本地和远程)
主人bug-fix-abug-fix-b型原始/主起源/bug-fix-a起源/bug-fix-b起源/bug-fix-c
现在我有本地分支机构master,bug-fix-a,bug--fix-b。主分支维护者将把我的更改合并到主分支中,并删除他已经合并的所有分支。
因此,当前状态为:
分支机构(本地和远程)
主人bug-fix-abug-fix-b型原始/主起源/bug-fix-c
现在我想调用一些命令来删除分支(在本例中为bug-fix-a、bug-fix-b),这些分支在远程存储库中不再表示。
它类似于现有命令git remote prune origin,但更类似于git local prune origin。
我认为没有内置命令来执行此操作,但可以安全地执行以下操作:
git checkout master
git branch -d bug-fix-a
当您使用-d时,git将拒绝删除分支,除非它完全合并到HEAD或其上游远程跟踪分支中。因此,您可以始终循环每个ref的git输出,并尝试删除每个分支。这种方法的问题是,我怀疑您可能不希望因为origin/bug-fix-d包含其历史而删除bug--fix-d。相反,您可以创建如下脚本:
#!/bin/sh
git checkout master &&
for r in $(git for-each-ref refs/heads --format='%(refname:short)')
do
if [ x$(git merge-base master "$r") = x$(git rev-parse --verify "$r") ]
then
if [ "$r" != "master" ]
then
git branch -d "$r"
fi
fi
done
警告:我尚未测试此脚本-请小心使用。。。
删除已合并到主控形状中的所有分支,但不要尝试删除主控形状本身:
git checkout master&&git pull origin master&&git fetch-p&&git branch-d$(git branch--merged|grep master-v)
或添加别名:
alias gitcleanlocal=“git checkout master&&git pull origin master&&git fetch-p&&git branch-d$(git branch--merged|grep master-v)”
说明:
git结帐主结帐主分支
git pull-origin master确保本地分支合并了所有远程更改
gitfetch-p删除对已删除的远程分支的引用
gitbranch-d$(gitbranch-master--merged|grepmaster-v)删除已合并到master中的所有分支,但不要尝试删除master本身
git远程修剪源修剪跟踪不在远程上的分支。
gitbranch——merged列出已合并到当前分支中的分支。
xargs git branch-d删除标准输入中列出的分支。
请小心删除gitbranch--merged列出的分支。该列表可能包含您不希望删除的主分支或其他分支。
要在删除分支之前让自己有机会编辑列表,可以在一行中执行以下操作:
git branch --merged >/tmp/merged-branches && \
vi /tmp/merged-branches && xargs git branch -d </tmp/merged-branches
我在这里找到了答案:如何删除所有已合并的git分支?
git branch --merged | grep -v "\*" | xargs -n 1 git branch -d
确保我们留住主人
您可以通过在第一个grep之后添加另一个grep来确保master或其他分支不会被删除。在这种情况下,您可以:
git branch --merged | grep -v "\*" | grep -v "YOUR_BRANCH_TO_KEEP" | xargs -n 1 git branch -d
因此,如果我们想保持主控、开发和登台,我们会:
git branch --merged | grep -v "\*" | grep -v "master" | grep -v "develop" | grep -v "staging" | xargs -n 1 git branch -d
将此设置为别名
由于它有点长,您可能需要在.zshrc或.bashrc中添加别名。我的别名称为gbpurge(用于git分支清除):
alias gbpurge='git branch --merged | grep -v "\*" | grep -v "master" | grep -v "develop" | grep -v "staging" | xargs -n 1 git branch -d'
然后重新加载.bashrc或.zshrc:
. ~/.bashrc
or
. ~/.zshrc
最安全的方法是对每个ref使用带有插值变量%(upstream:track)的“管道”命令git,当分支不再位于远程时,该变量将消失:
git fetch -p && for branch in $(git for-each-ref --format '%(refname) %(upstream:track)' refs/heads | awk '$2 == "[gone]" {sub("refs/heads/", "", $1); print $1}'); do git branch -D $branch; done
这种方法比使用“瓷”命令更安全,因为不会有意外匹配提交消息部分的风险。下面是一个使用“瓷”git命令的版本:
git fetch -p && for branch in $(git branch -vv | grep ': gone]' | awk '{print $1}'); do git branch -D $branch; done
这种工作方式是在命令之后
git fetch -p
运行时删除远程引用
git branch -vv
它将显示“已离开”作为远程状态。例如
$ git branch -vv
master b900de9 [origin/master: behind 4] Fixed bug
release/v3.8 fdd2f4e [origin/release/v3.8: behind 2] Fixed bug
release/v3.9 0d680d0 [origin/release/v3.9: behind 2] Updated comments
bug/1234 57379e4 [origin/bug/1234: gone] Fixed bug
这是脚本迭代的内容。
这里有一个解决方法,我用它来处理鱼壳。在Mac OS X 10.11.5、fish 2.3.0和git 2.8.3上测试。
function git_clean_branches
set base_branch develop
# work from our base branch
git checkout $base_branch
# remove local tracking branches where the remote branch is gone
git fetch -p
# find all local branches that have been merged into the base branch
# and delete any without a corresponding remote branch
set local
for f in (git branch --merged $base_branch | grep -v "\(master\|$base_branch\|\*\)" | awk '/\s*\w*\s*/ {print $1}')
set local $local $f
end
set remote
for f in (git branch -r | xargs basename)
set remote $remote $f
end
for f in $local
echo $remote | grep --quiet "\s$f\s"
if [ $status -gt 0 ]
git branch -d $f
end
end
end
几点注意事项
确保设置正确的base_branch。在本例中,我使用develop作为基本分支,但它可以是任何东西。
这一部分非常重要:grep-v“\(master\|$base_branch\|\*\)”。它确保您不会删除主分支或基本分支。
我使用gitbranch-d<branch>作为额外的预防措施,以便不删除任何尚未与上游或当前HEAD完全合并的分支。
一种简单的测试方法是用echo“将删除$f”替换gitbranch-d$f。
我想我还应该补充一句:使用风险自负!
这将删除本地不存在的所有远程分支(在ruby中):
bs = `git branch`.split; bs2 = `git branch -r | grep origin`.split.reject { |b| bs.include?(b.split('/')[1..-1].join('/')) }; bs2.each { |b| puts `git push origin --delete #{b.split('/')[1..-1].join('/')}` }
解释:
# local branches
bs = `git branch`.split
# remote branches
bs2 = `git branch -r | grep origin`.split
# reject the branches that are present locally (removes origin/ in front)
bs2.reject! { |b| bs.include?(b.split('/')[1..-1].join('/')) }
# deletes the branches (removes origin/ in front)
bs2.each { |b| puts `git push origin --delete #{b.split('/')[1..-1].join('/')}` }
我使用了一个简短的方法来完成这项任务,我建议你也这样做,因为这样可以节省一些时间,并提高你的可见度
只需将以下代码段添加到.bashrc(macos上的.bashprofile)中。
git-cleaner() { git fetch --all --prune && git branch --merged | grep -v -E "\bmaster|preprod|dmz\b" | xargs -n 1 git branch -d ;};
获取所有遥控器仅从git中获取合并的分支从此列表中删除“受保护/重要”分支删除其余部分(例如,清理和合并的分支)
您必须编辑grep正则表达式以满足您的需要(这里,它防止删除master、prepod和dmz)
我通常不会回答一个已经有16个答案的问题,但所有其他答案都是错误的,正确的答案很简单。问题是,“有没有一种简单的方法可以删除所有远程分支不再存在的跟踪分支?”
如果“简单”意味着一次性删除它们,不脆弱、不危险,并且不依赖并非所有读者都会拥有的工具,那么正确的答案是:不。
有些答案很简单,但他们没有按照要求做。其他人按照要求做,但并不简单:所有人都依赖于通过文本操作命令或脚本语言解析Git输出,这可能不是每个系统都有的。除此之外,大多数建议都使用瓷命令,其输出不被脚本解析(“瓷”是指用于人类操作的命令;脚本应该使用较低级别的“管道”命令)。
进一步阅读:
为什么不应该在脚本中解析git分支输出。跟踪分支以及git remote prune、git prune和git fetch--prune之间的差异
如果您想安全地执行此操作,对于问题中的用例(已在服务器上删除但仍作为本地分支存在的垃圾收集跟踪分支),并且仅使用高级Git命令,您必须
git fetch--prune(或git fetch-p,它是一个别名,或git prune remote origin,它在不获取的情况下执行相同的操作,并且可能不是您大多数时候想要的)。注意报告为已删除的任何远程分支。或者,为了稍后找到它们,可以使用gitbranch-v(任何孤立的跟踪分支都将标记为“[gone]”)。每个孤立跟踪分支上的gitbranch-d[分支名称]
(这是其他一些答案提出的)。
如果你想编写一个解决方案的脚本,那么每个ref都是你的出发点,就像Mark Longair在这里的回答和对另一个问题的回答一样,但我看不出不编写shell脚本循环或使用xargs之类的方法来利用它。
背景说明
要了解发生了什么,您需要认识到,在跟踪分支的情况下,您没有一个分支,而是有三个分支。(请记住,“分支”只是指向提交的指针。)
给定一个跟踪分支功能/X,远程存储库(服务器)将具有此分支并将其称为功能/X。您的本地存储库有一个分支remotes/origin/feature/X,这意味着,“这就是上次我们谈话时远程告诉我它的功能/X分支是什么。”最后,本地存储库还有一个分支功能/X,指向您的最新提交,并被配置为“跟踪”remotes/origin/feature/X,这意味着您可以拉和推以保持它们对齐。
有时,有人删除了遥控器上的功能/X。从那一刻起,您就剩下了本地功能/X(您可能不想再使用它了,因为功能X的工作大概已经完成了)和远程/原点/功能/X,这肯定是无用的,因为它的唯一目的是记住服务器分支的状态。
Git将允许您自动清理冗余的remotes/origin/feature/X,这就是gitfetch-prime所做的,但由于某些原因,它不允许您自动删除自己的功能/X。。。即使您的feature/X仍然包含孤立的跟踪信息,因此它具有识别已完全合并的前跟踪分支的信息。(毕竟,它可以为您提供信息,让您自己动手操作。)
所有这些都不适合我。我想要一些东西来清除所有跟踪远程分支的本地分支,在源位置,远程分支已被删除(消失)。我不想删除从未设置为跟踪远程分支的本地分支(即:我的本地dev分支)。我还想要一个简单的一行程序,它只使用git或其他简单的CLI工具,而不是编写自定义脚本。我最后使用了一点grep和awk来生成这个简单的命令。
这最终是我的~/.gitconfig:
[alias]
prune-branches = !git remote prune origin && git branch -vv | grep ': gone]' | awk '{print $1}' | xargs -r git branch -D
这里有一个git-config--global。。。用于轻松将其添加为git prune分支的命令:
git config --global alias.prune-branches '!git remote prune origin && git branch -vv | grep '"'"': gone]'"'"' | awk '"'"'{print $1}'"'"' | xargs -r git branch -d'
注意:在config命令中,我使用-d选项来gitbranch,而不是-d,正如我在实际配置中所做的那样。我使用-D是因为我不想听到Git抱怨未合并的分支。您可能也需要此功能。如果是这样,只需在config命令末尾使用-D而不是-D。
Windows解决方案
对于Microsoft Windows Powershell:
git结帐主机;git远程更新原点--prune;git branch-vv|选择字符串-模式“:gone]”|%{$_.toString().Trim().Split(“”)[0]}|%{git branch-d$_}
解释
git checkout master切换到master分支
git远程更新原点--修剪远程分支
gitbranch-vv获取所有分支的详细输出(git引用)
选择String-Pattern“:gone]”仅获取从远程删除的记录。
%{$_.toString().Trim().Split(“”)[0]}获取分支名称
%{gitbranch-d$_}删除分支
另一个答案,大量借鉴Patrick的答案(我喜欢这个答案,因为它似乎消除了任何关于去哪儿的歧义),将在git分支输出中匹配),但添加了*nix弯曲。
最简单的形式是:
git branch --list --format \
"%(if:equals=[gone])%(upstream:track)%(then)%(refname:short)%(end)" \
| xargs git branch -D
我的路径上有一个git-gone脚本:
#!/usr/bin/env bash
action() {
${DELETE} && xargs git branch -D || cat
}
get_gone() {
git branch --list --format \
"%(if:equals=[gone])%(upstream:track)%(then)%(refname:short)%(end)"
}
main() {
DELETE=false
while [ $# -gt 0 ] ; do
case "${1}" in
(-[dD] | --delete) DELETE=true ;;
esac
shift
done
get_gone | action
}
main "${@}"
注:--format选项似乎很新;我需要将git从2.10.something升级到2.16.3才能获得它。
编辑:修改为包含参考名的建议:来自Benjamin W。
NB2-我只在bash中进行了测试,因此有了hashbang,但可能可以移植到sh。
基于Git提示:删除旧的本地分支,这看起来类似于jason.rickman的解决方案。为此,我使用Bash实现了一个名为Git-gone的自定义命令:
$ git gone
usage: git gone [-pndD] [<branch>=origin]
OPTIONS
-p prune remote branch
-n dry run: list the gone branches
-d delete the gone branches
-D delete the gone branches forcefully
EXAMPLES
git gone -pn prune and dry run
git gone -d delete the gone branches
git gone-pn结合了修剪和列出“goned”分支:
$ git gone -pn
bport/fix-server-broadcast b472d5d2b [origin/bport/fix-server-broadcast: gone] Bump modules
fport/rangepos 45c857d15 [origin/fport/rangepos: gone] Bump modules
然后,您可以使用git-gone-d或git-gone-d来扣动扳机。
笔记
我使用的正则表达式是“$BRANCH/.*:gone]”,其中$BRANCH通常是源代码。如果您的Git输出本地化为法语等,这可能不会起作用。Sebastian Wiesner还为Windows用户将其移植到Rust。那个也叫git gone。
我已经用GitPython编写了一个Python脚本来删除远程不存在的本地分支。
import git
import subprocess
from git.exc import GitCommandError
import os
def delete_merged_branches():
current_dir = input("Enter repository directory:")
repo = git.Repo(current_dir)
git_command = git.Git(current_dir)
# fetch the remote with prune, this will delete the remote references in local.
for remote in repo.remotes:
remote.fetch(prune=True)
local_branches = [branch.name for branch in repo.branches]
deleted_branches = []
# deleted_branches are the branches which are deleted on remote but exists on local.
for branch in local_branches:
try:
remote_name = 'origin/'+ branch
repo.git.checkout(remote_name)
except GitCommandError:
# if the remote reference is not present, it means the branch is deleted on remote.
deleted_branches.append(branch)
for branch in deleted_branches:
print("Deleting branch:"+branch)
git_command.execute(["git", "branch", "-D",branch])
# clean up the work flow.
repo.git.checkout('master')
repo.git.pull()
if __name__ == '__main__':
delete_merged_branches()
希望有人觉得它有用,如果我错过了什么,请添加评论。
TL;博士:
删除所有不在远程上的本地分支
git fetch -p && git branch -vv | grep ': gone]' | awk '{print $1}' | xargs git branch -D
删除所有不在远程AND上的本地分支,这些分支是完全合并的,并且没有像前面许多答案中所说的那样使用。
git fetch -p && git branch --merged | grep -v '*' | grep -v 'master' | xargs git branch -d
解释
gitfetch-p将删除远程上不再存在的所有分支gitbranch-vv将打印本地分支,修剪后的分支将标记为已删除grep“:gone]”仅选择已删除的分支awk“{print$1}”筛选输出以仅显示分支的名称xargs git branch-D将循环所有行(分支)并强制删除该分支
为什么选择gitbranch-D而不是gitbranch-D,否则对于未完全合并的分支,您将拥有。
error: The branch 'xxx' is not fully merged.
还有一个答案,因为没有一个解决方案符合我对优雅和跨平台的需求:
删除不在远程上的本地分支的命令:
for b in $(git for-each-ref --format='%(if:equals=[gone])%(upstream:track)%(then)%(refname:short)%(end)' refs/heads); do git branch -d $b; done
要将其与gitconfig集成,以便可以使用git branch prune运行:
Bash
git config --global alias.branch-prune '!git fetch -p && for b in $(git for-each-ref --format='\''%(if:equals=[gone])%(upstream:track)%(then)%(refname:short)%(end)'\'' refs/heads); do git branch -d $b; done'
PowerShell(PowerShell)
git config --global alias.branch-prune '!git fetch -p && for b in $(git for-each-ref --format=''%(if:equals=[gone])%(upstream:track)%(then)%(refname:short)%(end)'' refs/heads); do git branch -d $b; done'
(需要帮助查找PowerShell和bash的通用命令)
为什么这个答案是最好的?
提供了一个完整的解决方案:向git添加一个git branch prune命令在Windows PowerShell中工作正常核心思想是@jason.rickman对每个裁判使用git的防弹方法分析和过滤是使用--filter完成的,因此不需要外部依赖项
说明:
在~\.gitconfig中添加新别名。执行此操作后,只需执行git分支修剪在此别名中:使用--prune标志获取分支,该标志“修剪不再位于远程的远程跟踪分支”对每个ref和--filter使用git,以获取分支列表(无远程)循环此列表并安全删除分支
真正的挑战是当维护者压缩提交时。然后,使用git内置功能(如--merged)的解决方案没有帮助。
gitdelete合并分支工具允许方便地删除分支。我特别喜欢互动模式。
安装(需要python3):
pip install git-delete-merged-branches
然后执行
git-delete-merged-branches --effort=3
--努力=3对于能够删除挤压的分支很重要。
选择
@teppeis/git delete squashed:安装node.js后,执行npx@teppeis/git delete squushed。支持主分支。git delete squashed:未维护:主分支的未命中功能@teppeis/git delete squashed基于此。
虽然上面的答案涵盖了如何手动修剪树枝,但这个答案增加了解决这一问题的自动化。git现在有了一个新设置,可以在每次提取操作中删除不再位于远程的过时分支。这很好,因为我们不再需要在每次删除分支时手动调用远程prune(git pull也调用git fetch)。
为每次提取启用修剪行为
要在全局配置中启用此功能,请执行以下操作:
git config --global fetch.prune true
让事情自动发生意味着你可以忘记在新机器上添加此设置。它只是起作用。
对特定远程设备上的每次提取启用修剪行为
git config --global remote.<name>.prune true
本地自动修剪
我们也可以在不使用--global标志的情况下对局部修剪应用相同的命令。
.git配置
上面的命令适用于global和local.gitconfig,如下所示:
...
[fetch]
prune = true
我可以建议将其添加到一个可靠的配置中或添加到您的dotfiles存储库(.gitconfig)中,以便将来自动进行设置。
配置设置在每次提取时调用以下命令:
git remote prune <remote name>
总结
要将引用作为正常工作流的一部分进行删减而不需要记住运行,请全局或远程设置fetch.prune<name>.在配置中对每个远程进行修剪。请参阅git-config。
另一个答案。
如果你正在寻找一些新奇的东西,这里是我在fzf中使用的别名。
LANG=C git for-each-ref --sort=-committerdate --format '%(refname:short) %(upstream:track)' refs/heads \
| awk '$2 == "[gone]" {print $1}' \
| fzf --multi --bind ctrl-a:select-all,ctrl-t:toggle-all \
--preview='git log --color=always --graph --format=compact {1}' \
--header='Select [gone] branch to remove' \
--header-first --reverse \
| xargs --no-run-if-empty git branch -d
解释:
强制LANG=C以避免[gone]模式上的本地化对每个引用使用git,以避免git分支中的任何格式更改使用awk过滤。我尝试使用--format=“%(if:equals=[gone])%…”,但这意味着要管理空行。使用fzf选择要删除的分支。添加快捷键:[ctrl-a]选择全部,[ctrl-t]反转选择([tab]照常选择)。您可以使用[enter]进行验证,也可以使用[esc]中止。我使用这个分支选择器作为确认阶段。在右侧显示所选分支的日志(以记住日期和内容…)。我使用下面列出的紧凑格式。您可以使用任何(不太宽)日志格式。--如果为空,则不运行。它不是强制性的,有助于避免在中止进程时出错(与没有分支名称的gitbranch-d相关)。
别名可能看起来很糟糕:
[alias]
gone = "!f() { LANG=C git for-each-ref --sort=-committerdate --format '%(refname:short) %(upstream:track)' refs/heads | awk '$2 == \"[gone]\" {print $1}' | fzf --multi --bind ctrl-a:select-all,ctrl-t:toggle-all --preview='git log --color=always --graph --format=compact {1}' --header='Select [gone] branch to remove' --header-first --reverse | xargs --no-run-if-empty git branch -d ; }; f"
[pretty]
compact = %Cred%h%Creset%C(auto)%d%Creset%n Author: %an <%aE>%n Date: %cd%n %s
(与上文相同,“)
列出远程分支现在已消失的所有本地分支
#!/bin/sh
LC_ALL=C git for-each-ref --format='%(refname:short) %(upstream:track)' refs/heads/ |\
sed -n '/ *\[gone\].*$/{s@@@;p}'
然后通过管道将其导入xargs git分支-D或-D。
此解决方案应与POSIX兼容。没有特定于shell的东西,只有POSIX兼容的sed/xargs。
LC_ALL=C通过强制英语使脚本语言保持中立对于每个引用,使用管道命令--format='%(refname:short)%(upstream:track)输出分支名称跟踪信息参考/仅负责本地分支机构sed-n'/*\[gone\].*$/匹配跟踪信息[gone],该信息不是有效分支名称的一部分,因此无需担心{s@@@;p}删除跟踪部分并仅打印这些行
nb:分支不能包含空格,因此我们不需要对xargs进行任何清理。