列出所有没有远程分支的本地分支

154
问题:我想找到一种删除所有本地分支的方法,这些分支没有对应远程分支。把分支名称传递给git branch -D {branch_name} 很容易,但是如何首先获取该列表呢?
例如:
我创建了一个没有远程分支的新分支:
$ git co -b no_upstream

我列出了所有我的分支,只有一个带有远程

$ git branch -a
master
* no_upstream
remotes/origin/HEAD -> origin/master
remotes/origin/master

我应该运行什么命令才能得到答案no_upstream

我可以运行git rev-parse --abbrev-ref --symbolic-full-name @{u},它会显示没有远程:

$ git rev-parse --abbrev-ref --symbolic-full-name @{u}
error: No upstream configured for branch 'no_upstream'
error: No upstream configured for branch 'no_upstream'
fatal: ambiguous argument '@{u}': unknown revision or path not in the working tree.
Use '--' to separate paths from revisions, like this:
'git <command> [<revision>...] -- [<file>...]'

但由于这是一个错误,它不允许我使用它或将其传递给其他命令。我打算将其用作一个 shell 脚本别名,如 git-delete-unbranched,或者制作一个超级简单的 Gem,例如 git-branch-delete-orphans


请参见https://dev59.com/pG445IYBdhLWcg3wRoLH。 - Tomer Cohen
1
可能是删除本地不再存在于远程的分支的重复问题。 - ks1322
12个回答

179

我建议使用 git branch --format 来指定你想要从 git branch 命令中获取的确切输出。这样做可以只提取出引用名称和上游,就像这样:

git branch --format "%(refname:short) %(upstream)"
它会按照以下格式输出分支以及远程分支(如果存在):
25-timeout-error-with-many-targets
31-target-suggestions refs/remotes/origin/31-target-suggestions
54-feedback-link refs/remotes/origin/54-feedback-link
65-digest-double-publish

一旦您获得这种漂亮格式的输出,只需通过 awk 进行管道连接就可以轻松获取列表:

git branch --format "%(refname:short) %(upstream)" | awk '{if (!$2) print $1;}'

以下输出结果:

25-timeout-error-with-many-targets
65-digest-double-publish

awk部分如果没有第二列,则打印第一列。

奖励:创建别名

通过在全局的.gitconfig文件(或任何地方)中创建别名,让其易于运行:

[alias]
  local-branches = "!git branch --format '%(refname:short) %(upstream:short)' | awk '{if (!$2) print $1;}'"

奖励:远程过滤

如果出于某种原因您有多个用于不同分支的跟踪远程,很容易指定您要检查的远程。只需将远程名称添加到 awk 模式中即可。在我的情况下,它是origin,所以我可以这样做:

git branch --format "%(refname:short) %(upstream)" | awk '$2 !~/\/origin\// { print $1 }'

重要提示:在别名中需要转义反斜杠,否则将导致无效的gitconfig文件。

  25-timeout-error-with-many-targets    206a5fa WIP: batch insert
  31-target-suggestions                 f5bdce6 [origin/31-target-suggestions] Create target suggestion for team and list on save
* 54-feedback-link                      b98e97c [origin/54-feedback-link] WIP: Feedback link in mail
  65-digest-double-publish              2de4150 WIP: publishing-state

有了这个格式良好的输出,只需将其通过cutawk管道传输,就可以得到您的列表:

git branch -vv | cut -c 3- | awk '$3 !~/\[/ { print $1 }'

“WIP:” 是什么意思呢?难道这不就是 stash 命令创建的东西吗? - igrek
@igrek 这只是我在提交消息中使用的前缀,与git无关。 - Jeremy Baker
6
这样不会漏掉被标记为“已删除”的远程分支吗? - Reid
任何类似的匹配都不会起作用,如果提交消息包含类似的内容。例如,我们所有的提交消息都是"[ABC-nnnn] 描述",因此每个消息都将匹配您的正则表达式。如果匹配"[origin"并不总是产生正确的结果。 - Aleks G
抱歉,更新后的答案不起作用。对于没有上游的两个分支,它返回以下内容... https://gist.github.com/rtfmoz2/587c0859a61936aa18133a34b62ea9fd - Kevin Davies
显示剩余2条评论

