GIT: Как отключить объединение двух веток - PullRequest
0 голосов
/ 12 марта 2020

Каким-то образом мне удалось создать прикрепленный график слияния. Названия коммитов не имеют значения. Это была детская площадка git. Больше всего меня беспокоит ситуация в целом, особенно отношения между локальными главными (значок p c) и исходными главными (с аватаром) ветвями.

Я хочу удалить слияние и получить прямой путь, как в:

[add] add ...
add git ...
[add] add .. (near duplicate)
add git ...(near duplicate)
[]1.added
etc.

В основном у меня есть две ветви, которые слились, но я хотел бы что-то сделать (я представляю быть), как разблокировать их, а затем сделать ребаз. В конце концов, я мог бы набрать sh их.

Более того, на самом деле, мне не нужны обе ветви. Они фактически одинаковы. Но мне любопытно, как я мог бы сделать это таким образом, чтобы сохранить оба, и я думаю, что по пути можно было бы удалить один из них.

Я пробовал несколько вещей, но случайно, и не Стоит отметить. Я действительно понятия не имею, как поступить.

[обновление] Я ищу командную строку / git ответ на команду предпочтительно, так как я sh, чтобы понять, что происходит. Тем не менее, я использую VScode для кодирования и имею в своем распоряжении различные менеджеры репозитория (Gitkracken, Fork, Gitx, Github, Sourcetree), так что ответ в этих контекстах будет хорошим началом.

Спасибо заранее ...

duplicated commits

1 Ответ

1 голос
/ 13 марта 2020

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

Это определенно возможно (хотя "unlock" - не вещь в Git). Возможно, вам понадобится некоторая базовая c Git инструкция (как следует из этого понятия «разблокировки»).

При работе с любой распределенной системой управления версиями необходимо учитывать ряд важных вещей, и особенно когда речь идет о DVCS Git. Во-первых, он распространяется , поэтому существует несколько копий некоторых или всех частей. Это делает вещи по своей сути сложными. Нам нужен какой-то способ укротить и контролировать сложность. Выбор

Git здесь начинается с концепции commit . Комитеты Git raison d'être . Они являются его основной c единицей хранения. 1 Каждый коммит получает уникальный номер. Было бы неплохо, если бы это был простой подсчет: коммит № 1, коммит № 2, ... но это не так. Вместо этого это уникальный га sh ID . Эти идентификаторы ha sh выглядят случайными, но на самом деле они не случайны. Фактически, если бы мы могли заранее предсказать точную секунду, в которую вы сделаете новый коммит, и знать, что вы положите в его сообщение о коммите, и все остальное о нем, мы могли бы предсказать его идентификатор sh. Но, конечно, мы не можем и не можем.

Каждый коммит содержит две вещи:

  • полная и полная копия всех ваших исходных файлов: снимок , который является основными данными коммита; и
  • некоторые метаданные: информация о коммите, например, кто его сделал, когда, и их сообщение в журнале о почему они сделали этот коммит.

Важнейшей частью метаданных является то, что каждый коммит содержит идентификаторы га sh некоторых предыдущих или родительских коммитов. То есть каждый последующий коммит говорит «мой предыдущий родительский коммит _____» (заполните бланк с идентификатором ha sh). Это связывает коммиты вместе, но только в обратном направлении.

После создания ни один коммит не может быть изменен, даже один бит, потому что его идентификатор ha sh является криптографическим c ha sh всех его биты. То есть вы можете извлечь существующий коммит из репозитория, возиться с ним и сохранить новый коммит, но любые изменения в нем приводят к сохранению нового и другой коммит, который просто добавляет в хранилище . Существующий коммит все еще там, и остается неизменным, под своим первоначальным идентификатором ha sh. Другими словами, коммит замораживается навсегда, как только он рождается. Это означает, что родительские коммиты не могут быть изменены для хранения идентификаторов их детей sh Дети знают своих родителей (которые существуют в то время, когда вы создаете ребенка), но родители никогда не знают своих детей (которые еще не родились на момент рождения родителя).

В конце концов, это также означает, что Чтобы запомнить цепочку коммитов, нам нужно запомнить только последнее звено в цепочке . То есть, если мы рисуем серию коммитов, используя заглавные буквы для обозначения реальных идентификаторов ha sh, мы получим что-то похожее на это:

