Apply Git Patch

Git

在实际开发过程中,可能会遇到这样的问题:因为重构,一些文件从 A 目录移动到了 B 目录,而后又对文件内容做了修改。这时,如果希望将其中的某些修改(比如和安全相关的布丁)应用回重构前的代码,就显得比较困难了。直接通过 Git 进行 cherry-pick 并不顺利,因为具体修改的 commit 中并不包含文件目录移动的信息。

可以简单使用下面的命令来构建一个场景:

git init;
echo "console.log('hello world');" > origin.js;
git add -A;
git commit -m "first commit";

git checkout -b "new_branch";
mv origin.js modified.js;
git add -A;
git commit -m "rename commit";

echo "console.log('hi')" >> origin.js;
git add -A;
git commit -m "modify commit";

git checkout master;

这时候,希望直接将 new_branch 中最后一个 commit cherry-pick 到 master 是比较困难的。

针对这种情况,可以考虑使用 Git Patch 功能。首先将修改的部分生成 Patch 文件,然后手动将 Patch 中的目录映射关系处理正确,最终将修改后的 Patch 应用到重构前的某个旧版本中。

创建 Patch

git diff 命令输出的结果就是一个 Patch,可以简单的将输出的内容存储到文件中,就生成了一个当前未签入内容的 Patch 文件:

git diff > change.patch

如果希望只是将部分修改的文件生成 Patch,可以先将需要的部分放入缓冲区中(git add),然后通过 git diff --cached 命令,仅针对缓冲区中的修改生成 Patch 文件。

以上这些生成的方案,比较适合为没有写权限的 Git 仓库提交修改的场景。直接将 Patch 文件通过 email 的形式发送,就可以进行修改的讨论了。

注:如果改动包含了二进制文件的修改,可以通过增加 --binary 命令来获取到这部分文件的修改 Patch。

针对已经签入的提交,也可以通过 git format-patchgit show 命令来生成 commit 对应的 Patch 文件。

git show commit-id > change.patch

可以生成单个 commit 的 Patch 文件;如果希望生成一组 commit 的 Patch,可以使用:

git format-patch A..B

上面的命令会生成为从 A 到 B 之间的所有 commit 生成对应的 Patch 文件(包含 B commit,但是不包含 A commit;如果需要包含 A,可以使用 A^..B 命令)。或者,如果希望将所有的改动合成到一个 Patch 文件中,可以使用:

git format-patch A..B --stdout > changes.patch

上面的 A 和 B 除了可以是 commit id 之外,也可以是 Branch 或者 Tag。

应用 Patch

将生成的 Patch 文件应用到当前的代码中,只需要使用:

git apply change.patch

Git 会将 Patch 中提到的修改应用到当前的项目中,但改动不会被自动提交;如果希望直接将 Patch 以 commit 的形式进行提交,可以直接使用:

git am change.patch

关于 Patch

Git 生成的 Patch 文件,除了提交作者、commit message 这些信息外,核心的部分是通过 diff 命令生成的修改内容。如果只是需要修改一下文件的位置,应该可以通过观察文件直接找到。更多关于 diff 命令生成的补丁文件的格式,可以参考 Wikipedia 中的相关描述。