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

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

Как я могу сделать это, сохраняя историю файлов в подкаталоге?

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

Просто чтобы прояснить, у меня есть следующая структура:

XYZ/
    .git/
    XY1/
    ABC/
    XY2/

Но я бы хотел этого:

XYZ/
    .git/
    XY1/
    XY2/
ABC/
    .git/
    ABC/
11.12.2008 13:57:03
Теперь это тривиально, git filter-branchсм. Мой ответ ниже.
jeremyjjbrown 20.08.2014 14:12:55
@jeremyjjbrown прав. Это больше не сложно сделать, но трудно найти правильный ответ в Google, потому что все старые ответы доминируют в результатах.
Agnel Kurian 14.10.2014 05:39:04
25 ОТВЕТОВ
РЕШЕНИЕ

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


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

  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. Теперь вы можете также удалить теги, которые не имеют отношения к подпроекту; Вы также можете сделать это позже, но вам может понадобиться снова обрезать репо. Я не сделал этого и получил WARNING: Ref 'refs/tags/v0.1' is unchangedдля всех тегов (так как все они не были связаны с подпроектом); Кроме того, после удаления таких тегов будет больше места. Видимо, git filter-branchдолжны быть в состоянии переписать другие теги, но я не мог проверить это. Если вы хотите удалить все теги, используйте git tag -l | xargs git tag -d.

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

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

    или, альтернативно, переписать только ветку HEAD и игнорировать теги и другие ветки:

    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
    

    и теперь у вас есть локальный git-репозиторий подкаталога ABC со всей его историей.

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

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

1219
20.06.2018 22:57:50
Очень хороший ответ Спасибо! И чтобы действительно получить именно то, что я хотел, я добавил «- --all» в команду filter-branch.
matli 12.12.2008 09:17:41
Зачем вам нужен --no-hardlinks? Удаление одной жесткой ссылки не повлияет на другой файл. Git объекты тоже неизменны. Только если вы измените права доступа владельца / файла, которые вам нужны --no-hardlinks.
vdboor 1.02.2010 09:58:15
Дополнительным шагом, который я бы порекомендовал, был бы «git remote rm origin». Это не позволит толчкам вернуться к исходному хранилищу, если я не ошибаюсь.
Tom 5.04.2010 19:51:30
Еще одна команда для добавления filter-branch- --prune-emptyудалить пустые коммиты.
Seth Johnson 12.09.2011 02:31:31
Как и Пол, я не хотел, чтобы теги проекта в моем новом репо, поэтому я не использовал -- --all. Я тоже бегал git remote rm originи git tag -l | xargs git tag -dдо git filter-branchкоманды. Это сократило мой .gitкаталог с 60М до ~ 300К. Обратите внимание, что мне нужно было выполнить обе эти команды, чтобы уменьшить размер.
saltycrane 17.11.2011 21:18:05

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

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

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

133
23.05.2017 12:18:26
сделать это, git filter-branch --index-filter "git rm -r -f --cached --ignore-unmatch ABC" --prune-empty HEADи это будет намного быстрее. index-filter работает с индексом, в то время как tree-filter должен извлекать и устанавливать все для каждого коммита .
fmarc 17.09.2009 19:58:12
в некоторых случаях испортить историю репозитория XYZ излишне ... просто простое "rm -rf ABC; git rm -r ABC; git commit -m'extracted ABC в его собственное репо '" будет работать лучше для большинства людей.
Evgeny 28.10.2010 23:24:09
Возможно, вы захотите использовать -f (force) в этой команде, если вы делаете это более одного раза, например, для удаления двух каталогов после их разделения. В противном случае вы получите «Невозможно создать новую резервную копию».
Brian Carlton 18.04.2011 17:59:03
Если вы используете --index-filterметод, вы также можете захотеть сделать это git rm -q -r -f, чтобы при каждом вызове не печаталась строка для каждого удаляемого файла.
Eric Naeseth 12.10.2011 19:55:18
Я бы предложил отредактировать ответ Пола только потому, что он такой тщательный.
Erik Aronesty 5.03.2014 15:38:08

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

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

2
12.06.2009 08:28:42

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

т.е.

