簡體   English   中英

“git reset HEAD”和“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:

  1. 要取消合並沖突: git reset HEAD. (注意尾隨點)
  2. 保存沖突的合並(以防萬一): git stash
  3. 返回master: git checkout master
  4. 拉取最新更改: git fetch upstream; git merge upstream/master git fetch upstream; git merge upstream/master
  5. 更正我的新分支: git checkout new-branch; git rebase master git checkout new-branch; git rebase master
  6. 要應用正確的隱藏更改(現在是堆棧中的第二個): git stash apply stash@{1}

TL;博士

鏈接的答案可能不應該建議git reset HEAD. . 它有效,但前提是您位於工作樹的頂部。 一個簡單的git reset (沒有選項也沒有額外的參數)可能是一個更好的主意。

在閱讀git reset文檔之前,這里有一些背景知識:

  • pathspec是一個文件名,可能包括路徑組件,例如dir/filea/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 進行下一次提交。 我喜歡這樣說的方式是索引保存您提議的下一次提交,或者至少是它的快照。 這樣,索引就完成了它作為暫存區的作用,這就是它也有這個名字的原因。 但索引不僅僅存儲提議的下一次提交。 特別是,當您進行合並並發生合並沖突時,索引將扮演擴展角色。

不過,首先,讓我們多談談這個暫存區角色。 作為暫存區,索引保存的是每個源文件的完整副本 它不僅保存更改的文件。 git status命令專門提到了改變的文件,而具體沒有提到沒有改變的文件。 但是暫存區包含所有文件。

這些文件最初來自當前提交。 如果您沒有替換暫存區域的文件,並且它仍然是當前提交的文件,那么它當前提交匹配。 所以git status只是沒有提到它。 它還在那里! 只是沒有提到! 但是,如果您確實更改了工作樹文件,然后運行git add ,您告訴 Git:彈出您現在擁有的文件,並使用相同的名稱放入另一個文件。 所以現在暫存文件與提交文件不同

對於我們在這里的特定目的而言,這並不重要,但值得注意的是,每個文件的暫存副本已經采用 Git 的壓縮和去重格式 這就是文件在提交時的樣子:名稱存儲在其他地方——就像它在索引中一樣——並且內容被壓縮和去重復存儲,就像它們在 Git 的索引中一樣。 你的工作樹中的文件沒有被壓縮和去重, 3所以git add必須在將它們存儲到 Git 的索引之前對其進行壓縮和去重,以便為新的提交暫存。

所有這些的目的是讓你意識到這些事情:

  • 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 resetgit 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:

  1. 要取消合並沖突: git reset HEAD. (注意尾隨點)

這就是問題所在。 A git reset --mixed HEAD ,可以拼寫為git reset ,在這里可能是更好的方法。 這會在git stash apply開始之前將索引放回 state。

(因為我們絕對不需要這個合並的結果——我們可以在以后任何時候重新創建它,只要我們仍然有兩個存儲提交——我們可以只運行git reset --hard 。但是這個特別的人做了不要那樣做。)

  1. 保存沖突的合並(以防萬一): git stash

這是完全沒有必要的。 它再進行兩次提交——每個存儲條目是兩個或三個提交; 請參閱我的這個舊答案——其中一個是當前提交的副本,其中一個包含工作樹中的跟蹤文件; 然后它運行git reset --hard (我們本可以早點完成)。

  1. 返回master: git checkout master

這當然完全符合我們的預期。 由於git reset --hardgit stash結束時,沒有暫存或未暫存的更改:當前提交、Git 的索引和您的工作樹都匹配,並且git status nothing to commit, working tree clean BDworkingtree 2

  1. 拉取最新更改: 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添加新提交——這將執行一個簡單的快進操作。

  1. 更正我的新分支: git checkout new-branch; git rebase master git checkout new-branch; git rebase master

這將完成通常的變基工作。

  1. 要應用正確的隱藏更改(現在是堆棧中的第二個): git stash apply stash@{1}

如果我們之前跳過了額外的(不必要的)存儲, git stash apply將在此處執行所需的操作。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM