Выделите множество подкаталогов в новый отдельный репозиторий Git.

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

Вместо того, чтобы отсоединять один подкаталог, я хочу отсоединить пару. Например, мое текущее дерево каталогов выглядит так:

/apps
  /AAA
  /BBB
  /CCC
/libs
  /XXX
  /YYY
  /ZZZ

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

/apps
  /AAA
/libs
  /XXX

Аргумент --subdirectory-filter для git filter-branch не будет работать, потому что он избавляется от всего, кроме данного каталога, при первом запуске. Я думал, что использование аргумента --index-filter для всех нежелательных файлов будет работать (хотя и утомительно), но если я попытаюсь запустить его более одного раза, я получу следующее сообщение:

Cannot create a new backup.
A previous backup already exists in refs/original/
Force overwriting the backup with -f

Любые идеи? ТИА


person prisonerjohn    schedule 05.06.2010    source источник


Ответы (10)


Вместо того, чтобы иметь дело с подоболочкой и использовать ext glob (как предложил kynan), попробуйте гораздо более простой подход:

git filter-branch --index-filter 'git rm --cached -qr --ignore-unmatch -- . && git reset -q $GIT_COMMIT -- apps/AAA libs/XXX' --prune-empty -- --all

Как упоминалось в комментарии void.pointer, это удалит из текущего репозитория все, кроме apps/AAA и libs/XXX.

Удаление пустых коммитов слияния

Это оставляет много пустых слияний. Их можно удалить другим проходом, как описано raphinesse в его ответ:

git filter-branch --prune-empty --parent-filter \
'sed "s/-p //g" | xargs -r git show-branch --independent | sed "s/\</-p /g"'

⚠️ Предупреждение. В приведенном выше примере должны использоваться версии GNU sed и xargs, иначе будут удалены все коммиты из-за сбоя xargs. brew install gnu-sed findutils, а затем используйте gsed и gxargs:

git filter-branch --prune-empty --parent-filter \
'gsed "s/-p //g" | gxargs git show-branch --independent | gsed "s/\</-p /g"' 
person David Smiley    schedule 25.07.2013
comment
спасибо, Дэвид, это решение работает со мной, в отличие от git stack, который дублирует каждую фиксацию более одного раза. - person Mahmoud Adam; 03.11.2013
comment
кроме того, флаг --ignore-unmatch должен быть передан в git rm, в противном случае он не удался для самого первого коммита (в моем случае репозиторий был создан с помощью git svn clone) - person Pontomedon; 24.07.2014
comment
Все, что я получаю от этой команды, — это множество повторяющихся родительских ошибок. - person aaa90210; 17.04.2015
comment
Предполагая, что у вас есть теги в миксе, вам, вероятно, следует добавить --tag-name-filter cat к своим параметрам. - person Yonatan; 27.05.2015
comment
Не могли бы вы добавить дополнительную информацию, объясняющую, что делает эта длинная команда? - person Burhan Ali; 18.09.2018
comment
Я приятно удивлен, что это прекрасно работает в Windows с использованием git bash, уф! - person Dai; 20.10.2018
comment
@BurhanAli Для каждой фиксации в истории удаляются все файлы, кроме тех, которые вы хотите сохранить. Когда все будет сделано, у вас останется только указанная вами часть дерева, а также только эта история. - person void.pointer; 15.02.2019
comment
К вашему сведению: если вы используете ; вместо && в команде фильтра индекса, вам не нужна опция --ignore-unmatch. - person void.pointer; 18.02.2019
comment
Имя папки чувствительно к регистру даже в Windows, если вы укажете имя пути, регистр которого отличается от фактического регистра, произойдет непредвиденный результат (вместо этого эта папка будет удалена). - person aruku7230; 02.04.2019
comment
Если один из путей не существует в данном коммите, я получаю fatal: bad revision <path> и выполнение команды прерывается. Чтобы было ясно, я указываю файлы, а не каталоги. - person Quolonel Questions; 24.05.2019
comment
какова цель атрибута --cached? - person Martin Delille; 19.06.2019
comment
Как следует из названия, index-filter работает с индексом, поэтому вам нужно --cached, чтобы git rm также работал с индексом (см. git help rm). - person kynan; 27.07.2019
comment
Я изменил это и использовал для 1 папки и 2 файлов, где было apps/AAA libs/XXX. Работал как шарм. Не знаю как, но именно это делает Stack Overflow замечательным. - person AlanSE; 30.08.2019
comment
Вы должны упомянуть в своем ответе, что переменная среды $GIT_COMMIT предназначена для получения папок из определенного коммита, и если не задано/пусто, исходит из HEAD - person colin; 29.11.2019
comment
Если кто-то должен использовать git-filter-branch — и не может использовать --subdirectory-filter, потому что он хочет работать с несколькими папками, как указано здесь, — то это почти наверняка лучший подход из вариантов, перечисленных как здесь, так и в других местах. --index-filter намного быстрее, чем работа с рабочим деревом. И, возможно, что еще более важно, вложенный скрипт работает исключительно с git командами и, следовательно, должен вести себя одинаково в разных операционных средах. - person Jeremy Caney; 28.12.2019
comment
Тем не менее, стоит отметить, что это, конечно, перепишет вашу историю, но не обновит ссылки на коммиты в ваших сообщениях о коммитах. Поэтому, если ваша команда использует #refs в своих сообщениях о коммитах, и для вас важно, чтобы они продолжали работать, вы должны знать об этом. И, конечно же, как и в случае любой перезаписи истории, вы, вероятно, захотите отправить ее в новый репозиторий, чтобы не столкнуться с потенциальными проблемами, когда другие соавторы отправляют коммиты из ныне потерянных историй. . (В качестве альтернативы убедитесь, что все в вашей команде повторно клонируют свою копию репозитория на каждом устройстве.) - person Jeremy Caney; 28.12.2019
comment
Could not delete refs/tags/v0.2.3 Я получаю эту ошибку. - person Sohail Si; 03.02.2020
comment
Я получил это сообщение об ошибке: Cannot create a new backup. A previous backup already exists in refs/original/ Force overwriting the backup with -f. Так что это сработало: git filter-branch --prune-empty -f ... - person jherb; 04.06.2020
comment
git filter-branch ужасно медленный и устарел, я рекомендую альтернативу git filter-repo stackoverflow.com/a/61410689/1507124 для всего нетривиального - person CervEd; 12.03.2021

