Способ создания бесхозной ветви без необходимости удаления файлов - PullRequest
0 голосов
/ 15 февраля 2019

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

1 Ответ

0 голосов
/ 15 февраля 2019

Мне совершенно не ясно, какого результата вы хотите, т. Е. Какова ваша настоящая цель.Какова бы ни была эта цель, однако, это, вероятно, неправильный путь для достижения этой цели.


Когда я создаю несвязанную ветвь, мне нужно удалить все файлы, чтобы ветвь была полностью пустой.

Это правда, но неправильно.: -)

В частности, давайте рассмотрим части операции и необходимые вам команды:

  1. git checkout --orphan newbranch: это изменяет ваше текущее состояние Git так, чтобываше index и work-tree не изменились, но вы находитесь на ветви newbranch, которая на самом деле еще не существует.

  2. git commit: это создает новый коммит из того, что есть в вашем индексе . 1 Ваше рабочее дерево не имеет значения.Если вы находились на несуществующей ветви, такой как newbranch из шага 1, это создает эффект создания ветви.Кончик ветки, в которой вы находитесь, теперь является только что созданным коммитом, а родительский элемент только что созданного вами коммита - это предыдущий коммит, который был в ветке - который в состоянии, возникающем на шаге 1, равен no commit, так что новый коммит имеет no parent.

Поэтому, когда я говорю, что это правда, но неправильно, я имею в виду, что ни одна ветвь не является когда-либо пусто.Ветвь - или, точнее, ветвь name - указывает на один конкретный коммит.Этот коммит должен существовать!У этого коммита есть родитель (ы) или нет родителя, если этот коммит является корневым коммитом.Если вы используете слово branch для обозначения слабо определенной цепочки коммитов, заканчивающейся одним конкретным коммитом , это тоже никогда не будет empty .Вам нужно было очистить index , потому что Git делает новые коммиты из того, что есть в индексе.

Предположительно, вы хотите создать новый коммит, имеющий нет файлов в нем.Это не пустая ветка!Это ветвь с одним коммитом - новый коммит не имеет родителя - в котором у одного коммита есть пустое дерево .См. Также Надежен ли полусекретный пустой объект дерева git и почему для него нет символического имени? Но использование этого пустого объекта дерева или коммитов, которые его используют, ограничено.


1 Если вы не знакомы с этим использованием индекса слова - также называемого промежуточной областью или кеш - ирабочее дерево, см., например, В чем разница между HEAD, рабочим деревом и индексом в Git? Подробнее о том, как пользователи Git и Git склонны объединять вещи, называемые branch см. также Что именно мы подразумеваем под "ветвью"?


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

Поскольку пустая ветка не имеет смысла, я должен догадаться, что вы имеете в виду: чтовы сделали git rm -r .; git commit, чтобы создать эту ветку newbranch, указывающую на коммит, чье деревоон пустое дерево.Теперь у вас есть сиротская ветка с одним корневым коммитом.Если вы запустите:

git checkout newbranch

Git удалит все ваши (отслеженные) файлы, оставив вас с пустым индексом.

Если вы сейчас запустите git cherry-pick <hash>, вы получите много жалоб наФорма, которую вы описываете.Это потому, что вишня - это слияние.Давайте нарисуем часть графика коммитов Git:

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

I   <-- newbranch (HEAD)

То есть имя master относится к коммиту H - верхушке нашей главной ветви.Родитель коммита H (каждая заглавная буква здесь обозначает фактический хэш-идентификатор) - коммит G.Родитель G - F, чей родитель находится в диапазоне ....

Commit I имеет no parent;это корневой коммит.Наш HEAD привязан к имени newbranch, которое указывает на фиксацию I.