A <-B <-C   <--master

Имя master запоминает га sh идентификатор последнего коммита, C. Мы говорим, что имя master указывает на C. Commit C содержит снимок плюс метаданные, а в метаданных C запоминает идентификатор коммита ha sh B, поэтому мы говорим, что C указывает на B. Точно так же B помнит га sh идентификатор коммита A.

Коммит A немного особенный, так как это первый коммит в истории. У него нет ранее зафиксированного коммита, поэтому у него нет сохраненного родителя. Git называет это root коммитом , и это означает, что мы можем перестать смотреть назад.

Чтобы добавить новый коммит, мы начнем с последнего - в этом случае C и распакуйте его файлы. Файлы внутри коммита имеют специальный, только для чтения, Git -только, замороженный и сжатый формат, 2 , поэтому для выполнения любой реальной работы с коммитом мы имеем извлечь его первым. После извлечения коммита C, Git знает, что текущий коммит C. Затем мы делаем обычное дело и делаем новый коммит:

A--B--C   <-- master
       \
        D

Новый коммит D указывает на C (это должна быть стрелка, но стрелы рисовать слишком сложно, поэтому я ' вместо этого мы заменили большинство из них соединительными линиями). Затем git commit выполняет свой волшебный c трюк: он записывает D 's sh ID в имя master, так что master теперь указывает на D:

A--B--C
       \
        D   <-- master

(и теперь мы можем выпрямить линии: больше нет необходимости в изгибе на графике).


1 Commits может быть разбито дальше, подобно тому, как атомы могут быть разбиты на протоны, нейтроны и электроны, но как только вы их разбиваете, они перестают быть атомами c, в некотором роде.

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

Поскольку ни один хешированный Git объект не может быть изменен, можно безопасно повторно использовать существующий объект. Коммиты всегда получают уникальные идентификаторы, потому что у них есть метки времени, родительские ссылки и так далее. Единственный способ повторно использовать идентификатор фиксации - это сделать тот же моментальный снимок , с теми же родителями , в то же время - точно таким же второе - как при создании более раннего снимка. Поэтому, если вы сделаете сегодня тот же моментальный снимок, который вы сделали вчера, со временем, установленным на вчера, с повторным использованием сообщения журнала вчера и всего остального со вчерашнего дня, вы снова получите тот же коммит ... который вы сделал вчера, так в чем проблема? 11

Существует возможность с помощью сценариев выполнять несколько коммитов одновременно в нескольких ветвях. Если вы начнете эти ветви указывать на один и тот же коммит, то они останутся указанными на один и тот же финальный коммит - сначала это удивительно, но не сломано.

Существует также теоретическая проблема с коллизиями ha sh. , из-за принципа вихревого отверстия , но это никогда не происходит на практике. См. Также Как недавно обнаруженное столкновение SHA-1 влияет на Git?


Имена ветвей - это всего лишь указатели на существующие коммиты

Что означает все это эта ветвь имен сама по себе очень мало делает. Единственное, что они делают, это запоминают коммит га sh ID . Так как идентификаторы ha sh большие и некрасивые и их невозможно запомнить людям, это на самом деле довольно полезно Это не так уж много работа .

В Git вы можете иметь любое количество имен веток, которые все указывают на один и тот же коммит. Вы также можете в любое время переместить любое из имен своих веток, если каждое из них указывает на один коммит, который у вас есть. Поэтому, если у нас есть:

A--B--C--D   <-- master

, мы можем добавить больше имен для D, запустив, например:

git branch dev

Позвольте мне нарисовать это сейчас так:

A--B--C--D   <-- master (HEAD), dev

Я добавил специальное имя HEAD в скобках здесь, прикрепленное к имени master. Это рисунок того, что Git делает в действительности: Git сохраняет имя ветви , то есть master, в файле, который он использует для HEAD, 3 чтобы "прикрепить" специальное имя к имени ветки. Вот как Git узнает, в какой ветке вы находитесь, а затем само название ветви, в данном случае master, - вот как Git знает, в каком коммите вы тоже.

Давайте сделаем новый коммит сейчас и назовем его E. Git запишет снимок и метаданные как обычно. Так как текущий коммит D, родительский элемент E будет D. Затем, когда Git сохранит коммит E в базе данных all-commits, Git запишет идентификатор E ha sh в любое имя ветви HEAD, к которому в данном случае относится master, что дает нам:

           E   <-- master (HEAD)
          /
A--B--C--D   <-- dev

HEAD все еще привязано к master, но теперь master указывает на последний коммит цепочки, который является E. Имя dev все еще указывает на D; коммиты с A по D теперь находятся на обеих ветках; и commit E только для master.

Это обычная повседневная разработка в Git:

  • , выберите ветку для присоединения HEAD, которая выбирает ее tip commit
  • извлекает все файлы из этого коммита, чтобы мы могли работать с ними / над ними
  • делать обычные вещи, которые мы делаем
  • сделать новый коммит: упаковать что бы ни было в Git index 4 , чтобы создать новый коммит, чьим родителем является текущий коммит, затем обновите имя текущей ветви, чтобы оно указывало на новый коммит.

При этом со временем ветви растут - по одному коммиту за раз.


3 Git фактически использует файл для этого по крайней мере сегодня. Однако нет гарантии, что когда-нибудь он не изменит методы: в общем, вы должны читать и писать HEAD, используя предоставленные программы: git rev-parse, git symbolic-ref, git update-ref и так далее, если вы ' переписываем скрипты низкого уровня; или git branch и т. п. для более нормального повседневного использования.

4 Индекс, который Git также называет промежуточной областью , не адресован должным образом в этом ответе, но именно так git commit действительно работает. Хотя индекс выполняет расширенную роль во время конфликтующих слияний, его основная функция заключается в том, чтобы действовать как область хранения для файлов, которые вы хотите поместить в next commit. Он начинает сопоставлять копии файлов из текущего коммита.

Технически, индекс содержит идентификаторы ha sh, а не фактические копии файлов. Но до тех пор, пока вы не начнете работать с git update-index и git ls-files --stage, вы можете просто думать об индексе как о хранении предварительно высушенных замораживанием копий каждого файла.


Объединение (истинное объединение)

В конце концов, у нас может быть что-то вроде этого:

          I--J   <-- master (HEAD)
         /
...--G--H
         \
          K--L   <-- feature

Теперь мы хотим объединить ветвь feature, которая в действительности является коммитом L, плюс история, с которой мы работаем в обратном направлении, L, K, H, G и т. д. - в текущую ветвь master, то есть J, затем I, затем H и G и т. д.

Для выполнения sh этого слияния мы запустим git merge feature. Git найдет не один, не два, а три коммитов:

  • Коммит # 1 будет базой слияния , но до того, как мы туда доберемся давайте найдем # 2 и # 3.
  • Commit # 2 - это текущий коммит, который действительно прост: он HEAD, то есть J.
  • Commit # 3 довольно тоже легко: это тот, кого мы назвали. Мы сказали git merge feature, а имя feature указывает на L, поэтому коммит № 3 - это коммит L.

база слияния является лучшей shared (common) commit, который мы находим, начиная с двух советов и работая в обратном направлении. В этом случае это очевидно: лучший коммит, который находится на обеих ветвях, это H.

Слияние теперь происходит путем сравнения снимков всех трех коммитов. (Помните, что каждый коммит имеет полный снимок всех файлов.) Сравнение H против J говорит Git, что мы изменили в нашей (master) ветке; сравнение H против L говорит git, что они изменили в их (feature) ветви. Слияние теперь просто - или сложно - объединяет эти два изменения, применяет объединенные изменения к снимку в базе слияния H и, если все идет хорошо, создает новый коммит, который вызывает Git коммит слияния .

Новый коммит слияния выполняется почти обычным образом: снимок содержимого индекса, сообщение журнала и родительский элемент на основе текущей ветви. Что особенного в этом коммите слияния, так это то, что у него тоже есть второй родитель. Вторым родителем слияния является коммит, который вы слили - в данном случае, коммит L. Так что если все пойдет хорошо, Git сделает этот новый коммит слияния M самостоятельно:

          I--J
         /    \
...--G--H      M   <-- master (HEAD)
         \    /
          K--L   <-- feature

Commit M указывает на обоих J и L, но, кроме этого, такой же, как и любой другой коммит. Обратите внимание, как текущее имя ветки master теперь указывает на последний коммит M; но обратите внимание также, как M возвращается к и J и L, так что все эти коммиты теперь включены на master.

Fast -forward "слияние"

Команда git merge может и по умолчанию сделает что-то, что вообще не является слиянием, если может. Предположим, у нас есть:

...--G--H   <-- master (HEAD)
         \
          I--J   <-- dev

Если мы запустим git merge dev, Git найдет три коммитов интереса как обычно: # 2 - HEAD, что H, # 3 - dev который равен J, а база слияния - лучшая общая фиксация на обеих ветвях, что снова ... H.

Если бы у нас было Git, сравните снимок в H и снимок в H, что будет отличаться? (Это простое упражнение. Подумайте об этом на мгновение. Какие файлы нам нужно изменить на go с файлов, сохраненных в H, на файлы в H?)

Поскольку ничего нет чтобы изменить go с H на H, мы получим только те изменения, которые go с H до J - --theirs - если мы осуществим истинное слияние. Мы можем заставить Git сделать истинное слияние, и если мы это сделаем, Git должным образом объединит изменения без изменений и сделает новый коммит слияния M:

...--G--H------M   <-- master (HEAD)
         \    /
          I--J   <-- dev

, который мы получим, если запустим git merge --no-ff dev. Но по умолчанию Git скажет: Объединение чего-либо с чем-либо дает что-то; применение чего-то к H получает снимок в J; так что давайте просто повторно использовать существующий коммит J! Запуск git merge dev или git merge --ff-only dev сделает fast-forward вместо слияния, что даст нам:

...--G--H
         \
          I--J   <-- master (HEAD), dev

, по сути, просто проверяя коммит J и перемещая master, чтобы указать J. (Специальное имя HEAD остается прикрепленным, как обычно.)

Squa sh merge

Вы также можете выполнить "squa sh merge", используя git merge --squash. Здесь Git проходит через большинство обычных движений для полного слияния. Это означает, что он работает для ситуации, подобной быстрому движению, но также и для ситуации, подобной истинному слиянию:

          I--J   <-- master (HEAD)
         /
...--G--H
         \
          K--L   <-- feature

Git будет выполнять сравнение и объединение как обычно - с тем же простой результат, как обычно, если у нас есть это:

...--G--H   <-- master (HEAD)
         \
          I--J   <-- dev

- и затем будьте готовы сделать новый коммит, чтобы сохранить снимок слияния. Однако вместо того, чтобы сделать новый коммит коммитом слияния, Git делает вид, что вы сказали ему --no-commit, подавляя коммит. Затем вы должны запустить git commit самостоятельно, а когда вы это сделаете, Git сделает обычный коммит с одним родителем:

...--G--H--S   <-- master (HEAD)
         \
          I--J   <-- dev

, например, где S снимок "squa sh слияния", полученный в результате коммита с простым слиянием J, или:

          I--J--S   <-- master (HEAD)
         /
...--G--H
         \
          K--L   <-- feature

, где S - снимок "squa sh слияния", полученный в результате истинного слияния J и L с использованием H в качестве базы слияния.

Обратите внимание, что в в обоих случаях любые коммиты на стороне "сдавленных" больше не нужны. Когда мы * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *} То, что * 14 * * * делает то же самое * * * * * * * * * * * * * * * * *}} Мы больше не хотим коммитов K-L.

