Отсоединить (переместить) подкаталог в отдельный Git-репозиторий

Возможно использование флеш-файлов cookie :

  • Вездесущая доступность (95 процентов посетителей, вероятно, будут иметь вспышку)
  • Вы можете хранить больше данные на файл cookie (до 100 КБ)
  • Общие для браузеров, поэтому более вероятно уникальная идентификация машины
  • . Очистка куки-файлов браузера не удаляет флеш-файлы cookie.

Вам нужно будет создать небольшой (скрытый) флеш-фильм для чтения и записи.

Независимо от выбранного маршрута, убедитесь, что ваши пользователи выбирают IN для отслеживания, иначе вы «вторгаются в их личную жизнь и становятся одним из плохих парней.

1679
задан Nick Volynkin 1 August 2016 в 08:25
поделиться

19 ответов

Обновление : Этот процесс так распространен, что команда мерзавца сделала его намного более простым с новым инструментом, git subtree. Посмотрите здесь: Отсоединение (перемещение) подкаталог в отдельный репозиторий Мерзавца

<час>

Вы хотите клонировать свой репозиторий и затем использовать git filter-branch для маркировки всего кроме подкаталога, Вы хотите в своем новом repo быть собранными "мусор".

  1. Для клонирования локального репозитория:

    git clone /XYZ /ABC
    

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

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

    cd /ABC
    for i in branch1 br2 br3; do git branch -t $i origin/$i; done
    git remote rm origin
    

    или для всех удаленных ответвлений:

    cd /ABC
    for i in $(git branch -r | sed "s/.*origin\///"); do git branch -t $i origin/$i; done
    git remote rm origin
    
  3. Теперь Вы могли бы хотеть также удалить теги, которые не имеют никакого отношения с подпроектом; можно также сделать это позже, но Вы, возможно, должны были бы сократить свой repo снова. Я не сделал так и добрался WARNING: Ref 'refs/tags/v0.1' is unchanged для всех тегов (так как они были все не связаны с подпроектом); дополнительно, после удаления таких тегов больше пространства будет освобождено. По-видимому git filter-branch должен смочь переписать другие теги, но я не мог проверить это. Если Вы хотите удалить все теги, используйте git tag -l | xargs git tag -d.

  4. Затем ответвление фильтра использования и сброс для исключения других файлов, таким образом, они могут быть сокращены. Давайте также добавим --tag-name-filter cat --prune-empty для удаления пустых фиксаций, и переписать теги (обратите внимание, что это должно будет разделить их подпись):

    git filter-branch --tag-name-filter cat --prune-empty --subdirectory-filter ABC -- --all
    

    или альтернативно, чтобы только переписать ГЛАВНОЕ ответвление и проигнорировать теги и другие ответвления:

    git filter-branch --tag-name-filter cat --prune-empty --subdirectory-filter ABC HEAD
    
  5. Затем удаляют резервное копирование, повторно порет так пространство, может быть действительно исправлен (хотя теперь операция является разрушительной)

    git reset --hard
    git for-each-ref --format="%(refname)" refs/original/ | xargs -n 1 git update-ref -d
    git reflog expire --expire=now --all
    git gc --aggressive --prune=now
    

    , и теперь у Вас есть локальный репозиторий мерзавца подкаталога ABC со всей его сохраненной историей.

Примечание: Для большей части использования, git filter-branch должен действительно иметь добавленный параметр -- --all. Да это действительно - - пространство - - all. Это должно быть последними параметрами для команды. Как обнаруженный Matli, это сохраняет ответвления проекта и отмечает включенный в новый repo.

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

1194
ответ дан 17 revs, 12 users 35% 1 August 2016 в 18:25
поделиться

Я уверен, что с поддеревом git все в порядке и замечательно, но мои подкаталоги управляемого кода git, которые я хотел переместить, были в затмении. Так что если вы используете egit, это больно легко. Возьмите проект, который вы хотите переместить, и объедините его в команду> отключите его, а затем объедините команду> поделиться им в новом месте. По умолчанию будет пытаться использовать старое место репо, но вы можете снять отметку с уже существующего выбора и выбрать новое место для его перемещения. Всем привет.

1
ответ дан stu 1 August 2016 в 08:25
поделиться

Поместите это в ваш gitconfig:

reduce-to-subfolder = !sh -c 'git filter-branch --tag-name-filter cat --prune-empty --subdirectory-filter cookbooks/unicorn HEAD && git reset --hard && git for-each-ref refs/original/ | cut -f 2 | xargs -n 1 git update-ref -d && git reflog expire --expire=now --all && git gc --aggressive --prune=now && git remote rm origin'
1
ответ дан grosser 1 August 2016 в 08:25
поделиться