Ручные шаги с помощью простых команд git

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

Разделить

Предположим, что ваше исходное хранилище: original_repo.

1 - Разделить приложения:

git clone original_repo apps-repo
cd apps-repo
git filter-branch --prune-empty --subdirectory-filter apps master

2 - Разделить библиотеки

git clone original_repo libs-repo
cd libs-repo
git filter-branch --prune-empty --subdirectory-filter libs master

Продолжайте, если у вас более 2 папок. Теперь у вас будет два новых и временных репозитория git.

Побеждайте, объединяя приложения и библиотеки

3 - Подготовьте новый репо:

mkdir my-desired-repo
cd my-desired-repo
git init

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

touch a_file_and_make_a_commit # see user's feedback
git add a_file_and_make_a_commit
git commit -am "at least one commit is needed for it to work"

Когда временный файл зафиксирован, команда merge в следующем разделе остановится, как и ожидалось.

Исходя из отзывов пользователей, вместо добавления случайного файла, такого как a_file_and_make_a_commit, вы можете добавить .gitignore или README.md и т. д.

4 - Сначала объедините репозиторий приложений:

git remote add apps-repo ../apps-repo
git fetch apps-repo
git merge -s ours --no-commit apps-repo/master # see below note.
git read-tree --prefix=apps -u apps-repo/master
git commit -m "import apps"

Теперь вы должны увидеть каталог apps внутри вашего нового репозитория. git log должен отображать все соответствующие исторические сообщения коммитов.

Примечание: как Крис отметил ниже в комментариях, для более новой версии (>= 2.9) git вам нужно указать --allow-unrelated-histories с git merge

5 - Объедините репозиторий libs таким же образом:

git remote add libs-repo ../libs-repo
git fetch libs-repo
git merge -s ours --no-commit libs-repo/master # see above note.
git read-tree --prefix=libs -u libs-repo/master
git commit -m "import libs"

Продолжайте, если у вас есть более 2 репозиториев для слияния.

Ссылка: Объединить подкаталог другого репозитория с git

