Как извлечь подкаталог git и сделать из него подмодуль?

120

Я начал проект несколько месяцев назад и сохранил все в основном каталоге. В моем основном каталоге "Project" есть несколько подкаталогов, содержащих разные вещи: Project / paper содержит документ, написанный в LaTeX. Project / sourcecode / RailsApp содержит мое приложение rails.

«Project» - это GITified, и было много коммитов как в «paper», так и в «RailsApp». Теперь, поскольку я хотел бы использовать cruisecontrol.rb для своего «RailsApp», мне интересно, есть ли способ сделать из «RailsApp» подмодуль без потери истории.

Кер
источник
2
Также очень хороший ответ: stackoverflow.com/questions/359424/…
Рено Линдеке
Возможный дубликат подкаталога Detach (move) в отдельный репозиторий Git
полковник тридцать два

Ответы:

123

В настоящее время есть гораздо более простой способ сделать это, чем вручную использовать git filter-branch: git subtree

Монтаж

ПРИМЕЧАНИЕ git-subtree теперь является частью git(если вы устанавливаете contrib) начиная с версии 1.7.11, так что, возможно, он у вас уже установлен. Вы можете проверить, выполнив git subtree.


Чтобы установить git-subtree из исходного кода (для более старых версий git):

git clone https://github.com/apenwarr/git-subtree.git

cd git-subtree
sudo rsync -a ./git-subtree.sh /usr/local/bin/git-subtree

Или, если вам нужны страницы руководства и все

make doc
make install

использование

Разделите больший на более мелкие части:

# Go into the project root
cd ~/my-project

# Create a branch which only contains commits for the children of 'foo'
git subtree split --prefix=foo --branch=foo-only

# Remove 'foo' from the project
git rm -rf ./foo

# Create a git repo for 'foo' (assuming we already created it on github)
mkdir foo
pushd foo
git init
git remote add origin git@github.com:my-user/new-project.git
git pull ../ foo-only
git push origin -u master
popd

# Add 'foo' as a git submodule to `my-project`
git submodule add git@github.com:my-user/new-project.git foo

Для получения подробной документации (справочная страница), пожалуйста, прочтите git-subtree.txt.

apenwarr
источник
10
git subtree rocks!
Саймон Вудсайд
3
Но разве цель git-subtree не избегать использования подмодулей? Я имею в виду, что вы действительно являетесь автором git-subtree (если нет коллизии псевдонимов), но похоже, что git-subtree изменился, хотя команда, которую вы показываете, все еще действительна. Я правильно понимаю?
Blaisorblade 01
18
git-subtree теперь является частью git (если вы устанавливаете contrib) с 1.7.11
Джереми
8
Хорошо git rm -rf ./fooудаляет fooиз , HEADно не фильтрует my-project«s полная история. Затем git submodule add git@github.com:my-user/new-project.git fooсоздает только fooподмодуль, начиная с HEAD. В этом отношении сценарий filter-branchлучше, поскольку он позволяет добиться «как если бы subdir был подмодулем с самого начала»
Грегори Пакош
спасибо за это - git subtree docs немного сбивает с толку, и это (для меня) наиболее очевидная полезная вещь, которую я хотел с ней сделать ...
hwjp
38

Оформить заказ git filter-branch .

В Examplesразделе справочной страницы показано, как извлечь подкаталог в собственный проект, сохраняя при этом всю его историю и отбрасывая историю других файлов / каталогов (именно то, что вы ищете).

Чтобы переписать репозиторий так, чтобы он выглядел как foodir/корень его проекта, и отбросить всю остальную историю:

   git filter-branch --subdirectory-filter foodir -- --all

Таким образом, вы можете, например, превратить подкаталог библиотеки в собственный репозиторий.
Обратите внимание на --то, что filter-branchпараметры отделяются от параметров редакции, а также --allна перезапись всех веток и тегов.

Пэт Нотц
источник
1
У меня это сработало. Единственным недостатком, который я заметил, была одна основная ветка со всеми коммитами.
aceofspades
@aceofspades: почему это недостаток?
naught101
2
Для меня весь смысл извлечения коммитов из репозитория git заключается в том, что я хочу сохранить историю.
aceofspades
13

Один из способов сделать это - удалить все, кроме файла, который вы хотите сохранить.

По сути, сделайте копию репозитория, а затем git filter-branchудалите все, кроме файлов / папок, которые вы хотите сохранить.

Например, у меня есть проект, из которого я хочу извлечь файл tvnamer.pyв новый репозиторий:

git filter-branch --tree-filter 'for f in *; do if [ $f != "tvnamer.py" ]; then rm -rf $f; fi; done' HEAD

