Как я могу иметь различные коммиты для слияния и разрешения конфликтов - PullRequest
0 голосов
/ 23 апреля 2020

Я слил ветвь разработки в свою ветвь функций, что привело к конфликтам слияния после разрешения тех, которые я зафиксировал и подтолкнул. Теперь проблема заключается в том, что изменения слияния и разрешения конфликтов находятся в одном коммите, и трудно найти, что было сделано для решения проблемы. конфликты. Как можно получить две отдельные фиксации, одну для слияния, а другую для исправления конфликтов при конфликтах слияния?

1 Ответ

1 голос
/ 23 апреля 2020

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

Я приведу план того, как захватить то, что вы можете, но не фактический код для него. Вместо этого я опишу, что такое установка, и что будет go неправильным.

Long

Проблема заключается в следующем:

  • Git builds new фиксирует из файлов, которые появляются в Git s index (он же область подготовки ).
  • Конфликты слияния , с маркерами конфликта, появляются только в вашем рабочем дереве .

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

Помните, что коммиты действуют как снимки: они имеют полная копия каждого файла. Но копия моментального снимка любого данного файла внутри коммита хранится в специальном, только для чтения, формате Git. Буквально нельзя изменить , и никакие программы, кроме Git, не могут его использовать. Следовательно, когда вы используете git checkout или git switch для выбора какого-либо конкретного коммита для просмотра и работы с / с, Git должен скопировать файлы из коммита в рабочую область: ваше рабочее дерево или рабочее дерево . Эти файлы являются обычными повседневными файлами. Зафиксированные файлы все еще там, в текущем коммите , так что это обеспечивает две копии каждого файла:

  • В текущем коммите есть замороженная копия: HEAD:README.md например. Запустите git show HEAD:<em>path</em>, чтобы увидеть его.

  • И есть обычный повседневный файл в README.md: используйте любой просмотрщик, который вам нравится, и любой редактор, который вы хотите изменить, чтобы изменить его. .

Но между этими двумя Git сохраняется третья копия 1 файла. Эта копия находится в Git index , который Git также называет областью подготовки . Эта копия находится в замороженном формате, но в отличие от подтвержденной копии, вы можете заменить ее, оптом, на новую копию. Вот что делает git add: он берет копию рабочего дерева, сжимает ее в специальный формат Git и помещает эту копию в индекс Git, готовый для фиксации.

  • Чтобы увидеть копию индекса, запустите git show :<em>path</em>, например, git show :README.md.

Обычно копия индекса соответствует либо копии HEAD (потому что вы только что извлекли коммит, либо просто зафиксировано) или копия рабочего дерева (потому что вы просто git add -дали файл) или соответствует обеим другим копиям (git status говорит nothing to commit, working tree clean). Но можно:

  • проверить некоторые коммиты (все три совпадают)
  • изменить файл рабочего дерева (HEAD и индексное соответствие, рабочее дерево нет)
  • git add измененный файл (HEAD не совпадает, индекс и рабочее дерево не соответствуют)
  • измените файл еще

и теперь все три копии другой. Здесь нет ничего принципиально неправильного: вот как Git работает, а git add -p и git reset -p предназначены для того, чтобы вы могли сознательно манипулировать подобным образом ситуацией. Они работают, копируя индексную копию файла во временный файл, а затем позволяют патчить этот временный файл, по одному diff-hunk за раз, и копировать его обратно в индексную копию.

В любом В этом случае это установка normal , когда вы не находитесь в конфликтующем слиянии:

  • HEAD представляет текущий коммит, а текущий коммит имеет копия каждого файла, который вы не можете изменить. Вы можете изменить, какой коммит является текущим коммитом (проверяя некоторые другие коммиты), но вы не можете изменить файлы, хранящиеся в этих коммитах. Существует простой доступ к HEAD копии подтвержденного файла, и git status, git diff и т. Д. Будут просматривать эти копии.

  • В индексе хранятся копии всех файл. Вы можете изменить эти копии. Обычно вы делаете это, копируя файл рабочего дерева, как он есть сейчас, в индексную копию, используя git add. Или вы можете изменить его, скопировав копию HEAD обратно в индекс, используя git reset.

  • В рабочем дереве хранится копия каждого файла. Эта копия ваша: Git перезаписывает ее, только когда вы указываете Git перезаписать ее. Git не использует его, когда вы git commit: Git использует копии из Git index .

