Как это исправить?
Это только исправимо. Обычно вы должны просто полностью удалить ветку. Затем вы можете создать новую ветку, даже такую, которая все еще называется develop
. Тогда это другая ветвь, даже если это одно и то же имя.
Эта последовательность команд может исправить это, но быть очень осторожным и убедиться, что вы понимаете каждую из них. :
git checkout master
git reset --hard develop
git push --force-with-lease origin master
Команда git reset --hard
отбросит любую незафиксированную работу, поэтому будьте особенно осторожны всякий раз, когда вы ее используете.
Я дам некоторые альтернативы ниже. Есть много вариантов с различными эффектами, особенно для любых других людей, которые могут использовать репозиторий GitHub. Если вы - единственный человек, использующий репозиторий GitHub, вы можете делать все, что захотите.
В любом случае, помните: это цель squa sh -and-merge: убить ветку . Идея состоит в том, что после этого squa sh -and-merge вы удалите имя ветви и навсегда откажетесь от всех 28 коммитов. Squa sh -and-merge сделал один новый коммит, содержащий ту же работу , что и 28 предыдущих коммитов, поэтому один новый коммит - новый и улучшенный замена для этих 28 коммитов.
Чтобы понять, что вы делаете, читайте дальше.
Чтобы действительно "получить" Git, вам нужно нарисовать графики
Я предположил (неправильно?), Что «squa sh и слияние» будет выполнять быстрое слияние для предотвращения расхождения. Я не хотел, чтобы все коммиты с develop
переместились на master
, поэтому вместо «перебазирования» я сделал «squa sh and merge». Я думаю, что я что-то упускаю в сква sh и слиянии.
Да. GitHub также выбрасывает лишнюю морщинку - или, может быть, убирает нужную морщину , в зависимости от того, как вы на нее смотрите. Сначала нам нужно немного побольше.
Git - это все о commits . Эти коммиты формируются в виде графика , в частности D прямой A cycli c G raph или DAG . Как это работает, на самом деле удивительно просто:
Каждый коммит имеет уникальный идентификатор ha sh. Этот идентификатор ha sh фактически является истинным именем коммита.
Каждый коммит состоит из двух частей: его данных - снимка всех ваших файлов - и некоторые метаданные , информация о самом коммите, например, кто его сделал (ваше имя и адрес электронной почты), когда (отметки даты и времени) и почему (ваше сообщение в журнале). Большая часть этих метаданных просто показывает вам, когда вы смотрите на коммит, но одна часть информации имеет решающее значение для самого Git: каждый коммит содержит необработанные идентификаторы ha sh его родительских коммитов.
Большинство коммитов имеют только одного родителя. Когда что-то содержит идентификатор кома ha sh, мы говорим, что это что-то указывает на коммит. Таким образом, коммит указывает на его родителя. Если у нас есть цепочка коммитов все подряд, мы можем нарисовать их следующим образом:
... <-F <-G <-H
, где заглавные буквы обозначают действительные идентификаторы ha sh. последний коммит - это коммит H
. Он указывает на своего родителя, который предшествовал ему: commit G
. Коммит G
указывает на его родителя F
и т. Д.
A имя ветви в Git - это просто имя, которое указывает на последний commit:
...--G--H <-- master
Когда мы создаем новую ветвь, мы на самом деле говорим Git: создаем новое имя, которое указывает на какой-то существующий коммит . Итак, если мы сделаем ветку develop
сейчас, мы получим это:
...--G--H <-- develop, master
Теперь нам нужно знать, какое имя мы на самом деле используем, поэтому мы прикрепляем специальное имя HEAD
с таким именем:
...--G--H <-- develop (HEAD), master
Когда мы делаем новые коммиты, мы просто Git записываем коммит - это вычисляет уникальный идентификатор ha sh ID нового коммита - со снимком и с родительским значением, установленным в current commit (H
на данный момент):
...--G--H
\
I
То, что происходит с именами теперь, также очень просто: то, к которому присоединено HEAD
, Git записывает там есть новый коммит с sh ID. Все остальные: ничего не происходит вообще. Итак, наш новый график:
...--G--H <-- master
\
I <-- develop (HEAD)
Поскольку мы добавляем больше коммитов, они просто продолжают увеличивать ветку:
...--G--H <-- master
\
I--J--K <-- develop (HEAD)
(Здесь develop
равно 3 ahead
из master
.)
Есть еще одна ключевая вещь, которую нужно понять: фиксирует G
и H
, и все те, что находятся перед master
, также включены develop
. * 1139 Другими словами, коммиты часто находятся на более чем одной ветви . (Это странная вещь в Git: многие другие системы контроля версий не работают таким образом.)
Истинное слияние и быстрая перемотка вперед
На данный момент , мы можем запустить git merge
локально в наших собственных репозиториях (но не в GitHub), используя:
git checkout master; git merge develop
Git примет короткий путь - ускоренная перемотка вперед вещь, которую вы упомянули. Мы вернемся к этому через мгновение.
В качестве альтернативы, мы можем запустить git merge --no-ff develop
, заставив Git произвести истинное слияние. Это то, что будет делать кнопка «слияние» на GitHub, поэтому давайте посмотрим на нее. «Истинное» слияние всегда делает новый коммит слияния . Истинные слияния необходимы , если две ветви имеют , например: , если у нас было:
o--o <-- branch1
/
...--o
\
o--o <-- branch2
, и мы хотели бы объединить их, мы были бы вынуждены использовать истинное слияние. Это не то, что мы имеем; у нас есть:
...--G--H <-- master (HEAD)
\
I--J--K <-- develop
(Помните, мы сейчас на master
: мы сделали git checkout master
, поэтому H
это текущий коммит. K
это последний коммит в develop
.)
GitHub просто вызывает истинное слияние в всех случаях, включая наш собственный простой. Чтобы выполнить истинное слияние, Git находит слияние с базой коммит (в данном случае это коммит H
) и сравнивает эту базу слияния дважды, один раз с текущим коммитом H
и один раз с commit K
.
Очевидно, commit H
соответствует commit H
. Таким образом, с нашей стороны нет изменений, которые мы могли бы перенести. Нам нужны только те изменения, которые они внесли, начиная с H
до K
. Результат будет таким же, как снимок в K
. Но мы форсируем слияние, поэтому Git делает новый коммит слияния, который я назову M
для merge
:
...--G--H---------M <-- master (HEAD)
\ /
I--J--K <-- develop
То, что делает M
особенным - это делает это слияние - то, что у него есть не один родитель, а два. Его первый родитель - это коммит H
, в котором мы были моментом go. Его второй родитель - commit K
. Его снимок является результатом объединения работы, которую мы проделали на H
-vs- H
- вообще не работает - с работой, которую они проделали на H
-vs- K
, поэтому снимок нового коммита слияния соответствует моментальному снимку коммита K
, но конечным результатом является этот новый коммит слияния.
Если мы разрешим Git выполнить ускоренную перемотку вперед вместо слияния вот что мы получили бы:
...--G--H
\
I--J--K <-- develop, master (HEAD)
(что позволяет нам убрать излом с рисунка, хотя я не стал беспокоиться). Git называет это слиянием в ускоренном режиме, хотя фактического слияния не происходит. К сожалению * * * * * * * * * * * * * * * * * * * * * * * *
* * * * * * * * * * * * * * * * * * * * * * * * * * * * * *} * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *} * * *}}}}}}}}}}}} выполнив истинное слияние, мы получили бы этот график:
...--G--H---------M <-- master (HEAD)
\ /
I--J--K <-- develop
Commit M
будет иметь тот же моментальный снимок, что и commit K
, и будет иметь двух родителей, H
и K
.
При выполнении сква sh слияния мы получаем вместо этого график:
...--G--H---------S <-- master (HEAD)
\
I--J--K <-- develop
Commit S
имеет тот же моментальный снимок, что и commit K
, но имеет только одного родителя, H
, Идея в том, что вам нужен один обычный коммит, который будет иметь тот же эффект, что и все коммиты, которые вы сделали на develop
. Этот единственный коммит - новый и улучшенный способ сделать работу. Старый способ - старые I-J-K
коммиты - больше не нужны и должны быть оставлены; в конце концов, через 30 или более дней ваш Git удалит их по-настоящему. Таким образом, вы просто запускаете принудительное удаление name develop
, производя:
...--G--H---------S <-- master (HEAD)
\
I--J--K [abandoned]
Если хотите, вы можете создать name develop
снова, но на этот раз указывая на коммит S
:
...--G--H--S <-- develop, master (HEAD)
\
I--J--K [abandoned]
Цель имен веток - позволить вам (и Git) найти коммиты
Обратите внимание, что при манипулировании График для добавления коммитов, мы перемещаем имена веток. Имена ветвей всегда указывают на последний коммит. Вот как Git определяет вещи для работы.
Однако мы можем заставить имя ветви перемещаться куда угодно. Если мы извлекли конкретную ветку, как в этом состоянии:
...--G--H--S <-- master (HEAD)
\
I--J--K <-- develop
, мы можем использовать git reset --hard
для переключения коммитов и как бы оттолкнуть коммит S
с пути:
S [abandoned]
/
...--G--H <-- master (HEAD)
\
I--J--K <-- develop
Обратите внимание, что коммит S
все еще существует . Просто, начиная с имени master
, мы находим коммит H
, затем работаем в обратном направлении и находим коммит G
и так далее. Мы никогда не продвигаемся вперед, поэтому мы не можем получить коммит S
. Он был заброшен, так же как и коммиты I-J-K
, если бы мы удалили имя develop
.
Если вы сохраните где-нибудь необработанный идентификатор * ha sh коммита - например, запишите его на бумаге, или на доске, или вырезать и вставить его в файл - вы можете использовать этот идентификатор sh позже, напрямую, чтобы Git нашел этот коммит. Если это все еще в хранилище (по умолчанию это будет не менее 30 дней), вы можете найти его таким образом. Но вы не найдете его с помощью большинства обычных команд git log
и других команд Git.
Обратите внимание, что если коммит находится в более чем одной ветви , например, если у нас есть:
...--P <-- branch1
\
Q <-- branch2
\
R <-- branch3
и мы удаляем имя branch2
- оно все еще может быть найдено. Результат удаления name branch2
:
...--P <-- branch1
\
Q--R <-- branch3
в конце концов. Коммит Q
по-прежнему существует в любом случае, но на этот раз мы можем найти его, начиная с R
и работая в обратном направлении.
Именно поэтому важно нарисовать график или часть этого. Коммиты, которые вы можете найти , начиная с какого-то имени и работая в обратном направлении, - это коммиты, которые вы сможете найти и завтра - при условии, что вы не перемещаете имена, то есть.
На данный момент у вас есть два варианта
Вы можете либо оставить свой комод sh -составить коммит и забыть остальных, полностью удалив имя develop
:
...--o--o--S <-- master
\
o--o--...--o [was develop; 28 commits, all abandoned]
или вы можете сформировать свой собственный локальный Git график, который будет выглядеть следующим образом:
S <-- origin/master
/
...--o--o <-- master (HEAD)
\
o--o--...--o <-- develop, origin/develop
Обратите внимание, что ваш master
определяет фиксацию за один шаг до фиксации S
, Имя origin/master
, являющееся вашей Git локальной копией GitHub master
, указывает на коммит S
.
Если ваш master
уже выглядит это, вы уже настроены. Если ваш master
выглядит следующим образом:
S <-- master (HEAD), origin/master
/
...--o--o
\
o--o--...--o <-- develop, origin/develop
вы бы запустили git reset --hard HEAD~1
, чтобы переместить его назад на один, чтобы он выглядел как первый рисунок.
Теперь, когда ваш (локальный) Git хранилище настроено правильно, вы должны указать Git на GitHub переместить его master
на один шаг назад. Для этого вам нужно использовать git push --force
или git push --force-with-lease
:
git push --force-with-lease origin master
Разница между ними заключается в том, что у --force-with-lease
есть Git (тот, что на GitHub), который проверяет, что вы думаю, что они имеют как master
, они действительно имеют как master
. Так что --force-with-lease
добавляет немного безопасности по сравнению с более простым --force
, если кто-то еще также работает с этим другим хранилищем.
В обычном повседневном git push
ваш Git вызывает другой Git - в этом случай, тот, что на GitHub - и выяснить, есть ли у вас новые коммиты для них. Если у вас есть новые коммиты для них, ваш Git отправляет их. Затем, в конце, ваш Git делает вежливый запрос к ним: Если все в порядке, пожалуйста, установите название вашей ветви ______, чтобы совершить га sh _______ . Git заполнит оба пробела: имя - это название ветви, которое вы используете, т. Е. master
в git push origin master
, а идентификатор ha sh - это идентификатор ha sh, который в настоящее время означает это имя в ваш Git.
Force-pu sh делает то же самое, за исключением того, что окончательный запрос вообще не вежлив: это требование, Установите название вашей ветви _______ на ________!
Другой Git, участвующий в git push
, примет вежливый запрос, если он не потеряет любые коммиты. В этом случае ваш запрос или команда специально разработаны, чтобы заставить их потерять commit S
. То есть они имеют:
...--o--o--S <-- master
\
o--o--...--o <-- develop
, и вы хотите, чтобы они оттолкнули S
с пути и заставили их master
указать на коммит до S
. Таким образом, вам понадобится какое-то мощное пу sh.
Вы можете вернуть Squa sh -merge, затем выполнить ребазинг
Команда git revert
работает добавив новый коммит, который отменяет эффект предыдущего коммита. Поскольку этот добавляет коммитов, Gits рады принять новый коммит. Этот конкретный метод здесь немного глуп, поэтому пока я нарисую эффект, вероятно, это не то, что вам нужно:
...--o--H--S--U <-- master
\
o--o--...--o <-- develop
Я дал начальную точку коммита H
для develop
a имя здесь. Причина в том, что снимок в коммите U
, отмена для S
, будет соответствовать снимку в коммите H
.
Теперь вы можете использовать git rebase
для скопировать всю серию коммитов, которые были develop
, в новую серию коммитов. Эти новые коммиты имеют разные идентификаторы ha sh и разные родительские связи, но имеют такие же снимки , как и раньше:
o--o--...--o <-- develop (HEAD)
/
...--o--H--S--U <-- master, origin/master
\
o--o--...--o <-- origin/develop
Вы сейчас находитесь в такой же ситуации, что и вы были до того, как вы случайно совершили коммит S
. Вам придется принудительно-pu sh ваш обновленный develop
на GitHub. (И тогда, конечно, вам все еще нужно объединить все!)
Вы можете go вперед и сделать реальное слияние
Вы можете оставить Squa sh commit S
в поместите и сделайте реальное слияние:
git fetch # if needed
git checkout master
git merge --ff-only origin/master # if needed
так, чтобы у вас было:
...--G--H--S <-- master (HEAD), origin/master
\
I--...--R <-- develop, origin/develop
, а затем:
git merge develop
для получения:
,--------<-- origin/master
.
...--G--H--S--------M <-- master (HEAD)
\ /
I--...--R <-- develop, origin/develop
Теперь вы можете git push origin master
заставить их принять коммит слияния M
, что даст вам:
...--G--H--S--------M <-- master (HEAD), origin/master
\ /
I--...--R <-- develop, origin/develop
в вашем локальном Git хранилище.
Вы можете удалите S, перемотайте вперед-объедините ваш develop
и force-pu sh
Наконец, это, вероятно, то, что вы хотели бы увидеть в своем личном, местном Git хранилище, прежде чем делать какие-либо принудительные толчки:
S <-- origin/master
/
...--G--H--I--...--R <-- develop, master (HEAD), origin/develop
Возможно, это то, что у вас есть:
S <-- origin/master
/
...--G--H <-- master (HEAD)
\
I--...--R <-- develop, origin/develop
или, может быть, у вас теперь есть:
S <-- master (HEAD). origin/master
/
...--G--H
\
I--...--R <-- develop, origin/develop
Чтобы получить то, что вы хотите, в или случае вы можете просто:
git checkout master
git reset --hard develop
Поскольку ваш develop
в настоящее время указывает на коммит R
, это проверяет фиксацию R
, делает ваш master
пункт, чтобы совершить R
, и ле дает вам график, который вы хотите. Затем вы можете использовать force-pu sh:
git push --force-with-lease origin master
для отправки любых ваших коммитов, которых у вас нет (нет, у них уже есть все эти коммиты), и командовать ими: I думаю, что ваш master
идентифицирует коммит S
. Если так, сделайте так, чтобы он идентифицировал commit R
сейчас! Они обычно подчиняются этой команде, и вы получите то, что хотите.