2016-05-06 7 views
1

Я пытаюсь понять, почему git rebase заставляет вновь созданный файл удаляться, если ветвь, которую я отключил, удалила ее. Например:Почему git rebase удаляет файл, добавленный в последней фиксации, если он был удален веткой rebase?

A1 - A2 - A3 
\ 
    B1 

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

Если B1 проверяется, и я исполню git rebase A3, test.txt по-прежнему удалены. Я ожидаю, что результатом будет:

A1 - A2 - A3 - B1 

Что будет означать, что test.txt все еще существует. Почему test.txt удаляется после rebase?

+1

Не должно быть, если я правильно читаю ваш вопрос. У вас есть сценарий для воспроизведения этого? (Напишите что-то, что делает пустой каталог, запускает 'git init', создает файлы и фиксирует, создает ветку, создает больше файлов и т. Д., И последнее, запускает' git rebase', чтобы показать проблему.) – torek

+0

Конечно, Я напишу один реальный быстрый. – maxbart

+0

Итак, вы можете скопировать и вставить этот скрипт http://pastebin.com/mPcmGCT5 edit: обновил скрипт, чтобы создать каталог (создает test_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, и есть только три коммитов, а не четыре. Как это может быть, когда скрипт запускает четыре команды git commit?

Теперь давайте добавим sleep 2 в сценарий, сразу после git checkout master, и повторно запустить его и посмотреть, что происходит ...

[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 

Вау, теперь у нас есть четыре фиксаций и надлежащее отделение!

Почему первый скрипт совершил три коммита, и добавив sleep 2, измените его, чтобы сделать четыре фиксации?

Ответ лежит на личности фиксации. Каждая фиксация имеет (предположительно!) Уникальный идентификатор, который является контрольной суммой содержимого фиксации.Вот что было в 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 

Мы имеем tree, то parent, два (имя, адрес электронной почты, метки времени) троек для автора и коммиттера пустую строку, а сообщение журнала. Родитель - это первая фиксация на главной ветке, а дерево - это дерево, которое мы создали, когда мы добавили test.txt (с его содержимым).

Затем, когда мы пошли, чтобы сделать второй фиксацией на ветке master, git создал новое дерево из новых файлов. Это дерево было бит-бит, идентичное тому, которое мы только что зафиксировали на ветке B, поэтому получило тот же уникальный идентификатор (помните, что в репо есть только одна копия этого дерева, так что это правильное поведение). Затем он сделал новый коммит объект с моим именем и адресом электронной почты и метками времени, как обычно, и сообщение журнала. Но эта фиксация была бит-бит-бит идентичной фиксации, которую мы только что сделали на ветке B, поэтому мы получили тот же идентификатор, что и раньше, и сделали ветку master.

Другими словами, мы повторно использовали фиксацию. Мы просто сделали это на другой ветке (так что master указал на то же сообщение, что и B).

Добавление sleep 2 изменение временная отметка на новой фиксации. Теперь два коммиты (в B и master) больше не бит-в-бит идентичен:

$ 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 

Различные временные метки = разные коммиты = гораздо более разумно установка.

На самом деле выполнение rebase, однако, все равно отбросило файл!

Оказалось, что это по дизайну. Когда вы запускаете git rebase, код установки не просто перечисляет каждую фиксацию для набора вишни, а вместо этого использует git rev-list --right-only, чтобы найти коммиты, которые должны упасть.

С фиксации, что добавляет test.txt находится в верхнем течении, Git просто отбрасывает его полностью: предположение в том, что вы послали его вверх по течению к кому-то, что они уже взяли его, и нет необходимости принимать его снова ,

Давайте изменим репродуктор скрипта снова й мы сможем вывезти sleep 2 на этот раз, ускоряя вещи, так что изменение master отличается, и не будет удалено из списка с помощью --cherry-pick --right-only.Мы все еще добавить 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 B и git 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 is just repeat cherry-pick» не совсем корректно: это повторяющийся вишневый выбор со специальными случаи сбрасывания.


На самом деле, для неинтерактивного 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" 

, где $revisions расширяется, в данном случае, к master...B.

Параметры --cherry-pick --right-only для git format-patch не задокументированы; нужно знать, чтобы посмотреть в документации git rev-list.

Интерактивная ребаза использует другую технику, но все же отбирает любые коммиты, которые уже находятся в восходящем направлении. Это отображается, если вы изменили на rebase -i тем, что инструкции по перестановке состоят из одной линии noop вместо ожидаемой отдельной строки pick.

2

Как git rebase documentation говорит:

Обратите внимание, что любые коммиты в голове, вводящие те же текстуальные изменения, как фиксация в ГОЛОВЫ .. < выше > опущены (то есть, патч уже принял вверх по течению с другой фиксации сообщение или временная метка будут пропущены).

В вашем случае B1 введите те же изменения, что и A2. Так что, когда вы делаете rebase, B1 опускается из процесса переадресации, так как <upstream> уже имеет этот патч. Вы можете добавить опцию -i, чтобы сделать интерактивную перезагрузку. Это позволяет вам видеть, что B1 не указан в списке задач переустановки. Хотя, вы можете выбрать эту фиксацию вручную, добавив pick B1 в список списка интерактивных переадресаций.