仅还原分支上的更改

时间:2018-08-02 14:38:39

标签: git version-control

我仍在尝试理解一些git概念。我对分支的理解是,每个分支可以有自己的更改,这些更改只会在该分支中,然后您可以将更改推送并合并到master中。我很草率,正在master分支上进行更改(未提交),因此所有这些更改都会传递到我不希望更改的新分支上。当我尝试将更改还原到上次推送的master分支时,它正在所有分支中还原这些更改。有什么方法可以还原本地master分支中的所有内容,并有选择地还原对已经进行的分支中特定文件的更改?

例如,假设我的仓库中有file1和file2。我对最新版本的回购协议感到满意。然后,我对master中的file1和file2进行了一些更改,但没有提交这些更改。然后,我决定要为每个文件更改创建一个分支,以便可以分别处理它们,因此我从file1_update创建了新分支file2_updatemaster。由于master进行了更改,因此这些更改会一直传递到file1_updatefile2_update。我想将file1_update中的file2和file2_update中的file1还原,然后将master中的所有内容还原为最新版本,而无需进行任何更改。有办法吗?

3 个答案:

答案 0 :(得分:1)

注意:在阅读下面的文本之前或之后(我建议在之后),您可能还需要查看Checkout another branch when there are uncommitted changes on the current branch

Git的真正作用是保存快照。这几乎是所有的:

$ git init          # create empty repository: no commits exist yet

然后重复:

... do some work ...
$ git add <files>   # copy the work into the index
$ git commit        # turn everything that is in the index, into a snapshot

每个git commit都打包 index (即临时区域 又称为 cache )中的所有内容,并将其转换为快照,它是永久的(通常是永久的),并且是完全只读的。

我们将稍后再讨论所有这些。

提交,哈希ID和分支名称

除了第一次提交,您总是坐在现有快照上创建一个 new 快照。新快照将获得一个提交哈希ID,即一些看起来很随机的十六进制字符串,例如b7bd9486b055c3f967a870311e704e3bb0654e4f。这是提交的真实名称:这就是Git如何使用提交来获取快照的方式。这样一来,您便可以在将来的某个时间找出现在保存的内容。

每个提交记录当时已存在快照的提交的哈希ID。如果我们使用单个大写字母(我们只能理解为大写字母)而不是笨拙的哈希ID,则可以将其称为第一个快照A。因此, second 快照是B,并在其中保存了A的实际哈希ID。我们说B 指向 A

A  <--B

制作第三张快照C时,我们是坐在B上进行的,因此C指向B

A <-B <-C

那么,我们和Git需要知道的是什么最新的快照?这就是分支名称的真正含义:分支名称,例如{ {1}},记录 last 快照。如果最新的是master,则我们有:

C

如果我们进行新的提交A--B--C <-- master ,则名称D现在需要记住masterD将指向DC不再需要记住master,因为C将:

D

提交中的箭头总是从子到父母指向后方,并且由于任何东西(不是Git本身)都不能改变任何现有提交中的任何内容,因此我们实际上不需要绘制它们。但是分支名称箭头 do 会随时间变化,因此我们应该继续绘制它们。