Используйте эту команду фильтра для удаления подкаталога, сохраняя при этом ваши теги и ветви:

git filter-branch --index-filter \
"git rm -r -f --cached --ignore-unmatch DIR" --prune-empty \
--tag-name-filter cat -- --all
3
ответ дан cmcginty 1 August 2016 в 08:25
поделиться

У меня была именно эта проблема, но все стандартные решения, основанные на git filter-branch, были чрезвычайно медленными. Если у вас небольшой репозиторий, то это может не быть проблемой, это было для меня. Я написал другую программу фильтрации git, основанную на libgit2, которая в качестве первого шага создает ветки для каждой фильтрации основного репозитория, а затем отправляет их для очистки репозиториев в качестве следующего шага. В моем репозитории (500Mb 100000 коммитов) стандартные методы git filter-branch заняли несколько дней. Моя программа занимает минуты, чтобы выполнить ту же фильтрацию.

Он имеет невероятное имя git_filter и живет здесь:

https://github.com/slobobaby/git_filter

на GitHub.

Надеюсь, это кому-нибудь пригодится.

4
ответ дан slobobaby 1 August 2016 в 08:25
поделиться

Вот как это работает, используя GitHub на компьютере с Windows. Допустим, у вас есть клонированный репозиторий в C:\dir1. Структура каталогов выглядит следующим образом: C:\dir1\dir2\dir3. Каталог dir3 - это каталог, в котором я хочу стать новым отдельным репо.

Github:

  1. Создайте свой новый репозиторий: MyTeam/mynewrepo

Bash Prompt:

  1. $ cd c:/Dir1
  2. $ git filter-branch --prune-empty --subdirectory-filter dir2/dir3 HEAD
    Возвращено: Ref 'refs/heads/master' was rewritten (fyi: dir2 / dir3 чувствительно к регистру.)

  3. $ git remote add some_name git@github.com:MyTeam/mynewrepo.git
    git remote add origin etc. не сработало, вернул "remote origin already exists"

  4. $ git push --progress some_name master

4
ответ дан James Lawruk 1 August 2016 в 08:25
поделиться

Правильный путь теперь следующий:

git filter-branch --prune-empty --subdirectory-filter FOLDER_NAME [first_branch] [another_branch]

GitHub теперь даже имеет небольшую статью о таких случаях.

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

Таким образом, ваш алгоритм должен быть:

  1. клонировать ваше удаленное репо в другой каталог
  2. , используя git filter-branch, оставляя только файлы в каком-то подкаталоге, нажать на новый удаленный
  3. создать коммит для удаления этого подкаталога из вашего исходного удаленного репо
6
ответ дан Olexandr Shapovalov 1 August 2016 в 08:25
поделиться

Я обнаружил, что для того, чтобы правильно удалить старую историю из нового репозитория, вам нужно проделать еще немного работы после шага filter-branch.

  1. Сделайте клон и фильтр:

    git clone --no-hardlinks foo bar; cd bar
    git filter-branch --subdirectory-filter subdir/you/want
    
  2. Удалите все ссылки на старую историю. «Origin» отслеживал ваш клон, а «original» - это то место, где фильтр-ветвь сохраняет старые данные:

    git remote rm origin
    git update-ref -d refs/original/refs/heads/master
    git reflog expire --expire=now --all
    
  3. Даже сейчас ваша история может застревать в пакете этот fsck не будет касаться Разорвите его в клочья, создав новый файл пакета и удалив неиспользуемые объекты:

    git repack -ad
    

объяснение этого в руководстве для фильтра ветвп .

94
ответ дан Josh Lee 1 August 2016 в 08:25
поделиться

Редактировать: добавлен скрипт Bash.

Ответы, данные здесь, работали только частично для меня; В кеше осталось много больших файлов. Что в итоге сработало (после нескольких часов работы в #git на freenode):

git clone --no-hardlinks file:///SOURCE /tmp/blubb
cd blubb
git filter-branch --subdirectory-filter ./PATH_TO_EXTRACT  --prune-empty --tag-name-filter cat -- --all
git clone file:///tmp/blubb/ /tmp/blooh
cd /tmp/blooh
git reflog expire --expire=now --all
git repack -ad
git gc --prune=now

В предыдущих решениях размер хранилища составлял около 100 МБ. Этот уменьшил его до 1,7 МБ. Может быть, это кому-нибудь поможет:)


Следующий скрипт bash автоматизирует задачу:

!/bin/bash