$ 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
23.05.2017 12:10:41

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

  1. Делаем клон и фильтр

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

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

    git repack -ad
    

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

96
5.01.2011 18:18:29
Я думаю, что что-то вроде git gc --aggressive --prune=nowвсе еще отсутствует, не так ли?
Albert 11.07.2012 20:14:46
@Albert Команда repack позаботится об этом, и не будет никаких незакрепленных объектов.
Josh Lee 11.07.2012 20:57:44
да, git gc --aggressive --prune=nowбольшая часть нового репо сократилась
Tomek Wyderka 2.04.2013 09:01:37
Просто и элегантно. Спасибо!
Marco Pelegrini 29.08.2016 02:30:59

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

Git-поддерево может быть полезно для этого

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

http://psionides.jogger.pl/2010/02/04/sharing-code-between-projects-with-git-subtree/

19
23.05.2017 12:18:26
git-subtree теперь является частью Git, хотя и находится в дереве contrib, поэтому не всегда устанавливается по умолчанию. Я знаю, что он установлен формулой Homebrew git, но без справочной страницы. Таким образом, apenwarr называет свою версию устаревшей.
echristopherson 10.05.2013 16:04:11

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

git filter-branch --index-filter \
"git rm -r -f --cached --ignore-unmatch DIR" --prune-empty \
--tag-name-filter cat -- --all
4
26.05.2014 14:51:21
что здесь кот?
rogerdpack 19.09.2016 18:35:16

Изменить: 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:   $0 </path/to/repo/> <directory/to/extract/> <newName>"
    echo
    echo "Example: $0 /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
40
11.04.2015 14:00:08

Для чего это стоит, вот как использовать 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
6.03.2015 01:26:14

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

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

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

Я потерял непрерывность после фильтра-ветви

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

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

12
17.04.2012 05:12:06
Мне нравится стиль этого графика. Могу я спросить, какой инструмент вы используете?
Slipp D. Thompson 30.03.2013 18:17:49
Башня для Mac. Мне это и вправду нравится. Почти само по себе стоит перейти на Mac.
MM. 2.04.2013 21:02:05
Да, хотя в моем случае моя подпапка targetdirбыла переименована в какой-то момент и git filter-branchпросто назвала это днем, удалив все коммиты, сделанные до переименования! Шокирующе, учитывая, как адепт Git отслеживает подобные вещи и даже переносит отдельные фрагменты контента!
Jay Allen 31.05.2013 09:25:17
О, также, если кто-то окажется в одной лодке, вот команда, которую я использовал. Не забывайте, что git rmтребуется несколько аргументов, поэтому нет причин запускать его для каждого файла / папки: BYEBYE="dir/subdir2 dir2 file1 dir/file2"; git filter-branch -f --index-filter "git rm -q -r -f --cached --ignore-unmatch $BYEBYE" --prune-empty -- --all
Jay Allen 31.05.2013 09:26:55

Поместите это в ваш 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
29.03.2013 20:18:09

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

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

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

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

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

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

3
23.05.2017 12:18:26

Легкий путь ™

Оказывается, это настолько распространенная и полезная практика, что повелители Git сделали это действительно легко, но вам нужно иметь более новую версию Git (> = 1.7.11, май 2012). Смотрите приложение о том, как установить последнюю версию Git. Кроме того, есть реальный пример в пошаговом руководстве ниже.

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

    cd <big-repo>
    git subtree split -P <name-of-folder> -b <name-of-new-branch>
    

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

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

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

    mkdir ~/<new-repo> && cd ~/<new-repo>
    git init
    git pull </path/to/big-repo> <name-of-new-branch>
    
  3. Ссылка нового репо на GitHub или где угодно

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

    git rm -rf <name-of-folder>
    

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

...

Прохождение

Это те же самые шаги, что и выше , но следуйте моим точным шагам для моего репозитория вместо использования <meta-named-things>.

Вот мой проект по реализации модулей браузера JavaScript в узле:

tree ~/node-browser-compat

node-browser-compat
├── ArrayBuffer
├── Audio
├── Blob
├── FormData
├── atob
├── btoa
├── location
└── navigator

Я хочу разделить одну папку btoa, в отдельный репозиторий Git

