Как исправить перебазирование ветки на изменение коммита, что привело к «перебазированию в процессе»? - PullRequest
1 голос
/ 07 июня 2019

Я относительно новичок в Git, поэтому детали будут оценены.Пожалуйста, укажите, что я делаю что-то совершенно не так.

Итак, вот как выглядит проблема:

  1. Я скачал с пульта дистанционного управления, у которого была только одна ветвь, master.
  2. Я сделал коммит на мастер
  3. Я перенес изменение после перебазирования мастера на недавно извлеченный источник / мастер (хотя это изменение еще не было объединено с удаленным)
  4. Я сделалновая ветка от мастера называется bug1 и внесла некоторые изменения
  5. Я выдвинул bug1 после перебазирования в origin / master после выборки (это изменение еще не объединено с удаленным)
  6. Я сделал ветку изmaster, называется bug2.
  7. Я исправил свой последний коммит на master (с необходимой информацией, которая была бы полезна для bug2)
  8. Я проверил bug2 и сделал git rebase master, надеясь, что всеИзменения, которые были сделаны в master, теперь будут отражены в bug 2.
  9. Я считаю, что из наблюдения я понял, что моя цель действительно была достигнута, работа над master теперь была в bug2.
  10. Что странно, когда я делаю git status, я получаю это:
rebase in progress; onto ec578ba
You are currently rebasing branch 'bug2' on 'ec578ba'.
  (fix conflicts and then run "git rebase --continue")
  (use "git rebase --skip" to skip this patch)
  (use "git rebase --abort" to check out the original branch)

Unmerged paths:
  (use "git reset HEAD <file>..." to unstage)
  (use "git add <file>..." to mark resolution)

    both modified:   c.py
    both modified:   f.py
    both modified:   s.py

Changes not staged for commit:
  (use "git add <file>..." to update what will be committed)
  (use "git checkout -- <file>..." to discard changes in working directory)

    modified:   t.py
    modified:   co.txt
    modified:   h.py

Untracked files:
  (use "git add <file>..." to include in what will be committed)

    blablaa.pyc

no changes added to commit (use "git add" and/or "git commit -a")

Кроме того, git branch -a показывает:

* (no branch, rebasing bug2)
  bug1
  bug2
  master
  remotes/origin/HEAD -> origin/master
  remotes/origin/master

Iдействительно не знаю, что я сделал не так ... Я думал, что все получится из того, что я узнал в Интернете, но, очевидно, мне нужно сделать "слияние", чтобы решить эту проблему ...

Может кто-тоПомогите?Спасибо вам большое!Кажется, это слишком специфическая проблема, Google, я даже не знаю правильных терминов, я думаю.

1 Ответ

1 голос
/ 08 июня 2019

Вы действительно находитесь в середине перебазирования, как говорит git status. (Если ваша версия Git 2.0 или выше git status довольно хорошая и надежная; в выпусках 1.7 и 1.8 было несколько существенных улучшений, так что если у вас действительно древний git status, вы все равно должны его использовать, но это не так хорош как современный Git.)

Причина, по которой вы застряли в слиянии, заключается в том, что git commit --amend является своего рода белой ложью: на самом деле не изменяет существующий коммит. Вместо этого он делает новый и улучшенный коммит, а затем говорит: Давайте использовать этот вместо старого. К сожалению, любая ветвь, которая уже имеет и зависит от старой, все еще имеет старую.

Давайте рассмотрим это подробно:

  1. Я скачал с пульта, у которого была только одна ветка, master.

Я предполагаю, что вы имеете в виду, что бежали:

git clone <url>
cd <new-clone>

здесь.

  1. Я сделал коммит мастеру

Таким образом:

<edit some file>
git add <that file>
git commit

Давайте теперь немного отступим и опишем, что такое коммит и что он делает, с кратким погружением в как делает это также. (Здесь будет много, но вы можете просто помнить, что было много, и вернуться к нему позже, когда это будет уместно.)

Коммиты и имена ветвей

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

