我从master分支创建了一个分支,比方说,A。在A分支上,我删除了一些文件并对其他文件进行了一些更改。现在,我希望在不删除文件的情况下将A合并到master中。我该如何实现?
答案 0 :(得分:3)
您在这里有很多选择。有些方法对某些目的更好,而其他方法对其他目的更好。我要猜测,哪个是最适合您的,但这是一个猜测。我将向您展示一些选项,但不是所有选项。
请记住,Git完全是关于 commits 而不是文件。提交 hold 文件-每个提交都具有该提交中每个文件的完整快照-但是Git与提交有关 。
每个提交都有唯一的哈希ID,这是一个由字母和数字组成的丑陋字符串。该哈希ID表示那个提交。没有其他提交具有相同的字母和数字字符串。这些就是Git关心的-分支{em>名称,例如master
或branch-A
,是给 you 的,而不是给Git的。 Git关心 commits 。
一旦作出,就不能更改任何提交。每个冻结所有时间。但是您可以进行您已经做出的(或者当然是其他人做出的)提交,并将其提取到工作区中,对工作区进行更改,然后告诉Git进行来自工作区的 新提交。实际上,这就是您使用Git的方式。
在正常使用Git的过程中,您可以从以下内容开始:
git checkout master
这告诉Git:让我提交其哈希ID以我的名字master
存储的提交。记住,这个名字是给你的。 Git关心哈希ID。 Git从提交中获取所有冻结的文件,将它们放在工作区(您的工作树)中,现在您可以查看并使用文件,因为它们是不再冻结为Git无法看到的某些内部仅Git格式。
现在您要做一些工作,git add
一些文件,依此类推,然后通常将运行git commit
。现在是时候观察每个提交的另一个功能了。
每个提交都存储其 parent 提交的原始哈希ID。因此,如果您只提取了master
所标识的提交,那么该提交具有一个 parent 提交,该提交具有另一个父级,依此类推。如果我们绘制这些提交,我们将看到类似以下的内容:
... <-F <-G <-H <-- master
您提取的提交具有一些丑陋的哈希ID H
,该ID存储在您的名字master
下。该提交(我们称为H
的提交)会记住其 parent 提交G
的哈希ID。
您刚刚进行了一些更改,然后运行git commit
。 Git现在将打包一个 all 文件的新快照,并为其赋予一个新的唯一哈希ID。我们将其简称为哈希ID I
。新的提交I
将记住提交H
作为其父项。让我们来画一下:
... <-F <-G <-H <-- master
\
I
现在是棘手的部分!哈希ID太难让人记住了,因此为了帮助我们,Git将以某种名称存储I
的哈希ID。我们将使用的名称是给git checkout
的名称。因此,Git将更新我们的master
以指向新的提交I
:
...--F--G--H
\
I <-- master
这不是您想要的,所以那不是您要做的。首先,您签出了master
。然后您告诉Git:给我起一个新名字branch-A
。 Git做到了,所以现在您有了:
...--F--G--H <-- master, branch-A
要记住您给git checkout
的名字,Git在您使用的名字后附加了一个特殊的名称HEAD
(用大写字母表示):
...--F--G--H <-- master, branch-A (HEAD)
然后您进行新的提交I
时,Git知道要更新附加HEAD
的名称。所以现在您有了:
...--F--G--H <-- master
\
I <-- branch-A (HEAD)
现在,我不知道您在这个branch-A
分支上所做的很多提交方式。一会儿我画三张。但是在您告诉Git的某个地方:删除一些文件。因此,提交快照I
或J
或K
或所有三个快照,会完全丢失某些文件:
...--F--G--H <-- master
\
I--J--K <-- branch-A (HEAD)
您不能更改任何这些提交。无论哪个提交缺少那些文件,那些提交都会永远缺少那些文件。
但是,如果您没有给予这些承诺给其他人,那么这里有个不错的选择。您可以忘记所有有关这些提交的信息。您可以进行新的 replacement 提交,这些提交是新的和改进的:它们与原始提交非常相似,除了新的替换文件之外,您不要删除文件。< / p>
此选项的主要缺点是您必须为每个此类提交替换一个。但是,有一种简单的方法可以做到这一点。此选项的另一个主要缺点是捕获:如果您没有给予这些内容,则会提交给其他人。记住,Git关心哈希ID。如果您将这些提交提交给其他人,则它们会在其存储库中拥有这些提交以及这些哈希ID。
如果其他人确实拥有这些提交,那么这对这里的提议并不完全致命,这仅意味着“其他人”可能也必须从旧的提交切换到新的提交。或者,您可能希望完全使用其他选项,在此不再赘述。
无论如何,让我们再次看看您现在所拥有的。记住,我猜测。您将必须拥有自己的外观-我建议运行git log --decorate --oneline --graph branch-A master
来做到这一点-但这又是我的画图:
...--F--G--H <-- master
\
I--J--K <-- branch-A (HEAD)
比方说,您在提交J
或I
中删除了文件。现在您要他们回来。您当前正在提交K
,因为您仍在branch-A
上,这就是HEAD
仍附加到branch-A
的原因。
您首先要做的是再进行一次新提交。它将获得一个新的哈希ID,我们将其称为L
(实际ID将像通常一样是一些丑陋的字母和数字字符串)。要进行此新提交,请在K
结束且所有内容都已清除的情况下保持干净-因此git status
说“在分支branch-A上,没有要提交的内容”,您可以将工作区域更改为放回文件。
还请记住,每个提交都具有其文件 all 的完整快照。因此,我们将从提交H
中取回文件,毕竟,提交git show
仍然与往常完全相同-所有提交都会及时冻结!我们只需要查看这些文件。
有几种方法可以做到这一点。一种是使用git show master:path/to/file
,如下所示:
path/to/file
这将在屏幕上显示保存在提交H
中的master
的内容(您的名字path/to/file
指向的提交)。当然,您不想看到它,而是想将其保存回git show master:path/to/file > path/to/file
git add path/to/file
。因此,您可以这样做:
git checkout
不过,git checkout master -- path/to/file
有一个奇怪的特征。它具有一种为我们执行此操作的模式,没有实际切换提交和/或分支。所以我们可以这样写:
git show
这等效于git add
,因为它创建或替换我们工作区中的文件,和到git add
,因为它像git commit
一样更新 index 。
(我们没有讨论过索引。索引,也称为暂存区或有时称为 cache ,在Git中非常重要。但是对于现在我没有时间将它放入这个答案。)
无论如何,对所有不应该删除的文件重复此操作后,现在可以运行...--F--G--H <-- master
\
I--J--K--L <-- branch-A (HEAD)
制作新的快照:
L
提交J
将文件放回。
我们刚刚说过您在I
中删除了它们。您必须确保此时J
,K
,L
和I
永远都具有这种形式,但是我们可以提取提交,更改工作区,然后进行 new 提交。因此,现在我们将让Git提取提交J
,也许进行一些更改,然后再次提交。然后,我们将让Git与K
一起工作,这是我们之前通过删除文件来搞砸的地方。此时,我们将Git从H
中添加 add ,以将文件 back 放回原处,然后让Git进行新的提交
从提交 I'-J'-K' <-- HEAD
/
...--F--G--H <-- master
\
I--J--K--L <-- branch-A
开始,我们将让Git完成所有这些。结果如下所示:
I'
I
是我们的I
的新副本,如果没有其他要求,也许带有不同的日期戳。 (如果我们真的根本不需要更改任何东西,我们可以直接重复使用J'
,Git会为我们解决这个问题。)然后J
是我们的{{1 }} ,但对L
进行了更改,将文件放回去,并添加到其中。然后K'
是我们的J'
的副本。 K'
类似于K
,但其父级为J'
,并且其中还有我们在J
意外删除的文件。
要实现所有这些,我们使用git rebase
选项运行 -i
,告诉它从当前分支复制提交({ {1}}),但也位于branch-A
上的提交除外,并将新副本放置在master
的末尾:
master
Git查看我们的图形:
git rebase -i master
看到这里...--F--G--H <-- master
\
I--J--K--L <-- branch-A (HEAD)
上有四个 only 提交。 (提交branch-A
在F-G-H
上,但是它们也在branch-A
上。)因此,Git现在将组成一个工作清单:
master
Git将在其中包含这些说明的文件上打开您选择的编辑器(与您选择的用于生成提交消息的编辑器相同)。您的工作是修改指令,并写出修改后的指令并退出编辑器。
由于提交pick <hash-of-I> <subject-line-from-I>
pick <hash-of-J> <subject-line-from-J>
pick <hash-of-K> <subject-line-from-K>
pick <hash-of-L> <subject-line-from-L>
是我们通过删除文件而搞砸的地方,所以我们要做的是通过添加修复 J
> J
。因此,我们重新安排了工作清单,将L
移到了L
之后:
J
然后,将关于提交pick <hash-of-I> <subject-line-from-I>
pick <hash-of-J> <subject-line-from-J>
pick <hash-of-L> <subject-line-from-L>
pick <hash-of-K> <subject-line-from-K>
的行上的单词pick
更改为单词L
。 (如果愿意,您可以在此处改用fixup
一词-它们几乎是同一回事,如果这是一个正确的教程,我们将首先在此处进行压扁,但是在这种情况下,您需要squash
。
如果错误是在fixup
上,则在选择提交I
的行之后,将fixup
使用L
。如果错误是I
,我们将其行保持原始顺序,只需将最后一个L
更改为pick
。这里的想法是,提交fixup
(将文件放入后退的提交)是针对 fix 的提交,它通过删除破坏了工作首先是文件。
无论如何,我们现在已经在此说明表上使用了我们的编辑器。我们已经完成了说明的更新,因此我们写完工作表并退出编辑器。 Git现在执行指令,给我们:
L
(在拾取和修复过程中,名称 I'-J'-K' <-- HEAD
/
...--F--G--H <-- master
\
I--J--K--L <-- branch-A
与任何分支名称都是暂时分离。)
如果一切顺利,则此重新设置过程的最后一步现在开始。 Git采用名称 HEAD
,这是Git为我们记住提交branch-A
的提交哈希ID的方式。它将分支名称附加到上次复制的副本或固定的上,在本例中为L
。然后,它重新连接我们的K'
。所以现在我们有了这个:
HEAD
我们不再需要原始提交 I'-J'-K' <-- branch-A (HEAD)
/
...--F--G--H <-- master
\
I--J--K--L [abandoned]
。它们已替换为闪亮的新提交I-J-K-L
。 名称 I'-J'-K'
为我们记住了branch-A
。如果我们运行K'
,它看起来 就会以某种方式将错误的提交更改为良好的提交。
我们没有-原始提交仍然存在-但是我们是唯一知道git log
用来识别错误提交的人,并且我们拥有唯一的 Git存储库错误的提交。我们从不把它们交给其他人,就宇宙的其余部分而言,它们甚至不存在。
现在我们有了 good 提交,并且如果我们从一开始就不会犯错,我们可以按照合并的方式进行合并。
答案 1 :(得分:0)
您可以使用
git checkout -- <file>... to discard changes in working directory
git add <fileName>
或git add .
(针对当前目录中的所有更改)添加要提交的文件。
git commit -m "Your message"
git push origin master
将更改推送到主版本