cd ~/node-browser-compat/
git subtree split -P btoa -b btoa-only

Теперь у меня есть новая ветка, для btoa-onlyкоторой есть только коммиты, btoaи я хочу создать новый репозиторий.

mkdir ~/btoa/ && cd ~/btoa/
git init
git pull ~/node-browser-compat btoa-only

Затем я создаю новый репозиторий на GitHub или Bitbucket, или как угодно, и добавляю его как origin

git remote add origin git@github.com:node-browser-compat/btoa.git
git push -u origin master

Счастливый день!

Примечание: Если вы создали репо с микросхемой README.md, .gitignoreи LICENSE, вам нужно будет тянуть первым:

git pull origin master
git push origin master

Наконец, я хочу удалить папку из большего репо

git rm -rf btoa

...

аппендикс

Последний Git на macOS

Чтобы получить последнюю версию Git с помощью Homebrew :

brew install git

Последний Git на Ubuntu

sudo apt-get update
sudo apt-get install git
git --version

Если это не работает (у вас очень старая версия Ubuntu), попробуйте

sudo add-apt-repository ppa:git-core/ppa
sudo apt-get update
sudo apt-get install git

Если это все еще не работает, попробуйте

sudo chmod +x /usr/share/doc/git/contrib/subtree/git-subtree.sh
sudo ln -s \
/usr/share/doc/git/contrib/subtree/git-subtree.sh \
/usr/lib/git-core/git-subtree

Спасибо rui.araujo из комментариев.

Очистка вашей истории

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

git filter-branch --prune-empty --tree-filter 'rm -rf <name-of-folder>' HEAD

После этого вы можете проверить, что ваш файл или папка больше не отображаются в истории Git

git log -- <name-of-folder> # should show nothing

Тем не менее, вы не можете «нажать» удаления на GitHub и тому подобное. Если вы попробуете, вы получите сообщение об ошибке, и вам придется это сделать git pullпрежде, чем вы сможете git push- и тогда вы вернетесь к тому, чтобы иметь все в своей истории.

Поэтому, если вы хотите удалить историю из «источника» - то есть удалить ее из GitHub, Bitbucket и т. Д. - вам необходимо удалить репо и повторно нажать на сокращенную копию репо. Но подождите - это еще не все ! - Если вы действительно хотите избавиться от пароля или чего-то в этом роде, вам потребуется удалить резервную копию (см. Ниже).

Делая .gitменьше

Вышеупомянутая команда удаления истории все еще оставляет кучу файлов резервных копий - потому что Git слишком любезен, чтобы помочь вам не испортить репо случайно. В конечном итоге он удалит потерянные файлы в течение нескольких дней и месяцев, но на некоторое время оставит их там, если вы поймете, что случайно удалили то, что не хотели.

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

rm -rf .git/refs/original/ && \
git reflog expire --all && \
git gc --aggressive --prune=now

git reflog expire --all --expire-unreachable=0
git repack -A -d
git prune

Тем не менее, я бы порекомендовал не выполнять эти шаги, если вы не знаете, что вам нужно - просто на тот случай, если вы удалили неправильный подкаталог, понимаете? Файлы резервных копий не должны быть клонированы, когда вы нажимаете репо, они просто будут в вашей локальной копии.

кредит

1301
24.04.2020 02:50:21
git subtreeвсе еще является частью папки contrib и по умолчанию не устанавливается во всех дистрибутивах. github.com/git/git/blob/master/contrib/subtree
onionjake 2.08.2013 14:06:19
@krlmlr sudo chmod + x /usr/share/doc/git/contrib/subtree/git-subtree.sh sudo ln -s /usr/share/doc/git/contrib/subtree/git-subtree.sh / usr / lib / git-core / git-subtree Для активации в Ubuntu 13.04
rui.araujo 26.08.2013 06:39:21
Если вы отправили пароль в публичный репозиторий, вам следует сменить пароль, а не пытаться удалить его из публичного репозитория, и надеяться, что его никто не видел.
Miles Rout 18.09.2013 03:43:35
Это решение не сохраняет историю.
Cœur 11.11.2015 02:06:11
Команда popdand pushdделает это довольно неявным и труднее понять, что она собирается делать ...
jones77 4.06.2018 16:39:27

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

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

