TL; DR
См. команду git merge-file
, которая позволяет вам делать то, что вы хотите.
Long
The -m
или --merge
флаг git checkout
имеет несколько различных значений.
При использовании с:
git checkout -m <commit-specifier>
он имеет значение, которое вы хотите, больше или меньше;проблема в том, что он применяется ко всем путям.
При использовании с:
git checkout -m [--] <paths>
он имеет другое значение: это означает, что Git должен для каждого именованного пути в <paths>
,заново создайте конфликты слияния в копии рабочего дерева файла, который имеет (или имел) несколько записей индекса более высокого уровня.
Здесь есть более фундаментальная проблема.Часть этого - просто хитрая фразеология - мы все говорим, например, «изменения в рабочем дереве» - но другая часть заключается в том, как думать о том, что делает Git:
... большинство, еслине все изменения в текущей ветви уничтожены
Это говорит о том, что вы думаете о том, что находится в копии рабочего дерева каждогофайл как изменяется, но на самом деле это не так.Git нигде не хранит изменения, 1 , а копии файлов рабочего дерева в основном предназначены для использования по мере необходимости: Git в основном использует моментальные снимки, а файлы хранятся в том, что я люблю называть стоп-кадром.высушенный формат, в объектах BLOB-объектов , связанных с фиксациями, и в индексе.
Там - это понятие текущей ветви , а также текущий коммит , но ветвь - это просто имя (хранится в HEAD
), в то время как коммит - это объект фиксации, идентифицируемый его хеш-идентификатором (хранится в имени ветки), постоянный (в основном) инеизменный (полностью).Фиксация содержит - косвенно - полный снимок каждого исходного файла.Индекс, который также важен в Git, также хранит снимок, но в отличие от коммитов, то, что находится в индексе, является изменчивым.
Между тем, каждый коммит хранит хэш-идентификатор некоторого набора родительский коммит - обычно один такой коммит.Когда у вас есть Git, показывающий какой-то коммит, Git фактически извлекает все файлы из родительского и самого коммита, 2 , затем сравнивает (все файлы в) два коммита и показывает, что вы хотите разные .Поэтому, когда вы смотрите на коммит, он выглядит как с изменениями.
Git делает то же самое с индексом: он сравнивает текущий коммит с индексом, показывая различия и вызовэти изменения для коммита .Затем он сравнивает индекс - который, по сути, представляет собой снимок, который вы предлагаете, будет быть следующим коммитом, если вы выполнили git commit
прямо сейчас - с рабочим деревом.Что бы ни отличалось между индексом и рабочим деревом, Git показывает эти различия, называя эти изменения не подготовленными для фиксации .Но во всех трех наборах файлов - зафиксированных файлах, файлах в индексе и файлах в рабочем дереве - на самом деле есть не изменений , а снимков .
То, что обычно делает git checkout
- есть куча исключений, потому что git checkout
- это действительно несколько разных команд, все втиснутых в один пользовательский глагол, - это извлечение файлов из моментального снимка фиксации, запись этих файлов в индекс (такчто индекс и фиксация совпадают), а затем записывает копии индекса в рабочее дерево (так, чтобы индекс и рабочее дерево соответствовали).Но прежде чем делать что-либо из этого, он сначала проверяет, чтобы убедиться, что вы не потеряете несохраненную работу, сравнивая текущий коммит с индексом и индекс с рабочим деревом: если эти два не совпадают, естьчто-то git checkout
будет стучать.
Как только выНапример, в режиме git checkout -- <paths>
вы фактически переключаетесь на совершенно другую серверную операцию. Эта операция начинается не с фиксации, а с индекса. В свое время файлы были скопированы из коммита в индекса, поэтому в индексе есть некоторый набор файлов. Этот набор, возможно, был обновлен с момента последней обычной проверки или полного сброса или чего-либо еще: каждый git add
означает копировать файл из рабочего дерева в индекс , и если файл рабочего дерева этого не сделал сопоставьте индексную копию, ну, теперь это так, набор файлов в индексе изменился. Индекс может даже содержать ненулевой этап записей, которые представляют текущие конфликты слияния из неполного git merge
. В этом случае индекс по существу хранит не одну, а три сублимированные копии некоторых файлов, от трех входов до более ранней операции git merge
. 3 Но, в одну или другой, этот вид git checkout
вообще не возвращается к коммиту: он просто берет файлы из индекса и записывает их, или для -m
повторно объединяет их и удаляет все, что есть. в рабочем дереве. Это делает без , сначала спрашивая, все ли в порядке. 4
(Edit: есть также git checkout --patch
, но на самом деле это вызывает третий режим. Операция исправления, которая сравнивает две версии файла и позволяет вам выбрать части этой разницы для применения к одному из две версии, фактически обрабатываются программой Perl, которая запускает git diff
между двумя версиями. Это реализует git checkout --patch
, git add --patch
, git stash --patch
и git reset --patch
.)
В любом случае, суть в том, что git checkout -m -- path
не делает то, что вы хотели. Вы можете получить то, что вы хотите, но не используя git checkout
. Вместо этого вам нужно извлечь три входных файла, которые вы хотите передать в git merge
- вывести эти три файла куда угодно; им даже не нужно быть в рабочем дереве для самого репозитория, а затем запустить на них команду git merge-file
.
1 Хорошо, за исключением случаев, когда вы сохраняете выходные данные git diff
или, в качестве особого случая, каждую из частей сохраненного конфликта слияния из git rerere
, но все они приведены ниже нормальный уровень видимости.
2 Из-за внутреннего формата сублимированного файла Git фактически не нужно извлекать идентичных файлов, только те, которые отличаются хотя бы одним битом.
3 Технически, это до три записи на файл. В таких случаях, как конфликт изменения / удаления, у вас будет всего две записи, например, для некоторого файла. Кроме того, когда вы завершите разрешение конфликта слияния и git add
файла, записи более высокого уровня исчезнут. Однако до тех пор, пока вы не совершите фиксацию, эти записи более высокого уровня хранятся в секретной невидимой индексной записи типа «REUC», в частности, чтобы вы могли использовать git checkout -m
для возврата конфликта. Нет возможности просмотреть или сохранить эту невидимую запись, один из нескольких недостатков в текущем формате индекса.
4 С точки зрения удобного дизайна это особенно плохо, потому что другая форма git checkout
очень осторожна, чтобы не потерять работу.