Git rebase с разветвленным пультом

Существует основной репозиторий git; основное репо; основная ветка - my-remote.
Я добавил ее в свой профиль: main-repo; основная ветвь - источник

Добавлена ​​фиксация на удаленный сервер после разветвления.
Добавлена ​​фиксация на источник после разветвления. Итак, теперь это одна фиксация впереди и одна фиксация позади.

Цель: перебазировать my-remote в исходное положение. Таким образом, источник должен сначала зафиксироваться с моего пульта, а затем с источника. Я понимаю, что это создаст новые коммиты (SHA).

Я клонирую свой профиль.
git clone url2. - источник

Добавлен удаленный репозиторий как еще один удаленный.
git remote add my-remote gitRepoToRemote

Я нахожусь в своей локальной главной ветке, которая отслеживает происхождение.
Пытаюсь перебазировать.
git fetch --all.
git rebase my-remote/master.

Возникли конфликты.
Разрешено вручную.
затем git add file-with-conflict.
продолжить с перебазированием git rebase --continue.
git push origin HEAD:master.
Получение ошибки. Почему я получаю эту ошибку, когда разрешаю конфликт перед отправкой?

 ! [rejected]        master -> master (non-fast-forward)
error: failed to push some refs to 'gitRepoToOrigin'
hint: Updates were rejected because the tip of your current branch is behind
hint: its remote counterpart. Integrate the remote changes (e.g.
hint: 'git pull ...') before pushing again.
hint: See the 'Note about fast-forwards' in 'git push --help' for details

person user7331530    schedule 04.05.2021    source источник
comment
@matt обновил вопрос с git push origin HEAD:master Вопрос: почему я получаю сообщение об ошибке, когда разрешаю конфликт перед отправкой?   -  person user7331530    schedule 05.05.2021
comment
Вы перемещаете локальную ветку. Вы не можете просто нажать, поскольку вы изменили историю. либо тянуть, либо силой толкать. Знайте, что принудительное нажатие может вызвать проблемы у других пользователей ветки.   -  person evolutionxbox    schedule 05.05.2021
comment
Репозитории находятся в том состоянии, о котором я упоминал (до цели в вопросе). Как применить фиксацию от my-remote к источнику? В начале я сначала хочу фиксацию моего удаленного компьютера, а затем фиксацию источника.   -  person user7331530    schedule 05.05.2021


Ответы (1)


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

(Вышеупомянутое немного излишне, но см. также предложение было было. )

Вместо того, чтобы называть всех и вся Брюсом или пытаться дать точный хэш-идентификатор каждой фиксации, что вызывает аналогичные (хотя и разные) проблемы, потому что люди плохо разбираются в хэш-идентификаторах, преобразуя каждый из них в Брюс- как писк в их головах — давайте нарисуем каждый репозиторий как серию коммитов с коммитами, обозначенными одной заглавной буквой. Начнем с того, что вы сначала назвали main-repo, а затем назвали my-remote позже, когда использовали git remote add:

my-remote:

...--F--G--H   <-- master

имя master существует в этом репозитории. Это имя хранит хэш-идентификатор коммита H (какой-то большой уродливый хэш-идентификатор). Коммит H, справа, самый новый. Это содержит:

  • полный снимок каждого файла и
  • метаданные, включающие хэш-идентификатор предыдущей фиксации G

так что коммит H ссылается на более ранний коммит G. Более ранняя фиксация G имеет моментальный снимок каждого файла (из более раннего), конечно, плюс хэш-идентификатор еще более ранней фиксации F. Это повторяется до самого начала времени: так Git хранит коммиты.

Затем вы использовали кнопку GitHub fork, чтобы создать копию этого репозитория, которую мы назовем origin. Эта копия имеет свои собственные имена веток, но совмещает фактические коммиты с исходным репозиторием.1 В любом случае, в этой копии на GitHub, имя master содержит тот же хэш-идентификатор:

origin:

...--F--G--H   <-- master

Затем вы клонировали ответвление GitHub на свой собственный компьютер (мы назовем этот ноутбук, даже если это настольный или стационарный компьютер, просто чтобы дать ему запоминающееся, но отчетливое имя). Это скопировало коммиты, но копии идентичны оригиналам — они должны быть такими; никакая часть любого коммита не может быть изменена после его создания, а затем скопированы их имена ветки в имена удаленного отслеживания, так что у вас было:

laptop:

...--F--G--H   <-- origin/master

Затем ваша команда git clone создала новую ветку master в репозитории вашего ноутбука на основе вашего аргумента -b master или отсутствия какого-либо аргумента -b, а также рекомендации GitHub, чтобы ваш Git использовал имя master здесь:

laptop:

...--F--G--H   <-- master, origin/master

и, наконец, на вашем ноутбуке ваш Git запустил git checkout master для заполнения рабочего дерева (и индекса Git, хотя мы не будем рассматривать это здесь) из коммита H, сделав master текущей ветвью и зафиксируйте H текущую фиксацию laptop:

laptop:

