获取特定分支的提交记录:Git 日志

358
我想列出所有只属于特定分支的提交记录。
以下命令会列出该分支以及其父分支(master)上的所有提交记录。
git log mybranch

我找到的另一个选项是排除主分支可达的提交并给我想要的结果,但我希望避免需要知道其他分支的名称。

git log mybranch --not master

我试图使用git for-each-ref,但它也列出了我的分支,所以实际上排除了所有内容:

git log mybranch --not $(git for-each-ref --format '^%(refname:short)' refs/heads/)

更新:

我正在测试一项新选项,我一段时间前发现了它,到目前为止似乎这可能是我一直在寻找的东西:

git log --walk-reflogs mybranch

更新(2013-02-13T15:08):

--walk-reflogs选项很好,但我查了一下发现reflogs有一个过期时间(默认为90天,gc.reflogExpire)。

我想我找到了我要找的答案:

git log mybranch --not $(git for-each-ref --format='%(refname)' refs/heads/ | grep -v "refs/heads/mybranch")

我只是将当前分支从可用分支列表中移除,并使用该列表来排除日志。这样,我只会得到那些仅被我的分支所包含的提交


可能与https://dev59.com/lnVD5IYBdhLWcg3wNIvc重复。 - StarPinkER
已经看到了那个问题,但不是同一个。 - dimirc
2
你确定这就是你想要的吗?我认为最好有一个目标:“我的分支上有什么不在上游中”,或者“我的分支上有什么不在主分支中”。此外,虽然git在修剪方面很快,但随着分支数量的增加,这将变得更加昂贵。我有一个脚本,我称之为“git missing”,它类似于bzr的“missing”命令。你可以在这里找到它:https://github.com/jszakmeister/etc/blob/master/git-addons/git-missing。 - John Szakmeister
1
我实际上需要这个用于 post-receive 钩子,因此“master”不会始终是要排除的分支。 - dimirc
1
是的,StarPinkER提到的重复命令对我很有效:git log $(git merge-base HEAD branch)..branch - charo
20个回答

299

按照听起来的意思,您应该使用cherry

git cherry -v develop mybranch

这将显示所有包含在mybranch中但不在develop中的提交。如果您省略最后一个选项(mybranch),它将与当前分支进行比较。

正如VonC指出的那样,您总是在将自己的分支与另一个分支进行比较,因此了解您的分支,然后选择要比较的分支。


7
这正是我所需的,但似乎应该有一个更直观的命令(即使在Git的世界中也是如此)。 - Seth
23
您可以使用 git cherry -v master 命令来将您当前的分支与主分支进行比较。 - Pithikos
9
使用git cherry的危险在于,只有当提交之间的文件差异在分支之间完全相同时才会匹配。如果进行了任何导致差异在分支之间不同的合并操作,那么git cherry就会将它们视为不同的提交。 - Ben
这只是给我一个“致命错误:未知提交mybranch”。 - Matt Arnold
2
@MattArnold,你需要将文本“develop”和“mybranch”更改为存在于你的仓库中的分支。 - Smilie
3
我不明白为什么这篇文章会得到赞,因为 OP 明确表示他想避免直接命名另一个分支。而且 git cherry -v develop mybranch 和 OP 提到的 git log mybranch --not develop 非常相似。 - fuujuhi

54
“但我希望避免需要知道其他分支的名称。”
我认为这是不可能的:Git中的分支始终基于另一个分支或至少另一个提交,正如在“git diff doesn't show enough”中所解释的那样。

enter image description here

您需要一个参考点来显示正确的提交记录。
如 "GIT - Where did I branch from?" 所述:
分支只是DAG中某些提交的指针
因此,即使 git log master..mybranch 是一个答案,如果mybranch基于myotherbranch,而myotherbranch又基于master,它仍然会显示太多提交记录。
为了找到该参考(分支的起点),您只能解析提交并查看它们所在的分支,如下所示:

42

我终于找到了实现OP想要的功能的方法。它非常简单:

git log --graph [branchname] 

git log --graph origin/[branchname] # if your local checkout isn't up to date

