按文件类型拆分 git 分支或提交

Posted

技术标签:

【中文标题】按文件类型拆分 git 分支或提交【英文标题】:Split a git branch or commit by file type 【发布时间】:2016-11-29 09:40:58 【问题描述】:

我有一个包含 htmljavascript 代码的分支。出于横切的原因,我需要先提交对 html 的大规模更改,然后再提交 js.目前我有一个分支,其中包含两种更改。

如何按文件类型将提交分类为两个较小的提交(或分支)?

【问题讨论】:

签出“clean”分支...然后创建一个名为HTML的分支...然后重新签出“clean”(因为您将签入@ 987654322@) 分支,创建一个名为JS 的分支。签出HTML 分支——只更改你的HTML ....提交..推送——然后签出你的JS分支..进行JS更改..提交——推送..然后签出“干净”分支..合并HTML——合并JS——推送“干净”分支——完成。 @Zak 我已经对 HTML 进行了更改,我需要将它们移动到 HTML 分支,而不是重做它们。我有数百个需要手动开发的文件。 【参考方案1】:

我从this answer 一起破解了它:

git checkout -b new-branch # checkout from master
git diff big-branch --name-only -- '*.html' | xargs git checkout html-comments-take-two --

如果文件名中有空格,可能需要通过管道传送到 sed 's, ,\\&,g'

我无法使用 **/*.html 样式路径规范让它工作,我不知道为什么。

【讨论】:

【参考方案2】:

您无法更改现有的提交,但您可以进行新的提交,其父提交与现有的“提交过多”提交相同。

在你开始之前,确保你有一个干净的工作树(“没有什么可提交的”)。这样就没有git reset 或任何可能失去任何东西的东西。如有必要,您可以进行新的提交,以便您可能需要zorg~2 而不是zorg~1(参见下图)。稍后您将能够从此提交中检索您保存的项目。

画出你现在拥有的东西

与 Git 一样,首先绘制(至少部分)提交图。您现在在某个分支上,这意味着您的分支 name 指向最尖端的提交,并且该提交指向某个父提交,依此类推:

...--A--B--C--D   <-- zorg

zorg 是您当前的分支,D 可能是这个太大的提交,C 是它之前的提交,没有任何一组更改。 (如果您必须进行更多提交,那么提交D 可能会退后一步;如果是,请调整以下数字。)

提示:使用git log --graph --oneline --decorate(也可以使用--all)让Git 为您绘制图形(尽管它是垂直绘制的,顶部是最近的东西,而不是水平方向的新东西)右边)。

画出你喜欢的东西

您无法更改D,但可以进行新的提交EF,您可以这样安排:

...--A--B--C--D     <-- ... we'll fill this in later ...
            \
             E--F   <-- ... likewise this ...

或者这样:

             F      <-- ...
            /
...--A--B--C--D     <-- ...
            \
             E      <-- ...

提交 D 将继续是您的“太大”提交,而 E 可能只有 HTML 更改,F 可能只有 JS 更改。 (如果F 是基于E 构建的,那么它确实有两个变化并且实际上在内容方面匹配提交D。如果F 是基于C 构建的,那么它只有 JS 发生变化。由您决定如何安排这些。)

每个...都要填写一个分支名称。您可以不理会现有的分支名称,并发明一两个新的分支名称,这就是我将首先展示的内容。

手动操作

假设您想要两个新的分支名称,EF 每个都将 C 作为它们的父级(所以,不是 C--E--F)。 Git 是 Git,有很多方法可以做到这一点,但一种简单的方法是使用 git checkout -b 创建它们,它会创建新的分支名称并打开它们(这样git status 就表示您在新分支上)。这个git checkout -b 命令还带有一个可选的提交说明符,它是创建新分支后在索引和工作树中的提交。我们希望EF 都从C 中跳出来,所以我们要创建新的分支“at”提交C

git checkout -b zorg-html zorg~1

名称zorg 标识提交D。添加~ 后缀意味着“从这个提交,后退到第一父链接,无论我在数字中说了多少次”。由于数字是 1(一),我们将退回一位父级,这会将我们从 D 带到 C。这意味着名称 zorg-html 当前将指向提交 C,我们将在这个新分支上。

现在我们在zorg-html(在提交C)我们只想替换所有的HTML文件。这些文件的正确版本在提交D 中,正如名称zorg 所指出的那样。获取这些文件的简单但困难的方法是:

git checkout zorg -- first_file second_file third_file ...

这-这有点疯狂git checkout-这次根本不更改分支,而是只提取特定的命名文件(@987654370 之后的文件名列表@part) 来自指定的提交(zorg,即提交D)。

如果文件都以.html结尾并且没有.html文件实际上不是HTML文件,这种简单方法的简单版本是:

git checkout zorg -- '*.html' '**/*.html'

也就是说,从***目录中获取每个名为 whatever.html 的文件,以及在任意数量的子目录中名为 whatever.html 的每个文件,从 zorg 提交(再次提交 D) .

