2016-05-06 6 views
1

私はgit rebaseを使って、削除したブランチが削除された場合に、新しく作成されたファイルが削除される原因を突き止めようとしています。例:B1がチェックアウトされていると私はgit rebase A3を実行rebaseブランチによって削除された最新のコミットで追加されたファイルをgit rebaseで削除するのはなぜですか?

A1 - A2 - A3 
\ 
    B1 

A2 = add a new file test.txt 
A3 = delete test.txt 
B1 = add the exact same file as A2 

場合、test.txtのはまだ削除されます。結果は次のようになります:

A1 - A2 - A3 - B1 

これは、test.txtがまだ存在することを意味します。リベース後にtest.txtが削除されるのはなぜですか? git rebase documentationとして

+1

質問を正しく読んでいない場合は、そうではありません。これを再現するスクリプトがありますか? (空のディレクトリを作成し、 'git init'を実行し、ファイルを作成し、コミットし、ブランチを作成し、さらにファイルを作成し、最後に' git rebase'を実行して問題を表示します) – torek

+0

確かに、本当に素早く書く。 – maxbart

+0

このスクリプトをコピーして貼り付けることができます。http://pastebin.com/mPcmGCT5 edit:ディレクトリを作成するためのスクリプトを更新しました(testing_gitを作成します) – maxbart

答えて

4

うわー、これは厳しいものでした! :-)

スクリプトを使用して、問題を再現しました。スクリプトがあり、それすべてについて非常に奇妙な何かはしかしだったので、最初、私はこれを残して、リベース段階をトリミング(若干変更さ):

#!/bin/sh 
set -e 
if [ -d testing_git ]; then 
    echo test dir testing_git already exists - halting 
    exit 1 
fi 

mkdir testing_git 
cd testing_git 

git init 
touch main.txt 
git add . 
git commit -m "initial commit" 

# setup B branch 
git checkout -b B 
echo hello > test.txt 
git add . 
git commit -m "added test.txt" 

# setup master 
git checkout master 
echo hello > test.txt 
git add . 
git commit -m "added test.txt" 
rm test.txt 
git add . 
git commit -m "remove test.txt" 

かつて私はこれを取得し、コミットを検査、実行します。

$ git log --graph --decorate | sed 's/@/ /' 
* commit 249e4893ea7458f45fe5cdc496ddc0292a3f03ef (HEAD -> master) 
| Author: Chris Torek <chris.torek gmail.com> 
| Date: Thu May 5 20:28:02 2016 -0700 
| 
|  remove test.txt 
| 
* commit a132dc9e3939b5338f7c784c58da9c83f4902c8d (B) 
| Author: Chris Torek <chris.torek gmail.com> 
| Date: Thu May 5 20:28:02 2016 -0700 
| 
|  added test.txt 
| 
* commit 81c4d9be82094fdb4c88ed0a53bdbd5c3dfd7a5a 
    Author: Chris Torek <chris.torek gmail.com> 
    Date: Thu May 5 20:28:02 2016 -0700 

     initial commit 

masterの親コミットはブランチBのコミットであり、4つではなく3つのコミットがあることに注意してください。スクリプトが4つのコマンドを実行すると、どのようになりますかgit commitコマンド?

は今

[edit] 
$ sh testrebase.sh 
[snip output] 
$ cd testing_git && git log --oneline --decorate --graph --all 
* cddbff1 (HEAD -> master) remove test.txt 
* c4ac1b2 added test.txt 
| * fefc150 (B) added test.txt 
|/ 
* 8c07bb6 initial commit 

おっと、今は4つのコミット、および適切な枝を持っている...のは、右git checkout master後、スクリプトにsleep 2を追加してみましょうし、それを再実行すると何が起こるかを参照してください!

最初のスクリプトで3回コミットしたのはなぜですか?sleep 2を追加すると4回コミットされますか?

答えはコミットのIDにあります。各コミットには一意のID(おそらく!)があり、コミットの内容のチェックサムです。ここでは、コミット-branch Bに最初の頃だったものです:

$ git cat-file -p B | sed 's/@/ /' 
tree c3cd0188a6a1490204e25547986e49b0b445dec8 
parent 81c4d9be82094fdb4c88ed0a53bdbd5c3dfd7a5a 
author Chris Torek <chris.torek gmail.com> 1462505282 -0700 
committer Chris Torek <chris.torek gmail.com> 1462505282 -0700 

added test.txt 

我々は、treeparent、2(名前、電子メール、タイムスタンプ)は、著者とコミッタのためのトリプル空白行を持っている、とログメッセージ。親は最初のマスターブランチにコミットされ、ツリーは、我々が(その内容で)test.txtを追加したときに我々が作った木です。