То, что вы получили, было результатом слияния сква sh или ребазера

Мы еще не покрыли ребаз - мы будем доберитесь через мгновение - но давайте посмотрим на это:

          I--J--S   <-- master (HEAD)
         /
...--G--H
         \
          K--L   <-- feature

Теперь мы можем запустить git merge feature, если захотим (хотя в целом это не очень хорошая идея). Git будет сравнивать H против S, чтобы увидеть, что мы изменили, и H против L, чтобы увидеть, что они изменились. Git затем объединит два набора изменений в меру своих возможностей.

Поскольку S уже содержит изменения H -vs- L, если нам повезет (или это не повезло?), Конфликта не будет, и Git осознает, что он может просто игнорировать H -vs- L полностью и используйте только H -vs- S часть. Или, может быть, у нас возникнут конфликты. Когда и получим ли мы конфликты, зависит от того, какой была часть H -vs- J, но довольно часто ее не получают. Может быть, мы разрешим некоторые конфликты вручную; в любом случае, мы включаем go и делаем новый коммит слияния, который я назову M, хотя S идет после M в алфавитном порядке:

...--G--H--I--J--S--M   <-- master (HEAD)
         \         /
          K-------L   <-- feature

Теперь у нас есть этот пузырь слияния в графе, и избыточный фиксирует K-L в качестве второго родителя слияния M.