https://github.com/slobobaby/git_filter

на GitHub.

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

4
10.03.2014 17:39:13

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

git filter-branch --prune-empty --subdirectory-filter <YOUR_SUBDIR_TO_KEEP> master
git push <MY_NEW_REMOTE_URL> -f .
26
20.04.2015 18:43:21
Это работает как шарм. YOUR_SUBDIR в приведенном выше примере является подкаталогом, который вы хотите сохранить, все остальное будет удалено
J.T. Taylor 19.04.2015 06:18:44
Обновления на основе вашего комментария.
jeremyjjbrown 20.04.2015 18:43:53
Это не отвечает на вопрос. В документации говорится, что The result will contain that directory (and only that) as its project root.это действительно то, что вы получите, т.е. исходная структура проекта не сохранилась.
NicBright 2.06.2017 13:11:41
@NicBright Можете ли вы проиллюстрировать свою проблему с XYZ и ABC, как в вопросе, чтобы показать, что не так?
Adam 26.10.2017 16:02:14
@jeremyjjbrown возможно ли повторно использовать клонированное репо и не использовать новое репо, т.е. мой вопрос здесь stackoverflow.com/questions/49269602/…
Qiulang 3.04.2018 12:34:57

Правильный путь сейчас заключается в следующем:

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

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

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

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

  1. клонировать ваше удаленное хранилище в другой каталог
  2. используя git filter-branchтолько оставленные файлы в некотором подкаталоге, нажмите на новый пульт
  3. создать коммит, чтобы удалить этот подкаталог из вашего исходного удаленного репо
6
12.11.2014 13:22:04

Более легкий путь

  1. установить git splits. Я создал его как расширение git, основанное на решении jkeating .
  2. Разделите каталоги в местный филиал #change into your repo's directory cd /path/to/repo #checkout the branch git checkout XYZ
    #split multiple directories into new branch XYZ git splits -b XYZ XY1 XY2

  3. Создайте пустой репо где-нибудь. Предположим, что мы создали пустой репозиторий xyzна GitHub с указанием пути:git@github.com:simpliwp/xyz.git

  4. Нажмите на новый репо. #add a new remote origin for the empty repo so we can push to the empty repo on GitHub git remote add origin_xyz git@github.com:simpliwp/xyz.git #push the branch to the empty repo's master branch git push origin_xyz XYZ:master

  5. Клонировать только что созданный удаленный репо в новый локальный каталог
    #change current directory out of the old repo cd /path/to/where/you/want/the/new/local/repo #clone the remote repo you just pushed to git clone git@github.com:simpliwp/xyz.git

3
23.05.2017 12:18:26
Преимущество этого метода по сравнению с «Простым способом» заключается в том, что пульт дистанционного управления уже настроен для нового репо, поэтому вы можете сразу же добавить поддерево. На самом деле этот путь кажется мне легче (даже без git splits)
M.M 12.05.2015 05:58:27
Реквизиты для AndrewD для размещения этого решения. Я разветвлял его репо, чтобы он работал на OSX ( github.com/ricardoespsanto/git-splits ), если это полезно для кого-то еще
ricardoespsanto 9.02.2018 13:33:56

Вот небольшая модификация CoolAJ86 «s „The Easy Way ™“ответ для того , чтобы разделить несколько папок суб (скажем , sub1и sub2) в новый репозиторий.

Easy Way ™ (несколько подпапок)

  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папки.

19
23.05.2017 12:26:37
Это сработало для меня с небольшой модификацией. Потому что мои sub1и sub2папки не существуют с первоначальной версией, я должен был изменить свой --tree-filterсценарий следующим образом : "mkdir <name-of-folder>; if [ -d sub1 ]; then mv <sub1> <name-of-folder>/; fi". Для второй filter-branchкоманды я заменил <sub1> на <sub2>, исключил создание <name-of-folder> и включил -fпосле, filter-branchчтобы переопределить предупреждение о существующей резервной копии.
pglezen 11.02.2016 19:38:06
Это не работает, если какой-либо из подкаталогов изменился за всю историю в git. Как это можно решить?
nietras 3.03.2016 12:06:21
@nietras см. ответ rogerdpack. Мне потребовалось некоторое время, чтобы найти его после прочтения и усвоения всей информации в этих других ответах.
Adam 30.10.2017 17:18:31

