git checkout --merge / - ours / - кажется, что они делают то же самое (не так?)? - PullRequest
1 голос
/ 17 апреля 2019

Я пытаюсь слиться с другой веткой (это сиротская ветка, если это имеет значение). Тем не менее, когда я делаю:

git merge <branch-name>

Кажется, слияние правильно. Тем не менее, если я сделаю:

git checkout --merge <branch-name> -- <file-names>

Большинство, если не все изменения в текущей ветке удаляются. Неважно, если я использую --merge, --ours или --theirs, результаты одинаковы.

Я бы ожидал, что checkout при использовании флага --merge будет делать то же самое, что и merge, за исключением только указанных файлов.

Что происходит? Есть что-то, чего я не понимаю?

1 Ответ

4 голосов
/ 17 апреля 2019

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 очень осторожна, чтобы не потерять работу.

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