51

git branch(不带任何选项)仅列出本地分支,但您不知道它们是否正在跟踪远程分支。

通常,这些本地分支应在合并到 main 后删除(如git-sweep的此问题中所示):

git branch --no-contains main --merged main| xargs git branch -d

自2020/2021年起,大多数仓库将main作为默认分支而非master
这不是你想要的那么完整,但这是一个开始。

使用--merged选项,只有已合并到指定提交的分支(即其尖端提交可从指定提交到达)才会被列出。


1
要包括没有本地分支的远程分支,请使用 git branch -a --merged - Asclepius
注意:现在许多代码库已经将主分支从“master”更改为“main”,因此如果是这种情况,请确保进行更新。 - Joshua Pinter
1
@JoshuaPinter 好观点,谢谢。10年后,我已经相应地更新了答案。 - VonC

35
我有类似的问题,我想删除所有跟踪已删除远程分支的本地分支。我发现git remote prune origin无法删除我想要消除的分支。一旦远程被删除,我也想让本地分支消失。这是对我有效的方法:
从我的~/.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-branches 添加:

git config --global alias.prune-branches '!git remote prune origin && git branch -vv | grep '"'"': gone]'"'"' | awk '"'"'{print $1}'"'"' | xargs -r git branch -d'

注意:在我的实际配置中,我将-d更改为-D,因为我不想听到Git抱怨未合并的分支。您也可能需要此功能。如果是这样,请只需在该命令的末尾使用-D而不是-d

另外,值得一提的是,您的全局配置文件几乎总是~/.gitconfig

(OS X修复)

如所述,由于使用了xargs -r(感谢Korny),因此此方法在OS X上无法正常工作。

-r是为了防止xargs在没有分支名称的情况下运行git branch -d,这将导致错误消息“fatal: branch name required”。如果您不介意错误消息,只需删除xargs-r参数即可。

然而,如果您不想看到错误消息(实际上,谁能责怪您),那么您需要使用其他检查空管道的工具。您可以尝试使用moreutils中的ifne。您需要在xargs之前插入ifne,这将阻止xargs在没有数据时运行。请注意:ifne认为任何内容都不是空的,包括空行,因此您仍可能会看到错误消息。我没有在OS X上测试过这个方法。

下面是带有ifnegit config命令:

git config --global alias.prune-branches '!git remote prune origin && git branch -vv | grep '"'"': gone]'"'"' | awk '"'"'{print $1}'"'"' | ifne xargs git branch -d'

1
能否将此转换为“git config --global…”命令?从指导/白痴式维基的角度来看,很容易传递,而不是说“查找您的git配置文件,使用编辑器进行编辑,然后运行命令”。 - Kannan Ekanath
1
请注意,在OSX上xargs -r不起作用 - Korny
好的,我不知道在OSX中“空时不运行”选项是什么意思。 - Karl Wilbur

22
这对我有效:

git branch -vv | grep -v origin

(如果你的远程仓库名不是origin,请替换它)
这将列出所有未跟踪远程分支的分支,听起来就是你正在寻找的。

3
这将列出之前与远程相关联的分支,即使你运行 git fetch --prune 也不再关联。 - RusinaRange
4
在我看来,最好寻找“:gone]”而不是过滤掉“origin”。 - fiorentinoing

20

晚些编辑:

更好的方式是

git for-each-ref  --format='%(refname:short) %(upstream)'  refs/heads \
| awk '$2 !~/^refs\/remotes/'

在任何GNU系统上

for b in `git branch|sed s,..,,`; do
    git config --get branch.$b.remote|sed Q1 && echo git branch -D $b
done
如果有很多分支,最好使用 git branch | sed | sort git config -l | sed | sort 的输出,然后使用 comm -23 来比较。

1
@nmr s/git.*Q1/test $(git config --get branch.$b.remote|sed q|wc -1) = 1/ - jthill