Каждый коммит имеет свой уникальный хэш-идентификатор. Идентификатор хэша - это большая уродливая строка типа 83232e38648b51abbcbdb56c94632b6906cc85a6; они показывают много мест, включая вывод git log, иногда в сокращенной форме (83232e3 и т.п.). Идентификатор хеша - это настоящее «истинное имя» коммита, и именно так Git находит коммит: Git должен иметь хеш-идентификатор в своих хитрых маленьких лапах, чтобы извлечь коммит. Так что, если бы не имена филиалов, вы бы записывали все это на бумаге все время или что-то в этом роде - но это глупо, у нас есть компьютер, у нас может быть просто it , записываем их в файл или что-то.

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

... <-F <-G <-H   <--branch

с заглавными буквами, обозначающими настоящие хэш-идентификаторы. name , branch, содержит идентификатор хэша последнего коммита H. H сам содержит идентификатор хеша более раннего коммита G, который содержит идентификатор хеша F и т. Д. Это позволяет Git идти назад через серию коммитов.

Чтобы создать новую ветку, вы просто выбираете существующий коммит и присоединяете к нему имя:

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

или

...--F--G--H   <-- newbranch, master

Теперь, когда у вас есть более одного имени, Git нужен способ узнать какое имя вы используете. Вот где HEAD приходит:

...--F--G--H   <-- newbranch (HEAD), master

означает, что текущее имя newbranch (и текущий коммит H).

ReУчастник, который мы отметили, что файлы в коммитах заморожены на все времена. Они все только для чтения. Они также сжаты, чтобы занять меньше места. Мне нравится называть их сублимированными . Будучи замороженным таким образом, если у вас есть много коммитов, которые продолжают использовать большинство одних и тех же файлов, Git может и на самом деле просто повторно использует уже замороженные файлы. Это один из нескольких приемов, которые Git использует для предотвращения быстрого роста хранилища, даже если каждый коммит сохраняет каждый файл. Но, хотя это отлично подходит для архивирования, очевидно, что оно совершенно бесполезно для выполнения любой новой работы. Вам нужны файлы, которые вы можете читать и писать. Таким образом, git checkout извлекает коммит, который становится вашим текущим коммитом, и получает все его высушенные сублимацией файлы, повторно скопированные в ваше рабочее дерево . Здесь ваши файлы имеют свой обычный повседневный формат. Вы можете использовать их и работать с ними.

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

Каждый раз, когда вы делаете новый коммит, Git на самом деле упаковывает предварительно зафиксированные index копии в коммит. Это происходит очень быстро и не требует и не использует то, что находится в вашем рабочем дереве! Итак, сначала вы должны заставить Git обновить свой индекс, о чем git add. Команда git add freeze сушит файл рабочего дерева и перезаписывает копию в индексе, или, если файл не был в индексе ранее, создает его там.

Упаковав файлы, добавив ваше имя, сообщение в журнал и т. Д., Git устанавливает родителя нового коммита как текущий коммит. Затем он записывает новый коммит в базу данных all-commits:

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

Теперь, когда есть коммит, последний шаг для git commit - сделать имя , в данном случае newbranch, указать коммит I вместо коммита H:

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

Хотя коммиты являются снимками, Git может показывать изменения

Если вы попросите Git показать, что вы делаете коммит I, или H, то Git может просто показать вам все файлы, которые в нем находятся. Но обычно это не то, чего мы хотим знать. Для коммита H нам хотелось бы знать: Что отличается в H от G? Так что когда git log -p или git show показывает коммит H, на самом деле это выглядит как G и H. Независимо от того, какие файлы отличаются, Git сравнивает эти два файла и сообщает, что изменилось. При просмотре commit I Git сравнивает снимки в H и I и сообщает вам, что изменилось.

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

Вернуться к вашим шагам

Итак, это возвращает нас к шагу 2: вы добавили новый коммит в master. Давайте нарисуем это так:

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

(origin/master - это метод запоминания вашим Git, где master находится или находился в репозитории Git, который вы клонировали ранее).

  1. Iтолкнул изменение после перебазирования мастера на вновь извлеченный источник / мастер (хотя это изменение еще не было объединено с удаленным)

То есть вы запустили:

git fetch origin              # or just `git fetch`, which does the same thing
git rebase origin/master      # or `git pull --rebase origin master`
git push origin master

