如何找到 Git 分支的最近父级?

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/3161204/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-09-10 08:39:52  来源:igfitidea点击:

How to find the nearest parent of a Git branch?

gitbranch

提问by Peter Farmer

Let's say I have the following local repository with a commit tree like this:

假设我有以下本地存储库,其中包含这样的提交树:

master --> a
            \
             \
      develop c --> d
               \
                \
         feature f --> g --> h

masteris my this is the latest stable release code, developis my this is the 'next' release code, and featureis a new feature being prepared for develop.

master是我的这是最新的稳定版本代码develop是我的这是“下一个”版本代码feature正在准备的新功能develop

What I want to be able to do on my remote repo using hooks, is for pushes to featureto be refused unless commit fis a direct descendant of developHEAD. i.e. the commit tree looks like this because feature has been git rebaseon d.

我希望能够使用钩子在我的远程仓库上做的feature是拒绝推送,除非提交fdevelopHEAD的直接后代。即提交树看起来像这样,因为功能已经git rebase开启d

master --> a
            \
             \
      develop c --> d
                     \
                      \
               feature f --> g --> h

So is it possible to:

那么是否有可能:

  • Identify the parent branch of feature?
  • Identify the commit in parent branch which fis a descendant of?
  • 确定feature?
  • 确定父分支中的提交哪个f是其后代?

From there I would check what HEAD of parent branch is, and see if fpredecessor matches the parent branch HEAD, to determine if the feature needs to be rebased.

从那里我将检查父分支的 HEAD 是什么,并查看f前任是否与父分支 HEAD 匹配,以确定该功能是否需要重新定位。

采纳答案by Chris Johnsen

Assuming that the remote repository has a copy of the developbranch (your initial description describes it in a local repository, but it sounds like it also exists in the remote), you should be able to achieve what I think you want, but the approach is a bit different from what you have envisioned.

假设远程存储库有一个开发分支的副本(您的初始描述在本地存储库中描述了它,但听起来它也存在于远程存储库中),您应该能够实现我认为您想要的,但是方法和你想象的有点不一样。

Git's history is based on a DAGof commits. Branches (and “refs” in general) are just transient labels that point to specific commits in the continually growing commit DAG. As such, the relationship between branches can vary over time, but the relationship between commits does not.

Git 的历史基于提交的DAG。分支(和一般的“refs”)只是临时标签,指向不断增长的提交 DAG 中的特定提交。因此,分支之间的关系会随着时间而变化,但提交之间的关系不会。

    ---o---1                foo
            \
             2---3---o      bar
                  \
                   4
                    \
                     5---6  baz

It looks like bazis based on (an old version of) bar? But what if we delete bar?

看起来baz是基于(旧版本)bar?但是如果我们删除bar呢?

    ---o---1                foo
            \
             2---3
                  \
                   4
                    \
                     5---6  baz

Now it looks like bazis based on foo. But the ancestry of bazdid not change, we just removed a label (and the resulting dangling commit). And what if we add a new label at 4?

现在看起来baz是基于foo. 但是祖先baz没有改变,我们只是删除了一个标签(以及由此产生的悬空提交)。如果我们在 处添加一个新标签4呢?

    ---o---1                foo
            \
             2---3
                  \
                   4        quux
                    \
                     5---6  baz

Now it looks like bazis based on quux. Still, the ancestry did not change, only the labels changed.

现在看起来baz是基于quux. 尽管如此,血统没有改变,只是标签改变了。

If, however, we were asking “is commit 6a descendent of commit 3?” (assuming 3and 6are full SHA-1 commit names), then the answer would be “yes”, whether the barand quuxlabels are present or not.

但是,如果我们问“ commit 是 commit6的后代3吗?” (假设36是完整的 SHA-1 提交名称),那么无论barquux标签是否存在,答案都是“是” 。

So, you could ask questions like “is the pushed commit a descendent of the current tip of the developbranch?”, but you can not reliably ask “what is the parent branch of the pushed commit?”.

因此,您可以问诸如“推送的提交是否是开发分支当前提示的后代?”之类的问题,但您不能可靠地问“推送提交的父分支是什么?”。

A mostly reliable question that seems to get close to what you want is:

一个似乎接近你想要的最可靠的问题是:

For all the pushed commit's ancestors (excluding the current tip of developand its ancestors), that have the current tip of developas a parent:

  • does at least one such commit exist?
  • are all such commits single-parent commits?