该命令将以图形格式显示从提供的分支可达的所有提交。但是,您可以通过查看提交图中第一个字符为*的提交行来轻松筛选该分支上的所有提交。
例如,让我们看下面的cakephp GitHub存储库上git log --graph master的摘录:
D:\Web Folder\cakephp>git log --graph master
*   commit 8314c2ff833280bbc7102cb6d4fcf62240cd3ac4
|\  Merge: c3f45e8 0459a35
| | Author: José Lorenzo Rodríguez <lorenzo@users.noreply.github.com>
| | Date:   Tue Aug 30 08:01:59 2016 +0200
| |
| |     Merge pull request #9367 from cakephp/fewer-allocations
| |
| |     Do fewer allocations for simple default values.
| |
| * commit 0459a35689fec80bd8dca41e31d244a126d9e15e
| | Author: Mark Story <mark@mark-story.com>
| | Date:   Mon Aug 29 22:21:16 2016 -0400
| |
| |     The action should only be defaulted when there are no patterns
| |
| |     Only default the action name when there is no default & no pattern
| |     defined.
| |
| * commit 80c123b9dbd1c1b3301ec1270adc6c07824aeb5c
| | Author: Mark Story <mark@mark-story.com>
| | Date:   Sun Aug 28 22:35:20 2016 -0400
| |
| |     Do fewer allocations for simple default values.
| |
| |     Don't allocate arrays when we are only assigning a single array key
| |     value.
| |
* |   commit c3f45e811e4b49fe27624b57c3eb8f4721a4323b
|\ \  Merge: 10e5734 43178fd
| |/  Author: Mark Story <mark@mark-story.com>
|/|   Date:   Mon Aug 29 22:15:30 2016 -0400
| |
| |       Merge pull request #9322 from cakephp/add-email-assertions
| |
| |       Add email assertions trait
| |
| * commit 43178fd55d7ef9a42706279fa275bb783063cf34
| | Author: Jad Bitar <jadbitar@mac.com>
| | Date:   Mon Aug 29 17:43:29 2016 -0400
| |
| |     Fix `@since` in new files docblocks
| |

正如您所看到的,只有提交记录8314c2ff833280bbc7102cb6d4fcf62240cd3ac4c3f45e811e4b49fe27624b57c3eb8f4721a4323b的提交行中的第一个字符是*。这些提交来自主分支,而其他四个提交则来自其他分支。


4
这不是完全相同的事情,但对于少量的提交来说足以完成工作。 - ryanwebjackson
是的,我的丑陋解决方案与此类似。我会做一些像 git log --oneline --graph | grep '^*' 这样的事情,其中 graph 根据它们的分支(某种程度上)装饰所有行,而 grep 选择具有特定装饰的所有行。在这种情况下,我只会得到当前分支。要使其正常工作,您需要为自己的需求正确获取 grep 正则表达式。 - NoJoshua

25

我正在使用以下命令:

git shortlog --no-merges --graph --abbrev-commit master..<mybranch>
或者
git log --no-merges --oneline --decorate master..<mybranch>

1
+1. --graph 选项只会在第一个字符处添加 *,因此可以避免使用该选项。对于我的分支,--abbrev-commit--decorate 似乎没有任何区别。 - Mayra Delgado

16
以下shell命令应该能够实现你想要的功能:
git log --all --not $(git rev-list --no-walk --exclude=refs/heads/mybranch --all)

注意事项

如果您当前已经检出了 mybranch 分支,则上述命令将无法正常工作。这是因为 mybranch 分支上的提交也可以通过 HEAD 访问,因此 Git 不认为这些提交是唯一归属于 mybranch 的。要在检出 mybranch 分支时使命令成功执行,您还需要添加一个排除条件来排除 HEAD 分支:

git log --all --not $(git rev-list --no-walk \
    --exclude=refs/heads/mybranch \
    --exclude=HEAD \
    --all)

但是,除非mybranch被检出,否则不应排除HEAD,否则您可能会显示与mybranch无关的提交。

同样地,如果您有一个名为origin/mybranch的远程分支,对应本地的mybranch分支,您将需要将其排除:

git log --all --not $(git rev-list --no-walk \
    --exclude=refs/heads/mybranch \
    --exclude=refs/remotes/origin/mybranch \
    --all)

如果远程分支是远程仓库的默认分支(通常仅对origin/master成立),您还必须将origin/HEAD排除在外:

git log --all --not $(git rev-list --no-walk \
    --exclude=refs/heads/mybranch \
    --exclude=refs/remotes/origin/mybranch \
    --exclude=refs/remotes/origin/HEAD \
    --all)

如果你已经检出了该分支,并且存在远程分支,并且远程分支是远程仓库的默认分支,则会排除很多内容:
git log --all --not $(git rev-list --no-walk \
    --exclude=refs/heads/mybranch \
    --exclude=HEAD
    --exclude=refs/remotes/origin/mybranch \
    --exclude=refs/remotes/origin/HEAD \
    --all)

