Как мне восстановить / повторно синхронизировать после того, как кто-то нажмет на перебазирование или сброс на опубликованную ветку?

88

Мы все слышали , что один никогда не должен перебазироваться опубликовал работу, что это опасно, и т.д. Тем не менее, я не видел никаких рецептов , размещенных на том , как справиться с ситуацией в случае перебазироваться будет опубликовано.

Теперь обратите внимание, что это реально возможно только в том случае, если репозиторий клонируется только известной (и желательно небольшой) группой людей, так что тот, кто нажимает на перебазирование или сброс, может уведомить всех остальных, что им нужно будет обратить внимание в следующий раз, когда они получить (!).

Одно очевидное решение, которое я видел, будет работать, если у вас нет локальных коммитов, fooи оно будет перебазировано:

git fetch
git checkout foo
git reset --hard origin/foo

Это просто отбросит локальное состояние fooв пользу его истории в соответствии с удаленным репозиторием.

Но как поступить с ситуацией, если в этой ветке были внесены существенные локальные изменения?

Аристотель Пагальцис
источник
+1 за простой рецепт случая. Он идеально подходит для персональной синхронизации между машинами, особенно если у них разные ОС. Об этом следует упомянуть в руководстве.
Филип Окли
Идеальный рецепт персональной синхронизации есть git pull --rebase && git push. Если вы работаете masterтолько над ним , то это почти всегда будет делать то, что вам нужно, даже если вы переустановили и нажали на другом конце.
Аристотель Пагальцис
Поскольку я синхронизирую и разрабатываю между ПК и машинами Linux, я считаю, что использование новой ветки для каждой перестановки / обновления работает хорошо. Я также использую вариант git reset --hard @{upstream}теперь, когда я знаю, что магическое заклинание refspec для «забудьте, что у меня было / было, используйте то, что я получил с пульта дистанционного управления». См. Мой последний комментарий к stackoverflow.com/a/15284176/717355
Филип Окли,
С помощью Git2.0 вы сможете найти старое происхождение вашей ветки (до того, как восходящая ветка была переписана с a push -f): см. Мой ответ ниже
VonC

Ответы:

75

Восстановить синхронизацию после нажатой перезагрузки в большинстве случаев не так уж и сложно.

git checkout foo
git branch old-foo origin/foo # BEFORE fetching!!
git fetch
git rebase --onto origin/foo old-foo foo
git branch -D old-foo

Т.е. сначала вы устанавливаете закладку для того места, где изначально находилась удаленная ветвь, затем вы используете ее для воспроизведения ваших локальных коммитов с этой точки и далее на перемещенную удаленную ветку.

Перебазирование похоже на насилие: если оно не решает вашу проблему, вам просто нужно больше. ☺

Вы, конечно, можете сделать это без закладки, если вы посмотрите origin/fooидентификатор фиксации перед перебазированием и воспользуетесь им.

Таким же образом вы справляетесь с ситуацией, когда вы забыли сделать закладку перед загрузкой. Ничего не потеряно - вам просто нужно проверить рефлог удаленной ветки:

git reflog show origin/foo | awk '
    PRINT_NEXT==1 { print $1; exit }
    /fetch: forced-update/ { PRINT_NEXT=1 }'

Это напечатает идентификатор фиксации, на который origin/fooуказывалось перед самой последней выборкой, изменившей свою историю.

Тогда вы можете просто

git rebase --onto origin/foo $commit foo
Аристотель Пагальцис
источник
11
Краткое примечание: я думаю, что это довольно интуитивно понятно, но если вы плохо знаете awk ... этот однострочный файл просто просматривает вывод git reflog show origin/fooдля первой строки, говорящей «fetch: принудительное обновление»; это то, что git записывает, когда выборка заставляет удаленную ветку делать что угодно, кроме быстрой перемотки вперед. (Вы также можете просто сделать это вручную - принудительное обновление, вероятно, самое последнее.)
Cascabel
2
Это не что иное, как насилие. Насилие иногда бывает забавным
Иоло
5
@iolo Верно, ребазинг - это всегда весело.
Дэн Бечард,
1
Как и в случае с насилием, почти всегда избегайте перебазирования. Но знаю как.
Боб Стейн,
2
Что ж, не нажимайте на перебазирование там, где это затронет других.
Аристотель Пагальцис
11

Я бы сказал, что раздел восстановления из восходящей перебазировки на странице руководства git-rebase охватывает почти все это.

На самом деле это ничем не отличается от восстановления после вашей собственной перестановки - вы перемещаете одну ветку и переставляете все ветки, которые имели ее в своей истории, на ее новую позицию.

Каскабель
источник
4
Ах, так оно и есть. Но хотя теперь я понимаю, о чем он говорит, я бы не знал раньше, пока не выясню это самостоятельно. И рецепта поваренной книги нет (пожалуй, в такой документации и нет). Я также заявлю, что называть «тяжелый случай» трудным - это FUD. Я утверждаю, что переписанная история тривиально управляема в масштабе большинства внутренних разработок. Суеверное отношение к этой теме меня раздражает.
Аристотель Пагальцис
4
@Aristotle: Вы правы, это очень управляемо, учитывая, что все разработчики знают, как использовать git, и что вы можете эффективно общаться со всеми разработчиками. В идеальном мире это был бы конец истории. Но многие проекты достаточно велики, так что апстрим-ребаз действительно пугает. (А есть такие места, как мое рабочее место, где большинство разработчиков даже не слышали о перебазировании.) Я думаю, что «суеверие» - это всего лишь способ дать самый безопасный и общий совет из возможных. Никто не хочет быть виновником чьего-либо репо.
Cascabel
2
Да, я понимаю мотив. И я полностью с этим согласен. Но есть огромная разница между «не пробуйте это, если вы не понимаете последствий» и «вы никогда не должны этого делать, потому что это зло», и только с этим я не согласен. Всегда лучше наставлять, чем внушать страх.
Аристотель Пагальцис
@ Аристотель: Согласен. Я действительно стараюсь стремиться к завершению «убедитесь, что вы знаете, что делаете», но особенно в Интернете, я стараюсь придавать этому достаточно веса, чтобы случайный посетитель из Google заметил. Вы правы, многое из этого, наверное, нужно приглушить.
Cascabel
11

