按文件类型拆分 git 分支或提交
Posted
技术标签:
【中文标题】按文件类型拆分 git 分支或提交【英文标题】:Split a git branch or commit by file type 【发布时间】:2016-11-29 09:40:58 【问题描述】:我有一个包含 html 和 javascript 代码的分支。出于横切的原因,我需要先提交对 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
,但可以进行新的提交E
和F
,您可以这样安排:
...--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 发生变化。由您决定如何安排这些。)
每个...
都要填写一个分支名称。您可以不理会现有的分支名称,并发明一两个新的分支名称,这就是我将首先展示的内容。
手动操作
假设您想要两个新的分支名称,E
和 F
每个都将 C
作为它们的父级(所以,不是 C--E--F
)。 Git 是 Git,有很多方法可以做到这一点,但一种简单的方法是使用 git checkout -b
创建它们,它会创建新的分支名称并打开它们(这样git status
就表示您在新分支上)。这个git checkout -b
命令还带有一个可选的提交说明符,它是创建新分支后在索引和工作树中的提交。我们希望E
和F
都从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~2
是H
;那么temp~3
是D
和temp~4
是C
。完成拆分提交后,您可以挑选剩余的提交。
使用git rebase -i
Git 是 Git,还有另一种方法可以做到这一点,如果在 D
之后有提交,则特别有用,即要拆分的提交。这个特殊的方法需要对 Git 有一定的了解,但最终是最短和最快的方法。我们从git rebase -i
开始,将提交D
(以及任何以后的提交)重新定位到C
,它已经在(或它们在)那里;但我们将D
的pick
行更改为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 add
或git add '*.html' '**/*.html'
)添加这些,然后git commit
结果。然后git add
剩余文件和git commit
再次,然后git rebase --continue
复制剩余提交并将分支标签移动到最尖端的结果提交。
【讨论】:
我可以让 */ 深度递归吗? @djechlin:**/
已经是这个意思了。在 Git 中,*/*.js
将匹配 dir1/foo.js
和 dir2/bar.js
,但不匹配 dir3/dir4/baz.js
。但是,**/*.js
将匹配所有三个。 (例如,尝试将这些模式提供给 git ls-files
。记得引用它们以便它们传递给 Git;否则大多数 shell 坚持为您扩展它们。)以上是关于按文件类型拆分 git 分支或提交的主要内容,如果未能解决你的问题,请参考以下文章