Предполагая, что никто не добавил новые коммиты в репозиторий Git на origin, шаг перебазирования ничего не сделал.Последний шаг, однако, git push origin master, делает что-то важное:

  • Ваш Git вызывает Git на origin
  • Ваш Git узнает, какие коммиты они имеют (по хешу)ID).
  • Ваш Git предлагает любые новые коммиты, которые у вас есть, что они не делают, то есть коммит I, также по хеш-идентификатору.
  • Ваш Git заканчиваетсяgit push сеанс с запросом к ним: Эй, другой Git, пожалуйста, установите master, чтобы он указывал на фиксацию I.

Если они подчиняются этому запросу- и здесь нет причин, по которым они не должны этого делать, - они получат:

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

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

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

Вы добавили:

(изменение еще не было объединено с удаленным, хотя)

, но это не имеет значения: это ни истина, ни ложь, поскольку нет ничего для объединить; но у них есть есть ваш новый коммит I и их master указывает на I.

Я сделал новую ветку изmaster вызвал bug1 и внес некоторые изменения

Итак, вы запустили, например:

git checkout -b bug1

, который добавил новую метку ветки bug1, указывающую на коммит I, и сделал это HEAD:

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

и затем вы изменили некоторые файлы, запустили git add и git commit, чтобы сделать новый коммит J:

...--F--G--H--I   <-- master, origin/master
               \
                J   <-- bug1 (HEAD)
Я выдвинул bug1 после перехода в origin / master после выборки (это изменение еще не объединено с удаленным)

Опять же, "еще не объединено" в значительной степени бессмысленно:ваш Git вызвал их Git, предложил им ваш коммит J и попросил их создать имя bug1, указывающее на коммит J.Нет никакого вопроса о слиянии во время любого git push: все зависит только от того, принимают ли они ваши запросы на изменение или создают какие-либо имена филиалов.Так что теперь у них есть только два названия веток: master указывает на I и bug1 указывает на J.Затем ваш собственный Git говорит itseif: Ах, они приняли запрос на создание bug1, поэтому я сейчас вспомню, что у них есть bug1, указывающий на коммит J. Это означает, что ваш собственный репозиторий можеттеперь можно нарисовать как:

...--F--G--H--I   <-- master, origin/master
               \
                J   <-- bug1 (HEAD), origin/bug1
Я сделал ветку от мастера, названную bug2.

То есть:

git checkout -b bug2 master

или эквивалентный.Это выбирает коммит I в качестве текущего коммита и создает для него новое имя, извлекает содержимое I в ваш индекс и рабочее дерево и присоединяет HEAD к новому имени:

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

(Если вы использовали git branch, это не отрегулировало HEAD, и вы все еще могли бы быть на коммите J, но в остальном это работает почти так же.)

Я исправил свой последний коммит на master (с необходимой информацией, которая была бы полезна для bug2)

Итак:

git checkout master

На этом шаге выбирается коммит I в качестве текущего коммита и присоединяет HEAD к имени master.При необходимости ваш индекс и дерево работы корректируются в соответствии с I, но если вы уже используете I, здесь нет работы;конечный результат:

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

Итак, единственное реальное изменение состояло в том, чтобы изменить, к какому имени HEAD было присоединено, если вы не были на коммите J, и в этом случае он также проверял коммитI.Тогда вы сделали:

<make some changes and run git add>
git commit --amend

Опция --amend фактически не изменяет коммит I вообще. Вместо этого он делает новый, другой коммит. Мы могли бы назвать это I' (чтобы отразить, что это копия I) или просто дать ему совершенно новое письмо K. Я сделаю последнее здесь. Сложность git commit --amend заключается в том, что вместо указания на I новый коммит указывает на родитель *1340* H:

             K   <-- master (HEAD)
            /
...--F--G--H--I   <-- bug2, origin/master
               \
                J   <-- bug1, origin/bug1

Обратите внимание, что фиксация I все еще существует и все еще запоминается через bug2 и origin/master. В репозитории Git на origin все еще есть коммит I; он все еще помнит коммит I через master. Ваш собственный bug1 запоминает коммит J, который запоминает коммит I, а Git over на источнике также имеет J (который обязательно помнит I - коммиты идентичны в каждом репозитории Git что у них есть).

