簡體   English   中英

Git日志(--follow)無法顯示重命名之外的歷史記錄

[英]Git log (--follow) not working to show history beyond renames

我嘗試通過 gitlog 在我的 git 中顯示文件的完整歷史記錄。 問題是該文件的父文件夾在歷史記錄中被重命名,我喜歡查看完整的歷史記錄。

git-log 文檔說參數--follow-M show 在重命名之后生成 git log。

我嘗試了 gitlog 參數的不同組合,例如

git log -M --oneline --all -- --follow newpath/my-file.php

git log -M --oneline --all -- newpath/my-file.php甚至

git rev-list --all -- newpath/my-file.php --objects --in-commit-order | git log --no-walk --oneline --stdin

但是無論我嘗試什么,歷史總是在文件的父文件夾被重命名的提交處結束。

我已經可以確認:

  • 只有文件夾在重命名提交中被重命名,文件內容 100% 不變,所以 git 應該簡單地發現舊路徑上的文件和新路徑上的文件是相同的並且應該重命名。

  • 重命名提交的git shot name-status顯示R100 oldpath/my-file.php newpath/my-file.php (確認文件內容 100% 相同)

  • 歷史的“舊一半”和“新一半”似乎是正確的,都包括 rename-commt

  • 當我運行git log -M --oneline --all -- --follow newpath/my-file.php時,最舊的提交是0979744 renamed: oldpath/ -> newpath/

  • 當我運行git log -M --oneline --all -- --follow oldpath/my-file.php時,最新提交是0979744 renamed: oldpath/ -> newpath/

所以一切看起來像我的 git 成功理解了新路徑中的文件和舊路徑中的文件被重命名。 誰能告訴我為什么即使我使用-M--follow選項,重命名提交的歷史仍然會中斷?

注釋中所述,-- --follow選項必須在獨立的--之前,這表示選項列表的結尾。

即使它下面的重命名現在似乎也有效,當我添加--grep="rename" --invert-grep以刪除“重命名”提交時,我得到 0 個結果

這是有道理的(但這是一個錯誤), 1因為--follow的工作方式 這里的問題是 Git 根本沒有任何類型的文件歷史記錄 Git 所擁有的只是存儲庫中的一組提交。 提交是歷史:

  • 每個提交都通過其大而丑陋的哈希 ID 進行編號,該 ID 對於該特定提交是唯一的。 沒有其他提交(在任何Git 存儲庫2中)具有該哈希 ID。

  • 每個提交都有每個文件的完整快照。

  • 每個提交還存儲先前提交的哈希 ID,或者,對於合並提交,兩個或多個先前提交。

所以這些數字將提交串在一起,向后:

... <-F <-G <-H

這里的大寫字母代表實際的提交哈希 ID,Git 通過它找到提交。 每個提交都有一個“后向箭頭”——前一次提交的存儲哈希 ID——因此,如果我們能記住鏈中最后一次提交的哈希 ID,我們可以讓 Git 向后工作。鏈。

分支名稱只是告訴 Git 哪個提交是該分支中的最后一個提交:

             I--J   <-- feature1
            /
...--F--G--H
            \
             K--L   <-- feature2

在這里,提交J是功能分支的最后一次提交,而提交L是另一個上的最后一次提交。 請注意,通過H向上提交在兩個分支上(很可能也在主分支或主分支上)。

git log命令簡單地處理提交,一次一個,從您選擇的任何“最后一次提交”開始。 默認的“最后一次提交”是您現在簽出的任何分支的尖端。 這個過程向后工作:Git 從最后一次提交開始並向后工作,一次提交一個。

git diff-M選項是--find-renames ,可以在git diff中啟用重命名檢測。 git log 的--follow選項對git log git log相同的操作,但也使用單個文件的名稱來查找。 (為git log提供-M選項使其在每個差異中使用重命名檢測器,但由於它不是在尋找一個特定的文件,這只會影響-p--name-status輸出樣式。使用--followgit log正在尋找那個特定的文件,我們稍后會看到。)

