[英]git log does not return the history of a file correctly
我對 git 日志命令有一個奇怪的問題。 雖然這個命令:
git log --pretty=format: --name-only --diff-filter=A
返回列表中的.xyz.yml文件,但是當我嘗試運行此命令時:
git log --pretty="%ad" --diff-filter=A -- .xyz.yml
要檢索此文件添加到此存儲庫的時間,它返回空。
有什么解決辦法嗎?
我會很感激任何線索。
編輯:
當我嘗試獲取完整歷史記錄時:
git log --full-history -- .xyz.yml
output 顯示了簡短且不完整的歷史記錄
commit b26d833b9da805d5d58c429a4af2d1a5c5b0bad9
Author: author name
Date: Mon Dec 19 14:07:17 2016 -0500
Code config (#606)
* Create .xyz.yml
Created a Code config that uses your setup (also, enabled our Duplication engine).
* Update .xyz.yml
* Update .xyz.yml
* Update .xyz.yml
* Update .xyz.yml
* Update .xyz.yml
* Update .xyz.yml
* Update .xyz.yml
即使文件不再存在於頭中,歷史記錄也不會顯示任何刪除...
我還查看了 GitHub 用戶界面中的提交歷史記錄,我在那里看到了一個完全不同的世界:
第一次提交日期甚至與我在 --full-history 中可以找到的日期不同。
(注意:如果您還不知道提交如何存儲每個文件的完整快照,並通過它們的元數據父信息鏈接在一起,請參閱我的答案here 。)
編輯前后的問題是不同的,但這兩個問題都與正在發生的事情相關。 git log
命令可以執行 Git 調用History Simplification的操作。 在git log
文檔中搜索這個兩個詞的短語,你會發現一個以這個措辭奇怪的段落開頭的部分:
有時您只對歷史的一部分感興趣,例如修改特定 <path> 的提交。 但是歷史簡化有兩個部分,一個是選擇提交,另一個是如何去做,因為有多種策略可以簡化歷史。
在我們在這里處理奇怪的措辭之前,請注意,只有在您要求時才會簡化歷史。 索取方法如下:
path
arguments,如git log --.xyz.yml
:這里的.xyz.yml
是一個路徑。 (在某些情況下--
是可選的,並將 arguments 的其余部分標記為路徑。如果命名路徑存在於當前提交中並且與其他git log
選項不同,則--
不是必需的。這是一個好主意但是,始終習慣於使用它,這樣您就不必弄清楚這個特定的git log
調用是否需要它。) 由於在您遇到麻煩的情況下,您確實使用了--.xyz.yml
,因此您確實要求簡化歷史記錄,即使您沒有意識到您要求它。 這就是我添加評論的原因; 您使用--full-history
解決問題的回復證明默認模式簡化實際上是問題所在。
然后你問:
完整歷史返回介紹提交。 什么原因?
答案在於文檔所稱的Default mode
:
將歷史簡化為最簡單的歷史,解釋樹的最終 state。 最簡單,因為如果最終結果相同,它會修剪一些側枝(即合並具有相同內容的分支)
這仍然是相當莫名其妙的。 最初的段落討論了選擇提交以及如何進行。 我認為這里缺少的是文檔從不談論git log
的真正工作原理。
我們需要知道——文檔沒有說明git log
的工作方式是掃描提交隊列。 這個隊列是一個優先級隊列,即“更高優先級”的提交會浮到隊列的最前面並首先被檢查; 已經在隊列中的“較低優先級”提交被這個較高優先級的提交推到行尾。 因此, git log
命令一次只處理此隊列中的一個提交。
隊列本身最初是從您在命令行上指定的任何提交中加載的。 例如,您可以運行:
git log branch1 branch2 branch3
這使用git rev-parse
將每個branch1
、 branch2
和branch3
轉換為提交 hash ID。 生成的三個提交 hash ID(假設我們得到三個不同的 ID)被加載到隊列中。 如果我們得到重復,此時隊列中有兩個甚至一個提交 hash ID。 例如,如果名稱branch2
和branch3
select 相同的提交,而branch1
選擇不同的提交,則隊列現在只有兩個提交。
(如果你不選擇任何起始提交, git log
將使用HEAD
作為起始點提交。它的姊妹命令git rev-list
沒有這個特殊功能,所以任何時候你使用git rev-list
代替 ZBA9F11ECC3497D9993B933 git log
,確保給它一個明確的起點。)
git log
代碼現在進入其主循環。 這個循環:
git log
arguments決定是否打印; 和git log
arguments 決定是否將其父提交放入隊列。 當我們詢問git log
來說明類似.xyz.yml
的文件時,是否打印提交的決定必須將提交的快照與其父快照進行比較。 我們現在想瀏覽一下本節的文檔:
更詳細的解釋如下。
假設您將
foo
指定為 <paths>。 我們將調用修改foo
的提交,TREESAME。 和 rest TREESAME。 (在為foo
過濾的差異中,它們分別看起來不同且相等。)[snip]
(閱讀 rest 並在閱讀此答案的 rest 之前或之后完成他們的示例。)
Git 在內部真正要做的是為此提交拍攝快照(無論它是什么),並刪除除您列出的文件之外的所有文件。 在這種情況下,您列出的一個文件是.xyz.yml
; 在他們的示例中,一個文件被命名為foo
。 但是你可以在這里給出一個目錄路徑,Git 將刪除除該目錄中的文件或多個路徑之外的所有文件,Git 也會刪除除這些路徑之外的所有文件。 這一切都適用於所謂的 TREESAM 測試。 當我們查看單個文件時,最容易理解,因為提交具有文件的某個特定版本,或者提交完全沒有文件:這是僅有的兩種可能性。 因此,如果兩個提交都缺少文件,或者兩者都有文件並使用相同版本的文件,則兩個提交是“相同的”(TREESAME)。
如果我們有一個普通的、日常的、非合並的提交和一個父提交,這一切都非常簡單。 考慮以下簡單的提交鏈:
... <-F <-G <-H
在這里,提交H
有一些快照。 H
的父母提交G
有一些快照。 當然, G
的父母F
也有一些快照,依此類推。 可能每個快照都是不同的,但如果我們將它們剝離為一個感興趣的文件——文件foo
或文件.xyz.yml
G
和H
可能具有相同的文件。 G
和H
互為 TREESAM。 但是,提交F
中的副本可能不同: F
和G
不是 TREESAME。
這意味着 Git不會提及提交H
。 它對文件沒有任何更改。 Git將提到提交G
:與其父F
相比,它對文件進行了更改。 這是 TREESAME 概念的第一次使用:通過向 Git 詢問特定文件,它僅將非 TREESAME 的提交打印到其父提交:至少我們詢問的文件中的一個已更改。
這僅處理簡單的普通提交,例如F
、 G
和H
。 合並提交呢? 我們的分支中可能有這些提交:
I--J
/ \
...--H M--N--...
\ /
K--L
當 Git 對 (M, N) 對進行 TREESAME 測試時,該部分很簡單。 雖然M
是一個合並提交,但它有一個快照,就像任何提交一樣。 因此,我們將M
和N
中的快照縮減為感興趣的文件,並確定結果是否為 TREESAME。 如果是這樣,我們不打印N
,然后轉到M
; 如果沒有,我們打印N
,然后繼續M
。
現在我們必須決定提交M
是否對其父級是 TREESAME。 但是等一下, M
沒有父母。 M
有兩個父母, J
和L
我們應該比較哪一個?
Git 的答案是比較所有這些:嘗試 TREESAME( J
, M
)和TREESAME( L
, M
)。 Git 現在知道M
是否對所有父母、某些父母或沒有父母都是 TREESAME。 如果M
對任何父級都是 TREESAME,則不打印; 否則,它會被打印。 現在真正的並發症開始了。
git log
可以將部分或全部父級放入隊列打印或未打印提交M
后, git log
現在必須決定:
J
放入隊列?L
放入隊列中? 當不做歷史簡化時,Git 會將雙親都放入隊列中。 (好吧,如果您使用了--first-parent
選項,則不會。但是,由於您沒有使用,我們將完全忽略該選項。)但是在進行歷史簡化時,默認選項是:
--full-history
,所有父母 go 進入隊列。--full-history
,請選擇一個 TREESAME 父母(從所有可能的TREESAME 父母中隨機選擇)。 如果沒有父級是 TREESAME,則選擇所有父級。 將這些放入隊列中。(請注意,某些合並提交可能有 3 個或更多父級;相同的規則適用於這些多父級合並提交。這里我們只有一個雙父級合並,因此短語“所有父級”的意思是“雙親”。)
現在,假設我們感興趣的文件是在提交K
或L
中引入的。 提交H
、 I
和J
中沒有它,而且——重要的是——它在M
中也沒有:合並省略了 file 。 由於提交M
,在剝離除我們一個感興趣的文件之外的所有文件后,在相同的剝離后提交J
是TREESAME,Git 跟隨M
回到J
,完全忽略提交L
。 (注意:可能是L
中也沒有我們的文件,但無論出於何種原因,Git 選擇跟隨提交J
而不是L
作為其單個 TREESAME 提交,同時進行歷史簡化。)
在這種情況下,歷史簡化代碼完全阻止git log
查看底行 commits 。 隊列從不包含首次引入文件的提交K
。 查找文件的掃描永遠不會找到它,因為 Git 從不仔細閱讀引入文件的歷史記錄(提交)。
這種簡化背后的想法是解釋為什么您擁有現在擁有的文件。 通過不遵循從合並提交M
返回到提交L
的歷史記錄,在我們的示例中,我們永遠找不到文件.xyz.yml
。 但這就是我們想要的,因為文件.xyz.yml
不在當前提交N
中,也不在我們開始的任何地方。 我們已經要求 Git 解釋那里的文件。 文件.xyz.yml
不存在,因此對它存在的原因的解釋是它從未在感興趣的歷史中存在:解釋為什么它仍然不存在的歷史。
當然,您的目標是弄清楚它是在哪里引入的,又是在哪里丟失的。 事實是它在合並時丟失了,當時有人決定:我們的合並結果中不需要這個愚蠢的.xyz.yml
! 讓我們把它擋在外面! 也就是說,它在某個提交L
中,而不是在其直接后繼合並M
中。
我知道這一點的方式來自您的最終git log
output,當您取出--diff-filter
選項時:
git log --full-history --.xyz.yml
我們看到一個添加文件的提交,以及一些修改它的提交,但我們沒有看到任何刪除它的提交。 我們沒有看到這個提交的原因是因為我們的合並M
對其父級中的至少一個是TREESAME :在我們的示例中是J
。 所以合並提交M
根本不打印。
如果它被打印出來,我們仍然有一個潛在的問題,因為打印合並的方式有點時髦。 所有提交都打印了它們的 hash ID 和日志消息。 如果您要求--name-status
或--patch
,您還可能會得到某種git diff
的結果。 對於普通提交,這是與(單個)父提交的差異。 但是,對於git log
,有一個問題:
-c
、 --cc
或-m
, git log
懶惰地完全跳過打印差異; 和-c
或--cc
,你會得到一個組合的 diff 。 組合差異省略了一些文件。 特別是,它們省略了至少一個父級和合並具有相同版本文件的任何文件,或者在這種情況下,兩者都缺少該文件。 因此,組合差異不會提及L
和M
之間的文件被刪除。 只有-m
style diff 會在此處提及刪除。
-m
選項對合並提交進行“虛擬拆分”。 如果合並X
有父母P1
, P2
, P3
, ..., Pn
,你會得到n diffs: P1
-vs- X
, P2
-vs- X
, P3
-vs- X
,等等到Pn
-vs- X
. 那么,對於我們的特殊情況,我們會得到兩個差異: J
vs M
和L
vs M
J
-vs- M
差異對於.xyz.yml
根本不會顯示任何內容,但L
-vs- M
會顯示刪除。
(請注意, -m
還修改了git log
決定是否打印合並的方式:現在它已經被拆分,如果它不是 TREESAME 到至少一個父級,它就會被打印出來。這也很重要,在這里。)
如果您想找出某個文件被刪除的位置,您可能需要git log --full-history --diff-filter=D -m -- path
。 這會強制git log
到 go 並檢查合並本身是否是文件在您開始的提交中不存在的原因。
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.