对于所有推送提交的祖先(不包括当前的develop提示及其祖先),将当前的develop提示作为父项:

  • 至少存在一个这样的提交吗?
  • 所有这些提交都是单亲提交吗?

Which could be implemented as:

可以实现为:

pushedrev=...
basename=develop
if ! baserev="$(git rev-parse --verify refs/heads/"$basename" 2>/dev/null)"; then
    echo "'$basename' is missing, call for help!"
    exit 1
fi
parents_of_children_of_base="$(
  git rev-list --pretty=tformat:%P "$pushedrev" --not "$baserev" |
  grep -F "$baserev"
)"
case ",$parents_of_children_of_base" in
    ,)     echo "must descend from tip of '$basename'"
           exit 1 ;;
    ,*\ *) echo "must not merge tip of '$basename' (rebase instead)"
           exit 1 ;;
    ,*)    exit 0 ;;
esac

This will cover some of what you want restricted, but maybe not everything.

这将涵盖您想要限制的一些内容,但可能不是所有内容。

For reference, here is an extended example history:

作为参考,这是一个扩展的示例历史:

    A                                   master
     \
      \                    o-----J
       \                  /       \
        \                | o---K---L
         \               |/
          C--------------D              develop
           \             |\
            F---G---H    | F'--G'--H'
                    |    |\
                    |    | o---o---o---N
                     \   \      \       \
                      \   \      o---o---P
                       \   \   
                        R---S

The above code could be used to reject Hand Swhile accepting H', J, K, or N, but it would also accept Land P(they involve merges, but they do not merge the tip of develop).

上面的代码可以用来拒绝Hand Swhile 接受H', J, K, or N,但它也会接受Land P(它们涉及合并,但它们不合并develop的提示)。

To also reject Land P, you can change the question and ask

要也拒绝LP,您可以更改问题并询问

For all the pushed commit's ancestors (excluding the current tip of developand its ancestors):

  • are there any commits with two parents?
  • if not, does at least one such commit have the current tip of developits (only) parent?

对于所有推送提交的祖先(不包括开发的当前提示及其祖先):

  • 有两个父母有任何承诺吗?
  • 如果没有,是否至少有一个这样的提交具有开发其(唯一)父级的当前提示?
pushedrev=...
basename=develop
if ! baserev="$(git rev-parse --verify refs/heads/"$basename" 2>/dev/null)"; then
    echo "'$basename' is missing, call for help!"
    exit 1
fi
parents_of_commits_beyond_base="$(
  git rev-list --pretty=tformat:%P "$pushedrev" --not "$baserev" |
  grep -v '^commit '
)"
case "$parents_of_commits_beyond_base" in
    *\ *)          echo "must not push merge commits (rebase instead)"
                   exit 1 ;;
    *"$baserev"*)  exit 0 ;;
    *)             echo "must descend from tip of '$basename'"
                   exit 1 ;;
esac

回答by Joe Chrysler

A rephrasal

改写

Another way to phrase the question is "What is the nearest commit that resides on a branch other than the current branch, and which branch is that?"

表达这个问题的另一种方式是“驻留在当前分支以外的分支上最近的提交是什么,那是哪个分支?”

A solution

一个办法

You can find it with a little bit of command line magic

你可以用一点命令行魔法找到它

git show-branch \
| sed "s/].*//" \
| grep "\*" \
| grep -v "$(git rev-parse --abbrev-ref HEAD)" \
| head -n1 \
| sed "s/^.*\[//" 

With awk:

使用awk

git show-branch -a \
| grep '\*' \
| grep -v `git rev-parse --abbrev-ref HEAD` \
| head -n1 \
| sed 's/[^\[]*//' \
| awk 'match(
 A---B---D <-master
      \
       \
        C---E---I <-develop
             \
              \
               F---G---H <-topic
, /\[[a-zA-Z0-9\/-]+\]/) { print substr(
git log --graph --decorate
, RSTART+1, RLENGTH-2 )}'

Here's how it works:

