2015-01-13 47 views
6

我們最近完成了從Mercurial到Git的轉換,一切都很順利,我們甚至能夠獲得所需的轉換,以使存儲庫中的所有內容看起來/工作相對正確。我們增加了一個.gitignore,並開始進行。我可以重寫整個git存儲庫的歷史記錄以包含我們忘記的內容嗎?

但是,只要我們與我們的任何舊功能部門合併/合作,我們都會遇到一些極端的減速。稍微探索一下,我們發現.gitignore僅僅被添加到develop分支,當我們看到其他提交沒有合併發展到他們的git chuggs,因爲它試圖分析我們所有的構建工件(二進制文件)等窒息...因爲這些舊分支沒有.gitignore文件。

我們想要做的是有效地插入一個新的根提交與.gitignore,所以它會追溯填充所有頭/標籤。我們對重寫歷史感到滿意,我們的團隊相對較小,因此讓每個人都停止執行此操作,並在歷史重寫完成後重新拉動它們的存儲庫是沒有問題的。

我發現約重訂主到一個新的根提交的信息和這個工程的主,問題是它的葉子在舊歷史上樹脫離我們的功能分支,它也重放用新的整個歷史承諾約會時間。

任何想法或我們在這一個運氣不幸?

回答

8

你想做什麼將涉及兩個階段:追溯添加一個新的根與一個合適的.gitignore和擦洗你的歷史記錄,以刪除不應該添加的文件。 git filter-branch命令可以同時執行這兩個操作。

設置

考慮一下你的歷史的代表。

$ git lola --name-status 
* f1af2bf (HEAD, bar-feature) Add bar 
| A  .gitignore 
| A  bar.c 
| D  main.o 
| D  module.o 
| * 71f711a (master) Add foo 
|/ 
| A foo.c 
| A foo.o 
* 7f1a361 Commit 2 
| A  module.c 
| A  module.o 
* eb21590 Commit 1 
    A  main.c 
    A  main.o 

爲了清楚起見,*.c文件代表C源文件和*.o編譯是應該被忽略的對象文件。

在條形特徵分支上,您添加了一個合適的.gitignore和刪除的不應該被跟蹤的對象文件,但是您希望在導入中無處不在反映該策略。

請注意,git lola是一個non-standard但是有用的別名。

git config --global alias.lola \ 
    'log --graph --decorate --pretty=oneline --abbrev-commit --all' 

新根提交

創建新的根提交如下。

$ git checkout --orphan new-root 
Switched to a new branch 'new-root' 

git checkout文檔記錄了新的孤兒分支可能出現的意料之外的狀態。

如果你想開始一個斷開連接的歷史,記錄一組的路徑是從之一start_point完全不同,那麼你應該通過運行創建孤兒分支後立即清除索引和工作樹git rm -rf .從工作樹的頂層。然後你就可以準備你的新文件,覆育工作樹,通過從其他地方複製它們,提取壓縮包等

繼續我們的例子:

$ git rm -rf . 
rm 'foo.c' 
rm 'foo.o' 
rm 'main.c' 
rm 'main.o' 
rm 'module.c' 
rm 'module.o' 

$ echo '*.o' >.gitignore 

$ git add .gitignore 

$ git commit -m 'Create .gitignore' 
[new-root (root-commit) 00c7780] Create .gitignore 
1 file changed, 1 insertion(+) 
create mode 100644 .gitignore 

現在歷史類似

$ git lola 
* 00c7780 (HEAD, new-root) Create .gitignore 
* f1af2bf(bar-feature) Add bar 
| * 71f711a (master) Add foo 
|/ 
* 7f1a361 Commit 2 
* eb21590 Commit 1 

這有點誤導,因爲它使得新根看起來像是bar-feature的後代,但它確實沒有父項。

$ git rev-parse HEAD^ 
HEAD^ 
fatal: ambiguous argument 'HEAD^': unknown revision or path not in the working tree. 
Use '--' to separate paths from revisions, like this: 
'git <command> [<revision>...] -- [<file>...]' 

記下孤兒的SHA,因爲您以後需要它。在這個例子中,它是

$ git rev-parse HEAD 
00c778087723ae890e803043493214fb09706ec7 

改寫歷史

我們希望git filter-branch使三大變化。

  1. 在新的根提交中拼接。
  2. 刪除所有的臨時文件。
  3. 使用新的根中的.gitignore,除非已經存在。

在命令行上,即incanted爲