现在,假设我们现在创建一个新的分支{em> name ,如A--B--C--D <-- master 。名称dev将记录一些提交ID。它可以记录这四个中的任何一个,但是默认设置是使用 current 提交ID(即dev持有的提交ID)来实现,

master

现在我们有两个 分支名称,我们需要知道:我们使用的是哪个分支名称?这是A--B--C--D <-- dev, master 出现的地方:我们附加了单词HEAD到这些名称之一。这是我们的当前分支,其提交ID存储在分支名称中,因此,如果我们位于HEAD上,则图片实际上是:

dev

现在,如果我们进行{em> new 提交A--B--C--D <-- dev (HEAD), master E将指向E,而Git将更新当前名称D)指向dev

E

如果我们现在运行A--B--C--D <-- master \ E <-- dev (HEAD) 并进行新的提交git checkout master,则F将指向后退,而不是指向F,而是指向E,这就是一个D指向— Git将更新master指向master

F

就是这样:分支名称就是现在所做的全部!它仅记录 latest 提交,Git将其称为 tip提交。好的东西全都在提交中:每次提交都是索引中所有内容的完整快照。

索引和工作树

内部提交的所有文件均采用特殊的,仅Git的压缩形式(通常为高度压缩,至少对于源文本文件而言)。 Git几乎是唯一可以读取它们或对其执行任何操作的程序。 1 因此,Git需要一种方式,您和计算机可以读取和写入普通格式的文件。这些文件进入您的工作树中,之所以如此,是因为您可以在此处使用它们。

Git具有所有文件的中间形式。它会将那些压缩的,仅Git的只读文件,然后将它们(实际上是关于它们的东西)复制到Git称为 index 的东西中。在这里,文件仍然以仅Git的形式压缩,但是在这里,它们 可以被覆盖。它还使用该索引来跟踪有关工作树文件的信息-到 index cache ,因此也就是这些名称。这就是Git发挥最大速度的地方。有类似的VCS没有索引,从理论上证明了它是不必要的,但是它们比Git慢(有时要慢得多)。

提供了该索引后,即使您确实不想使用Git,Git也会强制您使用。它不是将文件从提交直接复制到工作树,而是将文件从提交直接复制到索引,然后只有 then 将它们扩展到工作树中的正常形式。这就是Git让您每次都运行A--B--C--D--F <-- master (HEAD) \ E <-- dev 的原因:git add所做的是将文件从工作树复制到 (在此过程中将其压缩为Git格式)。

与其他VCS相比,git add是如此之快:Git可以立即获取索引中的所有内容,将其打包为提交,然后完成。压缩文件的所有艰苦工作已经完成! Git甚至不必查看工作树。

这也意味着在git commit之后,您刚刚进行的 new 提交与索引匹配。因此,在git commit之后,索引与 git checkout branch 的最前面提交匹配,因为Git在更新工作树时将提交复制到了索引。在branch git commit 更改为具有新提示提交之后,索引与 branch 的(新)提示提交匹配,因为Git复制了索引(将其冻结为快照)以进行提交。


1 没有什么可以改变它们的:这是一项设计功能;一切的实际内容都存储在加密校验和哈希ID下。 (这是哈希ID的真正来源。哈希ID对每一位都非常敏感,因此,如果您要更改某些内容(偶然地,例如磁盘错误,或者有意通过覆盖它),Git会检测到对象的校验和不再匹配用于检索对象的校验和键,这就是为什么一旦提交,所有内容都是只读的。

故意将 遗忘。这样做有时很棘手,并且它们很容易得到还原: Git的主要目的是添加东西,而不是删除它们,并且愿意添加新东西而不是忘记旧东西。 。在这里我们不会详细介绍。


“但是提交看起来像差异!”

如果您运行:

branch

或:

git show <commit>

您将看到每个提交都显示为补丁。 Git之所以可以做到这一点,是因为每个提交在其内部都存储了它的上一个提交(父类)。 Git仅提取两个快照并进行比较。不管有什么不同,都会显示出来。

(这里的合并提交很复杂,但我们也将忽略它。)

还原

现在可以很简单地描述什么还原操作: 2 Git将提交变成补丁,然后 reverse将应用到其他提交。

也就是说,如果补丁提交说“向文件A添加一行”,则Git 将从该文件中删除该行。如果补丁提交说“从文件B中删除一行”,则Git 将该行添加到该文件。

已将提交反向应用到当前提交(通过工作树并使用与当前提交匹配的索引),Git就像git log -p 一样将更新的文件复制到索引中,然后进行新的提交,自动提供提交日志消息。您可以使用各种标志来覆盖其中的一些标志,并且在补丁无法正确应用时会带来一些麻烦(请参见脚注2)。但这仅仅是它。


2 这实际上太简单了。还原实际上会调用Git的三向合并机制(git add也是如此)。然而,在简单,无冲突的情​​况下,“应用补丁并提交”(cherry-pick)或“反向应用补丁并提交”(revert)足以描述该过程。


在此过程中还原是一个较差的名称

Mercurial(在其他方面与Git相似,只是更慢且对用户更友好)将其称为git cherry-pick而不是hg backout,因为它回退了一次提交。动词revert(通常在 revert to 中带有辅助词 to )表示(至少对某些人而言)将整个内容改回。也就是说,不用说:

  

“ commit a123456更改了文件README.txt的一行,我希望又更改了一行一行”

人们有时的意思是:

  

“自提交a123456以来,README.txt已被更改很多,我希望返回a123456中的版本,所以这意味着我想要_____”

并且他们在空格中填入“将README.txt还原为a123456”,因此它们达到了hg revert

这不是git revert所做的。要做到这一点,需要从提交git revert中提取文件README.txt 。令人困惑的是,执行此操作的主要Git命令是a123456,使用的语法与git checkout不同。 (它应该是一个单独的命令,在Mercurial中应该是git checkout branch!)如果要在Git中使用此命令,可以编写:

hg revert

git checkout a123456 -- README.txt 从提交README.txt复制到索引中(照常),然后将其扩展为普通的,非Git格式的格式,作为文件a123456进入您的工作树

请注意,在所有现代版本的Git中,您也可以使用:

README.txt

它将在提交时在屏幕上显示该文件的内容,并且通常与重定向一起使用,因此您可以将其保存到工作树内部或外部的文件中:

git show a123456:README.txt

例如。这不会影响索引。

答案 1 :(得分:0)

如果您没有revert所做的更改,则无法committed,您可能需要先git stash一些文件,然后先git add个文件,然后再{ {1}}和添加的文件。

然后,开关使用git commit进行分支,然后使用git checkout mybranch重新添加隐藏的文件。

编辑示例

假设我在分支主机上,并且我修改了file1 + file2而没有提交。 然后我切换到分支toto(git stash pop),file1 + file2的更改将在toto的分支中可见,但是我只希望该分支上的file1更改。

好吧,我混入了存储文件2(将“重置”文件2文件),然后依次git checkout -b totogit add file1

在那之后,我回到master分支,然后git commit -m "yeahhhh"回到我的file2修改中。

答案 2 :(得分:0)

您的误解是您不会在“分支”中进行更改。您可以更改硬盘上文件的当前状态。在您签入之前,git不会将此更改与分支相关。