Мы увидим, как избавиться от M полностью через мгновение.

Rebase

Команда git rebase работает путем копирования коммитов. В начале я упомянул, что невозможно изменить какой-либо коммит, но вы можете взять коммит (или сравнить два коммита), возиться с файлами и сделать новый коммит. Мы можем использовать это свойство для копирования фиксации в новых и улучшенных версиях.

Давайте начнем с:

...--G--H--K--L   <-- master
         \
          I--J   <-- feature (HEAD)

Коммиты I и J являются довольно хорошо, но что, если у нас Git выяснить изменение , сделанное в go с H до I, и применить то же самое изменение к снимку в L? Давайте отсоединим HEAD, указав непосредственно на этот новый коммит, сделанный после L:

                I'  <-- HEAD
               /
...--G--H--K--L   <-- master
         \
          I--J   <-- feature

Коммит I' - наша копия I - что является почему мы называем это I' - поскольку у нас было Git копия сообщения о фиксации и все.

Разница между оригиналом I и копией I' в том, что I' имеет L в качестве его родителя и другой снимок, так что при сравнении I' с его родителем L получает тот же результат, что и при сравнении I с его родителем H.

Этот процесс копирования выполняется с помощью git cherry-pick. 5 Cherry-pick - общая операция Git «копировать фиксацию», и внутренне она использует тот же механизм, что и полный git merge, но в большинстве случаев вы можете думать о нем как о «копировать коммит». 6 скопировав I в I', теперь нам нужно скопировать J в J':

                I'-J'  <-- HEAD
               /