Вы можете, локально, отследить, кто что имеет, посмотрев на все ваши собственные имена ветвей - bug1, bug2 и master - и пройдя назад, по одному коммиту за раз, чтобы увидеть, какие коммиты вы можете достичь. Сделайте то же самое со своими именами удаленного отслеживания origin/bug1 и origin/master, чтобы увидеть, какие коммиты у вас есть, которые они могут достичь. 1

Теперь перейдем к проблемному шагу:

  1. Я проверил bug2 и сделал git rebase master, надеясь, что все изменения, которые были сделаны в master, теперь будут отражены в bug2.

Теперь мы должны посмотреть, что делает rebase - но вкратце, он копирует коммитов, как будто git cherry-pick. Копии коммитов основаны на аргументах, которые вы ему даете.


1 По крайней мере, это то, что у них есть , насколько вам известно вашему Гиту . Он не вызывал другого Git более чем за три секунды , и кто знает, как сильно он мог измениться за все это время! Здесь может быть важно знать, кто меняет исходный Git-репозиторий. Как быстро он обновляется?


Который совершает ребазу копий

Вернемся к графику, скорректированному на git checkout bug2:

             K   <-- master
            /
...--F--G--H--I   <-- bug2 (HEAD), origin/master
               \
                J   <-- bug1, origin/bug1

и команде:

git rebase master

Здесь вы использовали простейшую форму git rebase. Вы можете использовать git rebase --onto, чтобы отделить цель перебазирования - , куда поместить копии , более или менее - от ограничителя , то есть , что не скопировать . С простейшей формой это одно и то же: master - это и , куда помещать копии и , что не нужно копировать .

Git теперь начинается с bug2 и идет назад по графику, перечисляя коммиты. Это I, H, G и т. Д. Он также начинается с master и идет назад, перечисляя коммиты: K, затем H, затем G и так далее. Все, что находится в втором списке, получает исключено . 2 Так что остается коммит I.

Результирующий список - это список коммитов для копирования (в неправильном порядке, поэтому Git перевернет список, но здесь есть только одна запись). Rebase теперь будет пытаться скопировать коммит I после коммита K. Точный механизм копирования немного сложен - некоторые git rebase операции на самом деле запускают git cherry-pick, а некоторые нет - но в любом случае он должен иметь одинаковый результат и работать как git cherry-pick , По сути это означает, что объединяет изменения, которые кто-то сделал в пути H -vs- I, с изменениями, которые кто-то сделал в пути H -vs- K .

Конечно,этот "кто-то" был вы .Эти изменения противоречили друг другу.Git не смог решить конфликт самостоятельно.Если бы Git смог разрешить конфликты самостоятельно, Git попытался бы сделать новый коммит - копию I, за исключением того, что родитель нового коммита был бы K - и тогда это было бы сделано и установило быимя bug для указания нового коммита.Поскольку это копия I (но на самом деле этого не произошло), давайте назовем ее I', просто чтобы показать, как все это выглядело бы, если бы произошло :

               I'  <-- bug2 (HEAD)
              /
             K   <-- master
            /
...--F--G--H--I   <-- origin/master
               \
                J   <-- bug1, origin/bug1

Вы можете разрешить конфликты, выбрав все файлы из коммита K, т. Е. Отбросив все изменения из H -vs- I, которых еще нет в H -vs- K, и просто сохранитечто в K.Но тогда нет никакого смысла вообще беспокоиться о новом коммите: просто сделайте так, чтобы имя bug2 указывало прямо на коммит K, например:

             K   <-- bug2 (HEAD), master
            /
...--F--G--H--I   <-- origin/master
               \
                J   <-- bug1, origin/bug1

Вы можете сделать это с git rebase, вам просто нужна более длинная форма:

git rebase --onto master <anything that specifies commit I>

--onto master говорит Git: ставить копии после коммита, идентифицируемого master, т.е.после K, а остальное говорит Git: не копируйте commit I и ничего более раннего .Так что это сделало бы работу: скопировало бы no commit, затем переместило бы имя bug2.

