如何将 `git diff --name-status` 和 `git diff --stat` 命令的输出合并?

5

使用git diff --name-status命令,我可以查看文件名和文件状态,例如:

M       .bashrc
D       .ghc/.ghci.conf.un~
D       .ghc/ghci_history
M       .life
A       .profile
M       .spacemacs

通过 git diff --stat 命令,我可以查看行数和文件改动的统计信息:

 .bashrc             |   3 ++-
 .ghc/.ghci.conf.un~ | Bin 13912 -> 0 bytes
 .ghc/ghci_history   | 100 --------------------------------------------------------------------------------------------
 .life               |   2 ++
 .profile            |  23 +++++++++++++++++++++
 .spacemacs          |   3 +++

有没有办法将这两个命令的输出结果合并起来?我想要得到像这样的结果:
M  .bashrc             |   3 ++-
D  .ghc/.ghci.conf.un~ | Bin 13912 -> 0 bytes
D  .ghc/ghci_history   | 100 --------------------------------------------------------------------------------------------
M  .life               |   2 ++
A  .profile            |  23 +++++++++++++++++++++
M  .spacemacs          |   3 +++

当然,我可以通过调用字符串来手动执行这两个命令并操纵字符串来完成。但是我不确定这些命令的输出结果是否可靠和一致。也许有文档记录了相关信息。请问您能否提供一个shell命令,以便我能够从终端看到这些差异?

3个回答

5
DIFF=origin/master..HEAD
join -t $'\t' -1 2 -2 1 -o 1.1,2.1,2.2 \
        <(git diff --name-status $DIFF | sort -k2)
        <(git diff --stat=$((COLUMNS-4)),800 $DIFF | sed -e '$d' -e 's/^ *//;s/ /\t/' | sort) \
        | sed 's/\t/ /g'

或者,在~/.gitconfig文件的[alias]部分中完全采用POSIX。

ndiff = "!f() { TAB=`printf '\t'`; COLUMNS=`stty size|cut -d' ' -f2`; cd $GIT_PREFIX; git diff --name-status $1 | sort -k2 > /tmp/.tmpgitndiff ; git diff --stat=$COLUMNS,800 $1 |sed -e '$d' -e \"s/^ *//;s/ /${TAB}/\" | sort | join -t \"${TAB}\" -1 2 -2 1 -o 1.1,2.1,2.2 /tmp/.tmpgitndiff - | sed \"s/${TAB}/ /g\"; rm -f /tmp/.tmpgitndiff; }; f"

$ git ndiff origin/master..HEAD -- dev/ # example
M dev/main.scss   |   9 +
A dev/rs.js       |  19 ++

应该使用类似于local temp_file=$(mktemp)这样的方式,而不是硬编码一个文件名,这会导致多次使用时出错。 - o11c
我尝试从我的shell执行这两个命令,但似乎都无法正常工作。 - Shersh
@Shersh:请确保您正在运行 bash(对于第一个命令),而不是像busybox这样的简化版POSIX shell。 - drzraf
我已经找到了问题所在。我使用的是macOS。我需要在想要使用\t字符的任何地方都使用$'...'字符串。现在,使用join解决方案效果很好,输出非常漂亮!我会进行更多测试。 - Shersh
为了使它在所有情况下都可以运行,您还需要对git diff --stat的结果进行sort -k1排序。否则,它不总是适用于我。但至少我可以有一个单一的命令,在macOS和Ubuntu上都能够工作,这对我来说已经足够了。 - Shersh

3
使用的最简单方法是使用 wdiff:
$ wdiff -n -w '' -x '' -y '' -z '' <(git diff --name-status) <(git diff --stat)
vvv 2018-06-26 10:08:27-0700
M       foo/baz.py   | 19 +++++++++++--------
M       foo/bar.py   | 37 ++++++++-----------------------------
M       foo/qux.py   |  2 +-
 3 files changed, 20 insertions(+), 38 deletions(-)
-[w-z]选项设置插入/删除的起始/结束分隔符。 -n确保输出是按行分隔的......当传递了-[w-z]时可能并不重要,但对于一般的wdiff来说这是一个好习惯。
理论上,如果您的文件名看起来像该行中的其他任何内容,则会出现错误。幸运的是,良好的实践倾向于避免像M|19+++++++++++--------这样的文件名。
更正确的方法是使用paste,但这需要在之后通过sed将重复部分删除。

2
你也可以使用for循环来过滤每个可能的状态的输出:
for filter in A C D M R T U X B; do git diff --diff-filter="$filter" --stat | head -n -1 | sed "s/.*/$filter &/"; done;
  • --diff-filter 确保仅显示当前状态下的文件(例如,仅显示[A]dded文件)
  • --stat 显示您想要的状态
  • 然后 head 命令会删除每个统计输出的最后一行(例如,x files changed, n deletions
  • 最后 sed 命令将当前筛选器插入开头(例如,A

这意味着您总是按其状态而不是按git命令单独排序的方式获取文件。

编辑

如评论中@Shersh提到的,tail -n 在macOS上不能使用负整数。 有两种解决方法:

  1. 要么安装ghead:brew install coreutils(感谢jchook在this answer中的评论)
  2. 或者使用tac翻转行,从第二行开始使用tail -n +2,然后再次使用tac翻转:
for filter in A C D M R T U X B; do git diff --diff-filter="$filter" --stat | tac | tail -n +2 | tac | sed "s/.*/$filter &/"; done;

顺便说一句,你也可以使用tail -r代替tac,但这在所有系统上都不起作用,并且会失去对齐(请参见@Shersh的评论,无法在我的系统上进行测试)。


当某些过滤器没有输出时,我会看到以下错误:head: illegal line count -- -1 - Shersh
嗯...我运行时没有出现这种情况。你可以尝试在head命令的末尾添加2>/dev/null吗?就像这样head -n -1 2>/dev/null | - yaba
确实,在我的Ubuntu上我没有看到错误。错误出现在macOS上。那么我会在macOS上尝试这个技巧。 - Shersh
我已经弄清楚为什么在macOS上看到错误。macOS上的head不支持负参数。因此,我必须使用在这个问题中描述的_tail reverse trick_来使它在两个系统上工作:unix.stackexchange.com/a/169080/359940。我认为你的解决方案非常好,也是最简单的解决方法。但是,它不提供按|对齐的功能... - Shersh
最简单的方法是在Mac OS X上使用ghead(通过“brew install coreutils”安装,感谢jchook,在此答案中的评论)。这样可以保持对齐并使命令(相对)简单。我会相应地编辑我的答案。 - yaba

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