...--G--H--K--L   <-- master
         \
          I--J   <-- feature

Теперь, поскольку I'-J' - это наши новые и улучшенные коммиты, мы хотим, чтобы наши Git * отказались от оригиналов в пользу этих новых. Чтобы это произошло, наш Git просто очистит ярлык feature от коммита J и вместо этого сделает указатель на J'. Как только это будет сделано, наша Git может повторно присоединить HEAD к названию ветви feature:

                I'-J'  <-- feature (HEAD)
               /
...--G--H--K--L   <-- master
         \
          I--J   [abandoned]

Поскольку мы находим фиксирует, запуская с именем ветви, находя его сохраненный идентификатор ha sh и просматривая коммит, когда мы смотрим на этот репозиторий, он будет похож на , мы как-то изменили два коммита. Вместо J и затем I мы видим J' и затем I'. Но если мы обратим пристальное внимание, то увидим, что это разные идентификаторы ha sh.


5 Некоторые формы git rebase действительно запускают git cherry-pick. Другие (в основном, старые формы ребазирования) этого не делают, но имитируют это довольно близко.

6 Исключение составляют случаи, когда во время копирования возникают конфликты слияния, но мы не будем go в этом здесь.


Распределенные репозитории

В самом начале, я упомянул, что самое важное, что нужно иметь в виду, это то, что Git распределен и существует более одной копии репозитория.

В нашем случае, допустим, у нас есть локальный Git на нашей машине и еще один Git на GitHub. (В некоторой степени не имеет значения, где находится другой Git - GitHub, Bitbucket, GitLab, корпоративный сервер, что угодно: все они работают почти так же, как все они имеют Git за некоторым IP-адресом. Большая разница заключается в том, что хостинговые компании добавляют собственный пользовательский интерфейс через веб-сайт, а интерфейсы web отличаются.)

В любом случае, у нас есть Git вызов их Git - кем бы они ни были - по URL, который переводится в какой-то IP-адрес и путь, который мы даем серверу. Git хранит этот URL под именем, которое Git вызывает remote . Стандартное имя для любого пульта дистанционного управления - origin, поэтому мы будем использовать его в качестве имени здесь.

Поскольку Git более origin равно a Git хранилище, у него есть его собственные имена ветвей. Названия наших филиалов в нашем Git нашем . Их их . Им не нужно совпадать! В частности, когда мы добавляем коммиты в наши ветви, мы «опережаем» их ветви.

Давайте начнем с того, что на нашей машине вообще не будет репозитория Git (возможно, нам пришлось получить новый ноутбук или еще как). Мы git clone их Git репозиторий:

