Как подразумевается (но не указано) в комментариях, вам понадобится какой-то принудительный толчок. Причина не так уж сложна, но очень помогает нарисовать причину, а не говорить о ней. Проблема здесь, если выразиться забавно, заключается в том, что у вас есть три разных репозитория, все с именем Брюс, один новый коммит с именем Брюс и один переписанный этот новый коммит с именем Брюс. Теперь вам нужно сказать Брюсу забыть о Брюсе, потому что у Брюса есть новый Брюс, а Брюсу нужно соответствовать Брюсу.
(Вышеупомянутое немного излишне, но см. также предложение было было. )
Вместо того, чтобы называть всех и вся Брюсом или пытаться дать точный хэш-идентификатор каждой фиксации, что вызывает аналогичные (хотя и разные) проблемы, потому что люди плохо разбираются в хэш-идентификаторах, преобразуя каждый из них в Брюс- как писк в их головах — давайте нарисуем каждый репозиторий как серию коммитов с коммитами, обозначенными одной заглавной буквой. Начнем с того, что вы сначала назвали 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
текущей ветвью em> и зафиксируйте 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
, и они сказали ОК strong>, поэтому 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
git push origin HEAD:master
Вопрос: почему я получаю сообщение об ошибке, когда разрешаю конфликт перед отправкой? - person user7331530   schedule 05.05.2021