Ничего себе, это было сложно! :-)
Используя ваш сценарий, я воспроизвел проблему. Существовал что-то очень странное во всем этом, хотя, так что первый, я подрезал из шага перебазироваться, в результате чего это (слегка измененный) скрипт:
#!/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
.
Не должно быть, если я правильно читаю ваш вопрос. У вас есть сценарий для воспроизведения этого? (Напишите что-то, что делает пустой каталог, запускает 'git init', создает файлы и фиксирует, создает ветку, создает больше файлов и т. Д., И последнее, запускает' git rebase', чтобы показать проблему.) – torek
Конечно, Я напишу один реальный быстрый. – maxbart
Итак, вы можете скопировать и вставить этот скрипт http://pastebin.com/mPcmGCT5 edit: обновил скрипт, чтобы создать каталог (создает test_git) – maxbart