git pull --rebase потерял коммиты после git push --force коллеги
Я думал, что понял, как работает git pull --rebase, но этот пример меня смущает. Я бы предположил, что следующие два сценария дадут одинаковые результаты, но они отличаются.
во-первых, тот, который работает.
# Dan and Brian start out at the same spot:
dan$ git rev-parse HEAD
067ab5e29670208e654c7cb00abf3de40ddcc556
brian$ git rev-parse HEAD
067ab5e29670208e654c7cb00abf3de40ddcc556
# Separately, each makes a commit (different files, no conflict)
dan$ echo 'bagels' >> favorite_foods.txt
dan$ git commit -am "Add bagels to favorite_foods.txt"
brian$ echo 'root beer' >> favorite_beverages.txt
brian$ git commit -am "I love root beer"
# Brian pushes first, then Dan runs `git pull --rebase`
brian$ git push
dan$ git pull --rebase
dan$ git log
commit 9e1140410af8f2c06f0188f2da16335ff3a6d04c
Author: Daniel
Date: Wed Mar 1 09:31:41 2017 -0600
Add bagels to favorite_foods.txt
commit 2f25b9a25923bc608b7fba3b4e66de9e97738763
Author: Brian
Date: Wed Mar 1 09:47:09 2017 -0600
I love root beer
commit 067ab5e29670208e654c7cb00abf3de40ddcc556
Author: Brian
Date: Wed Mar 1 09:27:09 2017 -0600
Shared history
так что все в порядке. В другом сценарии представьте, что Дэн толкнул, а затем Брайан (грубо)толкнул-принудил его совершить. Теперь, когда Дэн бежит git pull --rebase
, его фиксации нет.
# Dan and Brian start out at the same spot:
dan$ git rev-parse HEAD
067ab5e29670208e654c7cb00abf3de40ddcc556
brian$ git rev-parse HEAD
067ab5e29670208e654c7cb00abf3de40ddcc556
# Separately, each makes a commit (different files, no conflict)
dan$ echo 'bagels' >> favorite_foods.txt
dan$ git commit -am "Add bagels to favorite_foods.txt"
brian$ echo 'root beer' >> favorite_beverages.txt
brian$ git commit -am "I love root beer"
# THIS TIME, Dan pushes first, then Brian force pushes.
dan$ git push
brian$ git push --force
dan$ git pull --rebase
dan$ git log # Notice, Dan's commit is gone!
commit 2f25b9a25923bc608b7fba3b4e66de9e97738763
Author: Brian
Date: Wed Mar 1 09:47:09 2017 -0600
I love root beer
commit 067ab5e29670208e654c7cb00abf3de40ddcc556
Author: Brian
Date: Wed Mar 1 09:27:09 2017 -0600
Shared history
происхождение версия ветви имела такое же состояние после Брайана push --force
как и в первом сценарии, поэтому я ожидал такого же поведения от git pull --rebase
. Я смущен, почему Dan ' commit был потерян.
Я понимаю, что pull -- rebase говорит: "возьмите мои локальные изменения и примените их после удаленных". Я не ожидаю, что местные изменения будут отброшены. Кроме того, если бы Дэн убежал ...--5--> (без --rebase
), его фиксация не потеряна.
так почему же Дэн теряет свою локальную фиксацию, когда он бежит git pull --rebase
? Сила толчка имеет смысл для меня, но разве это не должно оставить пульт в том же состоянии, как если бы Брайан толкнул первым?
как я могу думать об этом неправильно?
4 ответов
TL; DR: это код точки вилки
вы получаете эффект git rebase --fork-point
, который намеренно сбрасывает dan's commit из код репозитории тоже. См. также git rebase-commit выберите в режиме fork-point (хотя в моем ответе там я не упоминаю что-то я здесь).
если вы запустите git rebase
себя,выбрать ли это. The это когда:
- запустить
git rebase
С это root commit, что является просто причудливым способом сказать "commit без родителей". Мы также можем иметь обязательства с двумя или более родителями; это коммитов слияния. (Впрочем, здесь я ничего не нарисовал. Часто неразумно перебазировать цепочки ветвей, содержащие слияния, поскольку буквально невозможно перебазировать слияние иgit rebase
есть повторновыполнить слияние, чтобы приблизить его. Обычноgit rebase
просто опускает сливается полностью, что вызывает другие проблемы.)
3очевидно, к Принципу Дирихле, любой хэш, который уменьшает более длинную бит-строку до фиксированной длины k-битный ключ обязательно должен иметь столкновения на некоторых входах. Ключевым требованием для хэш-функции Git является то, что она избегает случайных столкновений. В "криптографии" часть не очень важна для Git, это просто затрудняет (но, конечно, не невозможно) для кого-то сознательно причиной столкновения. Коллизии приводят к тому, что Git не может добавлять новые объекты, поэтому они плохи, но-помимо ошибок в реализации-они фактически не нарушают git сам, просто дальнейшее использование Git для ваших собственных данных.
определение того, что копировать
одна проблема с перебазированием заключается в идентификация , которые обязуется копировать.
большую часть времени это кажется достаточно простым: вы хотите, чтобы git скопировал код совершает, а не чужой. Но это не всегда так-в больших распределенных средах, с администраторами и менеджерами и так далее, иногда кому-то подходит перебазировать чьи-то коммиты. В любом случае, это не как Git делает это в первую очередь. Вместо этого Git использует график.
именование a совершить, например, пишет
branch
- имеет тенденцию выбирать не только это commit, но также и родительская фиксация этой фиксации, родительская родительская и так далее, вплоть до корневой фиксации. (Если есть фиксация слияния, мы обычно выбираем все своих родительских коммитов и следовать за всеми из них назад к корню одновременно. Граф может иметь более одного корня, поэтому это позволяет нам выбирать несколько нитей, возвращающихся к нескольким корням, а также нити ветвей и слияния возвращаясь к одному корню.) Мы называем множество всех коммитов, которые мы находим, начиная с одного коммита и выполняя эти родительские обходы,набор достижимых коммитов.для многих целей, в том числе
git rebase
, нам нужно сделать этот массовый выбор остановка, и для этого мы используем операции git fancy set. Если мы напишемmaster..branch
в качестве селектора ревизий это означает: "все коммиты доступны из кончика филиала, за исключением любых коммитов, доступных из кончика мастер.- Взгляните еще раз на этот график.--169-->A--B--C--D <-- master \ E--F--G <-- branch
коммиты достижимые из
branch
areG
,F
,E
,B
иA
. Коммиты, доступные изmaster
areD
,C
,B
иA
. Так чтоmaster..branch
означает: вычесть набор A+B+C+D из большего набора A+B+E+F+G.в вычитании набора, удаляя то, чего никогда не было в первое место тривиально: вы просто ничего не делаете. Поэтому мы удаляем A+B из второго набора, оставляя E+F+G. альтернативно, мне нравится использовать метод, который я не могу нарисовать на StackOverflow: цвет коммитов красный (стоп) и зеленый( go), следуя всем обратным стрелкам на графике, начиная с Красного для коммитов, которые запрещены (
master
) и зеленый для фиксации (branch
). Просто убедитесь, что красный перезаписывает зеленый, или что вы сначала делаете красный и не перекрашиваете их, когда вы делаю зеленый. Это интуитивно очевидно4 это делает зеленый сначала, затем перезаписывает красным; или делает красный сначала и не перезаписи, дает тот же результат.в любом случае, это один из способов
git rebase
выбирает фиксации для копирования. Документация rebase называет это<upstream>
. Ты пишешь:git checkout branch; git rebase master
и Git знает цвет
master
фиксирует красный и текущий-ветвьbranch
фиксирует зеленый, а затем копирует только зеленые. Более того, то же самое имя-master
-говоритgit rebase
куда положить копии. Это довольно элегантно и эффективно: один аргумент,master
, предписывает как как скопировать и куда положить копии.проблема в том, что не всегда работает.
Существует несколько распространенных случаев, когда он ломается. Один происходит, когда вы хотите ограничить копии еще больше, например, разбить одну большую ветвь на две меньшие. Другой происходит, когда некоторые, но не все, ваши собственные коммиты уже скопированы (вишневый или сквош-"объединены") в другую ветвь, на которую вы перебазируетесь. Более редкий, но не неслыханный, иногда восходящий поток сознательно отбросил некоторые коммиты, и Вы тоже должны.
для некоторых из этих случаев
git rebase
может иметь дело с ними, используяgit patch-id
: он может фактически сказать, что коммит был скопирован, если два коммита имеют один и тот же идентификатор исправления. Для других, вы должны вручную разделить перебазировать цель (rebase называет это<newbase>
) с помощью--onto
флаг:git rebase --onto <newbase> <upstream>
который ограничивает коммиты для копирования в
<upstream>..HEAD
, при запуске копий после<target>
. Это-разделение таргетинга копий от<upstream>
аргумент-означает, что теперь вы можете выбрать любой<upstream>
что удаляет право фиксирует, а не некоторый набор, определенный "где копии идти."
4это фраза, которую математики используют, когда они не хотят писать доказательство. :-)
на
--fork-point
опцииесли вы регулярно совершает перебазироваться на
origin/whatever
филиал (или аналогичный), то есть сам,и регулярно перезагружается специально с целью удаление commits, может быть трудно решить, какие коммиты копировать. Но если у вас есть, в вашемorigin/whatever
reflog, серия хэшей фиксации, которые показывают, что некоторые коммиты были там раньше, но больше нет, для Git можно использовать это, чтобы отбросить из набора для копирования некоторые или все эти коммиты.я не был полностью уверен, как
--fork-point
реализован внутренне (это не очень хорошо документировано). Для ответ, я сделал тестовый репозиторий. Неудивительно, что этот вариант зависел от порядка:git merge-base --fork-point origin/master topic
возвращает отличный результат отgit merge-base --fork-point topic origin/master
.для этого ответа я посмотрел на исходный код. Это показывает, что ГИТ смотрит в reflog из первый аргумент non-option-назовите это
arg1
- а затем использует его, чтобы найти базу слияния с помощью далее такой аргументarg2
разрешен для идентификатора фиксации, полностью игнорируя любые дополнительные аргументы. Исходя из этого, результатgit merge-base --fork-point $arg1 $arg2
по сути5 выход из:git merge-base $arg2 $(git log -g --format=%H $arg1)
в более общем плане, среди двух коммитов для вычисления базы слияния из одного указывается первый аргумент commit в командной строке; другой коммит является (возможно, гипотетическим) коммитом, который является слиянием всех остальных коммитов в командной строке.
как следствие объединить базы не обязательно содержится в каждом из аргументы commit, если указано более двух коммитов. Это отличается от git-show-branch (1) при использовании .
так
--fork-point
пытается найти базу слияния между текущим хэшем для второго аргумента и гипотетической базой слияния текущего значения восходящего и все reflog-записанные значения. Это то, что приводит к исключению отброшенного коммита, такого как тот, который дан в нашем вот пример.помните, используя
--fork-point
режим просто изменяет, внутренне, доgit rebase
(без изменения его--onto
target). Предположим, например, что в свое время вверх по течению было:...--o--B1--B2--B3--C--D <-- upstream \ E--F--G <-- branch
цель, так сказать,
--fork-point
для обнаружения перезаписи этой формы:...--o-------------C--D <-- upstream \ B1--B2--B3 <-- upstream@{1} \ E--F--G <-- branch
и" нокаут " совершает
B1
,B2
иB3
опцииB3
как внутренний оставить на--fork-point
опция, Git рассматривает все таким образом:...--o-------------C--D <-- upstream \ B1--B2--B3--E--F--G <-- branch
так что все
B
коммиты являются "нашими". Коммиты на восходящей ветвиD
,C
иC
С родителяo
(и его родители, по корню).в нашем конкретном случае фиксация Дэна-та, которая отбрасывается-напоминает одну из этих
B
commits. Он упал с--fork-point
и хранил с--no-fork-point
.
5если бы, был один использовать
--all
, полученная несколько объединить базы, команда терпит неудачу и ничего не печатает. Если в результате (в единственном числе) слить базу-это не коммит, который уже в reflog, команда тоже не получается. Первый случай происходит с перекрестными слияниями в качестве ближайшего предка. Второй случай возникает, когда некоторые предок достаточно стар, чтобы истекли из reflog, или никогда не был в нем во-первых (я уверен, что оба варианта возможны). У меня есть пример этого второго вида неудачи прямо здесь:$ arg1=origin/next $ arg2=stash-exp $ git merge-base --all $arg2 $(git log -g --format=%H $arg1) 3313b78c145ba9212272b5318c111cde12bfef4a $ git merge-base --fork-point $arg1 $arg2 $ echo $? 1
я думаю идея пропуск
3313b78...
вот что это один из тех "возможно гипотетических коммитов", которые я цитирую из документации, но на самом деле это было бы правильным коммитом для использования сgit rebase
, а это is используется без--fork-point
.
Git до 2.0, и заключение
в версиях Git до 2.0 (или, возможно, в конце 1.9),
git pull
это было перебазирование вычислит эту точку развилки, ноgit rebase
никогда этого не делал. Это означало, что если вы хотел такое поведение, вы had использоватьgit pull
чтобы получить его. Теперь чтоgit rebase
и--fork-point
, вы можете выбрать, когда, чтобы получить его:- добавить опцию, если вы определенно do хочу.
- использовать
--no-fork-point
или явный восходящий поток (@{u}
достаточно, если у вас есть значение по умолчанию вверх по течению), Если вы определенно не хочу.
если вы запустите
git pull
, у вас нет .6 лиgit pull origin master
(предполагая, что текущая ветвь вверх по течениюorigin/master
) подавляет вилку-указывает путьgit rebase origin/master
было бы, я не знаю: я в основном избегаюgit pull
.
6по крайней мере, на данный момент когда я просто проверил, чтобы убедиться.
оба сценария не сбивают с толку, вот как push --force
строительство.
Я не буду объяснять первый сценарий, потому что Вы тоже это понимаете. Но во втором сценарии, когда brian$ git push --force
это происходит, локальный репозиторий Брайана ничего не знает о нажатии Дэна на пульт.
так что возьмите локальную копию Брайана и замените все там в пульте, потому что это force push
.
разницу между git pull
& git pull --rebase
есть
git pull
будет сравнивать изменения origin & local. Вот почему даже у вас есть незафиксированные изменения, которые вы можете git pull
.
git pull --rebase
не будет сравнивать изменения с локальными, но он берет все из источника. Вот почему вы не можете git pull --rebase
если есть незафиксированные изменения
надеюсь, вы понимаете, что я сказал. Есть вопросы??
Да, это выглядит правильно. Брайана git push --force
собирается установить последнюю фиксацию как ту, которую он имеет в своей локальной системе. Если бы Брайан сначала вытащил последние коммиты, а затем git push --force
результаты будут одинаковыми.
вот хороший поток с дополнительной информацией:Force "git push" для перезаписи удаленных файлов
Да, ваше понимание git pull --rebase
является правильным.
это потому, что commit 9e11404
нажал на remote, а после этого commit 2f25b9a
принудительно обновить удаленный (9e11404
если сила удалена из пульта).
когда дан выполнить git pull --rebase
, git обнаружен 9e11404
и origin / branch указывает на 2f25b9a
. Но git кажется 9e11404
уже существует в remote (.git\logs\refs\remotes\origin\branch
содержание update by push 9e1140410af8f2c06f0188f2da16335ff3a6d04c
), поэтому он ничего не делает для перезагрузки. Или вы можете использовать git pull --rebase=interactive
чтобы проверить, вы найдете это показывает noop
для перебазирования. Если вы хотите вручную rebase 9e11404
в верхней части origin / branch вы можете добавить pick 9e11404
в интерактивном окне результат будет таким, каким вы ожидали.