![](/img/trans.png)
[英]is there any difference between `git reset HEAD` and `git reset HEAD~`
[英]What is the difference between “git reset HEAD” and “git reset HEAD .”
這是一個簡寫,例如git add -A
是如何同時進行git add.
& git add -u
請參閱此問題以了解上下文https://stackoverflow.com/a/22207257/7680918#
事實證明,Git 足夠聰明,不會在不干凈應用的情況下丟棄存儲。 我能夠通過以下步驟獲得所需的 state:
- 要取消合並沖突:
git reset HEAD.
(注意尾隨點)- 保存沖突的合並(以防萬一):
git stash
- 返回master:
git checkout master
- 拉取最新更改:
git fetch upstream; git merge upstream/master
git fetch upstream; git merge upstream/master
- 更正我的新分支:
git checkout new-branch; git rebase master
git checkout new-branch; git rebase master
- 要應用正確的隱藏更改(現在是堆棧中的第二個):
git stash apply stash@{1}
鏈接的答案可能不應該建議git reset HEAD.
. 它有效,但前提是您位於工作樹的頂部。 一個簡單的git reset
(沒有選項也沒有額外的參數)可能是一個更好的主意。
在閱讀git reset
文檔之前,這里有一些背景知識:
pathspec是一個文件名,可能包括路徑組件,例如dir/file
或a/b/file.ext
,或者只是file.ext
或其他。
git reset
命令最多可以影響三件事:通過名稱HEAD
看到的當前分支、Git 的索引和工作樹中的文件。
git reset
命令過於復雜:它有多種“操作模式”,實際上。
要討論git reset
如何影響HEAD
,我們需要討論分支名稱如何工作1以及HEAD
如何附加到分支名稱。 2但是我們這里具體講的git reset
不會影響HEAD
本身,所以除了腳注,我們就不多說了。 不過,它確實會影響 Git 的索引,所以在繼續之前讓我們先談談 Git 的索引。
1簡而言之,分支名稱指向一個提交(通過存儲原始提交 hash ID)。 名稱HEAD
附加到分支名稱,因此通過“移動您的HEAD
”, git reset
分支名稱指向的提交更改。 也就是說,Git 將不同的提交 hash ID 寫入分支名稱。
2 git checkout
命令和 new-in-Git-2.23 git switch
命令,在使用某個分支名稱簽出提交時執行此“附加”。
Git 的索引是一種持久(存儲在磁盤上)數據結構,在大多數情況下,它記錄了 Git 需要知道的內容,以便 Git 進行下一次提交。 我喜歡這樣說的方式是索引保存您提議的下一次提交,或者至少是它的快照。 這樣,索引就完成了它作為暫存區的作用,這就是它也有這個名字的原因。 但索引不僅僅存儲提議的下一次提交。 特別是,當您進行合並並發生合並沖突時,索引將扮演擴展角色。
不過,首先,讓我們多談談這個暫存區角色。 作為暫存區,索引保存的是每個源文件的完整副本。 它不僅保存更改的文件。 git status
命令專門提到了改變的文件,而具體沒有提到沒有改變的文件。 但是暫存區包含所有文件。
這些文件最初來自當前提交。 如果您沒有替換暫存區域中的文件,並且它仍然是當前提交的文件,那么它與當前提交匹配。 所以git status
只是沒有提到它。 它還在那里! 只是沒有提到! 但是,如果您確實更改了工作樹文件,然后運行git add
,您告訴 Git:彈出您現在擁有的文件,並使用相同的名稱放入另一個文件。 所以現在暫存文件與提交文件不同。
對於我們在這里的特定目的而言,這並不重要,但值得注意的是,每個文件的暫存副本已經采用 Git 的壓縮和去重格式。 這就是文件在提交時的樣子:名稱存儲在其他地方——就像它在索引中一樣——並且內容被壓縮和去重復存儲,就像它們在 Git 的索引中一樣。 你的工作樹中的文件沒有被壓縮和去重, 3所以git add
必須在將它們存儲到 Git 的索引之前對其進行壓縮和去重,以便為新的提交暫存。
所有這些的目的是讓你意識到這些事情:
git status
不會告訴您當前提交中與索引中相同的文件。 當這些文件以某種方式不同時,它只說暫存提交。 這樣,即使您的提交都包含 10,000 個文件,您只需要注意您更改的兩三個文件。 當您 go 進行新提交時,例如,通過運行git commit
, Git 所做的就是使用文件的索引副本作為快照進行新提交。 由於它們已經采用了用於提交的格式,因此速度非常快。
但是,在這種特殊情況下,我們關心的是合並沖突后的索引。 所以我們需要了解擴展索引,而不僅僅是普通的日常建議下一個快照索引。
3從技術上講,是否對工作樹文件進行重復數據刪除取決於您的操作系統。 例如,在 Linux 或 FreeBSD 上使用 ZFS,您可以配置文件系統本身進行重復數據刪除。 這與 Git 無關,它自己進行單獨的重復數據刪除。
在正常的索引設置中,每個文件都處於 Git 稱為stage zero的位置。 我們可以使用git ls-files --stage
看到這一點以及暫存編號。 以下是 Git 的 Git 存儲庫的 Git 索引中的內容片段:
100644 908330a0a3d5d1c1bad56544ba5bb18c3b783c84 0 .travis.yml
100644 5ba86d68459e61f87dae1332c7f2402860b4280c 0 .tsan-suppressions
100644 65651beada79b6267b1d0bda518a88269374cfdf 0 CODE_OF_CONDUCT.md
100644 536e55524db72bd2acf175208aef4f3dfc148d42 0 COPYING
100644 ddb030137d54ef3fb0ee01d973ec5cee4bb2b2b3 0 Documentation/.gitattributes
100644 9022d4835545cbf40c9537efa8ca9a7678e42673 0 Documentation/.gitignore
100644 45465bc0c98f5d88cfe1ade092d29b5dc32c1e23 0 Documentation/CodingGuidelines
(完整的索引內容運行了將近 4000 行:每個文件一個條目,並且幾乎有 4000 個文件)。 分段編號是每一行中的第三個字段,如您所見,這里每個字段都為零。 這意味着沒有正在進行的合並沖突。 最后一個字段包含文件的名稱,如索引中所示; 請注意,許多名稱都嵌入了斜杠(這始終是正斜杠,即使在 Windows 上也是如此)。
當您發生合並沖突時,Git 所做的是在索引中保留每個文件的三個(或更准確地說,最多三個)版本。 4這三個文件位於三個非零暫存索引號處。 暫存編號 1 表示索引中的文件來自合並基本提交。 暫存編號 2 表示該文件來自“我們的”或HEAD
提交,而暫存編號 3 表示該文件來自“他們的”提交。 在非零階段存在任何條目表明該特定文件存在合並沖突。
您可以想到的一種方法是每個文件最多有四個“插槽”。 如果文件不沖突,則使用零位。 否則,插槽 1、2 和/或 3 將被占用。 因此,如果一個文件處於合並沖突 state 中,則它具有非零插槽編號(並且同一文件可能有更多條目,以及其他插槽編號)。 否則,它的插槽號為零(並且此文件沒有更多條目)。
每當任何文件發生合並沖突時 state, Git 將拒絕進行新的提交。 它實際上不能,因為只有零槽條目可以 go 進入提交:提交中沒有用於槽號的空間。 所以,在你可以提交之前,你必須解決沖突。
究竟如何解決沖突取決於您自己。 Git對您的要求很簡單:它只需要您告訴 Git:完全取出插槽 1、2 和/或 3 條目。 您可以通過多種方式做到這一點:
git rm
將刪除這些條目。git add
會將工作樹中的文件復制到索引中的零槽中,刪除非零條目。git reset
和git restore
都可以放入零階段文件,因此也可以刪除非零條目。最后一個將是答案的關鍵。
4最初的索引設計應該在這里允許超過三個,但實際上似乎沒有任何利用這一點。
開始這一切的最初問題是:
git reset HEAD
和:
git reset HEAD .
然后是如何在另一個 StackOverflow 答案中使用它(包括運行六個列出的命令)。
git reset HEAD.
命令可能最好寫成:
git reset HEAD -- .
說清楚.
這是一個pathspec 。 我們現在轉向git reset
文檔,特別是 SYNOPSIS 部分,內容如下:
概要
git reset [-q] [<tree-ish>] [--] <pathspec>… git reset [-q] [--pathspec-from-file=<file> [--pathspec-file-nul]] [<tree-ish>] git reset (--patch | -p) [<tree-ish>] [--] [<pathspec>…] git reset [--soft | --mixed [-N] | --hard | --merge | --keep] [-q] [<commit>]
這里有一些語法技巧需要理解:方括號[]
中的內容是可選的,尖括號<>
中的內容將替換為一些非空字符串,省略號 ( …
) 表示“我們剛才所說的一個或多個”。 (在這種情況下,這是一個或多個路徑規范。)括號包圍用豎線|
分隔的備選方案。 , 所以(--patch | -p)
意味着你可以在這里寫--patch
或-p
。 這些句法噱頭在大多數 Unix/Linux 文檔中大多是標准的,在 SYNOPSIS 部分。
這里還有更多技巧,特定於 Git:根據 gitrevisions 文檔, tree-ish
一詞意味着任何可以接受的東西,只要 Git 可以將其轉換為內部樹 ZA8CFDE6331BD59EB2AC96F8911C4B66 說明符。 在這種情況下,這意味着任何指定提交的東西都有效。 HEAD
指定了一個提交——當前提交——所以git reset HEAD
匹配這個git reset <tree-ish>
。
第一個概要條目需要git reset
,允許可選-q
,允許可選<tree-ish>
,允許可選--
,然后需要<pathspec>
。 所以:
git reset HEAD
與此形式不匹配,因為缺少路徑規范。 但:
git reset HEAD .
確實匹配這種形式:它省略了--
但這是允許的。
概要部分中的第二種形式需要git reset
部分,像以前一樣有一個可選的-q
,允許一個可選的--pathspec-from-file
——如果使用了,也可以使用--pathspec-file-nul
——然后有一個可選的<tree-ish>
。 所以:
git reset HEAD
匹配這個表格。 (這是文檔中的一個小故障!)
第三種形式需要--patch
或-p
所以兩個命令都不匹配這個。
最后一種形式需要git reset
(一如既往),然后允許選項--soft
、 --mixed
、 --hard
、 --merge
或--keep
、可選的-q
和可選的<commit>
(請注意,這不是<tree-ish>
而是特別是commit )。 這:
git reset HEAD
命令也匹配這種形式。 所以git reset HEAD
,沒有點作為路徑規范,可能是其中之一。
正如我上面所建議的,這是文檔中的一個小故障: git reset HEAD
應該采用兩個允許匹配中的哪一個? 我們只能通過閱讀文檔來了解這一點(即使這樣,我們也必須猜測一下,或者嘗試重置測試)。
下一部分是描述部分。 它說使用路徑規范的git git reset
的種類
...將與
<pathspec>
匹配的所有路徑的索引條目重置為<tree-ish>
的 state 。 (它不會影響工作樹或當前分支。)
這意味着將指定提交中的文件的 state(在本例中為HEAD
)復制到索引中。 沒有完全提到的(盡管通過對git restore
的引用在下一段中至少部分涵蓋了)是,這具有與清除非零階段條目相同的git add
。
所以這回答了什么:
git reset HEAD -- .
確實:它重置,如清除沖突以及復制與 pathspec 匹配的每個文件的HEAD
提交副本.
.
這給我們留下了一個問題:哪些文件與 pathspec 匹配.
? 當前的重置文檔存在缺陷。 它向我們介紹了 gitglossary page ,它告訴我們 pathspecs 可以相對於工作樹的頂部或當前工作目錄,並且每個更具體的頁面(即git reset
的頁面)都應該說。 它沒有說。 事實是.
這里是相對於當前工作目錄的。 因此,如果您不在樹的頂部:
git reset HEAD -- .
僅表示此目錄及以下目錄中的文件。
實驗( git rm --cached
一些現有文件,以及git add
一些新文件)表明git reset HEAD --.
將由於git rm
丟失的任何文件恢復到索引,並從索引中刪除任何不在HEAD
中的新文件。 如果文檔對此更清楚可能會很好,但也許我們可以將實驗結果視為確定/目標行為。
讓我們繼續執行另一個命令:
git reset HEAD
這是否意味着git reset
沒有--pathspec-from-file
選項但<tree-ish>
,或者這是否意味着git reset
沒有--hard
或--mixed
或除了<commit>
之外的任何東西? 好吧,如果是前者,它根本不會提供任何路徑規范。 --pathspec-from-file
背后的想法是在文件中而不是在命令行中提供路徑規范,但這樣就有一些路徑規范。 如果 Git 將其視為前者,則根本沒有路徑規范。
我們可以在這里進行測試:
$ git reset HEAD^{tree}
error: object fcb94a429496c28fa7f95926e9d46840671d0d88 is a tree, not a commit
fatal: Could not parse object 'HEAD^{tree}'.
這使用gitrevisions語法來確保我們提供git reset
與樹狀,而不是提交。 結果是立即出錯。 運行git reset HEAD
有效,因此這個特定的git reset
必須與第四個語法匹配,而不是第二個。 (文檔應該省略了--pathspec-from-file
周圍的方括號。)
第四種語法是由以下描述的:
git reset [<mode>] [<commit>]
部分,其中說:
此表單將當前分支頭重置為
<commit>
並可能根據<mode>
更新索引(將其重置為<commit>
的樹)和工作樹。 如果<mode>
被省略,默認為--mixed
。 [剪輯]
所以這是一個--mixed
重置,因為我們省略了<mode>
參數(這是概要中列舉的四個選項之一)。 我們選擇的提交(導致當前分支名稱移動到該提交)是HEAD
選擇的提交。 但HEAD
是當前分支名稱選擇的提交。 所以這個動作是來自某個提交——我們稱之為“提交 X”——提交 X。如果你從你現在站立的地方跳下來但安排降落在你現在站立的地方,那並不是一個真正的移動,是它? :-)
無論如何,這意味着重置當前分支頭短語變得無關緊要:當前分支頭只是停留在原處。 我們繼續並可能更新索引。 git reset
是否更新索引取決於<mode>
,我們剛才說的是--mixed
,其中文檔繼續說:
重置索引但不重置工作樹...
因此,這會將當前提交復制回索引中。 這有點像使用匹配每個文件的路徑規范,只是我們根本不需要路徑規范,實際上禁止使用路徑規范。 (使用路徑規范讓我們進入第一種語法,而不是第四種語法。)
與使用git reset
git 一樣,這具有撤消任何合並沖突 state 的副作用:索引中的任何非零階段條目都將替換為來自提交的階段零副本。
沒有很好的文檔記錄,但標准--mixed
重置始終是正確的,這種git reset
將從 Git 的索引中刪除任何不在所選提交中的文件。 我通過實驗發現, git reset HEAD --.
command 在這里的行為方式相同。
讓我在這里再復制一次:
事實證明,Git 足夠聰明,不會在不干凈應用的情況下丟棄存儲。
這部分意味着要解決的問題是:
<do some hacking>
<realize that this is the wrong commit>
$ git stash
$ git checkout somebranch
$ git stash pop
<receive merge conflict messages>
git stash pop
操作在其第一個( git stash apply
)步驟的中間停止,完全省略,不會運行,它的第二個( git stash drop
步驟放在這里)。
我能夠通過以下步驟獲得所需的 state:
- 要取消合並沖突:
git reset HEAD.
(注意尾隨點)
這就是問題所在。 A git reset --mixed HEAD
,可以拼寫為git reset
,在這里可能是更好的方法。 這會在git stash apply
開始之前將索引放回 state。
(因為我們絕對不需要這個合並的結果——我們可以在以后任何時候重新創建它,只要我們仍然有兩個存儲提交——我們可以只運行git reset --hard
。但是這個特別的人做了不要那樣做。)
- 保存沖突的合並(以防萬一):
git stash
這是完全沒有必要的。 它再進行兩次提交——每個存儲條目是兩個或三個提交; 請參閱我的這個舊答案——其中一個是當前提交的副本,其中一個包含工作樹中的跟蹤文件; 然后它運行git reset --hard
(我們本可以早點完成)。
- 返回master:
git checkout master
這當然完全符合我們的預期。 由於git reset --hard
在git stash
結束時,沒有暫存或未暫存的更改:當前提交、Git 的索引和您的工作樹都匹配,並且git status
nothing to commit, working tree clean
BDworkingtree 2
- 拉取最新更改:
git fetch upstream; git merge upstream/master
git fetch upstream; git merge upstream/master
這從名為upstream
的遠程獲取新提交,然后執行git merge upstream/master
將執行的任何操作; 這顯然取決於在git fetch
中獲得的upstream/master
上的提交與當前( master
)分支上的提交。
如果我們做出一些假設——master 通常與upstream/master
master
,除非上游 Git 的用戶向upstream
添加新提交——這將執行一個簡單的快進操作。
- 更正我的新分支:
git checkout new-branch; git rebase master
git checkout new-branch; git rebase master
這將完成通常的變基工作。
- 要應用正確的隱藏更改(現在是堆棧中的第二個):
git stash apply stash@{1}
如果我們之前跳過了額外的(不必要的)存儲, git stash apply
將在此處執行所需的操作。
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.