16
我合成了自己的Git命令来获取origin/***: gone分支:

我合成了自己的Git命令来获取origin/***: gone分支:

git remote prune origin && git branch -vv | cut -c 3- | grep ': gone]' | awk '{print $1}' | xargs -n1 -r echo git branch -d

git remote prune origin && git branch -vv 会以详细模式打印分支。

cut -c 3- 将移除第一个字符。

grep ': gone]' 将只打印gone的远程分支。

awk '{print $1}' 将打印分支名称。

xargs -n1 -r echo git branch -d 将打印 git branch -d 命令来删除分支(-n1将每次管理一个命令,-r避免在没有任何分支的情况下发布命令)。

提示: 删除"echo"命令以运行命令而不是仅打印命令,我在命令中留下了这个来检查在提交到git之前的命令。

提示2: 仅当您确定要删除未合并的分支时才使用git branch -D


从这里开始,我进行了我的个人 git gone - fiorentinoing
2
这绝对是我找到的最干净的解决方案,太棒了!我从未花时间学习“awk”,我不知道你可以这样做。 - adamjc
1
这是唯一一个为我获取了所有已合并、删除或在本地的分支的代码。 - Max
为什么剪掉?我得到相同的输出,无论有没有它。 - clhereistian
删除前三行比通过下面的grep命令丢弃它们更好,但结果是一样的,你是对的。 - fiorentinoing

5
这里最受欢迎的答案在我的系统上无法运行,请参见gist
下面这个方法可以工作且相当简洁。
git branch -v | awk '$3 == "[gone]" { print $1 }' | xargs git branch -D

如果您不想删除未合并的分支,请使用 -d。


这个可以。小提示:如果你使用“压缩合并”,你需要使用-D,因为分支在技术上并没有被合并。 - user3265569
1
重要提示 - OQ 是关于“列出”分支的,而这个答案是“删除”它们。 - Mogsdad

3

这是我在PowerShell中使用过的一些内容,带有注释以解释它正在做什么。为了使其更加清晰明了,我没有使用任何缩写的PowerShell命令(别名)。可以根据需要将其压缩到所需的加密级别 :)

$verboseList = @(git branch -vv)
foreach ($line in $verboseList)
{
    # Get the branch name
    $branch = [regex]::Match($line, "\s(\S+)\s").Captures.Groups[1].Value
    # Check if the line contains text to indicate if the remote is deleted
    $remoteGone = $line.Contains(": gone]")
    # Check if the line does not contain text to indicate it is a tracking branch (i.e., it's local)
    $isLocal = !($line.Contains("[origin/"))
    if ($remoteGone -or $isLocal)
    {
        # Print the branch name
        $branch
    }
}

1

您可以使用%(upstream:track)格式化

git branch --format "%(refname:short) %(upstream:track)" |
  awk '{if($2=="[gone]") print $1;}'

git branch -v 的输出不可靠,无法进行解析。

Git 版本 2.37.1


为什么我是10年来第一个发布这个答案的人? - Pavel Vlasov
好观点。已点赞。当我在2013年撰写我的答案时,我没有Git 2.5和upstream:track语法,后来通过git for-each-ref重新发现了它。 - VonC

1

现有的答案似乎都对我无效。

以下是我列出本地分支但不在远程的解决方案。

comm -13 <(git branch -r | sed 's/origin\/\(.*\)/\1/g' | cut -c 3-) <(git branch | cut -c 3-)

以下是它的工作原理:

  1. 考虑所有远程分支的列表(不包括 'origin/' 或前导空格) git branch -r | sed 's/origin\/\(.*\)/\1/g' | cut -c 3-

  2. 考虑所有本地分支的列表(不包括星号或前导空格) git branch | cut -c 3-

  3. 比较这两个列表,并显示在第二个列表中(即我的本地分支)而不在第一个列表中(即远程分支列表)的任何内容。 comm -13 <(git branch -r | sed 's/origin\/\(.*\)/\1/g' | cut -c 3-) <(git branch | cut -c 3-)

comm 可以采用 -1、-2 和 -3 标志的组合,表示要从哪个文件中抑制行(唯一于文件 1、唯一于文件 2 或共同于两个文件)


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接