Проверьте проект git_split на https://github.com/vangorra/git_split

Превратите каталоги git в свои собственные репозитории на своем месте. Не поддельное смешное дело. Этот скрипт возьмет существующий каталог в вашем git-репозитории и превратит этот каталог в отдельный независимый репозиторий. По пути он скопирует всю историю изменений для предоставленного вами каталога.

./git_split.sh <src_repo> <src_branch> <relative_dir_path> <dest_repo>
        src_repo  - The source repo to pull from.
        src_branch - The branch of the source repo to pull from. (usually master)
        relative_dir_path   - Relative path of the directory in the source repo to split.
        dest_repo - The repo to push to.
2
6.01.2016 02:42:05

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

1
10.02.2016 16:57:12
«Прекрасная и замечательная» часть поддерева состоит в том, что история вашего подкаталога приходит вместе с вами. Если вам не нужна история, тогда ваш мучительно простой метод - это путь.
pglezen 11.02.2016 19:55:51

Похоже, что большинство (все?) Ответов здесь полагаются на некоторую форму 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, но сохранит всю историю изменений этих файлов даже при переименовании. И это может не стоить того, если не будет потеряно «много» истории и т. Д. Но по крайней мере вы гарантированно не потеряете старые коммиты!

6
31.10.2017 10:41:55
Нашел иголку в стоге сена! Теперь я могу сохранить ВСЕ мою историю коммитов.
Adam 30.10.2017 17:19:59

Я рекомендую руководство GitHub по разделению подпапок в новый репозиторий . Шаги похожи на ответ Павла , но я понял, что их инструкции легче понять.

Я изменил инструкции так, чтобы они применялись для локального репозитория, а не для размещения на GitHub.


Разбиение подпапки в новый репозиторий

  1. Откройте Git Bash.

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

  3. Клонируйте репозиторий, содержащий подпапку.

git clone OLD-REPOSITORY-FOLDER NEW-REPOSITORY-FOLDER
  1. Измените текущий рабочий каталог на ваш клонированный репозиторий.

cd REPOSITORY-NAME
  1. Чтобы отфильтровать подпапку от остальных файлов в хранилище, запустите git filter-branch , предоставив следующую информацию:
    • FOLDER-NAMEПапка в вашем проекте, из которой вы хотите создать отдельный репозиторий.
      • Совет: пользователи Windows должны использовать /для разделения папок.
    • BRANCH-NAME: Ветка по умолчанию для вашего текущего проекта, например, masterили gh-pages.

git filter-branch --prune-empty --subdirectory-filter FOLDER-NAME  BRANCH-NAME 
# Filter the specified branch in your directory and remove empty commits
Rewrite 48dc599c80e20527ed902928085e7861e6b3cbe6 (89/89)
Ref 'refs/heads/BRANCH-NAME' was rewritten
5
31.08.2017 14:02:49
Хороший пост, но я замечаю, что в первом абзаце документа, который вы связали, говорится, что в If you create a new clone of the repository, you won't lose any of your Git history or changes when you split a folder into a separate repository.соответствии с комментариями ко всем ответам здесь filter-branchи subtreeсценарий приводит к потере истории, когда подкаталог был переименован. Можно ли что-нибудь сделать для решения этой проблемы?
Adam 30.10.2017 11:53:48
Нашел решение для сохранения всех коммитов, включая те, что были переименованы / перемещены в каталогах - это ответ rogerdpack на этот самый вопрос.
Adam 30.10.2017 17:42:43
Единственная проблема в том, что я больше не могу использовать клонированный
Qiulang 3.04.2018 13:17:18

Вы можете легко попробовать https://help.github.com/enterprise/2.15/user/articles/splitting-a-subfolder-out-into-a-new-repository/