git clone <url>

Наш Git на нашем компьютере создаст новый, полностью пустой репозиторий и добавит имя origin для хранения URL. Затем он вызовет их Git и попросит их перечислить имена своих ветвей и идентификаторы ha sh для коммитов, выбранных этими именами ветвей. Они предложат отправить эти tip коммиты для этих веток.

Для каждого коммита га sh ID наш Git скажет: Да, я бы хотел совершить . Допустим, это коммит H на master. Они обязаны предложить родителю этого коммита , G. Наш Git проверит: есть ли у меня родительский коммит? Конечно, наша объектная база данных Git пуста, поэтому мы этого не делаем. Поэтому мы тоже попросим G. Их Git предложит F, и мы возьмем его, и так далее, и, в конце концов, мы получим каждый коммит, который у них есть (ну, за исключением любых заброшенных, если они есть - иногда они есть!).

Теперь у нас будет:

...--G--H

в нашей базе данных коммитов. Но у нас пока нет ни одного имен . Мы закончили получать коммиты от них - у них было только master и коммит H и его историю, и мы получили все это - поэтому наш Git отключается от их Git. Теперь наш Git берет все имена своих ветвей, которые равны master, и переименовывает каждое, помещая наше удаленное имя, origin, впереди, с sla sh, чтобы отделить их :

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

Эти origin/* имена являются нашими Git именами удаленного слежения . Они помнят их Git ответвлений для нас.

В качестве последнего трюка наш git clone работает git checkout master. На самом деле у нас нет ветки master, но если вы попросите Git проверить ветку, которой у вас нет, ваш Git попытается создать эта ветка от соответствующего имени удаленного слежения. У нас есть origin/master, и он выбирает коммит H, поэтому наш Git создает наш master, указывающий на H, и присоединяет наш HEAD там:

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

Наше git clone теперь завершено.

Если мы сейчас создадим новые коммиты, они добавятся, как обычно:

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

Теперь мы можем отправить коммиты к ним , используя git push. Когда мы делаем это, мы выбираем две вещи:

  • , которые передают (и) отправку, и
  • , какие имена ветвей, чтобы они установили

Если мы запускаем git push origin master, мы выбираем commit J для send (потому что наше имя master выбирает commit J) и имя master to set (потому что мы сказали master).

Мы можем, если захотим, запустить git push origin master:dev, чтобы отправить J и попросить их set их dev вместо их master. Обычно вы этого не делаете - чаще всего вы сначала создаете свой собственный dev, чтобы у вас было J на dev, а затем git push origin dev - но это полезно в качестве примера. Мы отправляем коммиты, которые у нас есть (и, по-видимому, их нет), а затем наш git push просит их установить их ответвления имена. В отличие от наших Git, они не получают имена для удаленного отслеживания здесь! Имена удаленного отслеживания являются собственностью git clone и git fetch.

Чтобы отправить их J, сначала нам нужно будет отправить I. Мы тоже предложим им H, но они уже будут иметь его, поэтому они говорят нет, спасибо, у меня есть . Это позволяет нашим Git сжимать очень хорошо (мы знаем, что они совершили H и все ранее коммиты тоже!), Когда мы отправляем их I и J. Затем мы просим их установить имена своих филиалов.

Если хранилище на стороне сервера shared - если мы не единственные, кто его использует - их master может приобрели новые коммиты с момента нашего последнего разговора с ними. Возможно, кто-то еще побежал, например, git push origin master. Поэтому мы отправляем им I-J, и если у них есть:

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

, и мы просим их установить master для указания J, они, вероятно, скажут хорошо, сделано . Теперь у них есть:

...--G--H--I--J   <-- master

в их хранилище. Наш Git обновит наш origin/master соответственно. Но если у них есть:

...--G--H--K   <-- master
         \
          I--J

, и они подчинятся нашей вежливой просьбе, они получат:

          K   [abandoned]
         /
...--G--H--I--J   <-- master

, потому что любой *2034* находит коммитов - начинать с конца и работать в обратном направлении. Конец теперь J, чей родитель I, чей родитель H. От H до K пути в go нет: все стрелки односторонние и направлены назад. Так что в этом случае они скажут нет, я не установлю свой master.

Ваш Git представит вам это как ошибку:

  ! rejected (non-fast-forward)

, что означает, что вы должны получить их новые коммиты от их и включить их в свою работу, например, через git merge или git rebase.

Или вы можете отправить им команду вместо вежливого запроса: Установите для master значение J! Если они подчинятся этой команде, они потеряют потеряют коммит K. Скорее всего, вы не сможете получить их обратно. Кто бы ни сделал K, он может быть раздражен (но - в любом случае, можно надеяться - у того, кто сделал K, он все еще есть в * их клоне).

Запросы на извлечение и нажимаемые кнопки GitHub

Запросы Pull - это не вещь Git, а скорее что-то, предоставленное GitHub и другими провайдерами хостинга. Они дают вам возможность выполнять слияния в том, что они называют разветвленными хранилищами. (Форк - это всего лишь клон с некоторыми добавленными специальными функциями, большой из которых являются эти запросы на извлечение.)

GitHub предлагает три варианта при объединении PR. Одним из них является прямой git merge, выполняющий истинное слияние, даже если бы был возможен перемотка вперед. Один из них, называемый «перебазировать и объединить», выполняет git rebase, даже если в этом нет необходимости, всегда копирует все коммиты в новую цепочку, затем выполняет слияние в стиле ускоренной перемотки новой цепочки. Последний, называемый «squa sh and merge», эквивалентен выполнению git merge --squash.

Поскольку слитый стиль GitHub sh и стиль rebase всегда приводят к новому ха sh Идентификаторы , теперь вы можете получить ту же проблему, что мы наблюдали ранее, с squa sh с последующим слиянием.

Удаление слияния (или любого другого коммита)

В ваш собственный репозиторий, вы имеете полный контроль над всеми именами веток. Вы можете сделать так, чтобы любые имена веток указывали на любой коммит.

Предположим, что у вас есть это:

          I--M   <-- master (HEAD)
         /  /
...--G--H--I'  <-- origin/master

где I был ваш оригинал зафиксируйте свой master ранее, который вы отправили куда-то кому-то, кто скопировал его в I и поместил в их master. Ваш origin/master по-прежнему указывает на эту копию I'; ваш master указывает на ваше слияние M, чей первый родитель I, а второй родитель I'.

Вы получите это, если вы git fetch origin; git merge origin/master или просто git pull который работает git fetch origin master; git merge FETCH_HEAD. Проблема, опять же, в том, что тот, кто запускает origin, решил скопировать ваш коммит, по любой причине.

Если вы хотите отменить слияние M, теперь вы можете запустить:

git reset --hard HEAD^        # or HEAD~1 or HEAD~

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

Вы можете использовать необработанный идентификатор ha sh, который всегда работает: просто вырезайте его из вывода git log, и вы сказали Я хочу, чтобы мое текущее имя ветви выбрало этот коммит . Или вы можете использовать имя: например, имя ветви выбирает коммит, на который указывает имя. Здесь мы используем HEAD, что означает текущий коммит , но затем добавляем суффикс: ^, что означает первый родитель или ~1, что означает сосчитать обратно одного прародителя , что то же самое.

Это означает, что Git найдет слияние M, а затем взглянет на его первого родителя, который I. Вот где мы сказали git reset --hard, так что в итоге получим:

            __M   [abandoned]
           /  /
          I  /  <-- master (HEAD)
         /  /
...--G--H--I'  <-- origin/master

Рисовать немного сложно - коммит M все еще существует, но никто не указывает на это, поэтому мы не можем найти это. Если взять его из чертежа, результат будет более ясным:

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

Обратите внимание, что это работает , потому что мы никогда не давали коммит M любому другому Git. Только наш master знал, как найти коммит M. Мы можем сбросить его, и он не вернется.

Если мы сделали отправим M другому Git после того, как мы это сделали, например, через git push origin master, они должны были бы совершить M. Мы можем попытаться сбросить его с нашего Git, что будет работать немного, но origin/master в нашем хранилище и их master в их клон, все еще будет коммит слияния M. Чтобы избавиться от этого, мы должны убедить их изменить их master тоже.

В общем, как только вы поделились коммитом, вы ' Я получу это снова от каждого другого Git. Git построено для добавления коммитов, а не их удаления; действие по умолчанию для обмена добавить в мою коллекцию, объединяя при необходимости .

...