git 日志历史简化

发布于 2024-11-24 16:25:35 字数 1633 浏览 4 评论 0原文

假设我有以下历史记录

        D---E-------F
       /     \       \
      B---C---G---H---I---J
     /                     \
    A-------K---------------L--M

git log --ancestry-path D..M 会给我

            E-------F
             \       \
              G---H---I---J
                           \
                            L--M

但是,我只想要以下内容

            E
             \       
              G---H---I---J
                           \
                            L--M

或者

            E-------F
                     \
                      I---J
                           \
                            L--M

本质上,我只想遍历一条路径,而不是两条路径。

这可能吗?如果是这样,命令是什么?

编辑:

我尝试过使用 --first-parent,但这并不完全是这样。 git log --first-parent G..M 给了我

                    F
                     \
                  H---I---J
                           \
                            L--M

它包括 F,因为 F 是 I 的第一个父母。 相反,我希望

                  H---I---J
                           \
                            L--M

任何帮助

解决方案(对我有用):

正如 @VonC 所说,没有任何一行可以做到这一点。所以我最终使用了 bash 脚本。

  1. 对于 'git log --ancestry-path G..M' 中的每个提交,
  2. 确定 $commit 的父级是否包含我们之前所在的提交
  3. 如果是,则继续。做一些有趣的事情。
  4. 如果不是,则跳过该提交。

例如, git log --first-commit G..M 是

H - F - I - J - L - M

但是,F 的父级是 E,而不是 H。所以我们省略 F,给我

H - I - J - L - M

耶!

Let's say I have the following history

        D---E-------F
       /     \       \
      B---C---G---H---I---J
     /                     \
    A-------K---------------L--M

git log --ancestry-path D..M will give me

            E-------F
             \       \
              G---H---I---J
                           \
                            L--M

However, I would like just the following

            E
             \       
              G---H---I---J
                           \
                            L--M

Or

            E-------F
                     \
                      I---J
                           \
                            L--M

Essentially, I would like to traverse down only one path, not two.

Is this possible? And if so, what is the command?

Edit:

I've tried using --first-parent, but this isn't exactly it.
git log --first-parent G..M gives me

                    F
                     \
                  H---I---J
                           \
                            L--M

It includes F, because F is the first parent of I. Instead I'd like

                  H---I---J
                           \
                            L--M

Any help would be appreciated

Solution (that worked for me):

As @VonC stated, there isn't a single one-liner that does this. So I ended up using a bash script.

  1. For each commit in 'git log --ancestry-path G..M'
  2. Determine if $commit's parent includes the commit we were previously on
  3. If yes, continue. do something interesting.
  4. If no, skip that commit.

For example, git log --first-commit G..M is

H - F - I - J - L - M

However, F's parent is E, not H. So we omit F, giving me

H - I - J - L - M

