Редактировать: я совершенно пропустил git subtree split -P prefix
часть этого. Первоначальный ответ по-прежнему применим, но с возможно фатальным поворотом.
Когда вы запускаете git subtree split -P prefix [ options ] [ commit-range ]
, вы указываете Git скопировать некоторые коммиты в новые. У вас есть Git, скопируйте любые коммиты, содержащие любые файлы в заданном prefix
, но со следующими изменениями:
- Отбрасывать все файлы, которые не начинаются с заданного
prefix
.
- Переименуйте оставшиеся файлы, чтобы убрать
prefix
(и косую черту).
- Если фиксация совпадает с ранее скопированной фиксацией, полностью удалите ее.
(Вы можете сделать это и с git filter-branch
, хотя это будет медленнее, чем git subtree split
, и для этого потребуется сначала создать новую ветвь для фильтрации.)
Результатом является новый непересекающийся граф коммитов (или подграф, поскольку он теперь добавлен к вашему основному графу коммитов), укореняющийся в первом скопированном коммите и заканчивающийся в последнем скопированном. (Процесс копирования должен перечислять коммиты, в обычном обратном порядке Git, из одного коммита, а не из нескольких коммитов. это должно быть.) Затем вы можете дать этому новому подграфу имя ветви, используя опцию -b branch
git subtree
. Если вы не дадите ему имя, у вас будет короткий период (по умолчанию 14 дней), в течение которого вы можете что-то сделать с хэш-идентификатором подсказки, который печатает git subtree split
, и после этого копии имеют право на автоматическую сборку мусора.
В качестве краткой иллюстрации рассмотрим следующий график:
C--D--E
/ \
A--B H--I--J--K <-- master
\ /
F-----G
Допустим, коммит A
имеет в README
(и ничего больше), B
добавляет первую часть проекта, C-D-E
больше проекта, F
и G
были из ветки функций и добавляют поддерево с именем subbie
, содержащее различные файлы, H
объединяет поддерево, в I
оно переименовывается в feature
, в J
с ним ничего не происходит, а в K
добавляется feature/README_TOO
.
Если теперь вы разделите feature
как поддерево, это заставит Git скопировать:
I
: feature
сначала появляется как имя, содержащее, например, feature/__init.py
и feature/impl.py
.
K
: появляется feature/README_TOO
.
В качестве нового независимого подграфа коммитов это выглядит так:
C--D--E
/ \
A--B H--I--J--K <-- master
\ /
F-----G
I'--K' <-- dash-b-argument
Обратите внимание, что мы не копировали F
, G
и H
: у них нет файлов, имя которых начинается с feature/
. В коммите J
такие файлы есть, но они такие же, как и в коммите I
, поэтому мы его пропустили. При этом имена файлов в коммитах I'
и K'
не feature/__init__.py
и так далее, а просто __init__.py
и так далее.
Как я отметил в исходном ответе, история в репозитории это коммиты. Мы просматриваем историю, начиная с фиксации кончика ветки и работая в обратном направлении. Если мы начнем с K'
и вернемся к I'
, в истории останутся только эти два коммита. Чтобы обнаружить переименование, нам нужно было бы также скопировать коммиты F
и G
по крайней мере, а может быть, и H
(на этот раз H
нечего объединять, так как мы пропустим A-B-C-D-E
, так что мы, вероятно, просто отбросьте H
полностью). Но для этого нам нужно знать, как сохранить subbie/*
.
Вы можете изменить код git subtree
, чтобы разрешить дополнительные аргументы префикса «сохраняется как-как». Однако нет четкого способа изменить это постфактум. Базовый код git subtree
основан на уникальном префиксе: он был всегда удален, поэтому, чтобы отменить преобразование, мы всегда добавляем его обратно. Два очевидных варианта: никогда не удалять префиксы (поэтому никогда ничего не добавлять) или требовать, чтобы дополнительные неудаленные префиксы никогда не "сталкивались" с именами без префиксов. То есть для любой произвольно скопированной фиксации, если ее снимок имеет файл с именем pa/th/to/file.ext
, либо pa/th/to
является не префиксом, "сохраненным на месте" (поэтому префикс -P
добавляется обратно), либо pa/th/to
является таким префиксом (так что ничего не добавляется).
Оригинальный ответ
В Git у файлов нет истории. Нечего сохранять!
В Git только коммиты имеют (точнее, являются) историю. Каждый коммит представляет собой полный снимок исходного дерева плюс некоторые метаданные: имя, адрес электронной почты и отметка времени (как автора фиксации), еще одна тройка имя/адрес электронной почты/отметка времени (для коммиттера); сообщение журнала фиксации; и — важно для формирования истории — идентификатор родительского коммита.
(Некоторые коммиты, которые мы называем коммитами слияния, имеют двух или более родительских элементов. По крайней мере, один коммит — а именно первый сделанный — не имеет никаких родителей; мы называем это < em>корневой коммит. Но у большинства коммитов есть только один родитель, которым обычно является коммит, являющийся верхушкой некоторой ветки, непосредственно перед тем, как коммиттер сделал новый коммит, который стал кончик этой ветки.)
Сравнивая коммит с его родителем, мы узнаем, что произошло с течением времени. Если в предыдущей (родительской) фиксации было 10 файлов, а в последующей (дочерней) фиксации было 11 файлов, то кто-то должен был добавить файл. Если дочерний коммит имеет новую строку 20 в README.txt
, они должны были добавить эту строку. Но мы обнаруживаем их только динамически, сравнивая родительский и дочерний элементы. Это история, сформированная коммитами.
Код git blame
по мере того, как он работает от дочернего элемента обратно к родительскому (и затем рассматривает этот родитель как еще один дочерний элемент другого родителя), будет искать строки, взятые из других файлов, или целые файлы, переименованные из одного места. другому. Насколько хорошо работает этот поиск, это отдельный вопрос, но, как правило, если какой-то файл p/a/t/h.ext
существует в родительском, но не в дочернем, и какой-то другой файл n/e/w.name
существует в дочернем, но не в родительском, Git поместит эти два файла в список «кандидатов на обнаружение переименования».
Если два файла с разными именами абсолютно, на 100 % идентичны друг другу, Git почти всегда1 объединит их в пары. Чем менее идентичными они становятся, тем меньше вероятность того, что Git объединит их в пару. У этой пары есть ручки управления: у git diff
и друзей они имеют значение --find-renames
. Также есть --find-copies
и --find-copies-harder
. В git blame
аргумент -C
управляет вещами несколько по-другому. Я недостаточно экспериментировал с этим, чтобы точно сказать, как это работает, но один или два аргумента -C
определенно должны обнаружить переименование всего файла на основе документацию.
1Для git diff
поиск переименования полностью отключенпо умолчанию в версиях Git до 2.9, но включенпо умолчанию в версиях Git 2.9 и выше. Вы можете установить diff.renames
на true
, чтобы включить его без настройки определенного порога -M
/ --find-renames
в более старых версиях Git.
Существует также максимальный размер очереди сопряжения, настраиваемый как diff.renameLimit
. Преодолеть этот предел удается редко, хотя переименование каждого файла в каталоге (как Git обрабатывает переименование каталога) с большей вероятностью может его достичь. Лимит по умолчанию вырос с годами; раньше было 100, потом 200, а теперь 400 файлов.
person
torek
schedule
08.03.2017