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


1758

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

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

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

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

XYZ/
    .git/
    XY1/
    ABC/
    XY2/

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

XYZ/
    .git/
    XY1/
    XY2/
ABC/
    .git/
    ABC/

7
Теперь это тривиально, git filter-branchсм. Мой ответ ниже.
jeremyjjbrown

8
@jeremyjjbrown прав. Это больше не сложно сделать, но трудно найти правильный ответ в Google, потому что все старые ответы преобладают в результатах.
Агнель Куриан

Ответы:


1228

Обновление : этот процесс настолько распространен, что команда 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. Это должны быть последние параметры для команды. Как обнаружил Матли, это сохраняет ветки проекта и теги, включенные в новый репозиторий.

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


29
Очень хороший ответ Спасибо! И чтобы действительно получить именно то, что я хотел, я добавил «- --all» в команду filter-branch.
Матли

12
Зачем вам нужен --no-hardlinks? Удаление одной жесткой ссылки не повлияет на другой файл. Git объекты тоже неизменны. Только если вы измените права доступа владельца / файла, которые вам нужны --no-hardlinks.
vdboor

67
Дополнительным шагом, который я бы порекомендовал, был бы "git remote rm origin". Это не позволит толчкам вернуться к исходному хранилищу, если я не ошибаюсь.
Том

13
Еще одна команда для добавления filter-branch- --prune-emptyудалить пустые коммиты.
Сет Джонсон

8
Как и Пол, я не хотел теги проекта в моем новом репо, поэтому я не использовал -- --all. Я тоже бегал git remote rm originи git tag -l | xargs git tag -dдо git filter-branchкоманды. Это сократило мой .gitкаталог с 60М до ~ 300К. Обратите внимание, что мне нужно было выполнить обе эти команды, чтобы уменьшить размер.
соленый журавль

1321

Легкий путь ™

Оказывается, это настолько распространенная и полезная практика, что повелители 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

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

кредит


16
git subtreeвсе еще является частью папки contrib и по умолчанию не устанавливается во всех дистрибутивах. github.com/git/git/blob/master/contrib/subtree
луковица

11
@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

41
Если вы отправили пароль в публичный репозиторий, вам следует сменить пароль, а не пытаться удалить его из публичного репозитория, и надеяться, что его никто не видел.
Майлз Рут

8
Это решение не сохраняет историю.
Cœur

18
Команда popdand pushdделает это довольно неявным и труднее понять, что она собирается делать ...
jones77

133

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

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

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


53
сделать это, git filter-branch --index-filter "git rm -r -f --cached --ignore-unmatch ABC" --prune-empty HEADи это будет гораздо быстрее. index-filter работает с индексом, в то время как tree-filter должен извлекать и устанавливать все для каждого коммита .
fmarc

51
в некоторых случаях испортить историю репозитория XYZ является излишним ... просто простое "rm -rf ABC; git rm -r ABC; git commit -m'extracted ABC в его собственное репо '" будет работать лучше для большинства людей.
Евгений

2
Вы, вероятно, захотите использовать -f (force) в этой команде, если вы делаете это более одного раза, например, чтобы удалить два каталога после их разделения. В противном случае вы получите «Невозможно создать новую резервную копию».
Брайан Карлтон

4
Если вы используете --index-filterметод, вы также можете захотеть сделать это git rm -q -r -f, чтобы при каждом вызове не печаталась строка для каждого удаляемого файла.
Эрик Нусет

1
Я бы предложил отредактировать ответ Пола только потому, что он такой тщательный.
Эрик Аронесты

96

Я обнаружил, что для того, чтобы правильно удалить старую историю из нового репозитория, вам нужно проделать еще немного работы после этого 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
    

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


3
Я думаю, что что-то вроде git gc --aggressive --prune=nowвсе еще отсутствует, не так ли?
Альберт

1
@Albert Команда repack позаботится об этом, и не будет никаких незакрепленных объектов.
Джош Ли

да, git gc --aggressive --prune=nowбольшая часть нового репо сократилась
Томек Видерка

Просто и элегантно. Спасибо!
Марко Пелегрини

40

Изменить: 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

26

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

git filter-branch --prune-empty --subdirectory-filter <YOUR_SUBDIR_TO_KEEP> master
git push <MY_NEW_REMOTE_URL> -f .

3
Это работает как шарм. YOUR_SUBDIR в приведенном выше примере является подкаталогом, который вы хотите сохранить, все остальное будет удалено
JT Taylor

