Виділення (переміщення) підкаталогу в окремий сховище 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 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. Так, це дійсно --space-- all. Це повинні бути останніми параметрами для команди. Як виявив Матлі, це зберігає гілки та теги проекту, включені до нового репо.

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


29
Дуже гарна відповідь. Дякую! І щоб дійсно отримати саме те, що я хотів, я додав "- --all" до команди фільтр-гілка.
матлі

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К. Зауважте, що мені потрібно було виконати обидві ці команди, щоб отримати зменшення розміру.
saltycrane

1321

Easy Way ™

Виявляється, це настільки поширена і корисна практика, що владарі 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

Щасливий день!

Примітка. Якщо ви створили репо з a 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
onionjake

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

41
Якщо ви натиснули пароль на загальнодоступний сховище, вам слід змінити пароль, а не намагатися видаляти його з загальнодоступного репо-системи і сподіватися, що його ніхто не побачив.
Майлз Рут

8
Це рішення не зберігає історію.
Cœur

18
Команди popdта pushdкоманди роблять це досить неявним і складніше сприймати те, що він має намір робити ...
jones77

133

Відповідь Павла створює нове сховище, що містить / ABC, але не видаляє / ABC зсередини / XYZ. Наступна команда видалить / ABC зсередини / XYZ:

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

Звичайно, спершу тестуйте його у сховищі 'clone --no-hardlinks' та виконайте його за допомогою команд скидання, gc та prune списків Paul.


53
зробіть це, git filter-branch --index-filter "git rm -r -f --cached --ignore-unmatch ABC" --prune-empty HEADі це буде набагато швидше. index-filter працює над індексом, в той час як дерево-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 Про це піклується команда перепакування, і не буде жодних сипучих об'єктів.
Джош Лі

так, 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-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/


1
git-subtree тепер є частиною Git, хоча знаходиться в дереві contrib, тому не завжди встановлюється за замовчуванням. Я знаю, що він встановлений за формулою Homebrew git, але без його довідкової сторінки. таким чином apenwarr називає свою версію застарілою.
echristopherson

19

Ось невелика модифікація відповіді "Easy Easy ™" CoolAJ86 з метою розділення декількох підпапок (скажімо, sub1та sub2) в нове сховище git.

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 style (/). Наприклад, папку з іменем 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 копії сховища та видалити вручну папки, які ви хочете зберегти у кожному. Сторінка чоловіка підкріплює мене цим:

[...] уникайте використання [цієї команди], якщо для усунення вашої проблеми буде достатньо простого одного введення


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
Jay Allen

7

Щоб додати відповідь Павла , я виявив, що для остаточного відновлення простору я повинен підштовхнути HEAD до чистого сховища, що зменшує розмір каталогу .git / object / pack.

тобто

$ mkdir ... ABC.git
$ cd ... ABC.git
$ git init --баре

Після gc чорносливу також зробіть:

$ git push ... ABC.git HEAD

Тоді ви можете зробити

$ git клон ... ABC.git

і розмір ABC / .git зменшується

Насправді, деякі кроки, що займають багато часу (наприклад, git gc), не потрібні для натискання на очищення сховища, тобто:

$ git клон --no-hardlinks / XYZ / ABC
$ git filter-branch - підрозділ-фільтр ABC HEAD
$ git скидання - твердий
$ 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. Позбавтеся від subdir модуля, щоб корінь модуля став новим коренем проекту
  10. git mv moduleSubdir1/* .
  11. Видаліть реліктовий піддіар: rmdir moduleSubdir1
  12. Перевірте зміни в будь-якій точці: git status
  13. Створіть нове git repo та скопіюйте його 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 на це саме запитання.
Адам

Єдина проблема полягає в тому, що я більше не можу використовувати клоновану репо
Qiulang,

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
Upvoted для рекомендуючи новий filter-repoінструмент (який я представив в минулому місяці в stackoverflow.com/a/58251653/6309 )
VonC

На сьогоднішній день використання, git-filter-repoбезумовно, повинно бути кращим. Це набагато, набагато швидше і безпечніше git-filter-branch, і гарантії проти безлічі гатчей, з якими можна зіткнутися, переписуючи історію свого git. Сподіваємось, ця відповідь приверне більше уваги, оскільки саме на неї потрібно звернути увагу git-filter-repo.
Джеремі Кейні

4

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

Він має казкову назву 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

Підказка:

  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 просто видаляє посилання в історії, але не видаляє записи релогів, що містять дані. Звичайно, спершу тестуйте це.

Моє використання диска різко впало при цьому, хоча мої початкові умови були дещо іншими. Можливо - підпровідний фільтр заперечує цю потребу, але я сумніваюся в цьому.


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-NAME The BRANCH-NAMEis master

  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.