...--F--G--H   <-- master (HEAD), origin/master

1Неважно, делятся ли необработанные базовые коммиты или копируют их. Дело в том, что коммиты на 100%, бит в бит идентичны. Это означает, что если и my-remote, и origin хранятся на одной и той же физической машине, они могут совместно использовать базовые коммиты. Если они хранятся на отдельных физических машинах с отдельными системами постоянной памяти (вращающиеся диски, флэш-память и т. д.), им нужно будет использовать копии необработанных байтов фиксации, а не буквально совместно использовать базовое хранилище, но нам все равно. об этом.


Резюме на данный момент

На данный момент (некоторое время назад) содержимое всех трех репозиториев выглядит очень похоже. Главное — и заметное — отличие репозитория вашего ноутбука в том, что у вас есть origin/master и рабочая копия коммита H. Помимо этих различий (важных для выполнения работы, но в остальном не имеющих значения), все три выглядят следующим образом:

...--F--G--H   <-- master

Все меняется со временем

Затем вы сделали новую фиксацию на своем ноутбуке. Мы назовем этот коммит I, следующая буква после H. Итак, на ноутбуке у нас теперь есть:

laptop:

...--F--G--H   <-- origin/master
            \
             I   <-- master (HEAD)

Тем временем кто-то отправил новый коммит на master на GitHub для репозитория my-remote. Обычно я бы назвал эту фиксацию J, но по непонятной причине здесь я назову ее K. Итак, у у них есть вот это, что я тоже нарисую странно без всякой видимой причины:

my-remote:
             K   <-- master
            /
...--F--G--H

Обратите внимание, что репозиторий с именем origin еще совсем не изменился.

В этот момент вы запускаете git push на своем ноутбуке. Не слишком беспокоясь о том, какие именно аргументы вы передаете в git push (если есть), в итоге будет отправлена ​​единственная фиксация, которая есть у вашего ноутбука, а origin нет, в origin. Итак, теперь, на origin, у вас есть:

origin:

...--F--G--H
            \
             I   <-- master

Когда этот толчок завершится успешно, ваш ноутбук обновит свой файл origin/master. Это потому, что ваш git push заставил ваш ноутбук позвонить GitHub через Интернет и попросить GitHub подключиться к репозиторию origin (что сработало). Ваш ноутбук Git отправил данные, из которых состоит фиксация I, затем попросил GitHub установить их master, и они сказали ОК, поэтому Git вашего ноутбука знает, что origin Git имеет master, указывающее на коммит I. Итак, теперь на laptop у вас есть:

laptop:

...--F--G--H
            \
             I   <-- master (HEAD), origin/master

Нам не нужны все эти маленькие изломы на графиках, которые мы рисуем здесь, но я использую их намеренно. Обратите внимание, что ни origin, ни laptop пока ничего не знают о коммите K! Точно так же my-remote никогда не слышал о коммите I.

Веселье (?) начинается

В этот момент вы начинаете делать что-то необычное:

Добавлено удаленное репо как еще одно удаленное. git remote добавить мой-удаленный gitRepoToRemote

Ваш laptop Git теперь знает, что другой Git с именем my-remote существует. Он еще не подключен к нему.

git fetch --all

Большинство людей неправильно используют --all, но здесь вы использовали его правильно (поздравляем! ????). Параметр --all указывает Git запускать git fetch для каждого удаленного компьютера.

Это было немного излишним, потому что ваш Git вызывал как origin , так и my-remote, чтобы увидеть, что нового в этих двух репозиториях. В origin не было ничего нового и не могло быть новым, потому что вы были бы единственным, кто обновлял бы его. Но это нормально! Однако, когда ваш Git вызвал Git с именем my-remote, что-то было новым: у них была фиксация K, а у вас нет.

Таким образом, ваш Git получил коммит K от my-remote и поместил его в ваш репозиторий. Затем ваш Git создал (поскольку это был первый контакт с my-remote) имя для удаленного отслеживания my-remote/origin. Итак, теперь у laptop есть:

laptop:
             K   <-- my-remote/master
            /
...--F--G--H
            \
             I   <-- master (HEAD), origin/master

(и теперь вы можете понять, почему я сохранил излом на графике).

Вы можете сказать своему Git, чтобы он сообщил origin Git о коммите K (отправив копию K в origin в процессе2‹/sup). Но, как вы можете видеть на этом рисунке того, как все выглядит на laptop, в origin будет проблема: имя master может указывать только на один коммит. Вы можете выбрать либо фиксацию I или фиксацию K, но не оба варианта. Чтобы указать на оба коммита — чтобы вы могли найти I и K — вам нужно как минимум два имени.

Ваш репозиторий на laptop имеет необходимые имена: my-remote/master указывает на K, а master указывает на I. origin/master также указывает на I, так что теперь вы можете использовать свою master точку где угодно: чтобы найти коммит K, вы можете использовать имя my-remote/master, а чтобы найти I, вы можете использовать имя origin/master.


