如何漂亮地格式化任意差异的差异?

问题描述 投票:0回答:1

当 rebase 一个 git 分支时,特别是在冲突解决之后,我喜欢比较两个补丁,即 rebase 之前主分支和我的分支之间的差异以及 rebasing 之后主分支和我的分支之间的差异。我这样做的原因是为了确保正确解决冲突并且不会因此引入错误。

幸运的是,git 2.19 引入了精彩的git range-diff。大多数时候,它确实符合我的预期。它需要 rebase 之前和之后的所有提交,将它们一一匹配并显示不同之处。真的很整洁。

然而,有时,解决冲突是如此痛苦,并且 rebase 的提交数量如此之高,以至于我干脆决定压制它们并立即解决所有冲突。现在我有一个问题。在更新分支之前,我有 N 次提交,但在那之后,我只剩下一次提交,这是将 N 次提交压缩到主分支顶部的结果。所以 range-diff 不能再帮助我了。即使两个补丁非常相似,但提交数量不同,因此无法一一匹配。

另一种方法是将两个差异转储到两个文件中,然后比较它们。虽然这肯定有效,但结果的可读性不太好。这是一个快速比较。首先,差异的差异:

然后,范围差异:

我认为我们很容易同意后者更容易阅读。另一种方法可能是在解决冲突之前压缩提交。这样我两边只有一个提交,并且 range-diff 可以再次使用。这也行,但我是个懒人。我不想运行额外的 git 命令并生成一次性的垃圾提交。而且,我可能会弄错。这将是一种耻辱,因为我这样做是为了再次检查我一开始就做的事情是否正确。

所以我的问题如下:还有其他选择吗?我希望能够生成两个任意补丁的差异,这两个补丁不一定具有相同的提交次数。但我也喜欢 range-diff 将 diff 格式化为非常易于阅读的方式。还有其他方法可以使其发挥作用吗?有没有第三方工具可以达到类似的效果?

git range diff patch git-range-diff
1个回答
0
投票

我知道已经有一段时间了。我现在正在使用自制的解决方案,但从未花时间在这里分享。抱歉耽误了!

我基本上编写了一个 NodeJS 脚本,它通过连续运行多个 git 命令来手动执行差异比较。然后输出被着色,与

less -R
结合,它将显示得与
git range-diff
完全相同。

该脚本有一些副作用,因为它会在磁盘上写入两个文件以对它们运行差异,然后删除它们。对于大多数用户来说应该没问题,但我认为仍然值得一提。

您可以通过以下方式使用它:

node range-diff.js <rev1> <number-of-commits-1> <rev2> <number-of-commits-2>

这将首先计算以下两个差异并将它们写入磁盘:

git diff <rev1>~<number-of-commits-1> <rev1> > diffA
git diff <rev2>~<number-of-commits-2> <rev2> > diffB

然后它计算差异的差异并将其着色:

git diff --no-index diffA diffB

这非常方便,特别是如果我已经压扁并重新建立了

topic
分支。它让我可以将 N 次提交的更改与变基后单个提交中所做的更改进行比较:

node range-diff.js topic 10 topic-rebased 1

在上面的示例中,我将十个提交压缩为一个,然后重新调整它的基础。我仍然可以比较这两个历史。

您可以将输出通过管道传输到

less
以享受漂亮的颜色:

node range-diff.js topic 10 topic-rebased 1 | less -R

我想这应该是git的内置功能。我非常非常经常使用它。

这是代码:

const { spawn } = require('child_process');
const { join } = require('path');

const from = process.argv[2];
const m = process.argv[3];

const to = process.argv[4];
const n = process.argv[5];

const diffA = '"' + join(__dirname, 'diffA') + '"';
const diffB = '"' + join(__dirname, 'diffB') + '"';

function diff(commit, numberOfAncestors, patchFile) {
    return `git diff ${commit}~${numberOfAncestors} ${commit} > ${patchFile}`;
}

function rangeDiff(patchFile1, patchFile2) {
    return `git --no-pager diff --no-index ${patchFile1} ${patchFile2} || rm ${patchFile1} ${patchFile2}`;
}

function getPrefixColor(firstChar) {
    const PREFIX_COLORS = {
        '-': '\033[41m\033[30m',
        '+': '\033[42m\033[30m',
    };

    return PREFIX_COLORS[firstChar] || '';
}

function getTextTransparency(firstChar) {
    const TEXT_TRANSPARENCY = {
        '-': '\033[2m',
        '+': '\033[1m',
    };

    return TEXT_TRANSPARENCY[firstChar] || '';
}

function getTextColor(firstChar, secondChar) {
    let color = getTextTransparency(firstChar);

    const TEXT_COLORS = {
        '-': '\033[31m',
        '+': '\033[32m',
    };

    color += TEXT_COLORS[secondChar] || '';
    return color;
}

function colorLine(line) {
    const RESET_COLORS = '\033[0m';
    const firstChar = line.charAt(0);
    const secondChar = line.charAt(1);

    return (
        getPrefixColor(firstChar) + firstChar + RESET_COLORS +
        getTextColor(firstChar, secondChar) + line.slice(1) + RESET_COLORS
    );
}

function run(command) {
    return new Promise((resolve, reject) => {
        let output = '';
        let error = '';

        const process = spawn('/bin/sh');
        process.stdin.write(command);
        process.stdin.end();

        process.stdout.on('data', data => output += data);
        process.stderr.on('data', data => error += data);

        process.on('close', () => error ? reject(error) : resolve(output));
    });
}

run(`${diff(from, m, diffA)} && ${diff(to, n, diffB)} && ${rangeDiff(diffA, diffB)}`)
    .then(out =>
        console.log(out.toString().split('\n').map(colorLine).join('\n'))
    );
© www.soinside.com 2019 - 2024. All rights reserved.