2013-10-29 91 views
2

我幾天前從主服務器分支出來,並且已經對newbranch進行了兩次後續提交。從兩次提交之前的Git回滾

後來,我注意到我做的第一個提交中的文件不應該被改變。我還沒有推動任何東西到主分支,我所有的更改都在newbranch。當我最初從主人分支時,我怎樣才能將newbranch中的這一個文件回滾到原來的位置?

回答

1

有幾種不同的方式做你想要的結果,這取決於。

answer from euphoria83做到這一點...

在開始時,你有一個提交歷史看起來像這樣(每個字母代表一個承諾):

A - B - C   <-- master 
      \ 
      D 
       \ 
       E <-- HEAD=newbranch 

提交C是什麼在主站時你開始了。您做了一些更改並提交(創建D),然後進行一些更改並提交(創建E)。

現在您運行git checkout HEAD~1。這將更新你的工作目錄,以匹配提交d,併爲您提供一個指向直接提交D,而不是包含一個分支名稱的「分離的頭」(法國大革命的陰影!):

A - B - C   <-- master 
      \ 
      D  <-- HEAD 
       \ 
       E <-- newbranch 

(這git checkout <branch-name-or-rev>告訴git更改什麼HEAD名稱<rev>在這種情況下,HEAD~1,意思是「查找什麼提交HEAD名稱並返回一個」,所以這表示要從提交E回退一個,因爲這是一個特定的<rev>而不是分支名稱,它也具有「分離頭部」效果。)

現在您運行git checkout HEAD~1 filename。要更明確地說git,你可以說git checkout HEAD~1 -- filename--表示「剩餘的參數是文件名,而不是分支或修訂名稱」 - 如果您忽略了--,git會嘗試猜測它是分支名稱還是文件名。

這種形式的checkout,與-- filename說法,做了相當不同:它說來查找修訂HEAD~1像往常一樣,不過這一次,不要變化HEAD,只提取指定的文件。由於HEAD現在命名爲修訂D,因此git會備份一份,修訂爲C,並從該修訂中提取文件filename的版本並將其放入工作目錄中。

(請注意,分支名master名稱修訂C,所以你可以在這一點上已經寫git checkout master -- filename。)

接下來,git commit -a --amend告訴GIT中添加任何文件的改變,在這種情況下,你不實際上並不需要它,但通常這會添加其他您已修復的文件 - 然後執行「修改提交」。「修改提交」的意思是「創建一個新的提交,但使其父提交與我們的父提交相同」。這會創建一個新的提交 - 我們稱之爲D',其父代爲C。與往常一樣,新的提交變爲HEAD。由於HEAD已分離,因此分支名稱尚未移動。得到的承諾樹是這個樣子:

A - B - C    <-- master 
     | \ 
     \ D 
      \ \ 
      \ E  <-- newbranch 
       \ 
       D'  <-- HEAD 

D'的文件是相同的D,除了文件filename,也就是現在的相同版本C

接下來,git cherry-pick newbranch說,以獲得修訂通過newbranch - 這就是修訂E命名 - 和作出HEAD版本同樣的變化,作爲一個新的提交(我們稱之爲E'):

A - B - C    <-- master 
     | \ 
     \ D 
      \ \ 
      \ E  <-- newbranch 
       \ 
       D' - E' <-- HEAD 

現在你只需要給分支名稱HEAD。這是再安全地刪除newbranch,或者將其重命名閃開,然後重命名newnewnewbranch

git checkout -b newnew   # now HEAD=newnew which points to E' 
git branch -m newbranch oldnew # rename out of the way 
git branch -m newnew newbranch # and rename newnew to newbranch 

(您可以縮短這一點,但我們上的去...)


有一個更簡單的方法來做同樣的事情。只需運行git rebase -i master。這表示使用master作爲其「上游」來重新分配當前分支(newbranch)。也就是說,找到master(意思是DE)之後的所有提交,並將這些提交到master(這是他們之前的基礎)。如果沒有-i,那麼這將是愚蠢的重組DE到他們以前的方式,這只是一個無所事事的昂貴的方式 - 但與-i git打開了一系列命令的編輯器。命令將會提交pick提交DE。將pick的提交D更改爲edit,並寫出該文件。

rebase會挑選D給你,停下來讓你修改提交。在shell中,輸入:

git checkout master -- filename 
git commit -a --amend 

和以前一樣,並保存修改後的提交,即成爲D'。然後運行:

git rebase --continue 

的Git現在摘櫻桃提交E,給人提交E'。現在全部都完成了,所以分支完成,並且分支newbranch有你想要的提交。


answer from ДМИТРИЙ МАЛИКОВ做了一些不同的事情。它包含相同的git checkout master -- filename(拼寫filepath),但沒有類似rebase的序列。

所以,像以前一樣,在開始使用此:

A - B - C    <-- master 
      \ 
      D 
       \ 
       E  <-- HEAD=newbranch 

那麼你提取的filename正在修正C到工作目錄中的版本。如果你現在使用git commit(或者git commit -a,在這個特殊情況下是同樣的東西-git,這是因爲上面的checkout首先將提取的rev-C文件寫入臨時區),你會得到一個新的提交F,它將文件filename改回它的方式是在C

換言之,提交DE仍將對文件進行更改;新提交F將撤銷更改。

+0

哇!感謝您的解釋。 – larryq

2

git checkout master -- filepath

1

在new_branch:

git checkout HEAD~1 
git checkout HEAD~1 file_not_needed 
git commit -a --amend 
git cherry-pick new_branch 
git branch -f new_branch HEAD 
+0

這會讓你看起來像一個分支的「分離的HEAD」,所以你可能需要再多一步,給這個新分支命名。 – torek

+0

@torek完成,謝謝 – euphoria83