次に、をブランチでコミットするようになると、gitは新しいファイルから新しいツリーを作成しました。このツリーは、ビットごとの私達はちょうどブランチBにコミットしたものと同一であったので、それは同じ固有ID(覚えて、そこにそのツリーのコピーは1つだけのレポでありますので、これは正しい動作です)を得ました。そして、それは私の名前と電子メールやタイムスタンプいつものように、ログメッセージと共に新しいコミットオブジェクトを作りました。しかし、このコミットはちょうどブランチBで行ったコミットと少し似ていたので、前と同じIDを得て、ブランチmasterがそのコミットを指し示すようにしました。言い換えれば

、我々はをコミット再使用。 (masterBと同じコミットを指摘するように)私達はちょうど別の枝にそれを作りました。

sleep 2を追加すると、タイムスタンプが新しいコミットに変更されました。今(Bmasterで)2つのコミットは、もはやビットごとに同一ではない:

$ git cat-file -p B | sed 's/@/ /' > bx 
$ git cat-file -p master^ | sed 's/@/ /' > mx 
$ diff bx mx 
3,4c3,4 
< author Chris Torek <chris.torek gmail.com> 1462505765 -0700 
< committer Chris Torek <chris.torek gmail.com> 1462505765 -0700 
--- 
> author Chris Torek <chris.torek gmail.com> 1462505767 -0700 
> committer Chris Torek <chris.torek gmail.com> 1462505767 -0700 

異なるタイムスタンプ=異なるコミット=はるかに賢明なセットアップ。

実際にリベースを実行しても、ファイルは削除されました。

これは仕様であることが判明しました。 git rebaseを実行すると、セットアップコードはチェリーピッキングのすべてのコミットを単に一覧表示するのではなく、代わりにを使用して削除する必要があるコミットを見つけます。

ことがtest.txtを追加コミットを上流にあるので、Gitはちょうどそれを完全に削除します。ここでの仮定は、あなたが誰かに上流にそれを送った、彼らはすでにそれを取ったことで、再びそれを取る必要はありません。

はさんが再びを再生スクリプトを変更してみましょう - そして、私たちはアップのでmasterへの変更は異なり、--cherry-pick --right-onlyを経由して、リストから削除されないこと、物事をスピードアップ、この時間をsleep 2を取ることができるようになります。我々はまだ同じ一行でtest.txtが追加されますが、我々はまた、その中にmain.txtコミットを変更します:

# setup master 
git checkout master 
echo hello > test.txt 
echo and also slight difference >> main.txt 
git add . 
git commit -m "added test.txt" 

我々は先に行くとだけでなく、最終的なgit checkout Bgit rebase masterラインをオンにし、そして今回ができ、作品をリベース

$ git log --oneline --decorate --graph --all 
* c31b13a (HEAD -> B) added test.txt 
* da2ca52 (master) remove test.txt 
* 6972019 added test.txt 
* 0f0d2e8 initial commit 
$ ls 
main.txt test.txt 

このリベースはこれを実現しませんでした。それは私が期待したものではありません(他の答えが指摘されているにもかかわらず、はと書かれています)、それは「rebaseはちょうどチェリーピックを繰り返している」ということは間違いではないことを意味します。コミットを破棄するケース。


実際に、非対話型リベースするためには、この驚くべきビットを使用:

git format-patch -k --stdout --full-index --cherry-pick --right-only \ 
--src-prefix=a/ --dst-prefix=b/ --no-renames --no-cover-letter \ 
"$revisions" ${restrict_revision+^$restrict_revision} \ 
>"$GIT_DIR/rebased-patches" 

$revisionsmaster...Bに、この場合には、膨張あります。

--cherry-pick --right-onlyのオプションは、git format-patchには記載されていません。それらのためにgit rev-listのドキュメントを見ることを知っている必要があります。

対話型リベースは、異なる手法を使用しますが、まだアップストリームにあるコミットを選択します。 rebaserebase -iに変更した場合、リベース命令は、pick行の代わりにnoop行で構成されています。

2

は言う:HEADにコミットと同じテキストの変更を導入HEADでのコミットが... <上流>省略されている(つまり、すでに別の上流に受け入れパッチが

注意をコミットしていることメッセージまたはタイムスタンプはスキップされます)。

あなたのケースでは、B1A2と同じ変更を導入しています。そのため、rebaseを実行すると、B1は、<上流>に既にそのパッチがあるため、リベースプロセスから除外されます。対話型リベースを行うには、-iオプションを追加できます。それはあなたが見ることを可能にする、そのB1はrebaseプロセスのtodoリストに記載されていません。ただし、インタラクティブ・リベースのtodoリストにpick B1を追加することで、そのコミットを手動で選択できます。

関連する問題