Когда вы запускаете git push
, у вас есть предложение Git для другого Git:
- определенный коммит (по хеш-идентификатору или имени), плюс
- вся история коммитов, ведущая к этому конкретному коммиту.
(Другой Git скажет вашему Git, когда прекратить предлагать предыдущие коммиты, потому что у другого Git они уже есть.) Это именно то, что вы видите.
Это поможет остановить, выполнить резервное копирование и нарисовать график фиксации (или хотя бы его часть). Вы можете запустить git log --graph
- я обычно предпочитаю вариант «помощь от СОБАКИ», git log --all --decorate --online --graph
, A ll D ecorate O neline G raph = ADOG - чтобы Git нарисовал график довольно грубо для вас. Некоторые графические интерфейсы рисуют график по-своему.
Помните, что в Git имя ветви - это просто удобочитаемое имя для одного (1) хеш-идентификатора. Каждый коммит имеет уникальный хэш-идентификатор, назначаемый во время выполнения коммита, но каждый коммит также содержит хэш-идентификатор своих родительских коммитов - обычно один или два для коммитов слияния - и эти строки фиксируют коммиты вместе в цепочки, которые разветвляются и при коммитах слияния сливаются обратно вместе. Не имеет значения, рисуете ли вы график вертикально, с новыми коммитами вверху, как это делает Git, или горизонтально, с более новыми коммитами справа, как обычно я делаю в публикациях StackOverflow. На самом деле важно рисовать график , потому что, как только вы это сделаете, то, что делает Git, становится очевидным:
A <-B <-C <--master
Это представляет общую сумму коммитов в крошечном репозитории с тремя коммитами. Каждая заглавная буква обозначает фактический хэш-идентификатор. Ветвь name , master
, запоминает фактический хэш-идентификатор last commit, commit C
. Сам коммит C
запоминает хэш-идентификатор своего предыдущего или родительского коммит B
. B
запоминает хэш-идентификатор A
, и, поскольку A
- самый первый коммит, у него нет родителя.
Всякий раз, когда Git требуется, он запускается с любого коммита, который вы ему сообщаете, или с вашего текущего коммита по умолчанию, а затем работает обратно через этот график. Если ваш Git толкает один конкретный коммит к другому Git, ваш Git должен толкать этот коммит и каждому родителю до точки, где ваши коммиты соответствуют их коммитам. Предположим, у вас есть, например:
...--*--*------o--o---o--X--o--o <-- branch
\ /
o--o------o
, где *
представляет коммит, который уже есть на сервере - вы уже отправили его или, например, получили с сервера, а o
представляет коммит, который у вас есть, которого у него нет. В этот момент, если вы нажмете третий коммит (вправо) вдоль нижнего ряда, они получат три коммита: все три из нижнего ряда. Это потому, что, начиная с этого коммита и работая в обратном направлении, требуется три коммита, чтобы ваш новый соединился со своими существующими. Если вы нажмете одну из двух фиксаций в верхнем ряду сразу после *
, ваш Git отправит одну или две фиксации. Если вы выдвигаете коммит, который приходит после слияния, ваш Git будет выдвигать как минимум шесть коммитов. Нажатие коммита X
здесь толкает семь: X
само по себе и необходимое число предшествующих шести.
Если вы хотите выдвинуть один коммит, который добавляет второй коммит со звездочкой, вы должны сначала сделать единственный коммит, который добавляет второй коммит со звездочкой:
Y <-- new-name
/
...--*--*------o--o---o--X--o--o <-- branch
\ /
o--o------o
Если у вас есть эта настройка, если вы нажимаете коммит Y
, у них уже есть отмеченные коммиты (конечно), так что ваш Git фактически будет толкать один коммит, а именно коммит Y
.
Как сделать этот единственный коммит
Чтобы создать commit Y
, сначала вам нужно 1 , чтобы создать новое имя ветки, указывающее на окончательный коммит *
. Найдите его хэш-идентификатор или любое другое имя, которое однозначно его идентифицирует, и проверьте этот конкретный коммит и создайте для него имя:
git checkout -b new-name <hash-id>
этот отпускСам график остается неизменным, но новое имя указывает на этот коммит. 2 Команда git checkout -b
помещает вас в эту новую ветку одновременно с созданием имени. (Обратите внимание, что в этот момент отправка новой ветви на сервер отправит серверу no коммитов, а также просто создаст имя на сервере.)
/----------------------------- new-name (HEAD)
|
v
...--*--*------o--o---o--X--o--o <-- branch
\ /
o--o------o
Теперь вы можете запустить:
git cherry-pick <hash-of-desired-commit-X>
, который в данном случае:
git cherry-pick 9973b14ca19e4571046d8d25bc986ec07199e39c
Это заставляет Git проверить коммит 9973b14ca19e4571046d8d25bc986ec07199e39c
. Git извлечет моментальный снимок своего родительского коммита и моментальный снимок этого коммита и изменит их (а-ля git diff
). 3 Затем Git применит любые изменения в текущем коммите, чтобы сделать новый коммит, который является копией из X
:
Y <-- new-name (HEAD)
/
...--*--*------o--o---o--X--o--o <-- branch
\ /
o--o------o
и теперь у вас есть то, что вам нужно: коммит, который можно выдвинуть сам, потому что история Y
- его родитель, прародитель и т. Д. - уже существует на сервере .
1 Технически, вы можете сделать это без name , используя режим «detached HEAD». Я бы посоветовал против этого, на данный момент.
2 Вы также можете создать имя ветви, используя git branch
:
git branch new-name <hash-id>
Затем вам нужно запустить git checkout new-name
, чтобы получить на этой ветке, то есть присоединить ваш HEAD
к этому имени.
3 С технической точки зрения cherry-pick - это операция слияния, которая приводит к фиксации без слияния: merge-as-a-verb, для слияния , но не как прилагательное или существительное. Это позволяет черрику работать в ситуации, когда простой патч не сработает. Однако это конкретное осложнение здесь в основном не имеет значения: оно имеет значение только в том случае, если выбор вишни приводит к конфликту слияния.