В данном случае проще просто переместить имя bug2 самостоятельно.Это особенно легко , потому что вы никогда не использовали git push, чтобы сказать любому другому Git, чтобы имя bug2 запомнило что-нибудь.Так что больше никому не нужно говорить: О, эй, bug2 Я просил тебя сделать?Забудьте об этом, используйте это вместо этого.

Однако у вас do есть проблема с master и bug1.Вы сказали другому Git - тот, что в origin - запомнить * commit 1515 * как их master, и J как их bug1.Они, вероятно, до сих пор помнят это.Вы можете сказать, что другой Git: Вот коммит K.И, о, эй, тот коммит I, который вы помните как свой master ... запомните новый коммит K как свой master. , который требует принудительного толчка ,git push -f или git push --force-with-lease.Вы также должны сделать что-то со своим собственным bug1, например, использовать git rebase --onto, чтобы скопировать коммит J в новый и улучшенный коммит, который идет после K, а затем вызвать Git на origin,отправьте им новый и улучшенный коммит и заставьте их передвинуть их bug1.

Что теперь делать

Во-первых, вам нужно выйтиперебазировать ад.:-) Самый простой способ сделать это - использовать:

git rebase --abort

, который возвращает все так, как было до того, как вы начали.То есть теперь вы вернулись к:

             K   <-- master
            /
...--F--G--H--I   <-- bug2 (HEAD), origin/master
               \
                J   <-- bug1, origin/bug1

Теперь вы можете делать одно (или несколько) из многих вещей.Вот два простых варианта:

  • git checkout -B bug2 master или
  • git checkout master; git branch -D bug2; git checkout -b bug2

В результате:

             K   <-- bug2 (HEAD), master
            /
...--F--G--H--I   <-- origin/master
               \
                J   <-- bug1, origin/bug1

Теперь вы можете продолжать работать с bug2.

В какой-то момент - в любое время или позже - вы можете запустить:

git push --force-with-lease origin master

, который заставляет ваш Git вызывать их Gitпредложите им свой новый коммит K и скажите им: я думаю, что ваши master очков за коммит I.Если это так, укажите вместо этого K. 2 Это очень похоже на --force / -f, за исключением того, что обычная сила просто посылает им команду: Установите master! Если кто-то еще, предположительно, не вы, добавил больше , совершивший мимо I, вы вызовете Git на origin до потерять эти коммиты.В этом случае вы хотите их master "потерять" коммит I: его заменили на новый и улучшенный K.

Тогда, в конце концов, вы будетеВероятно, также хотите:

git checkout bug1
git rebase --onto master bug1~1

Это сспециальный бит синтаксиса - bug1~1 - сообщает Git: Начиная с bug1, работает в обратном направлении через один коммит. То есть он находит bug1 (коммит J) и затем отступает на один шаг назадсовершить I.Это коммиты , а не для копирования - I и все, что раньше - поэтому git rebase теперь скопирует J, выполнив сравнение I -vs- J и добавив эти измененияследовать за любым коммитом на вершине master.Если вы сделали несколько bug2 коммитов, но не новые master, и заставили origin/master запомнить K, это может выглядеть так:

               L--M   <-- bug2
              /
             K   <-- master, origin/master
            / \
...--F--G--H   J'   <-- bug1 (HEAD)
            \
             I
              \
               J   <-- origin/bug1

Теперь, когда вы 'Заменив J новым и улучшенным J', вы можете сказать Git на origin, что все в порядке, забывать все о I и J: они должны установить свои bug1 запомнить J' вместо:

git push --force-with-lease origin bug1

или, конечно, более простой git push -f origin bug1, который просто предполагает их bug1 указывает на J и заставляет их забытьI-J часть и просто запомните J'.


2 Опция --force-with-lease должна сообщить другому Git: Я думаю, что ваше имя указывает на фиксацию .Если это так, установите его на . Место, где он получает эту информацию, остается неизменным: ваш Git имеет ваш origin/master, помня, где ваш Git видел их Git's master в последний раз, когда вы запускали git fetch или удалось с git push origin master.Поэтому, если имя для обновления - master, старый хеш принадлежит вашему собственному origin/master;если имя для обновления bug1, старый хеш принадлежит вашему собственному origin/bug1.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...