person chfw    schedule 17.02.2017
comment
Начиная с git 2.9 вам нужно использовать --allow-unrelated-histories в командах слияния. В противном случае это, похоже, хорошо работает для меня. - person Chris; 11.09.2017
comment
Гений! Спасибо вам большое за это. Первоначальные ответы, которые я просмотрел, используя древовидный фильтр в очень большом репозитории, предсказывали, что git потребуется более 26 часов для завершения перезаписи git. Намного более довольны этим простым, но повторяемым подходом, и мы успешно переместили 4 подпапки в новый репозиторий со всей ожидаемой историей коммитов. - person shuttsy; 27.06.2018
comment
Есть ли недостатки в том, чтобы сделать это в два этапа, как описано выше? - person Mariusz Jamro; 26.07.2018
comment
Один недостаток: есть дополнительная фиксация слияния, потому что вам нужно будет сделать хотя бы одну фиксацию, иначе этот подход не будет работать. - person chfw; 26.07.2018
comment
Вы можете использовать первую фиксацию для начальной фиксации, которая добавляет файлы .gitignore и README.md. - person Jack Miller; 21.08.2018
comment
@JackMiller, твой тоже. - person chfw; 23.08.2018
comment
К сожалению, этот подход, похоже, нарушает историю отслеживания для файлов, добавленных на шаге git merge .. git read-tree, поскольку он записывает их как недавно добавленные файлы, и все мои git gui не подключаются к их более ранним коммитам. - person Dai; 20.10.2018
comment
Это хорошо работает. Однако есть ли способ объединить все ветки (просто создать новые в новом репо?) - person jschober; 25.12.2018
comment
@chfw у вас есть какие-нибудь идеи о том, как решить проблему с недавно добавленными файлами? Кажется, что в команде слияния мы каким-то образом потеряли относительный путь к каталогам. - person ksadjad; 07.08.2019
comment
@ksadjad, понятия не имею, если честно. Центральным моментом ручного слияния является выбор каталогов для формирования нового репо и сохранения их истории коммитов. Я не уверен, как справиться с такой ситуацией, когда коммит помещает файлы в dirA, dirB, dirDrop, а для нового репо выбираются только dirA и dirB, как история фиксации должна относиться к исходной. - person chfw; 07.08.2019

Зачем вам запускать filter-branch более одного раза? Вы можете сделать все это за один раз, поэтому не нужно заставлять это делать (обратите внимание, что для этого вам нужно включить extglob в вашей оболочке):

git filter-branch --index-filter "git rm -r -f --cached --ignore-unmatch $(ls -xd apps/!(AAA) libs/!(XXX))" --prune-empty -- --all

Это должно избавиться от всех изменений в нежелательных подкаталогах и сохранить все ваши ветки и коммиты (если только они не влияют только на файлы в удаленных подкаталогах в силу --prune-empty) - никаких проблем с дубликатами коммитов и т. д.

После этой операции нежелательные каталоги будут перечислены как неотслеживаемые git status.

$(ls ...) необходимо с.т. extglob оценивается вашей оболочкой вместо индексного фильтра, который использует встроенный sh eval (где extglob недоступен). Дополнительные сведения об этом см. в разделе Как включить параметры оболочки в git?.