解释

git rev-list命令是一个低级(plumbing)命令,它遍历给定的修订版本并转储遇到的SHA1标识符。可以将其视为等效于git log,只显示SHA1标识符,没有日志消息、作者姓名、时间戳或其他“花哨”的信息。

--no-walk选项,顾名思义,防止git rev-list遍历祖先链。因此,如果您键入git rev-list --no-walk mybranch,它只会打印一个SHA1标识符:mybranch分支的末尾提交的标识符。

--exclude=refs/heads/mybranch --all参数告诉git rev-list从每个引用开始,除了refs/heads/mybranch

所以,当您运行git rev-list --no-walk --exclude=refs/heads/mybranch --all时,Git会打印每个引用的末尾提交的SHA1标识符,除了refs/heads/mybranch。这些提交及其祖先是您不感兴趣的提交 - 这些是您不想看到的提交。

另外的提交是您想要查看的提交,因此我们收集git rev-list --no-walk --exclude=refs/heads/mybranch --all的输出,并告诉Git显示除了那些提交及其祖先之外的所有内容。

--no-walk参数对于大型仓库是必需的(并且对于小型仓库是一种优化):否则,Git将不得不打印,并且shell将不得不收集(并在内存中存储)比必要多得多的提交标识符。对于大型仓库,收集的提交数量很容易超过shell的命令行参数限制。

Git bug?

我原本期望以下内容可以工作:

git log --all --not --exclude=refs/heads/mybranch --all

但实际上并没有。我猜这可能是Git中的一个漏洞,但也有可能是有意为之。


7
我来这里是想了解如何使用Mercurial的hg log -b <branch>命令。我不明白为什么有人说git不友好。/s - weberc2
我不知道为什么 git log --all --not --exclude=refs/heads/mybranch --all 不起作用,但是 git log refs/heads/mybranch --not --exclude=refs/heads/mybranch --all 起作用了,关于排除 HEAD 和 origin 的注意事项相同。 - Ben C
这是所有类似“仅此分支”的问题在SO上的最佳且唯一真实答案。如果可以忽略标签,则可以通过使用git log --branches --remotes --not $(git rev-list --no-walk --exclude=mybranch --branches --remotes)来解决有关是否排除HEAD的问题。请注意,这里的exclude具有不同的引用语法。 - fuujuhi
或者使用@BenC的解决方案:git log mybranch --not --exclude=mybranch --branches --remotes,这非常紧凑。 - fuujuhi

15

快速回答:

git log $(git merge-base master b2)..HEAD

假设:

  1. 你有一个分支

  2. 进行了几次提交

  3. 创建了一个名为b2的分支

  4. 执行git log -n1; 提交Id是b2和主分支之间的合并基础

  5. b2中进行几次提交

  6. git log会显示b2和主分支的日志历史记录

  7. 使用提交范围,如果您对此概念不熟悉,我建议您搜索一下谷歌或者stackoverflow

    对于您实际的上下文,您可以执行以下操作:

    git log commitID_FOO..comitID_BAR
    

    ".."是log命令的范围运算符。

    简单来说,它会给出所有比commitID_FOO更新的日志...

  8. 看一下第4个点,合并基础

    因此:git log COMMITID_mergeBASE..HEAD将显示差异

  9. Git可以通过以下方式为您检索合并基础:

    git merge-base b2 master
    
  10. 最后你可以做:

    git log $(git merge-base master b2)..HEAD
    

5
你可以尝试这样做:
#!/bin/bash

all_but()
{
    target="$(git rev-parse $1)"
    echo "$target --not"
    git for-each-ref --shell --format="ref=%(refname)" refs/heads | \
    while read entry
    do
        eval "$entry"

        test "$ref" != "$target" && echo "$ref"
    done
}

git log $(all_but $1)

或者,借鉴Git用户手册中的方法: 显示某个分支独有的提交记录
#!/bin/bash
git log $1 --not $( git show-ref --heads | cut -d' ' -f2 | grep -v "^$1" )