Это используется git filter-branch --tree-filterдля прохождения каждой фиксации, запуска команды и повторного подтверждения полученного содержимого каталогов. Это чрезвычайно разрушительно (поэтому вы должны делать это только с копией своего репозитория!) И может занять некоторое время (около 1 минуты для репозитория с 300 коммитами и примерно 20 файлами).

Приведенная выше команда просто запускает следующий сценарий оболочки для каждой ревизии, который вам, конечно же, придется изменить (чтобы он исключал ваш подкаталог вместо tvnamer.py):

for f in *; do
    if [ $f != "tvnamer.py" ]; then
        rm -rf $f;
    fi;
done

Самая большая очевидная проблема заключается в том, что он оставляет все сообщения фиксации, даже если они не связаны с оставшимся файлом. Скрипт git-remove-empty-commits исправляет это ..

git filter-branch --commit-filter 'if [ z$1 = z`git rev-parse $3^{tree}` ]; then skip_commit "$@"; else git commit-tree "$@"; fi'

Вам нужно снова -fзапустить аргумент force filter-branchс чем-либо refs/original/(что в основном резервное копирование)

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

Опять же, запускайте это только на копии вашего репозитория! - но в целом, чтобы удалить все файлы, кроме thisismyfilename.txt:

git filter-branch --tree-filter 'for f in *; do if [ $f != "thisismyfilename.txt" ]; then rm -rf $f; fi; done' HEAD
git filter-branch -f --commit-filter 'if [ z$1 = z`git rev-parse $3^{tree}` ]; then skip_commit "$@"; else git commit-tree "$@"; fi'
DBR
источник
4
git filter-branchимеет (в настоящее время?) встроенную опцию для удаления пустых коммитов, а именно --prune-empty. Лучшее руководство git filter-branch- в ответах на этот вопрос: stackoverflow.com/questions/359424/…
Blaisorblade 01
4

Оба CoolAJ86 и apenwarr ответы очень похожи. Я ходил туда и обратно между двумя, пытаясь понять биты, которые отсутствовали в любом из них. Ниже представлена ​​их комбинация.

Сначала перейдите в Git Bash в корень репозитория git, который нужно разделить. В моем примере это~/Documents/OriginalRepo (master)

# move the folder at prefix to a new branch
git subtree split --prefix=SubFolderName/FolderToBeNewRepo --branch=to-be-new-repo

# create a new repository out of the newly made branch
mkdir ~/Documents/NewRepo
pushd ~/Documents/NewRepo
git init
git pull ~/Documents/OriginalRepo to-be-new-repo

# upload the new repository to a place that should be referenced for submodules
git remote add origin git@github.com:myUsername/newRepo.git
git push -u origin master
popd

# replace the folder with a submodule
git rm -rf ./SubFolderName/FolderToBeNewRepo
git submodule add git@github.com:myUsername/newRepo.git SubFolderName/FolderToBeNewRepo
git branch --delete --force to-be-new-repo

Ниже приведена копия вышеизложенного с замененными настраиваемыми именами и использованием вместо этого https. Корневая папка теперь~/Documents/_Shawn/UnityProjects/SoProject (master)

# move the folder at prefix to a new branch
git subtree split --prefix=Assets/SoArchitecture --branch=so-package

# create a new repository out of the newly made branch
mkdir ~/Documents/_Shawn/UnityProjects/SoArchitecture
pushd ~/Documents/_Shawn/UnityProjects/SoArchitecture
git init
git pull ~/Documents/_Shawn/UnityProjects/SoProject so-package

# upload the new repository to a place that should be referenced for submodules
git remote add origin https://github.com/Feddas/SoArchitecture.git
git push -u origin master
popd

# replace the folder with a submodule
git rm -rf ./Assets/SoArchitecture
git submodule add https://github.com/Feddas/SoArchitecture.git
git branch --delete --force so-package
ShawnFeatherly
источник
3

Если вы хотите перенести какое-то подмножество файлов в новый репозиторий, но сохранить историю, вы, в основном, получите совершенно новую историю. В основном это будет работать следующим образом:

  1. Создать новый репозиторий.
  2. Для каждой ревизии вашего старого репозитория объединяйте изменения вашего модуля в новый репозиторий. Это создаст «копию» вашей существующей истории проекта.

Это должно быть довольно просто автоматизировать, если вы не против написать небольшой, но сложный сценарий. Прямолинейно, да, но также и болезненно. В прошлом люди переписывали историю в Git, вы можете поискать это.

В качестве альтернативы: клонируйте репозиторий и удалите документ в клоне, удалите приложение в оригинале. Это займет одну минуту, это гарантированно сработает, и вы сможете вернуться к более важным вещам, чем попытки очистить историю git. И не беспокойтесь о пространстве на жестком диске, занятом избыточными копиями истории.

Дитрих Эпп
источник