person kynan    schedule 21.10.2011
comment
Интересная идея. У меня похожая проблема, но я не смог заставить ее работать, см. stackoverflow.com/questions/8050687/ - person manol; 10.11.2011
comment
Это почти то, что мне было нужно, хотя в моем репозитории было разбросано как файлы, так и папки... Спасибо :) - person notlesh; 05.12.2011
comment
хм. даже при включенном extglob я получаю сообщение об ошибке рядом с моей скобкой: синтаксическая ошибка рядом с неожиданным токеном `(' моя команда выглядит так: git filter-branch -f --index-filter git rm -r -f --cached -- ignore-unmatch src/css/themes/!(some_theme*) --prune-empty -- --all ls с src/css/themes/!(some_theme*) возвращает все остальные темы, поэтому extglob работает ... - person robdodson; 02.12.2012
comment
Вы можете заставить свою оболочку оценивать глобус, как описано в stackoverflow.com/a/8079852/396967. - person kynan; 03.12.2012
comment
все, что мне нужно было сделать, это правильно сбежать от персонажей. (например, \!, (, \|, ) ). - person Mike Graf; 30.01.2013
comment
@MikeGraf Я не думаю, что это даст желаемый результат: побег будет соответствовать буквальному ! и т.д. на вашем пути. - person kynan; 31.01.2013
comment
Кайнан, ты прав, на самом деле это не сработало. Он запустился, но не дал желаемого результата.. (Я не понимаю, почему он не работает, он скомпилирован!! :P) - person Mike Graf; 31.01.2013
comment
В Ubuntu я обнаружил, что ls -x не помещает это в одну строку. Мне пришлось использовать $(ls -md apps/!(AAA) libs/!(XXX) | sed -e 's/, /,/g' | sed -e 's/ /\\ /g' | sed - e 's/&/\\&/g' | sed -e 's/,/ /g' | tr -d '\r\n') -m разделяет вывод запятыми вместо вывода на основе столбца I получал. И seds для работы с пробелами, амперсандами в именах файлов перед заменой запятых пробелами и избавлением от новых строк. - person Bae; 20.03.2014
comment
Кайнан указал (stackoverflow.com/a/8079852/396967), что ls -w ‹num› вернет ls на одну строку. - person Bae; 24.03.2014
comment
Я получал такие ошибки, как: /usr/lib/git-core/git-filter-branch: строка 415: foo.txt: команда не найдена. Оказалось, что это было вызвано многострочным выводом из ls и добавлением -w 1000 решили проблему. - person retroj; 23.07.2019
comment
Еще одна проблема, с которой я столкнулся, заключалась в том, что я хотел сохранить файлы, соответствующие шаблонам: foo* и quux/foo*. Шаблон !(foo*) соответствовал quux, поэтому все его содержимое удалялось, несмотря на второй шаблон. Решение было таким: !(foo*|quux) quux/!(foo*) - person retroj; 23.07.2019
comment
Ответ @david-smiley (более свежий) использует очень похожий подход, но имеет то преимущество, что он полагается исключительно на команды git, и поэтому не так подвержен различиям в интерпретации ls в разных операционных системах, как обнаружил @Bae. - person Jeremy Caney; 28.12.2019

Простое решение: git-filter-repo

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

Чтобы создать новый репозиторий из подмножества каталогов в существующем репозитории, вы можете использовать команду:

git filter-repo --path <file_to_keep>

Отфильтруйте несколько файлов/папок, объединив их в цепочку:

git filter-repo --path keepthisfile --path keepthisfolder/

Таким образом, чтобы ответить на исходный вопрос, с git-filter-repo вам понадобится следующая команда:

git filter-repo --path apps/AAA/ --path libs/XXX/
person elmo    schedule 24.04.2020
comment
Это определенно отличный ответ. Проблема со всеми другими решениями заключается в том, что мне не удалось извлечь содержимое ВСЕХ ветвей каталога. Однако git filter-repo извлек папку из всех веток и отлично переписал историю, как бы очистив все дерево от всего, что мне не нужно. - person Teodoro; 19.06.2020
comment
Я использовал ваш ответ. Это отличное решение. Потом как-то пропало во многих открытых вкладках. Мне пришлось буквально искать в истории моего браузера, чтобы найти вас и сказать спасибо. - person blueray; 26.08.2020

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

Мне удалось сделать это, используя комбинацию git subtree и git-stitch-repo . Эти инструкции основаны на:

Во-первых, я вытащил каталоги, которые хотел сохранить в отдельный репозиторий:

cd origRepo
git subtree split -P apps/AAA -b aaa
git subtree split -P libs/XXX -b xxx

cd ..
mkdir aaaRepo
cd aaaRepo
git init
git fetch ../origRepo aaa
git checkout -b master FETCH_HEAD

cd ..
mkdir xxxRepo
cd xxxRepo
git init
git fetch ../origRepo xxx
git checkout -b master FETCH_HEAD

Затем я создал новый пустой репозиторий и импортировал/вставил в него последние два:

cd ..
mkdir newRepo
cd newRepo
git init
git-stitch-repo ../aaaRepo:apps/AAA ../xxxRepo:libs/XXX | git fast-import

Это создает две ветки, master-A и master-B, каждая из которых содержит содержимое одного из сшитых репозиториев. Чтобы объединить их и очистить:

git checkout master-A
git pull . master-B
git checkout master
git branch -d master-A 
git branch -d master-B

Теперь я не совсем уверен, как/когда это происходит, но после первых checkout и pull код волшебным образом сливается с основной веткой (любое понимание того, что здесь происходит, приветствуется!)

Кажется, все сработало, как и ожидалось, за исключением того, что если я просматриваю историю коммитов newRepo, то обнаруживаю дубликаты, когда набор изменений затронул как apps/AAA, так и libs/XXX. Если есть способ удалить дубликаты, то это было бы идеально.

person prisonerjohn    schedule 07.06.2010
comment
Отличные инструменты вы нашли здесь. Понимание при оформлении заказа: git pull — это то же самое, что git fetch && git merge. Часть выборки безобидна, поскольку вы получаете ее локально. Поэтому я думаю, что эта команда проверки такая же, как git merge master-B, что немного более очевидно. См. kernel.org/pub/software/scm/git. /docs/git-pull.html - person phord; 28.07.2010
comment
К сожалению, в настоящее время инструмент git-stitch-repo не работает из-за плохих зависимостей. - person Henrik; 28.01.2013
comment
@ Хенрик С какой именно проблемой вы столкнулись? У меня это работает, хотя мне пришлось добавить export PERL5LIB="$PERL5LIB:/usr/local/git/lib/perl5/site_perl/" в мою конфигурацию bash, чтобы он мог найти Git.pm. Затем я установил его с помощью cpan. - person ; 27.03.2013
comment
Для выполнения этой задачи можно использовать git subtree add. См. stackoverflow.com/a/58253979/1894803. - person laconbass; 06.10.2019

Я написал фильтр git, чтобы решить именно эту проблему. Он имеет фантастическое имя git_filter и находится на github здесь:

https://github.com/slobobaby/git_filter

Он основан на отличном пакете libgit2.

Мне нужно было разделить большой репозиторий с большим количеством коммитов (~ 100000), и решения, основанные на git filter-branch, запускались несколько дней. git_filter делает то же самое за минуту.

person slobobaby    schedule 17.02.2014

Используйте расширение git «git splits»

git splits — это bash-скрипт, являющийся оболочкой для git branch-filter, который я создал как расширение git на основе на решении jkeating.

Он был сделан именно для этой ситуации. В случае вашей ошибки попробуйте использовать параметр git splits -f для принудительного удаления резервной копии. Поскольку git splits работает с новой веткой, она не перезапишет вашу текущую ветку, поэтому резервная копия не нужна. Подробнее см. в файле readme и обязательно используйте его для копии/клона репозитория (на всякий случай!).

  1. установите git splits.
  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 apps/AAA libs/ZZZ

  3. Создайте где-нибудь пустой репо. Предположим, что мы создали пустой репозиторий с именем xyz на GitHub, который имеет путь: [email protected]: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 [email protected]: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 [email protected]:simpliwp/xyz.git

person AndrewD    schedule 12.02.2015
comment
Не представляется возможным добавить файлы в раздел и обновить их позже, верно? - person Alex; 21.05.2017
comment
Кажется, это медленно работает в моем репо с кучей коммитов. - person Shinta Smith; 18.10.2017
comment
git-split использует git --index filter что очень медленно по сравнению с --subdirectory-filter. Для некоторых репозиториев это все еще может быть жизнеспособным вариантом, но для больших репозиториев (несколько гигабайт, 6-значные коммиты) --index-filter эффективно запускает недели, даже на выделенном облачном оборудовании. - person Jostein Kjønigsen; 14.03.2018

Ага. Принудительно перезапишите резервную копию, используя флаг -f при последующих вызовах filter-branch, чтобы отменить это предупреждение. :) В противном случае, я думаю, у вас есть решение (то есть удалить нежелательный каталог за один раз с помощью filter-branch).

person Jakob Borg    schedule 05.06.2010

Удалите резервную копию, находящуюся в каталоге .git в refs/original, как предлагается в сообщении. Каталог скрыт.

person user5200576    schedule 07.08.2015

person    schedule
comment
Прочитав все остальные комментарии, я на правильном пути. Тем не менее, ваше решение просто работает. Он импортирует все ветки и работает с несколькими каталогами! Здорово! - person jschober; 25.12.2018
comment
Цикл for стоит признать, поскольку другие подобные ответы не включают его. Если у вас нет локальной копии каждой ветки в вашем клоне, то filter-branch не будет учитывать их как часть своей перезаписи, что потенциально может исключить файлы, представленные в других ветках, но еще не объединенные с вашей текущей веткой. (Хотя также стоит выполнить git fetch для любых веток, которые вы ранее проверили, чтобы убедиться, что они остаются текущими.) - person Jeremy Caney; 28.12.2019