+1. 我在我的回答中写道,你需要解析提交记录才能找到你的分支的起源,而你似乎已经做到了。 - VonC
你使用过 log --walk-reflogs 吗?我正在阅读关于这个选项的内容,它给了我到目前为止需要的结果(仍在测试中)。 - dimirc
@dimirc Reflogs是完全不同的东西。它记录了分支点随时间的变化,通常用于恢复目的。我不确定你在post-receive hook中做了什么,但如果你解释一下,也许人们可以更明智地回答你的问题。 - John Szakmeister
我只需要在一次推送中解析/检查所有提交消息。我想要处理的情况是,如果有人在主分支上推送多个提交并创建一个新分支,也有多个提交。post-receive钩子需要检查两个refs/branches的更改,例如master和newbranch,我需要知道新分支的边界以避免重复解析提交消息,因为可能是来自master的提交。 - dimirc
1
好的。那么我认为我概述的上述方法确实是你想要的。使用reflog,条目将在一段时间后消失,我认为这会给你带来一些麻烦。你可能还想看看使用git show-ref --tags - John Szakmeister
显示剩余4条评论

4

这将输出当前分支上的提交记录。如果传递任何参数,它只会输出哈希值。

git_show_all_commits_only_on_this_branch

#!/bin/bash
function show_help()
{
  ME=$(basename $0)
  IT=$(cat <<EOF
  
  usage: $ME {NEWER_BRANCH} {OLDER_BRANCH} {VERBOSE}
  
  Compares 2 different branches, and lists the commits found only 
  in the first branch (newest branch). 

  e.g. 
  
  $ME         -> default. compares current branch to master
  $ME B1      -> compares branch B1 to master
  $ME B1 B2   -> compares branch B1 to B2
  $ME B1 B2 V -> compares branch B1 to B2, and displays commit messages
  
  )
  echo "$IT"
  exit
}

if [ "$1" == "help" ]
then
  show_help
fi

# Show commit msgs if any arg passed for arg 3
if [ "$3" ]
then
  OPT="-v"
fi

# get branch names
OLDER_BRANCH=${2:-"master"}
if [ -z "$1" ]
then
  NEWER_BRANCH=$(git rev-parse --abbrev-ref HEAD)
else
  NEWER_BRANCH=$1
fi

if [ "$NEWER_BRANCH" == "$OLDER_BRANCH" ]
then
  echo "  Please supply 2 different branches to compare!"
  show_help
fi

OUT=$(\git cherry $OPT $OLDER_BRANCH $NEWER_BRANCH)

if [ -z "$OUT" ]
then
  echo "No differences found. The branches $NEWER_BRANCH and $OLDER_BRANCH are in sync."
  exit;
fi

if [ "$OPT" == "-v" ]
then
  echo "$OUT"
else
  echo "$OUT" | awk '{print $2}'
fi

2
这给我产生了大约100条无关的日志信息。 - Arlie Stephens
嘿@ArlieStephens-我试过了,似乎对我仍然有效? 我只是在上面添加了帮助和更多的错误消息。让我知道结果如何! - Brad Parks
有趣。看着你现在拿到的代码,我认为它可能跟原来一样简单,前提是我关心的分支是从主分支上分出来的。使用当前代码,我可以明确指定父分支。(如我所记,我的开发分支来自一个发布分支,而不是主分支。) - Arlie Stephens
@ArlieStephens - 是的,我不认为我改变了任何功能...我更新了一次,并在进行了一些本地测试后使文档更加精确...并添加了一个“详细”选项,这个选项之前就有,但我没有记录它。 - Brad Parks

4
我发现这种方法相对容易。 检出分支,然后进行操作。
  1. Run

    git rev-list --simplify-by-decoration -2 HEAD
    

这将提供两个SHA:

1)分支的最后提交[C1]

2)第一个提交的父提交[C2]所在的分支

  1. Now run

    git log --decorate --pretty=oneline --reverse --name-status <C2>..<C1>
    
在运行第一个命令时,您会得到 C1 和 C2 两个字符串。在第二个命令中使用这些值而不要包含 <> 符号。这将给出分支内文件更改历史的列表。

嗨,谢谢你的建议,但我完全按照说明操作了,输出结果是从2015年存储库的开头开始的,而我的工作是在2022年。还有其他人能让它工作吗? - sming

4
git rev-list --exclude=master --branches --no-walk

将列出除master之外的每个分支的提示。

git rev-list master --not $(git rev-list --exclude=master --branches --no-walk)

该命令将列出master历史记录中未在任何其他分支历史记录中的每个提交。

对于设置筛选管道以进行提交选择的选项,顺序很重要,因此--branches必须遵循其应用排除模式,--no-walk必须遵循提供提交的筛选器,rev-list不应遍历。


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