重命名檢測器以這種方式工作:

  • 你給 Git 兩次提交,之前之后,或者說, FG (你可以把新的提交放在左邊,舊的放在右邊,但是git log本身總是把舊的放在左邊,新的放在右邊。)

  • 你讓 Git 比較這兩個提交中的快照。

  • 這些提交中的一些文件是 100% 相同的:它們具有相同的名稱相同的內容。 Git 的內部存儲系統已經對這些文件進行了重復數據刪除,這使得git diffgit log很容易確定這些文件是相同的,因此它可以在適當的時候跳過它們。

  • 其他文件名稱相同但內容不同。 Git 假設,默認情況下,如果兩個文件具有相同的名稱——例如path/to/file.ext :注意嵌入的斜杠只是文件名的一部分——它們代表“相同的文件”,即使內容發生了變化。 所以該文件被修改,從舊的/左側提交到新的/右側提交。 如果您要求--name-status ,您將獲得M , modified ,作為該文件名的狀態。

  • 有時,左側提交有一個名為fileL的文件,而右側提交根本沒有該文件。 顯然,在從舊(左)到新(右)的變化中,該文件被刪除 使用--name-status你會得到D的狀態。

  • 有時,右側提交有一個名為fileR的文件,而左側提交則沒有。 顯然,該文件是新添加的,並且使用--name-status您將獲得A的狀態。

  • 但是如果左邊的fileL和右邊的fileR應該被認為是“同一個文件”呢? 也就是說,如果我們將 fileL重命名為fileLfileR 這就是 Git 的重命名檢測器的用武之地。給定這樣的刪除/添加對,可能fileR內容fileL內容足夠接近或完全相同。 如果:

    • 您已打開重命名檢測器,它實際上會執行此內容檢查,並且
    • 內容檢查說“完全一樣”(由於重復數據刪除而很快知道)或“足夠相似”(慢得多,但由相同的重命名檢測器開關啟用),

    然后——也只有那時——Git 會聲明fileL重命名fileR --name-status輸出將包括R相似性索引值和兩個文件名,而不是在左側和右側提交中匹配的單個文件名。

現在您知道了重命名檢測器的工作原理——並且必須打開它——你可以看到--follow是如何工作的。 請記住,使用git log ,您可以給它一個文件名,並告訴它不要顯示修改該特定文件的提交。 3結果是您只看到確實修改該文件的提交: git log訪問的所有提交集合的一個子集。 因此,假設您運行git log --follow -- newpath/my-file.php

  • git log遍歷歷史,一次提交一次,像往常一樣向后。

  • 在每次提交時,它都會將此提交(較新,在右側)與其父提交(較舊,在左側)進行比較。 如果沒有--follow它仍然會這樣做,但只需查看您命名的文件是否已更改M狀態,來自git diff --name-status )或添加刪除AD )。 4但使用--follow ,它還會查找R狀態。

  • 如果文件更改——具有MAD狀態—— git log打印出這個提交,但如果沒有,它只會抑制打印輸出。 使用--follow ,我們添加R狀態,如果發生這種情況,添加兩個文件名。 如果狀態R ,那么git log之前一直在尋找newpath/my-file.php 但現在它知道,從提交開始,該文件被稱為oldpath/my-file.php (再次注意,這里沒有文件夾。文件名是整個字符串,包括所有斜杠。)

因此,使用--follow ——打開重命名檢測器—— git log可以獲得重命名狀態,因此可以看到文件被重命名。 它還在尋找一個特定的文件名,在本例中為newpath/my-file.php 如果它檢測到重命名, git log不僅會打印提交,還會更改它正在尋找的一個名稱 現在,它不是newpath/my-file.php ,而是從父提交向后,它正在尋找oldpath/my-file.php


1 --follow代碼本身......不是很好; 整個實現需要重新設計,這可能會比我想的更簡單的hack更好地解決這個問題。

2從技術上講,其他一些 Git 存儲庫可能有一個不同的提交來重用該哈希 ID,只要您從不將這兩個提交相互介紹。 但在實踐中,你不會找到一個。