git filter-branch \ 
    --parent-filter ' 
    test $GIT_COMMIT = eb215900cd15ca2cf9ded74f1a0d9d25f65eb2bf && \ 
       echo "-p 00c778087723ae890e803043493214fb09706ec7" \ 
     || cat' \ 
    --index-filter ' 
    git rm --cached --ignore-unmatch "*.o"; \ 
    git ls-files --cached --error-unmatch .gitignore >/dev/null 2>&1 || 
     git update-index --add --cacheinfo \ 
     100644,$(git rev-parse new-root:.gitignore),.gitignore' \ 
    --tag-name-filter cat \ 
    -- --all 

說明:在你的新的根

  • --parent-filter選項鉤提交。
    • eb215...是舊的根提交的完整SHA,比較。git rev-parse eb215
  • --index-filter選項有兩個部分:
    • 運行git rm如上刪除任何東西,因爲水珠圖案的報價,由混帳解釋,而不是外殼從整個樹匹配*.o
    • 檢查現有的.gitignoregit ls-files,如果它不在那裏,則指向新根中的那個。
  • 如果您有任何標籤,它們將使用標識操作cat進行映射。
  • 唯一--終止選項,--all是所有參考的簡寫。

您看到的輸出將與

Rewrite eb215900cd15ca2cf9ded74f1a0d9d25f65eb2bf (1/5)rm 'main.o' 
Rewrite 7f1a361ee918f7062f686e26b57788dd65bb5fe1 (2/5)rm 'main.o' 
rm 'module.o' 
Rewrite 71f711a15fa1fc60542cc71c9ff4c66b4303e603 (3/5)rm 'foo.o' 
rm 'main.o' 
rm 'module.o' 
Rewrite f1af2bf89ed2236fdaf2a1a75a34c911efbd5982 (5/5) 
Ref 'refs/heads/bar-feature' was rewritten 
Ref 'refs/heads/master' was rewritten 
WARNING: Ref 'refs/heads/new-root' is unchanged 

原始照片仍然是安全的。例如,主分支現在生活在refs/original/refs/heads/master之下。查看您新重寫的分支中的更改。當你準備刪除的備份,運行

git update-ref -d refs/original/refs/heads/master 

你可以煮了命令,以覆蓋所有備份裁判在一個命令,但是我建議每個人仔細審查。

結論

最後,新的歷史

$ git lola --name-status 
* ab8cb1c (bar-feature) Add bar 
| M  .gitignore 
| A  bar.c 
| * 43e5658 (master) Add foo 
|/ 
| A foo.c 
* 6469dab Commit 2 
| A  module.c 
* 47f9f73 Commit 1 
| A  main.c 
* 00c7780 (HEAD, new-root) Create .gitignore 
    A  .gitignore 

觀察到所有的目標文件都不見了。 bar-feature中對.gitignore的修改是因爲我使用了不同的內容來確保它將被保留。爲了完整:

$ git diff new-root:.gitignore bar-feature:.gitignore 
diff --git a/new-root:.gitignore b/bar-feature:.gitignore 
index 5761abc..c395c62 100644 
--- a/new-root:.gitignore 
+++ b/bar-feature:.gitignore 
@@ -1 +1,2 @@ 
*.o 
+*.obj 

新根裁判不再有用,所以用

$ git checkout master 
$ git branch -d new-root 
+2

你是我的弗利英雄! – Aren

+0

@Aren不客氣!樂意效勞。 –

-1

免責聲明對其進行處理:這是理論(基於文件),我沒有這樣做。 克隆並嘗試。

根據我的理解,您從未犯過的文件會被您想要添加到歷史記錄根目錄的.gitignore所過濾。

因此,如果您將主分支重新綁定到僅包含.gitignore的newroot提交,您實際上不會修改提交的內容,並且此後應該能夠重新綁定任何和所有其他分支已經進入新的提交,並且rebase將爲你完成這項工作。

因爲提交的內容是相同的,所以補丁ID應該保持不變,並且rebase只會應用那些必要的。

雖然您需要逐個重新綁定每個分支,但可以輕鬆地編寫腳本。

更多信息可在in the git rebase documentation節中找到: 從頁面末尾的上一頁回收。

編輯:好的沒關係,測試,並不完全這樣工作。你必須爲新歷史中的每個分支「手動」給出重新分配點,這是一個痛苦。 仍然可以工作,但它顯然是比接受的答案更糟糕的解決方案。

+0

這是不正確的和誤導。 – Jubobs

+0

這是我第一次嘗試的方法,我遇到的問題是,一旦您將主人重新命名爲新的歷史記錄,功能分支沒有任何分歧點,因此您無法有效地重新設置功能分支,因爲您必須將櫻桃逐一挑選舊歷史的正確部分。 – Aren

+0

您可以將任何分支重新綁定到與舊分支中分支開始的提交相對應的新提交,並提供新歷史記錄中的所有提交都具有相同內容(也就是說,分支分支的基本基本上是除了在開頭添加.gitignore之外,沒有任何操作) –

相關問題