2Поскольку origin является ответвлением my-remote, origin теоретически может видеть фиксацию K более непосредственно. Теоретически GitHub может организовать это. Если, как и когда они это сделают, зависит от них: вашему Git не нужно ничего об этом знать. Если, как и когда GitHub когда-либо добавит веб-механизм вместо этого несколько окольного метода извлечения и повторной отправки, чтобы ввести фиксацию K в origin, также вплоть до GitHub. Однако с сегодняшнего дня вы должны использовать обходной метод.


Перебазировать

Команду rebase можно обобщить следующим образом: У меня есть несколько коммитов, которые мне больше всего нравятся, но в этих коммитах есть что-то, что мне не нравится. Я знаю, что не могу ничего изменить в каком-либо существующем коммите, поэтому вместо этого я хотел бы скопировать существующие коммиты в новые и улучшенные, в то, что мне не нравится, изменено, а то, что мне нравится, сохранено.

Давайте еще раз посмотрим, что у нас есть на laptop на данный момент:

laptop:
             K   <-- my-remote/master
            /
...--F--G--H
            \
             I   <-- master (HEAD), origin/master

Что нам не нравится в коммите I, так это две вещи:

  • у него нет обновлений от коммита K, и
  • это происходит после фиксации H, а не после фиксации K.

Что нам делает в нем нравится:

  • он имеет некоторые изменения по сравнению с коммитом H, и
  • у него есть тщательно продуманное, восхитительно подробное сообщение о коммите, объясняющее, почему внесены эти изменения.

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

Не слишком беспокоясь о том, как это проходит — иногда это становится грязным:

Получил конфликты. Решается вручную. затем git добавить файл с конфликтом. продолжить с перебазированием git rebase --continue.

но похоже, что вы все сделали правильно — конечным результатом является новая фиксация:

laptop:
               J   <-- master (HEAD)
              /
             K   <-- my-remote/master
            /
...--F--G--H
            \
             I   <-- origin/master

Этот новый коммит, J, следует за существующим коммитом K и вносит правильные изменения (измененные по мере необходимости из-за уже разрешенных конфликтов) и содержит правильный текст сообщения фиксации. Таким образом, нам нужен новый коммит J. Он полностью устаревает старый коммит I.

Откуда мы знаем, что J заменяет I и что I больше не используется? Единственный ответ здесь заключается в том, что мы это знаем, потому что мы это сделали. Git не знает и не заботится. Только мы знаем и заботимся. Наш Git на laptop имеет master, указывающий на коммит J сейчас. Коммит I был заброшен, за исключением того факта, что наш origin/master все еще может его найти.

Теперь мы хотим сказать origin сделать то же самое

Теперь мы можем запустить:

git push origin master

or:

git push origin HEAD:master

чтобы наш Git вызывал GitHub, подключался к репозиторию origin и отправлял им новый коммит J. Эта часть работает, но затем мы попросим их установить master, чтобы они указывали на фиксацию J, а они отказались. Они говорят «нет» конкретно с ошибкой, которая гласит: Если я это сделаю, я откажусь от некоторых существующих коммитов:

 ! [rejected]        master -> master (non-fast-forward)

Это сообщение non-fast-forward означает, что я потеряю часть коммитов из моего master.

Коммит, который они потеряют, в данном случае — это коммит I. Коммит I устарел из-за фиксации J. Но они этого не знают. Мы должны заставить их отказаться от коммита I.

Мы делаем это, используя один из различных вариантов форсирования (скорее подходящий для Дня Звездных войн):

git push --force origin master

or:

git push --force-with-lease origin master

Между ними есть разница, но в данном конкретном случае (origin — это ваша вилка, управляемая вами, а не навязанная кем-то еще) это не имеет большого значения. Оба они работают как обычные git push, за исключением того, что, в конце концов, вместо отправки вежливого запроса: пожалуйста, если все в порядке и ничего не теряется, обновите свой master, чтобы он запомнил коммит J сейчас, это своего рода push заканчивается командой в форме обновить master, чтобы указать на фиксацию J! Git на GitHub выполнит эту команду и оставит в репозитории origin это :

origin:
               J   <-- master
              /
             K
            /
...--F--G--H
            \
             I   ???

В конце концов Git на GitHub — это может занять день, неделю или больше — запустит git gc и удалит коммит I, и теперь мы должны начать рисовать его репозиторий как:

origin:

...--F--G--H--K--J   <-- master

упростить его. Мы также можем начать делать это с вашим ноутбуком, за исключением того, что нам нужно, чтобы my-remote/master указывало на K, поэтому мы получаем:

laptop:
                J   <-- master (HEAD), origin/master
               /
...--F--G--H--K   <-- my-remote/master

Git вашего ноутбука на самом деле зависает, чтобы зафиксировать I в течение довольно долгого времени — не менее 30 дней по умолчанию — прежде чем выбросить его, но, поскольку он не виден (в выводе git log), нам не нужно беспокоиться рисование его в, в этот момент.

person torek    schedule 05.05.2021