Это сработало для меня. Проблемы, с которыми я столкнулся на приведенных выше этапах:

  1. в этой команде является мастерgit filter-branch --prune-empty --subdirectory-filter FOLDER-NAME BRANCH-NAMEBRANCH-NAME

  2. если последний шаг завершается неудачно при фиксации из-за проблем защиты, следуйте - https://docs.gitlab.com/ee/user/project/protected_branches.html

0
10.01.2019 07:23:15

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

1) Клонируйте репозиторий, который вы хотите разбить

git clone git@git.thehost.io:testrepo/test.git

2) Переместить в папку git

cd test/

2) Удалите ненужные папки и зафиксируйте его

rm -r ABC/
git add .
enter code here
git commit -m 'Remove ABC'

3) Удалить ненужные папки (ы) истории формы с BFG

cd ..
java -jar bfg.jar --delete-folders "{ABC}" test
cd test/
git reflog expire --expire=now --all && git gc --prune=now --aggressive

для нескольких папок вы можете использовать запятую

java -jar bfg.jar --delete-folders "{ABC1,ABC2}" metric.git

4) Убедитесь, что история не содержит файлов / папок, которые вы только что удалили

git log --diff-filter=D --summary | grep delete

5) Теперь у вас есть чистый репозиторий без ABC, так что просто вставьте его в новый источник

remote add origin git@github.com:username/new_repo
git push -u origin master

Вот и все. Вы можете повторить шаги, чтобы получить другой репозиторий,

просто удалите XY1, XY2 и переименуйте XYZ -> ABC на шаге 3

0
28.03.2019 14:07:25
Почти идеально ... но вы забыли "git filter-branch --prune-empty", чтобы удалить все старые коммиты, которые теперь пусты. Делать, прежде чем нажать на оригинал мастера!
ZettaCircl 24.05.2019 12:02:51
Если вы допустили ошибку и все еще хотите «отрешиться» после удаления старого пустого коммита, выполните: «git push -u origin master --force-with-lease»
ZettaCircl 24.05.2019 12:10:55

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

Фильтрация с помощью фильтра репо

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

# 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 был новым и пустым до пуша

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

В моем случае я также хотел переместить пару каталогов для более согласованной структуры. Сначала я filter-repoвыполнил эту простую команду git mv dir-to-rename, а затем обнаружил, что с помощью этой --path-renameопции я могу получить немного «лучшую» историю . Вместо того, чтобы видеть последние изменения 5 hours agoна перемещенных файлах в новом репо, я теперь вижуlast year (в пользовательском интерфейсе GitHub), что соответствует измененному времени в оригинальном репо.

Вместо...

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
Ноты:
  • Я думал, что сообщение в блоге Git Rev News хорошо объясняет причины создания еще одного инструмента фильтрации репо.
  • Сначала я попытался создать подкаталог, соответствующий имени целевого репо в исходном репозитории, а затем выполнить фильтрацию (используя git filter-repo --subdirectory-filter dir-matching-new-repo-name). Эта команда правильно преобразовала этот подкаталог в корень скопированного локального репозитория, но она также вызвала историю только трех коммитов, которые потребовались для создания подкаталога. (Я не осознавал, что это --pathможно указать несколько раз; тем самым я избавился от необходимости создавать подкаталог в репозитории с исходным кодом.) Поскольку к моменту, когда я заметил, что кто-то зафиксировал репозиторий с исходным кодом, я не смог перенести историю, которую я только что использовал 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
  • Я был озадачен установкой, так как не знал о шаблоне расширения с git, но в конечном итоге я клонировал git-filter-repo и сделал ссылку на него $(git --exec-path):
ln -s ~/github/newren/git-filter-repo/git-filter-repo $(git --exec-path)
5
21.11.2019 22:39:09
Проголосовал за рекомендацию нового filter-repoинструмента (который я представил в прошлом месяце на stackoverflow.com/a/58251653/6309 )
VonC 21.11.2019 22:03:11
Использование git-filter-repoдолжно определенно быть предпочтительным подходом в этой точке. Это намного, намного быстрее и безопаснее git-filter-branch, и защищает от многих ошибок, с которыми можно столкнуться при переписывании своей истории мерзавцев. Надеюсь, что этот ответ получит больше внимания, так как это тот, который нужно рассмотреть git-filter-repo.
Jeremy Caney 28.12.2019 09:14:16