Если вы действительно хотите это сделать, вы можете - ну, в основном . 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
, который выполняет низкоуровневое слияние трех входных файлов.
Заключение
Так что вы могли бы, для каждого низкого Уровень конфликтующего файла:
- Извлеките где-нибудь три копии файла. (Примечание:
git checkout-index
имеет опции для этого. Вот как git mergetool
предоставляет три копии вашему инструменту слияния.) git add
конфликтующий файл из рабочего дерева, чтобы разрешить конфликт принимая размеченную версию в качестве правильного разрешения. - Запустите
git merge --continue
для подтверждения слияния. - Используйте
git merge-file
для файлов, сохраненных на шаге 1, чтобы заново создать конфликты. - Устраните конфликты вручную.
git add
результирующих файлов, чтобы скопировать их в индекс Git. - Создать новый коммит.
Это большая работа, чтобы сделать что-то, чего не делает 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 не сохраняет их (я думаю, что должно) - но вы можете вручную сохранить их в своем сообщении журнала, если ничего больше.