На этом этапе, running git cherry-pick <hash-of-G> создает эти жалобы, и причина в том, что он выполняет трехстороннее слияние коммитов F в качестве базы слияния, I как --ours и G как --theirs.Трехстороннее слияние состоит из запуска двух git diff с, чтобы увидеть, кто и какую работу сделал:

  • git diff --find-renames <hash-of-F> <hash-of-G>: это то, что они сделали.Git сравнивает снимок в F со снимком в G, чтобы увидеть, что они изменились.

  • git diff --find-renames <hash-of-F> <hash-of-I>: это то, что мы сделал.Git сравнивает снимок в F со снимком в I, чтобы увидеть, что мы изменили.Но мы сделали I, используя пустое дерево, так что разница в том, что мы удалили каждый файл!

Задача механизма слияния - объединить наши изменения - удалитьвсе файлы - с их изменениями, какими бы они ни были.Для любого файла, который они не изменили, Git берет наше изменение и удаляет файл.Для любого файла, который они изменили , Git объявляет конфликт слияния.Если деревья в F и G не совпадают, нам гарантирован как минимум один конфликт слияния, и вишня остановится и оставит вас для завершения работы.

Есть лиспособ автоматизировать конфликты, чтобы принять измененные версии файлов?

Конечно: используйте git ls-files --stage, чтобы найти записи в индексе, в их различных промежуточных слотах.Для любого файла, который существует на этапах 1 и 3, т. Е. Находится как в базе слияния, так и в --theirs, в этом примере F и G, в слоте 2 не будет файла, поскольку мы удалили всефайлы (commit I в этом примере).Ваша задача состоит в том, чтобы заменить три промежуточных слота с более высоким номером одним входом ступень-ноль.Предположительно, вы хотите получить файл из этапа 3 - из коммита --theirs - в промежуточном слоте ноль, и есть простой способ получить его: git add копию файла, находящегося в рабочем дереве.

В этом конкретном конфликтном случае, «удален в нашем» и «изменен в их», Git оставляет измененный файл в рабочем дереве, а также в промежуточном слоте 3. Таким образом, git add поместит копию рабочего деревав нулевой слот и удалите записи в слотах 1 и 3 - в этом слоте нет ничего, что можно было бы удалить.(Если бы было, git add удалило бы это, но если бы было, то, что находится в рабочем дереве прямо сейчас, могло бы не подходить.)

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

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

I <-J   <-- newbranch (HEAD)

Новый коммит J содержиткопия каждого файла в коммите G, отличная от копии того же файла в коммите F.В коммите I остается пустое дерево, и поэтому оно практически бесполезно.

Если коммит J действительно является желаемым результатом, есть более простой 2 способ получить его:

  1. Создать новый пустой временный индекс.
  2. Для каждого файла в F и G, если файлы совпадают, ничего не делать, но если они различаются, обновить временныйиндекс для хранения имени G и идентификатора хэша.
  3. Создание нового коммита из этого временного индекса.

Шаг 2 - единственная сложная часть, но ее легко автоматизировать:

git diff-tree -r --find-renames -z | ...automation-program...

-r превращает git diff-tree в подкаталоги.--find-renames и -z являются необязательными: --find-renames включает детектор переименования (в противном случае переименованные файлы D выбираются из первого коммита и A добавляются ко второму под другим именем), а -z упорядочиваетчтобы данные различий заканчивались символами ASCII-NUL, а не символами новой строки, и чтобы не требовалось "расставлять" сложные имена файлов (см. ФОРМАТ ВЫВОДА RAW в gitдокументация diff-tree ).Вам нужно будет написать программу автоматизации самостоятельно, но она может просто вызывать git update-index --cacheinfo с каждой строкой информации о кеше, используя режим и информацию о хеше, считанную из git diff-tree.

Шаг 1состоит из экспорта переменной окружения GIT_INDEX_FILE, указывающей на имя временного файла, который не существует: Git создаст его по мере необходимости.Шаг 3 - запустить git commit-tree, эквивалентный git commit, а затем использовать git update-ref, чтобы создать или обновить ссылочное имя для ссылки на новый коммит.Вы можете выбрать родителей - или вообще не иметь родителей - для J напрямую.См. Их документацию для использования.


2 Ну, вычислительно проще.Ясно, что нужно написать еще больше работы, вместо запуска git add ..

...