2010-05-09 4 views
17

Я хочу похож на this question. Тем не менее, я хочу каталог, который расщепляется в отдельный репозиторий оставаться подкаталог в этом репо:Как разбить репозиторий git при сохранении подкаталогов?

У меня есть это:

foo/ 
    .git/ 
    bar/ 
    baz/ 
    qux/ 

И я хочу, чтобы разделить его на два полностью независимых репозиториев:

foo/ 
    .git/ 
    bar/ 
    baz/ 

quux/ 
    .git/ 
    qux/ # Note: still a subdirectory 

Как это сделать в git?

Я мог бы использовать метод из this answer, если есть способ переместить все содержимое нового репо в подкаталог на протяжении всей истории.

ответ

16

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

Вот пример со страницы человека:

git filter-branch --index-filter 'git rm --cached --ignore-unmatch filename' HEAD 

Это просто удаляет одно имя файла; то, что вы хотите сделать, это удалить все, кроме заданного подкаталога. Если вы хотите быть осторожным, вы можете явно перечислить каждый путь, чтобы удалить, но если вы хотите просто пойти олл-ин, вы можете просто сделать что-то вроде этого:

git filter-branch --index-filter 'git ls-tree -z --name-only --full-tree $GIT_COMMIT | grep -zv "^directory-to-keep$" | xargs -0 git rm --cached -r' -- --all 

Я ожидаю, что, вероятно, более элегантный способ ; если у кого-то есть что-нибудь, предложите это!

Несколько замечаний по этой команде:

  • фильтр-ветвь внутренне устанавливает GIT_COMMIT тока фиксации sha1
  • Я не ожидал бы --full-tree быть необходимыми, но, видимо, фильтр-отрасль работает индекс -фильтр из каталога .git-rewrite/t вместо верхнего уровня репо.
  • grep, вероятно, перебор, но я не думаю, что это проблема скорости.
  • --all применяет это ко всем реф. Я полагаю, вы действительно этого хотите. (-- отделяет его от опций фильтрации)
  • -z и -0 сообщают ls-tree, grep и xargs, чтобы использовать завершение NUL для обработки пробелов в именах файлов.

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

--prune-empty 

Это будет удалить все коммиты, которые пусты после применение индексного фильтра.

+0

Помимо вложенных одинарных кавычек (что я взял на себя смелость заменить), это работало почти идеально. Единственная проблема заключалась в том, что пустые записи фиксируют, что в журнале остаются несуществующие каталоги. Я удалил их с помощью 'git filter-branch -f -commit-filter ', если [z $ 1 = z \' git rev-parse $ 3^{tree} \ ']; затем skip_commit "$ @"; else git commit-tree "$ @"; fi '"$ @" ', который я нашел по адресу http://github.com/jwiegley/git-scripts/blob/master/git-remove-empty-commits – Thomas

+0

@Thomas: Спасибо, что исправил мою неосторожную ошибку! Кроме того, вы должны иметь возможность использовать фильтр фиксации в той же команде, что и фильтр индекса. Фильтры запускаются в порядке, указанном в документации; commit-фильтр, естественно, после фильтров, которые изменяют содержимое фиксации. Вероятно, вы также захотите использовать '--remap-to-ancestor', что приведет к тому, что refs, указывающие на пропущенные коммиты, будут перемещены в ближайший предк, а не исключать их. – Cascabel

+0

@Jefromi: аргумент 'index-filter' должен быть более легко выражен как' git rm -r -f --cached --ignore-unmatch $ (ls! (Directory-to-keep)) ', см. Мои ответы http : //stackoverflow.com/a/8079852/396967 и http://stackoverflow.com/a/7849648/396967 – kynan

3

Это то, что я в конечном итоге делает, чтобы решить эту проблему, когда у меня было это сам:

git filter-branch --index-filter \ 
'git ls-tree --name-only --full-tree $GIT_COMMIT | \ 
grep -v "^directory-to-keep$" | \ 
sed -e "s/^/\"/g" -e "s/$/\"/g" | \ 
xargs git rm --cached -r -f --ignore-unmatch \ 
' \ 
--prune-empty -- --all 

Решение основано на ответ Jefromi и на Detach (move) subdirectory into separate Git repository плюс много комментариев здесь на SO.

Причина, по которой решение Jefromi для меня не срабатывало, состояло в том, что у меня были файлы и папки в моем репо, чьи имена содержали специальные символы (в основном пробелы). Дополнительно git rm жаловался на непревзойденные файлы (разрешено с --ignore-unmatch).

Вы можете держать фильтрацию агностик в каталог не будучи в корне Repo или перемещается вокруг:

grep --invert-match "^.*directory-to-keep$" 

И, наконец, вы можете использовать это, чтобы отфильтровать фиксированный набор файлов или каталогов:

egrep --invert-match "^(.*file-or-directory-to-keep-1$|.*file-or-directory-to-keep-2$|…)" 

Для очистки после этого вы можете использовать следующие команды:

$ git reset --hard 
$ git show-ref refs/original/* --hash | xargs -n 1 git update-ref -d 
$ git reflog expire --expire=now --all 
$ git gc --aggressive --prune=now 
3

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

#!/bin/bash 

# usage: 
# git filter-branch --prune-empty --index-filter \ 
# 'this-script file-with-list-of-files-to-be-kept' -- --all 

if [ -z $1 ]; then 
    echo "Too few arguments." 
    echo "Please specify an absolute path to the file" 
    echo "which contains the list of files that should" 
    echo "remain in the repository after filtering." 
    exit 1 
fi 

# save a list of files present in the commit 
# which is currently being modified. 
git ls-tree -r --name-only --full-tree $GIT_COMMIT > files.txt 

# delete all files that shouldn't be removed 
while read string; do 
    grep -v "$string" files.txt > files.txt.temp 
    mv -f files.txt.temp files.txt 
done < $1 

# remove unwanted files (i.e. everything that remained in the list). 
# warning: 'git rm' will exit with non-zero status if it gets 
# an invalid (non-existent) filename OR if it gets no arguments. 
# If something exits with non-zero status, filter-branch will abort. 
# That's why we have to check carefully what is passed to git rm. 
if [ "$(cat files.txt)" != "" ]; then 
    cat files.txt | \ 
    # enclose filenames in "" in case they contain spaces 
    sed -e 's/^/"/g' -e 's/$/"/g' | \ 
    xargs git rm --cached --quiet 
fi 

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

+1

Большое спасибо за обмен! Это помогло мне в тестовом репо. Я также добавил 'if [" $ (cat $ 1) "==" "]; затем echo «Нет содержимого в исключающем файле» exit 1 fi' проверить, есть ли файл. Также кажется, что нужно предоставить полный путь к исключающему файлу. – Denis

+0

стр. Кроме того, исключить файл должен иметь последнюю строку пустой/мусор. – Denis

1

Очиститель метод:

git filter-branch --index-filter ' 
       git read-tree --empty 
       git reset $GIT_COMMIT path/to/dir 
     ' \ 
     -- --all -- path/to/dir 

или придерживаться только основных команд, в суб git read-tree --prefix=path/to/dir/ $GIT_COMMIT:path/to/dir для сброса.

Задание path/to/dir в rev-list args делает обрезку рано, с фильтром это дешево, это не имеет большого значения, но это хорошо, чтобы избежать потраченного впустую усилия в любом случае.

 Смежные вопросы

  • Нет связанных вопросов^_^