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


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

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

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

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

XYZ/
    .git/
    XY1/
    ABC/
    XY2/

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

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

1589


источник


Ответы:


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

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


1155



Easy Way ™

Оказывается, это такая распространенная и полезная практика, что повелители 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(btw, «origin» - это просто соглашение, а не часть команды - вы можете назвать его «удаленным сервером» или как вам нравится)

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

...

аппендикс

Последняя игра на OS X

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

brew install git

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

http://brew.sh

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

Поэтому, если вы хотите удалить историю из «origin», то есть удалить ее из 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

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

кредит


1117



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

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

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


132



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

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

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

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

    git repack -ad
    

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


94



Изменить: добавлен скрипт 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

38



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

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

21



Update: The git-subtree module was so useful that the git team pulled it into core and made it git subtree. See here: Detach (move) subdirectory into separate Git repository

git-subtree may be useful for this

http://github.com/apenwarr/git-subtree/blob/master/git-subtree.txt (deprecated)

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


19



Here is a small modification to CoolAJ86's "The Easy Way™" answer in order to split multiple sub folders (let's say sub1and sub2) into a new git repository.

The Easy Way™ (multiple sub folders)

  1. Prepare the old repo

    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
    

    Note: <name-of-folder> must NOT contain leading or trailing characters. For instance, the folder named subproject MUST be passed as subproject, NOT ./subproject/

    Note for windows users: when your folder depth is > 1, <name-of-folder> must have *nix style folder separator (/). For instance, the folder named path1\path2\subproject MUST be passed as path1/path2/subproject. Moreover don't use mvcommand but move.

    Final note: the unique and big difference with the base answer is the second line of the script "git filter-branch..."

  2. Create the new repo

    mkdir <new-repo>
    pushd <new-repo>
    
    git init
    git pull </path/to/big-repo> <name-of-new-branch>
    
  3. Link the new repo to Github or wherever

    git remote add origin <git@github.com:my-user/new-repo.git>
    git push origin -u master
    
  4. Cleanup, if desired

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

    Note: This leaves all the historical references in the repository.See the Appendix in the original answer if you're actually concerned about having committed a password or you need to decreasing the file size of your .git folder.


13



The original question wants XYZ/ABC/(*files) to become ABC/ABC/(*files). After implementing the accepted answer for my own code, I noticed that it actually changes XYZ/ABC/(*files) into ABC/(*files). The filter-branch man page even says,

The result will contain that directory (and only that) as its project root."

In other words, it promotes the top-level folder "up" one level. That's an important distinction because, for example, in my history I had renamed a top-level folder. By promoting folders "up" one level, git loses continuity at the commit where I did the rename.

I lost contiuity after filter-branch

My answer to the question then is to make 2 copies of the repository and manually delete the folder(s) you want to keep in each. The man page backs me up with this:

[...] avoid using [this command] if a simple single commit would suffice to fix your problem


10