这种git checkout将更新后的文件同时写入索引和工作树,所以此时你可以简单地git commit得到结果。

现在,要创建提交 F,我们重复整个过程:

git checkout -b zorg-js zorg~1  # new zorg-js branch starting at C
git checkout zorg -- '*.js' '**/*.js'
git commit

(假设和之前的 HTML 文件一样,每个 JS 文件都被命名为 .js 并且没有一个名为 .js 的文件是 other 而不是 JS 文件)。现在我们有了:

             F      <-- zorg-js
            /
...--A--B--C--D     <-- zorg
            \
             E      <-- zorg-html

显然,您可以为所有这些分支选择更好的名称。

如果您希望在提交E 之后提交F,只需省略将创建一个新分支的git checkout -b 并切换回提交C。当您提取所有.js 文件并提交F 时,这将使您留在分支zorg-html 上提交E,因此F 的父级将是E,您将拥有:

...--A--B--C--D     <-- zorg
            \
             E--F   <-- zorg-html # zorg-html is clearly a bad name

如果你想要的只是一些简单的食谱,你可以在这里停下来。如果您想了解许多处理此问题和其他问题的方法,请继续阅读。

如果你想在zorg 上使用E--F 怎么办?

没问题。 Git 是 Git,有多种方法可以做到这一点。例如,您可以在开始之前重命名zorg

git branch -m zorg gary-oldman

现在你有了这个:

A--B--C--D   <-- gary-oldman

您可以安全地创建一个新的zorg

当然,任何上游设置都使用重命名的分支。没什么大不了的,您可以使用git branch --set-upstream-to 为每个分支设置新的上游。

当然,Git 就是 Git,还有另一种方法可以做到!您可以创建一个新的分支名称​​现在,指向提交D,只要您需要它就记住它——您将需要它用于两个git checkout 命令。然后你可以git reset分支名zorg让它指向提交C

git checkout zorg  # make sure zorg is the current branch
git branch temp    # save its tip commit under a new name
git reset --hard zorg~1  # and move zorg back to commit C

现在,当您进行新提交时,他们会将名称 zorg 向前移动,但名称 temp 仍会为您记住提交 D

A--B--C--D   <-- temp
       \
        E    <-- zorg

现在要访问提交D,您将使用名称temp,并重新找到提交C,您将使用temp~1

请注意,如果您有“过去”D 的额外提交(例如在 HTML 和 JS 更改后保存所做的工作):

A--B--C--D--H--I--J   <-- temp, or zorg, or whatever

你仍然可以做到这一切。只是现在,要命名提交 C,您将需要它的 SHA-1 哈希“真实名称”(它永远不会改变,但很难正确输入——鼠标剪切和粘贴在这里很有帮助),或从小费倒数。这里temp 可能命名为commit J,而temp~1 是commit I,而temp~2H;那么temp~3Dtemp~4C。完成拆分提交后,您可以挑选剩余的提交。

使用git rebase -i

Git 是 Git,还有另一种方法可以做到这一点,如果在 D 之后有提交,则特别有用,即要拆分的提交。这个特殊的方法需要对 Git 有一定的了解,但最终是最短和最快的方法。我们从git rebase -i 开始,将提交D(以及任何以后的提交)重新定位到C,它已经在(或它们在)那里;但我们将Dpick 行更改为edit

Git 现在让我们进入 rebase 会话,并提交 D。现在我们想要git reset HEAD~1(或git reset --mixed HEAD~1--mixed 只是默认值)返回提交C。这将设置当前提交——我们处于分离 HEAD 模式,所以这只是将HEAD 本身调整为C 并重置索引以匹配C,但保留为D 设置的工作树。现在我们只是有选择地git add 我们想要的文件:所有.html 的文件。使用您喜欢的任何方法(例如find ... | xargs git addgit add '*.html' '**/*.html')添加这些,然后git commit 结果。然后git add 剩余文件和git commit 再次,然后git rebase --continue 复制剩余提交并将分支标签移动到最尖端的结果提交。

【讨论】:

我可以让 */ 深度递归吗? @djechlin: **/ 已经是这个意思了。在 Git 中,*/*.js 将匹配 dir1/foo.jsdir2/bar.js,但不匹配 dir3/dir4/baz.js。但是,**/*.js 将匹配所有三个。 (例如,尝试将这些模式提供给 git ls-files。记得引用它们以便它们传递给 Git;否则大多数 shell 坚持为您扩展它们。)

以上是关于按文件类型拆分 git 分支或提交的主要内容,如果未能解决你的问题,请参考以下文章

Git 在分支之前拆分提交

git 按子文件夹拆分存储库并保留所有旧分支

iTerm 文件类型高亮、Git分支显示

我可以从 Visual Studio 将 Git 链接类型(分支、提交等)添加到我的 TFS(AZURE)工作项吗?

将提交的文件拆分并推送到两个不同的分支

在 Powershell 中,按记录类型拆分大型文本文件的最有效方法是啥?