-
Notifications
You must be signed in to change notification settings - Fork 670
/
history.txt
223 lines (149 loc) · 9.36 KB
/
history.txt
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
== 關於歷史 ==
Git分散式本性使得歷史可以輕易編輯。但你若篡改過去,需要小心:只重寫你獨自擁有
的那部分。正如民族間會無休止的爭論誰犯下了什麼暴行一樣,如果在另一個人的克隆
裡,歷史版本與你的不同,當你們的樹互操作時,你會遇到一致性方面的問題。
一些開發人員強烈地感覺歷史應該永遠不變,不好的部分也不變所有都不變。另一些覺
得代碼樹在向外發佈之前,應該整得漂漂亮亮的。Git同時支持兩者的觀點。像克隆,分
支和合併一樣,重寫歷史只是Git給你的另一強大功能,至于如何明智地使用它,那是你
的事了。
=== 我認錯 ===
剛提交,但你期望你輸入的是一條不同的信息?那麼鍵入:
$ git commit --amend
來改變上一條信息。意識到你還忘記了加一個檔案?運行git add來加,然後運行上面的
命令。
希望在上次提交裡包括多一點的改動?那麼就做這些改動並運行:
$ git commit --amend -a
=== 更複雜情況 ===
假設前面的問題還要糟糕十倍。在漫長的時間裡我們提交了一堆。但你不太喜歡他們的
組織方式,而且一些提交信息需要重寫。那麼鍵入:
$ git rebase -i HEAD~10
並且後10個提交會出現在你喜愛的$EDITOR。一個例子:
pick 5c6eb73 Added repo.or.cz link
pick a311a64 Reordered analogies in "Work How You Want"
pick 100834f Added push target to Makefile
之後:
- 通過刪除行來移去提交。
- 通過為行重新排序行來重新排序提交。
- 替換 `pick` 使用:
* `edit` 標記一個提交需要修訂。
* `reword` 改變日誌信息。
* `squash` 將一個提交與其和前一個合併。
* `fixup` 將一個提交與其和前一個合併,並丟棄日誌信息。
保存退出。如果你把一個提交標記為可編輯,那麼運行
$ git commit --amend
否則,運行:
$ git rebase --continue
這樣儘早提交,經常提交:你之後還可以用rebase來規整。
=== 本地變更之後 ===
你正在一個活躍的項目上工作。隨着時間推移,你做了幾個本地提交,然後你使用合併
與官方版本同步。在你準備好提交到中心分支之前,這個循環會重複幾次。
但現在你本地Git克隆摻雜了你的改動和官方改動。你更期望在變更列表裡,你所有的變
更能夠連續。
這就是上面提到的 *git rebase* 所做的工作。在很多情況下你可以使用 *--onto* 標
記以避免交互。
另外參見 *git help rebase* 以獲取這個讓人驚奇的命令更詳細的例子。你可以拆分提
交。你甚至可以重新組織一棵樹的分支。
=== 重寫歷史 ===
偶爾,你需要做一些代碼控制,好比從正式的照片中去除一些人一樣,需要從歷史記錄
裡面徹底的抹掉他們。例如,假設我們要發佈一個項目,但由於一些原因,項目中的某
個檔案不能公開。或許我把我的信用卡號記錄在了一個文本檔案裡,而我又意外的把它
加入到了這個項目中。僅僅刪除這個檔案是不夠的,因為從別的提交記錄中還是可以訪
問到這個檔案。因此我們必須從所有的提交記錄中徹底刪除這個檔案。
$ git filter-branch --tree-filter 'rm top/secret/file' HEAD
參見 *git help filter-branch* ,那裡討論了這個例子並給出一個更快的方法。一般
地, *filter-branch* 允許你使用一個單一命令來大範圍地更改歷史。
此後,+.git/refs/original+目錄描述操作之前的狀態。檢查命令filter-branch的確做
了你想要做的,然後刪除此目錄,如果你想運行多次filter-branch命令。
最後,用你修訂過的版本替換你的項目克隆,如果你想之後和它們交互的話。
=== 製造歷史 ===
[[makinghistory]]
想把一個項目遷移到Git嗎?如果這個項目是在用比較有名氣的系統,那可以使用一些其
他人已經寫好的腳本,把整個項目歷史記錄導出來放到Git裡。
否則,查一下 *git fast-import* ,這個命令會從一個特定格式的文本讀入,從頭來創
建Git歷史記錄。通常可以用這個命令很快寫一個腳本運行一次,一次遷移整個項目。
作為一個例子,粘貼以下所列到臨時檔案,比如/tmp/history:
----------------------------------
commit refs/heads/master
committer Alice <[email protected]> Thu, 01 Jan 1970 00:00:00 +0000
data <<EOT
Initial commit.
EOT
M 100644 inline hello.c
data <<EOT
#include <stdio.h>
int main() {
printf("Hello, world!\n");
return 0;
}
EOT
commit refs/heads/master
committer Bob <[email protected]> Tue, 14 Mar 2000 01:59:26 -0800
data <<EOT
Replace printf() with write().
EOT
M 100644 inline hello.c
data <<EOT
#include <unistd.h>
int main() {
write(1, "Hello, world!\n", 14);
return 0;
}
EOT
----------------------------------
之後從這個臨時檔案創建一個Git倉庫,鍵入:
$ mkdir project; cd project; git init
$ git fast-import --date-format=rfc2822 < /tmp/history
你可以從這個項目checkout出最新的版本,使用:
$ git checkout master .
命令*git fast-export* 轉換任意倉庫到 *git fast-import* 格式,你可以研究其輸
出來寫導出程序, 也以可讀格式傳送倉庫。的確,這些命令可以發送倉庫文本檔案
通過只接受文本的渠道。
=== 哪兒錯了? ===
你剛剛發現程序裡有一個功能出錯了,而你十分確定幾個月以前它運行的很正常。天啊!
這個臭蟲是從哪裡冒出來的?要是那時候能按照開發的內容進行過測試該多好啊。
現在說這個已經太晚了。然而,即使你過去經常提交變更,Git還是可以精確的找出問題所在:
$ git bisect start
$ git bisect bad HEAD
$ git bisect good 1b6d
Git從歷史記錄中檢出一個中間的狀態。在這個狀態上測試功能,如果還是有問題:
$ git bisect bad
如果可以工作了,則把"bad"替換成"good"。Git會再次幫你找到一個以確定的好版本和
壞版本之間的狀態,通過這種方式縮小範圍。經過一系列的迭代,這種二分搜索會幫你
找到導致這個錯誤的那次提交。一旦完成了問題定位的調查,你可以返回到原始狀態,
鍵入:
$ git bisect reset
不需要手工測試每一次改動,執行如下命令可以自動的完成上面的搜索:
$ git bisect run my_script
Git使用指定命令(通常是一個一次性的腳本)的返回值來決定一次改動是否是正確的:
命令退出時的代碼0代表改動是正確的,125代表要跳過對這次改動的檢查,1到127之間
的其他數值代表改動是錯誤的。返回負數將會中斷整個bisect的檢查。
你還能做更多的事情: 幫助文檔解釋了如何展示bisects, 檢查或重放bisect的日誌,並
可以通過排除對已知正確改動的檢查,得到更好的搜索速度。
=== 誰讓事情變糟了? ===
和其他許多版本控制系統一樣,Git也有一個"blame"命令:
$ git blame bug.c
這個命令可以標註出一個指定的檔案裡每一行內容的最後修改者,和最後修改時間。但
不像其他版本控制系統,Git的這個操作是在綫下完成的,它只需要從本地磁碟讀取信息。
=== 個人經驗 ===
在一個中心版本控制系統裡,歷史的更改是一個困難的操作,並且只有管理員才有權這
麼做。沒有網絡,克隆,分支和合併都沒法做。像一些基本的操作如瀏覽歷史,或提交
變更也是如此。在一些系統裡,用戶使用網絡連接僅僅是為了查看他們自己的變更,或
打開檔案進行編輯。
中心繫統排斥離線工作,也需要更昂貴的網絡設施,特別是當開發人員增多的時候。最
重要的是,所有操作都一定程度變慢,一般在用戶避免使用那些能不用則不用的高級命
令時。在極端的情況下,即使是最基本的命令也會變慢。當用戶必須運行緩慢的命令的
時候,由於工作流被打斷,生產力降低。
我有這些的一手經驗。Git是我使用的第一個版本控制系統。我很快學會適應了它,用了
它提供的許多功能。我簡單地假設其他系統也是相似的:選擇一個版本控制系統應該和
選擇一個編輯器或瀏覽器沒啥兩樣。
在我之後被迫使用中心繫統的時候,我被震驚了。我那有些脆弱的網絡沒給Git帶來大麻
煩,但是當它需要像本地硬碟一樣穩定的時候,它使開發困難重重。另外,我發現我自
己有選擇地避免特定的命令,以避免踏雷,這極大地影響了我,使我不能按照我喜歡的
方式工作。
當我不得不運行一個慢的命令的時候,這種等待極大地破壞了我思緒連續性。在等待服
務器通訊完成的時候,我選擇做其他的事情以度過這段時光,比如查看郵件或寫其他的
文檔。當我返回我原先的工作場景的時候,這個命令早已結束,並且我還需要浪費時間
試圖記起我之前正在做什麼。人類不擅長場景間的切換。
還有一個有意思的大眾悲劇效應:預料到網絡擁擠,為了減少將來的等待時間,每個人
將比以往消費更多的頻寬在各種操作上。共同的努力加劇了擁擠,這等於是鼓勵個人下
次消費更多頻寬以避免更長時間的等待。