这是它的工作原理:

  1. Display a textual history of all commits, including remote branches.
  2. Ancestors of the current commit are indicated by a star. Filter out everything else.
  3. Ignore all the commits in the current branch.
  4. The first result will be the nearest ancestor branch. Ignore the other results.
  5. Branch names are displayed [in brackets]. Ignore everything outside the brackets, and the brackets.
  6. Sometimes the branch name will include a ~# or ^# to indicate how many commits are between the referenced commit and the branch tip. We don't care. Ignore them.
  1. 显示所有提交的文本历史,包括远程分支。
  2. 当前提交的祖先用星号表示。过滤掉其他一切。
  3. 忽略当前分支中的所有提交。
  4. 第一个结果将是最近的祖先分支。忽略其他结果。
  5. 分支名称显示在 [括号中]。忽略括号外的所有内容和括号。
  6. 有时,分支名称会包含 ~# 或 ^# 以指示引用的提交和分支提示之间有多少提交。我们不在乎。别理他们。

And the Result

结果

Running the above code on

运行上面的代码

vim  ~/.gitconfig

Will give you developif you run it from H and masterif you run it from I.

develop如果你从 H 运行它,master如果你从 I 运行它,会给你。

The code is available as a gist

该代码可作为要点

回答by rcde0

You can also try:

你也可以试试:

[alias]
            parent = "!git show-branch | grep '*' | grep -v \"$(git rev-parse --abbrev-ref HEAD)\" | head -n1 | sed 's/.*\[\(.*\)\].*/\1/' | sed 's/[\^~].*//' #"

回答by NIKHIL C M

git parent

git 父母

You can just run the command

你可以只运行命令

git parent

git parent

to find the parent of the branch, if you add the @Joe Chrysler's answer as a git alias. It will simplify the usage.

找到分支的父级,如果您将@Joe Chrysler的答案添加为git alias。它将简化使用。

Open gitconfig file located at "~/.gitconfig"by using any text editor. ( For linux). And for Windows the ".gitconfig" path is generally located at c:\users\your-user\.gitconfig

"~/.gitconfig"使用任何文本编辑器打开位于 的 gitconfig 文件。(对于Linux)。对于 Windows,“.gitconfig”路径通常位于c:\users\your-user\.gitconfig

git branch --contains develop | grep "^ *feature$"

Add the following alias command in the file:

在文件中添加以下别名命令:

git show-branch | grep '*' | grep -v "$(git rev-parse --abbrev-ref HEAD)" | head -n1 | sed 's/.*\[\(.*\)\].*//' | sed 's/[\^~].*//'

Save and exit the editor.

保存并退出编辑器。

Run the command git parent

运行命令 git parent

That's it!

就是这样!

回答by Daniel Stutzbach

I have a solution to your overall problem (determine if featureis descended from the tip of develop), but it doesn't work using the method you outlined.

我有一个解决您的整体问题的解决方案(确定是否feature从 的尖端下降develop),但使用您概述的方法不起作用。

You can use git branch --containsto list all the branches descended from the tip of develop, then use grepto make sure featureis among them.

您可以使用git branch --contains列出从 的尖端下降的所有分支develop,然后使用grep来确保feature在其中。

#!/bin/bash
git log --oneline --merges "$@" | grep into | sed 's/.* into //g' | uniq --count | head -n 10

If it is among them, it will print " feature"to standard output and have a return code of 0. Otherwise, it will print nothing and have a return code of 1.

如果在其中,则打印" feature"到标准输出,返回码为 0。否则,不打印任何内容,返回码为 1。

回答by Murali Mopuru

This working fine for me.

这对我来说很好用。

#!/bin/bash
HEAD="`git rev-parse --abbrev-ref HEAD`"
echo "Comparing to $HEAD"
printf "%12s  %12s   %10s     %s\n" "Behind" "BehindMerge" "Ahead" "Branch"
git branch | grep -v '^*' | sed 's/^\* //g' | while read branch ; do
    ahead_merge_count=`git log --oneline --merges $branch ^$HEAD | wc -l`
    if [[ $ahead_merge_count != 0 ]] ; then
        continue
    fi
    ahead_count=`git log --oneline --no-merges $branch ^$HEAD | wc -l`
    behind_count=`git log --oneline --no-merges ^$branch $HEAD | wc -l`
    behind_merge_count=`git log --oneline --merges ^$branch $HEAD | wc -l`
    behind="-$behind_count"
    behind_merge="-M$behind_merge_count"
    ahead="+$ahead_count"
    printf "%12s  %12s   %10s     %s\n" "$behind" "$behind_merge" "$ahead" "$branch"
done | sort -n

Courtesy answers from: @droidbot and @Jistanidiot

礼貌回答:@droidbot 和@Jistanidiot

回答by saeedgnu

Since none of the answers above worked on our repository, I want to share my own way, using latest merges in git log:

由于上述答案都不适用于我们的存储库,我想分享我自己的方式,使用最新的合并git log

git log --decorate --simplify-by-decoration --oneline \ # selects only commits with a branch or tag
      | grep -v "(HEAD" \                               # removes current head (and branch)
      | head -n1 \                                      # selects only the closest decoration
      | sed 's/.* (\(.*\)) .*//' \                    # filters out everything but decorations
      | sed 's/\(.*\), .*//' \                        # picks only the first decoration
      | sed 's/origin\///'                              # strips "origin/" from the decoration

Put it in a script named git-last-merges, which also accepts a branch name as argument (instead of current branch) as well as other git logarguments

把它放在一个名为 的脚本中git-last-merges,它也接受一个分支名称作为参数(而不是当前分支)以及其他git log参数

From the output, we can manually detect the parent branch(es) based on own branching conventions and number of merges from each branch.

从输出中,我们可以根据自己的分支约定和每个分支的合并数量手动检测父分支。

EDIT:If you use git rebaseon child branches often (and merges are fast-forwarded often so there aren't too many merge commits), this answer won't work well, so I wrote a script to count ahead commits (normal and merge), and behind commits (there shouldn't be any behind merge in parent branch) on all branches comparing to the current branch. Just run this script and let me know if works for you or not

编辑:如果您git rebase经常在子分支上使用(并且合并经常快进,所以没有太多合并提交),这个答案不会很好用,所以我写了一个脚本来计算提交(正常和合并) , 以及与当前分支相比的所有分支上的落后提交(父分支中不应有任何落后合并)。只需运行此脚本,让我知道是否适合您

 A---B---D---E---F <-origin/master, master
      \      \
       \      \
        \      G---H---I <- origin/hotfix, hotfix
         \
          \
           J---K---L <-origin/develop, develop
                \
                 \
                  M---N---O <-origin/feature/a, feature/a
                       \   \
                        \   \
                         \   P---Q---R <-origin/feature/b, feature/b
                          \
                           \
                            S---T---U <-origin/feature/c, feature/c

回答by Matt Stuvysant

A solution

一个办法

The solution based on git show-branchdid not quite work for me (see below), so I've combined it with the one based on git logand ended up with this:

该解决方案基于git show-branch并没有为我(见下文)相当的工作,所以我用一个组合是基于git log并结束了与此:

cur_branch=$(git rev-parse --abbrev-ref HEAD)

Limitations and Caveats

限制和注意事项

  • HEAD can be detached (many CI tools do so to ensure they build correct commit in a given branch), but origin branch and local branchhave to be both at par or "above"the current HEAD.
  • There must be no tagsin the way (I presume; I have not tested the script on commits with a tag between child and parent branch)
  • the script relies on the fact "HEAD"is always listed as the first decorationby the logcommand
  • runningthe script on masterand developresults (mostly) in <SHA> Initial commit
  • HEAD 可以分离(许多 CI 工具这样做是为了确保它们在给定分支中构建正确的提交),但源分支和本地分支必须与当前 HEAD相同或“高于”当前 HEAD。
  • 不得有任何标记(我猜想;我还没有在子分支和父分支之间带有标记的提交中测试脚本)
  • 脚本依赖于以下事实“HEAD”总是被列为第一个装饰用的log命令
  • 运行该脚本masterdevelop结果(大部分)在<SHA> Initial commit

The results

结果

git show-branch -a           |
  grep '\*'                  | # we want only lines that contain an asterisk
  grep -v "$cur_branch"      | # but also don't contain the current branch
  head -n1                   | # and only the first such line
  sed 's/.*\[\(.*\)\].*//' | # really, just the part of the line between []
  sed 's/[\^~].*//'            # and with any relative refs (^, ~n) removed

Despite local branch existence (e.g. only origin/topicis present since the commit Owas checked-out by directly by its SHA), the script should print as follows:

尽管存在本地分支(例如,仅origin/topic在提交O由其 SHA 直接检出后才存在),脚本应打印如下:

  • For commits G, H, I(branch hotfix) → master
  • For commits M, N, O(branch feature/a) → develop
  • For commits S, T, U(branch feature/c) → develop
  • For commits P, Q, R(branch feature/b) → feature/a
  • For commits J, K, L(branch develop) → <sha> Initial commit*
  • For commits B, D, E, F(branch master) → <sha> Initial commit
  • 对于提交G, H,I(branch hotfix) →master
  • 对于提交M, N,O(branch feature/a) →develop
  • 对于提交S, T,U(branch feature/c) →develop
  • 对于提交P, Q,R(branch feature/b) →feature/a
  • 对于提交J, K, L(branch develop) → <sha> Initial commit*
  • 对于提交B, D, E, F(branch master) →<sha> Initial commit

* - or masterif develop's commits were on top of master's HEAD (~ the master would be fast-forwardable to develop)

* - 或者master如果develop's commits 在 master 的 HEAD 之上(~ master 可以快速开发)



Why did not show-branch work for me

为什么 show-branch 对我不起作用

The solution based on git show-branchproved unreliable for me in the following situations:

在以下情况下,基于git show-branch的解决方案对我来说是不可靠的:

  • detached HEAD– including detached head case means replacing grep '\*' \for `grep '!' \ – and that is just the beginning of all the troubles
  • runningthe script on masterand developresults in developand `` respectively
  • branches on masterbranch (hotfix/branches) end up with the developas a parent since their closest masterbranch parent was marked with !instead of *for a reason.
  • 分离的 HEAD- 包括分离的头盒意味着替换grep '\*' \“grep”!\ – 而这只是所有麻烦的开始
  • 运行该脚本master,并develop导致develop和``分别
  • master分支(hotfix/分支)上的分支最终develop以 为父级,因为它们最近的master分支父级被标记为 ,!而不是*出于某种原因。

回答by VonC

Remember that, as described in "Git: Finding what branch a commit came from", you cannot easily pinpoint the branch where that commit has been made (branches can be renamed, moved, deleted...), even though git branch --contains <commit>is a start.

请记住,如“Git:查找提交来自哪个分支”中所述,即使git branch --contains <commit>是一个开始,您也无法轻松确定进行该提交的分支(可以重命名、移动、删除分支...)。

  • You can go back from commit to commit until git branch --contains <commit>doesn't list the featurebranch and list developbranch,
  • compare that commit SHA1 to /refs/heads/develop
  • 您可以从提交返回到提交,直到git branch --contains <commit>不列出feature分支并列出develop分支,
  • 将提交 SHA1 与 /refs/heads/develop

If the two commits id match, you are good to go (that would mean the featurebranch has its origin at the HEAD of develop).

如果两个提交 id 匹配,您就可以开始了(这意味着feature分支的起点位于 的 HEAD develop)。

回答by Mark Reed

JoeChrysler's command-line magic can be simplified. Here's Joe's logic - for brevity I've introduced a parameter named cur_branchin place of the command substitution `git rev-parse --abbrev-ref HEAD`into both versions; that can be initialized like so:

JoeChrysler 的命令行魔法可以被简化。这是 Joe 的逻辑——为简洁起见,我在两个版本中都引入了一个参数cur_branch来代替命令替换`git rev-parse --abbrev-ref HEAD`;可以像这样初始化:

git show-branch -a |
  awk -F'[]^~[]' '/\*/ && !/'"$cur_branch"'/ {print ;exit}'  

Then, here's Joe's pipeline:

然后,这是乔的管道:

-F'[]^~[]' 

We can accomplish the same thing as all five of those individual command filters in a relatively simple awkcommand:

我们可以在一个相对简单的awk命令中完成与所有五个单独的命令过滤器相同的事情:

/\*/                      

That breaks down like this:

分解如下:

&& !/'"$cur_branch"'/

split the line into fields at ], ^, ~, and [characters.

分割线成字段]^~,和[字符。

{ print ;               

Find lines that contain an asterisk

查找包含星号的行

  exit }

...but not the current branch name

...但不是当前的分支名称

##代码##

When you find such a line, print its second field (that is, the part between the first and second occurrences of our field separator characters). For simple branch names, that will be just what's between the brackets; for refs with relative jumps, it will be just the name without the modifier. So our set of field separators handles the intent of both sedcommands.

当你找到这样的一行时,打印它的第二个字段(即我们的字段分隔符第一次和第二次出现之间的部分)。对于简单的分支名称,这就是括号之间的内容;对于具有相对跳转的引用,它将只是没有修饰符的名称。所以我们的字段分隔符集处理两个sed命令的意图。

##代码##

Then exit immediately. This means it only ever processes the first matching line, so we don't need to pipe the output through head -n 1.

然后立即退出。这意味着它只处理第一条匹配的行,所以我们不需要通过head -n 1.