Начиная с мерзавцем 1,9 / 2,0 Q1 2014, вы не должны будете отмечать свой предыдущий филиал происхождения , прежде чем перебазирования его на переписанной вверх по течению ветви, как описано в Аристотель Pagaltzis «s ответ :
См совершать 07d406b и совершать d96855f :

После работы с topicветкой, созданной с помощью git checkout -b topic origin/master, история ветки удаленного отслеживания origin/masterмогла быть перемотана и перестроена, что привело к истории этой формы:

                   o---B1
                  /
  ---o---o---B2--o---o---o---B (origin/master)
          \
           B3
            \
             Derived (topic)

где origin/masterиспользуется , чтобы указать на фиксаций B3, B2, B1и теперь он указывает на B, и ваша topicветка была начата на вершине его обратно , когда origin/masterбыл B3.

В этом режиме в качестве точки разветвления используется журнал origin/masterпоиска для поиска B3, поэтому его topicможно перебазировать поверх обновленного сorigin/master помощью:

$ fork_point=$(git merge-base --fork-point origin/master topic)
$ git rebase --onto origin/master $fork_point topic

Вот почему в git merge-baseкоманде появилась новая опция:

--fork-point::

Найдите точку, в которой ветка (или любая история, к которой ведет <commit>) разветвилась из другой ветки (или любой ссылки) <ref>.
Это не просто ищет общего предка двух коммитов, но также принимает во внимание рефлог, <ref>чтобы увидеть, была ли история, ведущая к <commit>разветвлению, от более раннего воплощения ветки<ref> .


Команда « git pull --rebase» вычисляет точку разветвления ветвления, для которой выполняется перебазирование с использованием записей рефлога baseветви « » (обычно ветки удаленного отслеживания), на которой была основана работа ветки, чтобы справиться со случаем, когда «база» ветка была перемотана и перестроена.

Например, если история выглядела так, где:

  • текущая вершина baseветки " " находится в точке B, но более ранняя выборка заметила, что ее вершина была раньше, B3а затем B2и затем B1 до перехода к текущей фиксации, и
  • перебазируемая ветка поверх последней "базы" основана на фиксации B3,

он пытается найти B3, перейдя через выход « git rev-list --reflog base» (т.е. B, B1, B2, B3) до тех пор , пока не найдет фиксации , что является предком текущего наконечника « Derived (topic)».

Внутри у нас есть get_merge_bases_many()способ вычислить это за один раз.
Нам нужна база Derivedдля слияния и фиктивная фиксация слияния, результатом которой станет объединение всех исторических подсказок " base (origin/master)".
Когда такая фиксация существует, мы должны получить единственный результат, который точно соответствует одной из записей рефлога " base".


Git 2.1 (3 квартал 2014 г.) добавит, чтобы эта функция была более надежной: см. Commit 1e0dacd от John Keeping ( johnkeeping)

правильно обработать сценарий со следующей топологией:

    C --- D --- E  <- dev
   /
  B  <- master@{1}
 /
o --- B' --- C* --- D*  <- master

где:

  • B'является исправленной версией B, не идентичной патчу B;
  • C*и D*являются патч-идентичны Cи , Dсоответственно , и конфликт текстуально , если применяется в неправильном порядке;
  • Eтекстуально зависит от D.

Правильный результат в git rebase master devтом , что Bидентифицируется как вилочная точка devи master, таким образом , что C, D, Eявляются коммитами которые должны быть преобразованными на master; но Cи Dявляются патч-идентичными с C*и D*и поэтому могут быть отброшены, так что конечный результат будет:

o --- B' --- C* --- D* --- E  <- dev

Если точка разветвления не идентифицирована, то выбор Bветки, содержащей, B'приводит к конфликту, а если идентичные патчу фиксации не идентифицированы правильно, то выбор Cветки, содержащей D(или что то же самое D*), приводит к конфликту.


« --fork-point» Режим « git rebase» регресс , когда команда была переписана в С задней в 2.20 эре, которая была исправлена с Git 2,27 (Q2 2020).

См. Фиксацию f08132f (09 декабря 2019 г.) от Junio ​​C Hamano ( gitster) .
(Объединено Junio ​​C Hamano - gitster- в коммите fb4175b , 27 марта 2020 г.)

rebase: --fork-pointисправление регрессии

Подписано: Алекс Торок
[jc: обновил исправление и использовал тесты Алекса]
Подписано: Junio ​​C Hamano

" git rebase --fork-point master" работал нормально, так как он внутренне называл " git merge-base --fork-point", который знал, как обрабатывать короткое имя ссылки и сокращать его до полного имени ссылки перед вызовом базовой get_fork_point()функции.

Это больше не верно после того, как команда была переписана на C, поскольку ее внутренний вызов, сделанный напрямую get_fork_point(), не уменьшает краткую ссылку.

Переместите логику «dwim the refname to the full refname», которая используется в «git merge-base», в базовую get_fork_point()функцию, чтобы другой вызывающий функцию в реализации «git rebase» вел себя так же, чтобы исправить это регресс.

VonC
источник
1
Обратите внимание, что теперь git push --force (git 1.8.5) можно делать более осмотрительно: stackoverflow.com/a/18505634/6309
VonC