Как извлечь один файл с историей коммитов из git-репо с помощью index-filter & co
Моя ситуация была, у меня есть git-репозиторий, преобразованный из SVN в HG в GIT, и я хотел извлечь только один исходный файл. У меня также были странные символы, такие как aÌ (несоответствие кодировки повреждено Unicode ä) и пробелы в именах файлов.
Кажется, это не очень легко, и именно поэтому я отвечу на свой вопрос, несмотря на многие похожие вопросы, касающиеся git [index-filter|subdirectory-filter|filter-tree], поскольку мне нужно было использовать все предыдущие, чтобы добиться этого!
Итак, вопрос: "Как я могу извлечь один файл из репозитория и поместить его в корень нового репо?"
6 ответов
Сначала быстрое замечание, что даже заклинание, как в комментарии к разделению набора файлов внутри репозитория git на их собственный репозиторий, сохраняя соответствующую историю
SPELL='git ls-tree -r --name-only --full-tree "$GIT_COMMIT" | grep -v "trie.lisp" | tr "\n" "\0" | xargs -0 git rm --cached -r --ignore-unmatch'
git filter-branch --prune-empty --index-filter "$SPELL" -- --all
не поможет с файлами с именем как imaging/DrinkkejaI<0300>$'\302\210'.txt_74x2032.gif
, aI<0300>$'\302\210'
часть когда-то была одной буквой: ä
,
Таким образом, чтобы извлечь отдельный файл, в дополнение к ответвлению фильтра мне также нужно было сделать:
git filter-branch -f --subdirectory-filter lisp/source/model HEAD
В качестве альтернативы вы можете использовать --tree-filter: (тест необходим, поскольку файл ранее находился в другом каталоге, см.: Как я могу переместить каталог в репозитории Git для всех коммитов?)
MV_FILTER='test -f source/model/trie.lisp && mv ./source/model/trie.lisp . || echo "Nothing to do."'
git filter-branch --tree-filter $MV_FILTER HEAD --all
Чтобы увидеть все имена файлов, используйте:
git log --pretty=oneline --follow --name-only git-path/to/file | grep -v ' ' | sort -u
Как описано на http://whileimautomaton.net/2010/04/03012432
Также выполните следующие действия:
$ git reset --hard
$ git gc --aggressive
$ git prune
$ git remote rm origin # Otherwise changes will be pushed to where the repo was cloned from
Более быстрый и легкий для понимания фильтр, который выполняет то же самое:
git filter-branch --index-filter '
git read-tree --empty
git reset $GIT_COMMIT -- $your $files $here
' \
-- --all -- $your $files $here
Обратите внимание, что все становится намного проще, если вы объедините это с дополнительным этапом перемещения нужного файла (ов) в новый каталог.
Это может быть довольно распространенный вариант использования (например, перемещение нужного отдельного файла в корневой каталог).
Я сделал это (используя git 1.9) следующим образом (сначала переместив файл (ы), затем удалив старое дерево):
git filter-branch -f --tree-filter 'mkdir -p new_path && git mv -k -f old_path/to/file new_path/'
git filter-branch -f --prune-empty --index-filter 'git rm -r --cached --ignore-unmatch old_path'
Вы даже можете легко использовать подстановочные знаки для нужных файлов (без возни с grep -v).
Я думаю, что это ('mv' и 'rm') также может быть сделано в одной ветке фильтра, но это не сработало для меня.
Я не пробовал это со странными персонажами, но я надеюсь, что это все равно поможет. Облегчение кажется мне хорошей идеей.
Подсказка:
Это длительное действие на больших репо. Поэтому, если вы хотите выполнить несколько действий (например, получить кучу файлов, а затем переставить их в 'new_path/subdirs'), неплохо было бы выполнить часть 'rm' как можно быстрее, чтобы получить меньшее и более быстрое дерево.
Я нашел элегантное решение, используя git log и git am здесь:https://www.pixelite.co.nz/article/extracting-file-folder-from-git-repository-with-full-git-history/
Если он уйдет, вот как это сделать:
в исходном репо,
git log --pretty=email --patch-with-stat --reverse --full-index --binary -- path/to/file_or_folder > /tmp/patch
если файл находился в подкаталоге, или если вы хотите его переименовать
sed -i -e 's/deep\/path\/that\/you\/want\/shorter/short\/path/g' /tmp/patch
в новом пустом репо
git am < /tmp/patch
Следующее переписает историю и сохранит только те коммиты, которые касаются списка файлов, которые вы даете. Возможно, вы захотите сделать это в клоне вашего хранилища, чтобы не потерять оригинальную историю.
FILES='path/to/file1 other-path/to/file2 file3'
git filter-branch --prune-empty --index-filter "
git read-tree --empty
git reset \$GIT_COMMIT -- $FILES
" \
-- --all -- $FILES
Затем вы можете слить эту новую ветку в ваш целевой репозиторий обычным способом. merge
или же rebase
Команды в соответствии с вашим вариантом использования.
Появилась новая команда
git filter-repo
Настоящее время.
У него больше возможностей и лучшая производительность.
См. Страницу руководства