Но, когда вы входите в состояние конфликта слияния, индекс расширяется. Вместо того, чтобы просто содержать одну копию конфликтующего файла, теперь он содержит three . Теперь все становится сложнее.


1 Технически, индекс содержит ссылки , а не фактические копии, но эффект тот же, если вы не начнете используя git ls-files --stage и git update-index, чтобы углубиться в детали низкого уровня.


Слияние с конфликтами

Как вы обнаружили, при запуске:

git checkout somebranch
git merge other

иногда Git может выполнить слияние и завершить sh самостоятельно, а иногда получает некоторое выполненного слияния, но выплевывает некоторые CONFLICT сообщения и останавливается в середина слияния.

На самом деле существует два разных вида конфликтов, которые я люблю называть высокий уровень и низкий уровень . Те, с которыми большинство людей сталкиваются первыми, потому что они являются наиболее распространенными, являются конфликтами низкого уровня. Они генерируются в Git коде ll-merge.c, где ll означает «низкий уровень», отсюда и название.

При объединении в Git используется довольно стандартный алгоритм трехстороннего слияния . Git фактически использует рекурсивный вариант по умолчанию; Вы можете отключить его, используя git merge -s resolve, но редко для этого есть причина. Для любого трехстороннего слияния требуются три входных файла: общая (общая) базовая версия слияния, левая или локальная или --ours версия и правая или удаленная или --theirs версия. Слияние просто сравнивает базу как слева, так и справа. Это производит набор изменений, которые будут сделаны. Слияние объединяет изменения: если левая сторона фиксирует написание слова в строке 42, примите это изменение; если правая сторона удаляет строку 79, также примите это изменение.

Конфликты - или, более конкретно, низкоуровневые конфликты - возникают, когда левая и правая стороны пытаются внести разные изменения в одно и то же область одного файла. Здесь Git просто не знает, принять ли изменение левой стороны, изменение правой стороны, оба или ни одного. Таким образом, он останавливает объединение с конфликтом (после объединения с тем, что он может объединить самостоятельно).

Высокие уровни конфликты возникают, когда происходят изменения всего файла. То есть, изменение левой стороны может включать направление: переименовать README.md в README.rst. Если правая сторона не переименовала README.md, или переименовала его, но тоже на README.rst, это нормально. Но что, если правая сторона говорит переименовать README.md в README.html: как Git объединить эти изменения?

Опять Git просто дает и объявляет конфликт. На этот раз, однако, это конфликт высокий уровень .

В обоих случаях, то, что Git делает в Git index , просто: это просто сохраняет все копии . Чтобы иметь возможность различать три различных файла README.md - при отсутствии сложных конфликтов переименования - просто нумерует файлы в индексе:

  • git show :1:README.md показывает вам базовая версия слияния;
  • git show :2:README.md показывает вам версию --ours; и
  • git show :3:README.md показывает вам версию --theirs.

Git записывает новую копию рабочего дерева README.md с маркерами конфликта, но исходные три входы все еще там, в индексе. Ваша задача как лица, выполняющего слияние, не обязательно состоит в том, чтобы исправить копию work-tree . Git эта копия не нужна: эта для you . Git нужна окончательная версия в индексе Git.

Указанные выше номера индексов номера слотов , а окончательная копия переходит в нулевой слот , который стирает остальные три слота. Ваша задача состоит в том, чтобы придумать правильный README.md и поместить его в нулевой слот.

Один из простых способов сделать это - редактировать рабочее дерево README.md - заполненное его маркерами конфликта - до тех пор, пока вы иметь правильный объединенный результат. Затем вы записываете этот файл обратно в рабочее дерево и запускаете git add README.md. Это копирует README.md из вашего рабочего дерева в индекс, как обычно: копия попадает в нулевой слот, стирая остальные три слота.

Существование трех других записей слотов - :1:README.md, :2:README.md и / или :3:README.md - это то, что помечает файл как конфликтующий . Теперь, когда все они исчезли, файл больше не конфликтует.