Yay!

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(3

ヤ经典坏疍 2024-12-01 16:25:35

我不认为这是直接可能的(除非您事先知道要包含/排除的确切列表,这否定了遍历 DAG 的目的)

实际上, OP Ken Hirakawa 设法通过以下方式获得预期的线性历史记录:

git log --pretty=format:"%h%n" --ancestry-path --reverse $prev_commit..$end_commit

对于每个提交,确保它是上一个提交的直接子级。

这是 Ken Hirakawa 编写的脚本


这是我的脚本,用于创建在 历史简化 部分中提到的 DAG git log 手册页,对于 --ancestry-path

您会在末尾找到我用来创建类似历史记录的 bash 脚本(使用根目录的名称和您的用户名来调用它) 。

我定义:

$ git config --global alias.lgg "log --graph --pretty=format:'%Cred%h%Creset -%C(yellow)%d%Creset %s %C(bold blue)<%an>%Creset' --abbrev-commit --date=relative"

我得到:

$ git lgg
* d7c4459 - (HEAD, M, fromA) M <VonC>
*   82b011d - (L) Merge commit 'J' into fromA <VonC>
|\
| * 190265b - (J, master) J <VonC>
| *   ef8e325 - (I) Merge commit 'F' <VonC>
| |\
| | * 4b6d976 - (F, fromB) F <VonC>
| * | 45a5d4d - (H) H <VonC>
| * |   834b239 - (G) Merge commit 'E' <VonC>
| |\ \
| | |/
| | * f8e9272 - (E) E <VonC>
| | * 96b5538 - (D) D <VonC>
| * | 49eff7f - (C) C <VonC>
| |/
| * 02c3ef4 - (B) B <VonC>
* | c0d9e1e - (K) K <VonC>
|/
* 6530d79 - (A) A <VonC>

从那里,我不能排除提交 I 的父级之一。

祖先路径确实返回:

$ git lgg --ancestry-path D..M
* d7c4459 - (HEAD, M, fromA) M <VonC>
* 82b011d - (L) Merge commit 'J' into fromA <VonC>
* 190265b - (J, master) J <VonC>
*   ef8e325 - (I) Merge commit 'F' <VonC>
|\
| * 4b6d976 - (F, fromB) F <VonC>
* | 45a5d4d - (H) H <VonC>
* | 834b239 - (G) Merge commit 'E' <VonC>
|/
* f8e9272 - (E) E <VonC>

这与日志手册页一致:

常规 D..M 计算作为 M 祖先的提交集,但排除作为 D 祖先的提交集.
这对于了解自 D 以来导致 M 的历史发生了什么很有用,从某种意义上说,“M 拥有哪些不存在的东西”在D”中。
此示例中的结果将是除 AB(当然还有 D 本身)之外的所有提交。

当我们想要找出M中的哪些提交被D引入的错误污染并需要修复时,我们可能会只想查看实际上是 D 后代的 D..M 子集,即排除 CK >.
这正是 --ancestry-path 选项的作用。


#!/bin/bash

function makeCommit() {
  local letter=$1
  if [[ `git tag -l $letter` == "" ]] ; then
    echo $letter > $root/$letter
    git add .
    git commit -m "${letter}"
    git tag -m "${letter}" $letter
  else
    echo "commit $letter already there"
  fi
}

function makeMerge() {
  local letter=$1
  local from=$2
  if [[ `git tag -l $letter` == "" ]] ; then
    git merge $from
    git tag -m "${letter}" $letter
  else
    echo "merge $letter already done"
  fi
}

function makeBranch() {
  local branch=$1
  local from=$2
  if [[ "$(git branch|grep $1)" == "" ]] ; then
    git checkout -b $branch $from
  else
    echo "branch $branch already created"
    git checkout $branch
  fi
}

root=$1
user=$2
if [[ ! -e $root/.git ]] ; then
  git init $root
fi
export GIT_WORK_TREE="./$root"
export GIT_DIR="./$root/.git"
git config --local user.name $2

makeCommit "A"
makeCommit "B"
makeCommit "C"
makeBranch "fromB" "B"
makeCommit "D"
makeCommit "E"
makeCommit "F"
git checkout master
makeMerge "G" "E"
makeCommit "H"
makeMerge "I" "F"
makeCommit "J"
makeBranch "fromA" "A"
makeCommit "K"
makeMerge "L" "J"
makeCommit "M"

I don't think this is directly possible (unless you know in advance the exact list to include/exclude, which negates the purpose of walking the DAG)

Actually, the OP Ken Hirakawa managed to get the expected linear history by:

git log --pretty=format:"%h%n" --ancestry-path --reverse $prev_commit..$end_commit

And for each commit, making sure it is a direct child of the previous commit.

Here is the script writtten by Ken Hirakawa.


Here is my script to create the DAG mentioned in the History Simplification section of the git log man page, for --ancestry-path:

You will find at the end the bash script I used to create a similar history (call it with the name of the root dir, and your username).

I define:

$ git config --global alias.lgg "log --graph --pretty=format:'%Cred%h%Creset -%C(yellow)%d%Creset %s %C(bold blue)<%an>%Creset' --abbrev-commit --date=relative"

I get:

$ git lgg
* d7c4459 - (HEAD, M, fromA) M <VonC>
*   82b011d - (L) Merge commit 'J' into fromA <VonC>
|\
| * 190265b - (J, master) J <VonC>
| *   ef8e325 - (I) Merge commit 'F' <VonC>
| |\
| | * 4b6d976 - (F, fromB) F <VonC>
| * | 45a5d4d - (H) H <VonC>
| * |   834b239 - (G) Merge commit 'E' <VonC>
| |\ \
| | |/
| | * f8e9272 - (E) E <VonC>
| | * 96b5538 - (D) D <VonC>
| * | 49eff7f - (C) C <VonC>
| |/
| * 02c3ef4 - (B) B <VonC>
* | c0d9e1e - (K) K <VonC>
|/
* 6530d79 - (A) A <VonC>

From there, I cannot exclude one of the parents of commit I.

The ancestry-path does return:

$ git lgg --ancestry-path D..M
* d7c4459 - (HEAD, M, fromA) M <VonC>
* 82b011d - (L) Merge commit 'J' into fromA <VonC>
* 190265b - (J, master) J <VonC>
*   ef8e325 - (I) Merge commit 'F' <VonC>
|\
| * 4b6d976 - (F, fromB) F <VonC>
* | 45a5d4d - (H) H <VonC>
* | 834b239 - (G) Merge commit 'E' <VonC>
|/
* f8e9272 - (E) E <VonC>

which is consistent with the log man page:

A regular D..M computes the set of commits that are ancestors of M, but excludes the ones that are ancestors of D.
This is useful to see what happened to the history leading to M since D, in the sense that "what does M have that did not exist in D".
The result in this example would be all the commits, except A and B (and D itself, of course).

When we want to find out what commits in M are contaminated with the bug introduced by D and need fixing, however, we might want to view only the subset of D..M that are actually descendants of D, i.e. excluding C and K.
This is exactly what the --ancestry-path option does.


#!/bin/bash

function makeCommit() {
  local letter=$1
  if [[ `git tag -l $letter` == "" ]] ; then
    echo $letter > $root/$letter
    git add .
    git commit -m "${letter}"
    git tag -m "${letter}" $letter
  else
    echo "commit $letter already there"
  fi
}

function makeMerge() {
  local letter=$1
  local from=$2
  if [[ `git tag -l $letter` == "" ]] ; then
    git merge $from
    git tag -m "${letter}" $letter
  else
    echo "merge $letter already done"
  fi
}

function makeBranch() {
  local branch=$1
  local from=$2
  if [[ "$(git branch|grep $1)" == "" ]] ; then
    git checkout -b $branch $from
  else
    echo "branch $branch already created"
    git checkout $branch
  fi
}

root=$1
user=$2
if [[ ! -e $root/.git ]] ; then
  git init $root
fi
export GIT_WORK_TREE="./$root"
export GIT_DIR="./$root/.git"
git config --local user.name $2

makeCommit "A"
makeCommit "B"
makeCommit "C"
makeBranch "fromB" "B"
makeCommit "D"
makeCommit "E"
makeCommit "F"
git checkout master
makeMerge "G" "E"
makeCommit "H"
makeMerge "I" "F"
makeCommit "J"
makeBranch "fromA" "A"
makeCommit "K"
makeMerge "L" "J"
makeCommit "M"
你如我软肋 2024-12-01 16:25:35

我必须承认我不理解你的解决方案 - 它不适用于我的示例 - 但是如果我正确理解了你的用例(给定一对提交,你想要一个任意线性路径它们之间,没有分割),我有同样的问题,以下解决方案似乎有效:

  • 使用 --ancestry-path 运行日志,并确保记下每个提交的子项
  • 迭代结果,保持跟踪“最后接受的孩子”,并每年更新一次当提交引用已接受的子项时(或者还没有已接受的子项 - 初始情况)
  • 实际上以某种有用的方式打印生成的“已接受”条目

生成的脚本如下所示:

#!/bin/bash
output_set=""; child_to_match=""; # init
while read -r; do
  if { [ -n "$REPLY" ]; } && { [[ "${REPLY:41}" =~ "$child_to_match" ]] || [ -z "$child_to_match" ]; }; then
    child_to_match=${REPLY:0:40}
    output_set="$output_set $child_to_match"
  fi
done <<<  "$(git rev-list --ancestry-path --children $1)"
if [[ -n $output_set ]]; then
  git show -s $output_set "${@:2}"
fi

它可以像 single-ancestry-path 那样调用.sh RANGE_EXPRESSION DECORATION_ARGS,通常支持与 git log 相同的装饰参数(实际上是 git show,每次提交都会调用一次),因此采用著名的来自 https://stackoverflow.com/a/9074343/74296lg2 示例,调用可能看起来像这样: 例如:

single-ancestry-path.sh master..MyBranch --abbrev-commit --decorate --format=format:'%C(bold blue)%h%C(reset) - %C(bold cyan)%aD%C(reset) %C(bold green)(%ar)%C(reset)%C(bold yellow)%d%C(reset)%n''          %C(white)%s%C(reset) %C(dim white)- %an%C(reset)'

已经九年了,所以我希望有一个更简单的答案,但我找不到。

I have to admit I didn't understand your solution - it didn't work for my example - but if I understood your use-case correctly (given a pair of commits, you want an arbitrary linear path between them, with no splits), I have the same problem, and the following solution seems to work:

  • Run the log with --ancestry-path, and making sure you take note of the children of each commit
  • Iterate through the results, keeping track of the "last child accepted", and updating it every time a commit references an accepted child (or there is no accepted child yet - initial case)
  • Actually print the resulting "accepted" entries in some useful way

A resulting script looks like:

#!/bin/bash
output_set=""; child_to_match=""; # init
while read -r; do
  if { [ -n "$REPLY" ]; } && { [[ "${REPLY:41}" =~ "$child_to_match" ]] || [ -z "$child_to_match" ]; }; then
    child_to_match=${REPLY:0:40}
    output_set="$output_set $child_to_match"
  fi
done <<<  "$(git rev-list --ancestry-path --children $1)"
if [[ -n $output_set ]]; then
  git show -s $output_set "${@:2}"
fi

It can be called like single-ancestry-path.sh RANGE_EXPRESSION DECORATION_ARGS, supporting generally the same decoration arguments as git log (it is in fact git show, being called once per commit), so taking the famous lg2 example from https://stackoverflow.com/a/9074343/74296, the call might look like this: eg:

single-ancestry-path.sh master..MyBranch --abbrev-commit --decorate --format=format:'%C(bold blue)%h%C(reset) - %C(bold cyan)%aD%C(reset) %C(bold green)(%ar)%C(reset)%C(bold yellow)%d%C(reset)%n''          %C(white)%s%C(reset) %C(dim white)- %an%C(reset)'

It's been 9 years, so I would have hoped there would be an easier answer, but I can't find one.

辞旧 2024-12-01 16:25:35

我也不喜欢合并所带来的问题,并在我的主流历史中放弃了它。每当主分支上有大量合并时,我都会以相同的内容重新提交它,但作为单个提交。

 D---E--------F 联合开发者
   /               
  B---C---G'---H---I'--J 组长
 /                       
A-----K----------------L'--M 主流

这里,G'、I' 和 L' 是我重新提交合并结果的点。
分支描述只是描述了一个场景,我可以在其中可视化发生的问题树。因此,G 和 G'(类似于 I 和 I')的内容将是相同的,团队负责人已合并到开发人员的最新工作中。和L'一样,L也集成了主流的功能。

我完全理解避免问题并不等于解决问题,并且对那些现在面临问题的人表示同情。

I too dislike the problems that result from merging and have dispensed with having it in my mainstream history. Whenever there is a large merge onto a main branch I will recommit it with identical contents but as a single commit.

    D---E--------F                           Co-Developer
   /               
  B---C---G'---H---I'--J                     Team Leader
 /                       
A-------K----------------L'--M               Main Stream

Here, G', I' and L' would be points where I have re-commited merge results.
The branch descriptions simply describe a scenario where I can visualize the problem tree occurring. So the contents of G and G' (similarly I and I') would be the same, the team leader having merged in the work-to-date of the developer. And L' the same as L, the feature integrated onto the mainstream.

I totally understand that avoiding a problem is not the same as solving it, and sympathize with those facing the problem now.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文