3 --follow選項只能跟隨一個文件名 如果沒有--follow ,您可以為git log提供多個名稱,或者一個“目錄”的名稱,即使 Git 根本不存儲目錄。 沒有--followgit log代碼在通用路徑規范上運行。 使用--follow ,它只處理一個文件名。 這是 Git 在這里使用的算法所施加的限制。

4它也可以有T ,類型改變,我認為這很重要。 完整的狀態字母集是ABCDMRTUXX表示 Git 中的一個錯誤, U只能在未完成的合並期間出現, B只能在帶有-B選項的git diff中出現, CR只能在--find-copies中出現--find-copies--find-renames-C-M )選項已啟用。 請注意, git diff可能會根據您的diff.renames設置自動啟用--find-renames renames ,但git log不會。


--follow中的錯誤

這個從git log的輸出顯示中刪除一些提交的過程稱為歷史簡化 文檔中有很長的部分描述了這一點,它以這個相當奇怪的聲明開頭:

有時您只對歷史的一部分感興趣,例如修改特定 <path> 的提交。 但是History Simplification有兩個部分,一個是選擇提交,另一個是如何去做,因為有多種策略可以簡化歷史。

這個奇怪的措辭——“一部分是選擇提交,另一部分是如何去做”——試圖說明的是,啟用歷史簡化后, git git log有時甚至不會執行一些提交。 特別是,考慮一個合並提交,其中兩個提交字符串組合在一起:

          C--...--K
         /         \
...--A--B           M--N--O   <-- branch
         \         /
          D--...--L

要顯示所有提交, git log將必須遍歷提交O ,然后是N ,然后是M ,然后是KL (按某種順序),然​​后是K之前的所有提交和L之前的所有提交,然后返回CD ,然后在提交B處重新加入單個線程,並從那里繼續向后。

但是,如果我們不打算顯示所有提交,也許——只是也許——在提交M處,我們可以返回只提交K或只提交L並完全忽略合並的另一“邊”。 這將節省大量工作和時間,並避免向您展示無關緊要的內容。 這通常是一件非常好的事情。

然而,對於--follow來說,這通常是一件非常糟糕的事情。 這是--follow的問題之一:有時 Git 在進行這種簡化時會走“錯誤的腿”。 添加--full-history可以避免這種情況,但我們立即遇到了另一個問題。 --follow選項只有一個文件名 如果我們在提交的兩個分支中的一個中進行了重命名,但在另一個中沒有,並且git log首先沿着重命名分支向下,那么當它沿着另一分支向下時它可能會查找錯誤的名稱

如果文件在兩條腿中重命名,以便將其從M重命名為KM重命名為L ,或者如果 Git 碰巧首先沿着正確的腿你不關心另一條腿,一切正常。 但這是需要注意的。 (這不是用--grep打擊你的問題,否則它會在沒有--grep的情況下發生。)

認為您看到的錯誤是--grep正在“過早”啟動,事實上。 --grep選項通過從git log的輸出中消除任何在其提交消息中具有( --invert-grep )或缺少( --grep without --invert-grep )某些特定文本的提交來起作用。 然后,假設重命名提交(導致git log --follow知道使用名稱oldpath/my-file.php的提交)被您的--grep選項跳過 Git 不會看到R狀態,也不會知道將名稱從newpath/my-file.php更改為oldpath/my-file.php 因此git log --follow將繼續尋找路徑,並且您將只獲得那些既符合 grep 標准使用新名稱修改文件的提交。

這個錯誤可以通過讓git log --follow運行差異引擎來修復,即使它會因為其他原因跳過提交。 但更一般地說--follow需要完全重寫:它有一堆奇怪的特殊情況代碼通過差異引擎線程化,只是為了使這個案例工作。 它需要處理多個路徑名和/或路徑規范,並使用--reverse和其他選項。 它需要一種將新舊名稱堆疊到提交路徑上的方法,以便使用--full-history ,沿着合並的兩條腿,它知道要尋找哪條路徑。 請注意,這還有其他含義:如果在合並的兩條腿上都有不同的重命名怎么辦? 如果有人在合並中手動修復了重命名/重命名沖突,我們該如何處理?

暫無
暫無

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

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