Вы можете использовать любую процедуру, которая вам нравится, чтобы поместить правильный файл в нулевой слот. Это все, что действительно волнует Git: правильный файл go в нулевой слот, а остальные три слота удаляются. Необычный инструмент, который вызывается git mergetool, может быть удобен для вас, но, в конце концов, он работает, копируя конечный результат в нулевой слот и стирая другие слоты. Git вообще не заботится о вашем файле рабочего дерева; Git просто нужно исправить свой индекс.

Когда вы получаете высокий уровень конфликт, такой как конфликт переименования / переименования или конфликт изменения / удаления, Git записывает это в индексе Git тоже - но на этот раз это записано тем, что есть некоторые слоты, которые не заняты . Помните, слоты go с источником файла: база слияния = слот 1, наши = 2, их = 3. Так что, если база слияния имела README.md, у нас есть README.rst, а у них README.html, что вы получите:

  • :1:README.md существует, но: 2: и: 3: не
  • :2:README.rst существует, но: 1: и: 3: не
  • :3:README.html существует, но: 1: и: 2: не

Ваша задача - удалить все три из них и поместить что-то в какой-то слот ноль. Он не обязательно должен называться README.md или README.rst или как угодно: возможно, вы можете создать файл нулевого слота с именем README.who-knows.

Ваш новый коммит слияния, когда вы его сделаете, будет состоять из все файлы в слоте ноль. Вы не можете сделать коммит, пока все промежуточные слоты с большими номерами не будут очищены. Таким образом, вы должны разрешить каждый конфликтующий файл самостоятельно: только тогда вы сможете запустить git merge --continue или git commit, чтобы получить окончательный результат коммит слияния.

Вы можете просто запустите git add для всех конфликтующих файлов. Если у рабочего дерева есть конфликт README.md с низким уровнем конфликта, с маркерами конфликта, это копирует версию рабочего дерева в нулевой слот индекса и стирает три других слота. Если это был единственный конфликт, то теперь вы можете это сделать. Проблема в том, что вы потеряли все три входных файла: вам придется позже объединить и разрешить конфликты. Но вы можете просто использовать git add для каждого файла и затем фиксировать.

Это не очень хорошо работает с конфликтами высокого уровня: если есть конфликт переименования / переименования, какое имя вы должны использовать? Если возникает конфликт изменения / удаления, сохраняете ли вы измененный файл или удаляете удаление?

Что бы вы здесь ни выбрали, вы разрешили этот конфликт. Фиксация слияния будет хранить в качестве своего нового снимка все, что вы поместили в записи индекса нулевого слота.

Если вы сохранили конфликтующие файлы и хотите вернуть конфликты, единственный способ получить это - повторно выполнить слияние или, что эквивалентно, сохранить данные о конфликте слияния (входные файлы и / или индекс). Не ясно, что из этого легче: у обоих есть много потенциальных проблем. Я думаю, что с наименьшим количеством ловушек стоит использовать git merge-file, который выполняет низкоуровневое слияние трех входных файлов.

Заключение

Так что вы могли бы, для каждого низкого Уровень конфликтующего файла:

  1. Извлеките где-нибудь три копии файла. (Примечание: git checkout-index имеет опции для этого. Вот как git mergetool предоставляет три копии вашему инструменту слияния.)
  2. git add конфликтующий файл из рабочего дерева, чтобы разрешить конфликт принимая размеченную версию в качестве правильного разрешения.
  3. Запустите git merge --continue для подтверждения слияния.
  4. Используйте git merge-file для файлов, сохраненных на шаге 1, чтобы заново создать конфликты.
  5. Устраните конфликты вручную.
  6. git add результирующих файлов, чтобы скопировать их в индекс Git.
  7. Создать новый коммит.

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

git checkout <hash>
git merge <hash>

где два значения hash являются идентификаторами ha sh для двух коммитов, которые somebranch и otherbranch идентифицировали во время выполнения исходной команды git merge. Эти два значения ha sh легко найти из коммита слияния: они являются его первым и вторым родителем соответственно. Следовательно, если $M содержит объединение ha sh ID:

git rev-parse $M^1 $M^2

показывает вам два идентификатора ha sh, которые необходимо повторить слияние для повторного получения конфликтов. Единственное, чего здесь не хватает, это любые параметры, которые вы указали для команды git merge. Git не сохраняет их (я думаю, что должно) - но вы можете вручную сохранить их в своем сообщении журнала, если ничего больше.

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