1
Обновления на основе вашего комментария.
jeremyjjbrown

2
Это не отвечает на вопрос. Из документов говорится, что The result will contain that directory (and only that) as its project root.это действительно то, что вы получите, то есть исходная структура проекта не сохранилась.
NicBright

2
@NicBright Можете ли вы проиллюстрировать свою проблему с XYZ и ABC, как в вопросе, чтобы показать, что не так?
Адам

@jeremyjjbrown возможно ли повторно использовать клонированное репо и не использовать новое репо, т.е. мой вопрос здесь stackoverflow.com/questions/49269602/…
Qiulang

19

Обновление : модуль 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/


1
git-subtree теперь является частью Git, хотя и находится в дереве contrib, поэтому не всегда устанавливается по умолчанию. Я знаю, что он установлен формулой Homebrew git, но без справочной страницы. Таким образом, apenwarr называет свою версию устаревшей.
Эхристоферсон

19

Вот небольшая модификация 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папки.


1
Это сработало для меня с небольшой модификацией. Потому что мои 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

Это не работает, если какой-либо из подкаталогов изменился за всю историю в git. Как это можно решить?
Nietras

@nietras см. ответ rogerdpack. Мне потребовалось некоторое время, чтобы найти его после прочтения и усвоения всей информации в этих других ответах.
Адам

12

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

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

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

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

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

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


1
Мне нравится стиль этого графика. Могу я спросить, какой инструмент вы используете?
Слипп Д. Томпсон

3
Башня для Mac. Мне это и вправду нравится. Почти само по себе стоит перейти на Mac.
ММ.

2
Да, хотя в моем случае моя подпапка targetdirбыла переименована в какой-то момент и git filter-branchпросто назвала это днем, удалив все коммиты, сделанные до переименования! Шокирующе, учитывая, как адепт Git отслеживает подобные вещи и даже переносит отдельные фрагменты контента!
Джей Аллен

1
О, также, если кто-то окажется в одной лодке, вот команда, которую я использовал. Не забывайте, что для этого 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
Джей Аллен

7

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

т.е.

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

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

$ 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

6

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

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

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

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

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

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

6

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


1
Нашел иголку в стоге сена! Теперь я могу сохранить ВСЕ мою историю коммитов.
Адам

5

Я рекомендую руководство 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

Хороший пост, но я замечаю, что первый абзац документа, который вы связали, гласит: « 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сценарий приводит к потере истории, когда подкаталог был переименован. Можно ли что-нибудь сделать для решения этой проблемы?
Адам

Нашел решение для сохранения всех коммитов, включая те, что были переименованы / перемещены в каталогах - это ответ rogerdpack на этот самый вопрос.
Адам

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

5

При запуске 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)

1
Проголосовал за рекомендацию нового filter-repoинструмента (который я представил в прошлом месяце на stackoverflow.com/a/58251653/6309 )
VonC

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

4

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

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

https://github.com/slobobaby/git_filter

на GitHub.

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


4

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

git filter-branch --index-filter \
"git rm -r -f --cached --ignore-unmatch DIR" --prune-empty \
--tag-name-filter cat -- --all

что здесь кот?
rogerdpack

4

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


3

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

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

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

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

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

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


3

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

  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


Преимущество этого метода по сравнению с «Простым способом» состоит в том, что пульт дистанционного управления уже настроен для нового репо, поэтому вы можете сразу добавить поддерево. На самом деле этот путь кажется мне легче (даже без git splits)
ММ

Реквизиты для AndrewD для размещения этого решения. Я разветвлял его репо, чтобы он работал на OSX ( github.com/ricardoespsanto/git-splits ), если это полезно для кого-то еще
ricardoespsanto

2

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

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


2

Проверьте проект 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.

1

Поместите это в ваш 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

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


3
«Прекрасная и замечательная» часть поддерева состоит в том, что история вашего подкаталога приходит вместе с вами. Если вам не нужна история, тогда ваш мучительно простой метод - это путь.
pglezen

0

Вы можете легко попробовать 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

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

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


Почти идеально ... но вы забыли "git filter-branch --prune-empty", чтобы удалить все старые коммиты, которые теперь пусты. Делать, прежде чем нажать на оригинал мастера!
ZettaCircl

Если вы допустили ошибку и все еще хотите «отрешиться» после удаления старого пустого коммита, выполните: «git push -u origin master --force-with-lease»
ZettaCircl
Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.