将所有已完成的替换更改为其他内容

Change all done replacements to something else

在重写遗留应用程序时,我将 foo 大量替换为 bar,同时进行了许多手动更改。一些替换必须手动撤消,并且原始代码中已经有许多其他 bar

现在,我看到每个被 bar 替换的 foo 实际上应该是 baz

一个例子:

所需的操作很简单:将 foobar 替换为 baz。我想知道是否有使用 git 或任何 Linux 工具的简单方法。

重新制定

也许这个单句表述更清楚:

给定一个文件的两个版本,将baz放在旧版本包含foo和新版本包含bar的每个地方。

更多详情

实际上有三个完整的单词不同长度的单词替换,比如

perl -pe 's/\babc\b/pqrs/gi; s/\bdefg\b/uvw/gi; s/\bhi\b/xyz/g'

替换 foo:grep -rl 'foo'。 | xargs sed -i 's/foo/bar/g'

替换栏:grep -rl 'bar'。 | xargs sed -i 's/bar/baz/g'

您可以使用 git diff--word-diff=porcelain 模式(以及传递给 -U 选项的足够大的值,以保留更改之间的所有上下文)并使用足够简单的脚本处理其输出,该脚本将纠正错误的替换。

--word-diff[=<mode>] Show a word diff, using the <mode> to delimit changed words. By default, words are delimited by whitespace; see --word-diff-regex below. The <mode> defaults to plain, and must be one of:

  • ...
  • porcelain: Use a special line-based format intended for script consumption. Added/removed/unchanged runs are printed in the usual unified diff format, starting with a +/-/` ` character at the beginning of the line and extending to the end of the line. Newlines in the input are represented by a tilde ~ on a line of its own.

您将在下方找到上述方法的基于 sed 实现的原型。

用法:

fix_wrong_replacements path revision replacement_fix

哪里

  • path是文件在工作树中的(相对)路径
  • revision 是修订版,自此之后进行了必须修复的错误替换
  • replacement_fix

    形式的字符串

    /orig_pattern/incorrect_replacement_str/correct_replacement_str/

效果

假设比较时 path 处文件的工作副本 其提交的修订 revision 包含替换结果 orig_patternincorrect_replacement_str 的某些实例, 识别这些替换并将它们更改为 correct_replacement_str.

例子:

# In last two commits (and, maybe, in the working copy) some "int"s
# were incorrectly changed to "unsigned", now change those to "long"
$myname main.c HEAD~2 /int/unsigned/long/

# In the working copy of somefile.txt all "abc" case-insensitive words
# were changed to "pqrs", now change them to "xyz"
$myname somefile.txt HEAD '/[aA][bB][cC]/pqrs/xyz/'

已知limitations/issues:

  • 它适用于单个文件。要修复提交、提交范围或本地更改中的所有错误替换,必须确定已更改文件的列表并在循环中为所有这些文件调用此脚本。

  • 如果在原始(错误的)替换过程中使用了不区分大小写的模式,则 replacement_fix[=103= 的 orig_pattern 部分] 参数必须为每个字母使用 [aA][bB] 等正则表达式原子。

  • 不处理紧邻其他更改的替换。

  • 有时可能会添加多余的空行(因为git diff --word-diff的输出略有不一致)

fix_wrong_replacements:

#!/usr/bin/env bash

myname="$(basename "[=11=]")"

if [ $# -ne 3 ]
then
    cat<<END
Usage:

    $myname <path> <revision> <replacement_fix>

where
    - <path> is the (relative) path of the file in the working tree
    - <revision> is the revision since which the wrong replacements that
      must be fixed were made
    - <replacement_fix> is a string of the form

        /orig_pattern/incorrect_replacement_str/correct_replacement_str/

Effects:

    Assuming that the working copy of the file at <path> when compared
    to its committed revision <revision> contains results of replacing
    certain instances of <orig_pattern> with <incorrect_replacement_str>,
    identifies those replacements and changes them to <correct_replacement_str>.


Examples:

    # In last two commits (and, maybe, in the working copy) some "int"s
    # were incorrectly changed to "unsigned", now change those to "long"
    $myname main.c HEAD~2 /int/unsigned/long/

    # In the working copy of somefile.txt all "abc" case-insensitive words
    # were changed to "pqrs", now change them to "xyz"
    $myname somefile.txt HEAD '/[aA][bB][cC]/pqrs/xyz/'
END
    exit 1
fi

file=""
revision=""
s=(${3//// })
orig_pattern="${s[0]}"
incorrect_replacement="${s[1]}"
correct_replacement="${s[2]}"

pat="-$orig_pattern\n+$incorrect_replacement"

git_word_diff()
{
    git diff -U100000                                       \
             --word-diff=porcelain                          \
             --word-diff-regex='[[:alpha:]][[:alnum:]]*'    \
             "$@"
}


word_diff_file="$(mktemp)"
trap "rm $word_diff_file" EXIT

git_word_diff "$revision" -- "$file" > "$word_diff_file"
sed -n -e '
    1,5 d;

    /^-/ N;
    /\n~$/ d;
    /\n[- ]/ D;

    /^'"$pat"'$/ {x;G;s/\n'"$pat"'$/'"$correct_replacement"'/;x;d;};
    /^-.*\n+/ {s/^-.*\n+//;H;x;s/\n//;x;d;};

    /^~$/ {s/.*//;x;p;d;};

    {s/^.//;H;x;s/\n//;x;};
' "$word_diff_file" > "$file"