Отсоединить (переместить) подкаталог в отдельный Git-репозиторий - PullRequest
1673 голосов
/ 11 декабря 2008

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

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

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

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

XYZ/
    .git/
    XY1/
    ABC/
    XY2/

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

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

Ответы [ 24 ]

1225 голосов
/ 25 июля 2013

Легкий путь и торговля;

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

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

    pushd <big-repo>
    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

  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.

...

Пошаговое

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

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

tree ~/Code/node-browser-compat

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

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

pushd ~/Code/node-browser-compat/
git subtree split -P btoa -b btoa-only
popd

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

mkdir ~/Code/btoa/
pushd ~/Code/btoa/
git init
git pull ~/Code/node-browser-compat btoa-only

Далее я создаю новый репозиторий на Github или bitbucket, или что-то еще, и добавляю это origin (кстати, «origin» - это просто соглашение, а не часть команды - вы можете назвать его «remote-server») или как угодно)

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

С Днем Рождения!

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

git pull origin -u master
git push origin -u master

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

git rm -rf btoa

...

Приложение

Последний Git на OS X

Чтобы получить последнюю версию git:

brew install git

Чтобы получить кофе для OS X:

http://brew.sh

Последний 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, а просто подтверждает, что их больше нет. Если вы действительно хотите удалить исторические ссылки (т. Е. У вас есть подтвержденный пароль), вам нужно сделать следующее:

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

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

Credit

1190 голосов
/ 11 декабря 2008

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

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

132 голосов
/ 05 июня 2009

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

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

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

94 голосов
/ 20 октября 2009

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

Существует объяснение этого *1022* в руководстве для filter-branch .

39 голосов
/ 09 июня 2011

Редактировать: добавлен скрипт 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
23 голосов
/ 20 августа 2014

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

git filter-branch --prune-empty --subdirectory-filter <YOUR_SUBDIR_TO_KEEP> master
git push <MY_NEW_REMOTE_URL> -f .
19 голосов
/ 22 марта 2010

Обновление : Модуль 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-projects-with-git-subtree/

18 голосов
/ 06 августа 2015

Вот небольшая модификация CoolAJ86 * "Легкий путь и торговля"; ответьте , чтобы разделить несколько подпапок (скажем, 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.

11 голосов
/ 17 апреля 2012

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

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

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

I lost contiuity after filter-branch

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

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

7 голосов
/ 25 июля 2009

Чтобы добавить к ответ Пола , я обнаружил, что, чтобы в конечном итоге восстановить пространство, мне нужно отправить 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
...