if (( $# < 3 ))
then
    echo "Usage:   [111] </path/to/repo/> <directory/to/extract/> <newName>"
    echo
    echo "Example: [111] /Projects/42.git first/answer/ firstAnswer"
    exit 1
fi


clone=/tmp/${3}Clone
newN=/tmp/${3}

git clone --no-hardlinks file://$1 ${clone}
cd ${clone}

git filter-branch --subdirectory-filter $2  --prune-empty --tag-name-filter cat -- --all

git clone file://${clone} ${newN}
cd ${newN}

git reflog expire --expire=now --all
git repack -ad
git gc --prune=now
39
ответ дан Simon A. Eugster 1 August 2016 в 08:25
поделиться

Исходный вопрос требует, чтобы XYZ / ABC / (* файлы) стали ABC / ABC / (* файлами). После реализации принятого ответа для моего собственного кода, я заметил, что он на самом деле меняет XYZ / ABC / (* файлы) на ABC / (* файлы). Страница руководства ветки фильтра даже говорит:

Результат будет содержать этот каталог (и только этот) в качестве корня проекта . "

Другими словами, он продвигает папку верхнего уровня «вверх» на один уровень. Это важное различие, потому что, например, в моей истории я переименовал папку верхнего уровня. Продвигая папки «вверх» на один уровень, git теряет непрерывность на коммите, где я сделал переименование.

I lost contiuity after filter-branch

Мой ответ на этот вопрос заключается в том, чтобы сделать 2 копии репозитория и вручную удалить папки, которые вы Я хочу сохранить в каждом. Страница man поддерживает меня следующим образом:

[...] избегайте использования [этой команды], если достаточно простого коммита для решения вашей проблемы

11
ответ дан MM. 1 August 2016 в 08:25
поделиться

Это уже не так сложно, вы можете просто использовать команду git filter-branch на клоне вашего репозитория, чтобы отбросить ненужные вам подкаталоги, а затем отправить их на новый пульт.

git filter-branch --prune-empty --subdirectory-filter <YOUR_SUBDIR_TO_KEEP> master
git push <MY_NEW_REMOTE_URL> -f .
24
ответ дан jeremyjjbrown 1 August 2016 в 08:25
поделиться

Обновление : Модуль git-subtree был настолько полезен, что команда git втянула его в ядро ​​и сделала его git subtree. Смотрите здесь: Отсоединение (перемещение) подкаталога в отдельный репозиторий Git

git-subtree может быть полезным для этого

http://github.com/apenwarr /git-subtree/blob/master/git-subtree.txt (устарело)

http://psionides.jogger.pl/2010/02/04/sharing-code-between -проекты-с-ГИТ-поддерево /

19
ответ дан Community 1 August 2016 в 08:25
поделиться

Ответ Пола создает новый репозиторий, содержащий / ABC, но не удаляет / ABC из / XYZ. Следующая команда удалит / ABC из / XYZ:

git filter-branch --tree-filter "rm -rf ABC" --prune-empty HEAD

Конечно, сначала протестируйте его в репозитории 'clone --no-hardlinks' и следуйте за ним с помощью команд reset, gc и prune Пол списков.

132
ответ дан Community 1 August 2016 в 08:25
поделиться

Как я упомянул выше , мне пришлось использовать обратное решение (удаление всех коммитов, не касаясь моего dir/subdir/targetdir), которое, казалось, работало довольно хорошо, удаляя около 95% коммитов (по желанию). Однако остаются две небольшие проблемы.

ПЕРВЫЙ , filter-branch проделал большую работу по удалению коммитов, которые вводят или модифицируют код, но, очевидно, коммитов слияния находятся ниже его места в Gitiverse.

Это косметическая проблема, с которой я, вероятно, могу жить ( он говорит ... отступая медленно с отведенными глазами) .

ВТОРОЕ несколько оставшихся коммитов в значительной степени ВСЕ дублированы! Кажется, я приобрел второй, избыточный график, который охватывает почти всю историю проекта. Интересная вещь (которую вы можете увидеть из рисунка ниже) заключается в том, что мои три локальные ветви не все находятся на одной временной шкале (что, разумеется, почему оно существует, а не просто сбор мусора).

Единственное, что я могу себе представить, это то, что один из удаленных Коммиты, возможно, были единственным коммитом слияния, который filter-branch фактически удалил , и который создал параллельную временную шкалу, поскольку каждая теперь не слитая цепь получила свою собственную копию коммитов. ( пожимает плечами Где мои ТАРДИ?) Я почти уверен, что смогу решить эту проблему, хотя я очень очень хотел бы понять, как это произошло.

В случае сумасшедшего mergefest-O-RAMA я, скорее всего, оставлю его в покое, так как он так прочно укоренился в моей истории коммитов - угрожая мне, когда я подхожу, - кажется, это не так. на самом деле вызывает какие-то не косметические проблемы и потому что это довольно симпатично в Tower.app.

3
ответ дан Community 1 August 2016 в 08:25
поделиться

Вот небольшая модификация CoolAJ86 "The Easy Way & trade;" ответьте , чтобы разбить несколько подпапок (скажем, sub1 и sub2) в новый репозиторий git.

Легкий путь и торговля; (несколько подпапок)

  1. Подготовить старое репо

    pushd <big-repo>
    git filter-branch --tree-filter "mkdir <name-of-folder>; mv <sub1> <sub2> <name-of-folder>/" HEAD
    git subtree split -P <name-of-folder> -b <name-of-new-branch>
    popd
    

    Примечание: <name-of-folder> НЕ должно содержать начальных или конечных символов. Например, папка с именем subproject ДОЛЖНА передаваться как subproject, НЕ ./subproject/

    Примечание для пользователей Windows: , если глубина вашей папки> 1, <name-of-folder> должны иметь разделитель папок в стиле * nix (/). Например, папка с именем path1\path2\subproject ДОЛЖНА передаваться как path1/path2/subproject. Более того, не используйте команду mv, а move.

    Заключительное примечание: уникальная и большая разница с базовым ответом - вторая строка сценария "git filter-branch..."

  2. Создать новый репо

    mkdir <new-repo>
    pushd <new-repo>
    
    git init
    git pull </path/to/big-repo> <name-of-new-branch>
    
  3. Свяжите новое репо с Github или где угодно

    git remote add origin <git@github.com:my-user/new-repo.git>
    git push origin -u master
    
  4. Очистка, при желании

    popd # get out of <new-repo>
    pushd <big-repo>
    
    git rm -rf <name-of-folder>
    

    Примечание : Это оставляет все исторические ссылки в репозитории. См. Приложение в исходном ответе, если вы на самом деле беспокоитесь о введении пароля или вас необходимо уменьшить размер файла вашей папки .git.

  5. [1 125]
19
ответ дан Community 1 August 2016 в 08:25
поделиться

Похоже, что большинство (все?) Ответов здесь полагаются на некоторую форму git filter-branch --subdirectory-filter и тому подобное. Это может работать «чаще всего», однако в некоторых случаях, например, в случае, когда вы переименовали папку, например:

 ABC/
    /move_this_dir # did some work here, then renamed it to

ABC/
    /move_this_dir_renamed

Если вы сделаете обычный стиль фильтра git для извлечения «move_me_renamed», вы потеряете история изменений файла, произошедшая со спины, когда она изначально была move_this_dir ( ref ).

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

  1. Клонировать многомодульный проект локально
  2. Ветви - проверьте, что там: git branch -a
  3. Выполните проверку для каждой ветви, которая будет включена в разделение, чтобы получить локальную копию на вашей рабочей станции: git checkout --track origin/branchABC
  4. Сделайте копию в новом каталоге: cp -r oldmultimod simple
  5. Перейдите в новую копию проекта: cd simple
  6. Избавьтесь от других модулей, которые не нужны в этом проекте:
  7. git rm otherModule1 other2 other3
  8. Сейчас остается только подкаталог целевого модуля
  9. Избавьтесь от подкаталога модуля, чтобы корень модуля стал новым корнем проекта
  10. git mv moduleSubdir1/* .
  11. Удалите подкаталог реликвии: rmdir moduleSubdir1
  12. Проверять изменения в любой момент: git status
  13. Создать новое git-репо и скопируйте его URL, чтобы указать этот проект в него:
  14. git remote set-url origin http://mygithost:8080/git/our-splitted-module-repo
  15. Убедитесь, что это хорошо: git remote -v
  16. Передайте изменения в удаленное хранилище: git push
  17. Перейдите к удаленному репо и проверьте, что все это есть
  18. Повторите это для любой другой необходимой ветви: git checkout branch2

Это следует github doc "Разделение подпапки в новом хранилище" шаги 6-11, чтобы подтолкнуть модуль к новому репо.

Это не сэкономит вам места в папке .git, но сохранит всю историю изменений этих файлов даже при переименовании. И это может не стоить того, если не будет потеряно «много» истории и т. Д. Но, по крайней мере, вы гарантированно не потеряете старые коммиты!

5
ответ дан Adam 1 August 2016 в 08:25
поделиться

При выполнении git filter-branch использование более новой версии git (2.22+, возможно?), это говорит для использования этого нового инструмента git-filter-repo. Этот инструмент, конечно, упростил вещи для меня.

Фильтрация с фильтром-repo

Команды для создания XYZ repo от исходного вопроса:

# create local clone of original repo in directory XYZ
tmp $ git clone git@github.com:user/original.git XYZ

# switch to working in XYZ
tmp $ cd XYZ

# keep subdirectories XY1 and XY2 (dropping ABC)
XYZ $ git filter-repo --path XY1 --path XY2

# note: original remote origin was dropped
# (protecting against accidental pushes overwriting original repo data)

# XYZ $ ls -1
# XY1
# XY2

# XYZ $ git log --oneline
# last commit modifying ./XY1 or ./XY2
# first commit modifying ./XY1 or ./XY2

# point at new hosted, dedicated repo
XYZ $ git remote add origin git@github.com:user/XYZ.git

# push (and track) remote master
XYZ $ git push -u origin master

предположения: * удаленный XYZ repo был новым и пустым перед нажатием

Фильтрация и перемещение

В моем случае, я также хотел переместить несколько каталогов для более последовательной структуры. Первоначально, я выполнил то простое filter-repo команда, сопровождаемая [1 110], но я нашел, что мог получить немного "лучшую" историю с помощью --path-rename опция. Вместо того, чтобы видеть в последний раз изменил 5 hours ago на перемещенных файлах в новом repo, который я теперь вижу last year (в UI GitHub), который соответствует измененным временам в исходном repo.

Вместо...

git filter-repo --path XY1 --path XY2 --path inconsistent
git mv inconsistent XY3  # which updates last modification time

я в конечном счете работал...

git filter-repo --path XY1 --path XY2 --path inconsistent --path-rename inconsistent:XY3
Примечания:
  • я думал сообщение Новостного блога версии Мерзавца объясненный хорошо обоснование позади создания еще одного инструмента repo-фильтрации.
  • я первоначально попробовал путь создания подкаталога, соответствующего цели repo имя в исходном репозитории и затем фильтрация (использующий git filter-repo --subdirectory-filter dir-matching-new-repo-name). Та команда правильно преобразовала тот подкаталог в корень скопированного локального repo, но это также привело к истории только трех фиксаций, которые потребовалось для создания подкаталога. (Я не понял, что --path мог быть указан многократно; таким образом, устраняя потребность создать подкаталог в источнике repo.), Так как кто-то согласился на источник repo к тому времени, когда я заметил, что мне не удалось продвинуть историю, я просто использовал git reset commit-before-subdir-move --hard после эти clone команда и добавил --force к эти filter-repo команда, чтобы заставить ее воздействовать на немного измененный локальный клон.
git clone ...
git reset HEAD~7 --hard      # roll back before mistake
git filter-repo ... --force  # tell filter-repo the alterations are expected
  • я был озадачен на установке, так как я не знал о дополнительном шаблоне с [1 120], но в конечном счете я клонировался git-filter-repo и symlinked это к [1 121]:
ln -s ~/github/newren/git-filter-repo/git-filter-repo $(git --exec-path)
1
ответ дан 22 November 2019 в 20:09
поделиться

Вам может понадобиться что-то вроде "git reflog expire --expire = now --all" перед сборкой мусора, чтобы фактически очистить файлы. git filter-branch просто удаляет ссылки в истории, но не удаляет записи журнала ссылок, содержащие данные. Конечно, сначала проверьте это.

При этом резко упало использование моего диска, хотя мои начальные условия были несколько иными. Возможно, --subdirectory-filter отменяет эту необходимость, но я сомневаюсь в этом.

2
ответ дан 22 November 2019 в 20:09
поделиться

Чтобы добавить к ответу Пола , я обнаружил, что для окончательного восстановления места мне нужно отправить HEAD в чистый репозиторий, что уменьшает размер .git / каталог objects / pack.

ie

$ mkdir ...ABC.git
$ cd ...ABC.git
$ git init --bare

После удаления gc также выполните:

$ git push ...ABC.git HEAD

Затем вы можете выполнить

$ git clone ...ABC.git

, и размер ABC / .git уменьшится

На самом деле, это занимает некоторое время шаги (например, git gc) не требуются при нажатии на очистку репозитория, например:

$ git clone --no-hardlinks /XYZ /ABC
$ git filter-branch --subdirectory-filter ABC HEAD
$ git reset --hard
$ git push ...ABC.git HEAD
7
ответ дан 22 November 2019 в 20:09
поделиться
Другие вопросы по тегам:

Похожие вопросы: