Переписать историю git filter-branch create / разбить на подмодули / подпроекты
В настоящее время я импортирую проект cvs в git.
После импорта я хочу переписать историю, чтобы переместить существующий каталог в отдельный подмодуль.
Предположим, у меня есть такая структура:
file1
file2
file3
dir1
dir2
library
Теперь я хочу переписать историю, чтобы каталог library
всегда подмодуль git. Скажем, разбить указанные каталоги на их собственные подмодули / подпроекты
Это мой текущий код:
Перезапись-подмодуль файла (который называется)
cd project
git filter-branch --tree-filter $PWD/../$0-tree-filter --tag-name-filter cat -- --all
File rewrite-submodule-tree-filter
#! / Bin/ Баш функция gitCommit() { сбросить GIT_DIR сбросить GIT_WORK_TREE git add -A if [ -n "$(git diff --cached --name-only)" ] затем # что-то совершить git commit -F $_msg фи } _git_dir=$GIT_DIR _git_work_tree=$GIT_WORK_TREE сбросить GIT_DIR сбросить GIT_WORK_TREE _DIR =$PWD if [ -d "библиотека"] затем _MSG =$(временные файлы) git log ${GIT_COMMIT}^! --format="%B" > $_msg git rm -r - кэшированная библиотека библиотека CD if [ -d ".git" ] затем gitCommit еще мерзавец gitCommit фи CD.. export GIT_DIR=$_git_dir export GIT_WORK_TREE=$_git_work_tree Подмодуль git добавить -f ./lib фи GIT_DIR = $ _ GIT_DIR GIT_WORK_TREE = $ _ git_work_tree
Этот код создает файл.gitmodules, но не запись коммита подмодуля (строка Subproject commit <sha1-hash>
, вывод git diff
) в основном хранилище и файлы в каталоге library
все еще версионированы в основном хранилище, а не в хранилище подпроектов.
Заранее спасибо за любую подсказку
.Gitmodules выглядят так:
[подмодуль "библиотека"] путь = библиотека url = ./library
3 ответа
Я решил свой вопрос, вот решение:
git-submodule-split library another_library
скрипт git-submodule-split
:
#! / Bin / Баш set -eu если [ $# -eq 0 ] затем echo "Использование: $0 submodules-to-split" фи export _tmp=$(mktemp -d) export _libs="$@" потому что я в $_libs делать mkdir -p $_tmp/$i сделанный git filter-branch --commit-filter ' функция gitCommit() { git add -A if [ -n "$(git diff --cached --name-only)" ] затем git commit -F $_msg фи } >/dev/null # из git-filter-branch git checkout-index -f -u -a || die "Не удалось проверить индекс" # файлы, которые были удалены $ commit, теперь все еще находятся в рабочем дереве; # удалите их, иначе они будут добавлены снова git clean -d -q -f -x _git_dir=$GIT_DIR _git_work_tree=$GIT_WORK_TREE _git_index_file=$GIT_INDEX_FILE сбросить GIT_DIR сбросить GIT_WORK_TREE сбросить GIT_INDEX_FILE _MSG = $ (временные файлы) cat / dev / stdin> $ _msg потому что я в $_libs делать если [-d "$ i"] затем сбросить GIT_DIR сбросить GIT_WORK_TREE сбросить GIT_INDEX_FILE CD $ I if [-d ".git"] затем gitCommit еще git init> / dev / null gitCommit фи CD.. rsync -a -rtu $i/.git/ $_tmp/$i/.git/ export GIT_DIR=$_git_dir export GIT_WORK_TREE=$_git_work_tree export GIT_INDEX_FILE=$_git_index_file git rm -q -r - кэшированный $ i Подмодуль git add ./$i >/dev/null мерзавец добавить $ я фи сделанный рм $_msg export GIT_DIR=$_git_dir export GIT_WORK_TREE=$_git_work_tree export GIT_INDEX_FILE=$_git_index_file if [ -f ".gitmodules" ] затем git add .gitmodules фи _new_rev=$(git write-tree) сдвиг git commit-tree "$_new_rev" "$@"; ' --tag-name-filter cat -- -all потому что я в $_libs делать if [ -d "$_tmp/$i/.git" ] затем rsync -a -i -rtu $_tmp/$i/.git/ $i/.git/ CD $ I git reset --hard CD.. фи сделанный rm -r $_tmp git for-each-ref refs/original --format="%(refname)" | пока читаю я; сделать git update-ref -d $i; сделанный git reflog expire --expire=now --all git gc --aggressive --prune= сейчас
У меня есть проект с utils
библиотека, которая начала быть полезной в других проектах, и хотела разделить свою историю на подмодули. Не думал сначала смотреть на SO, поэтому я написал свой, он строит историю локально, так что это немного быстрее, после чего, если вы хотите, вы можете установить вспомогательную команду .gitmodules
файл и тому подобное, и толкайте сами истории подмодулей куда угодно.
Сама раздетая команда находится здесь, документ в комментариях, в неразборчивой, которая следует. Запустите его как свою собственную команду, с subdir
установить, как subdir=utils git split-submodule
если вы разделяете utils
каталог. Он взломан, потому что он одноразовый, но я тестировал его в подкаталоге Documentation в истории Git.
#!/bin/bash
# put this or the commented version below in e.g. ~/bin/git-split-submodule
${GIT_COMMIT-exec git filter-branch --index-filter "subdir=$subdir; ${debug+debug=$debug;} $(sed 1,/SNIP/d "$0")" "$@"}
${debug+set -x}
fam=(`git rev-list --no-walk --parents $GIT_COMMIT`)
pathcheck=(`printf "%s:$subdir\\n" ${fam[@]} \
| git cat-file --batch-check='%(objectname)' | uniq`)
[[ $pathcheck = *:* ]] || {
subfam=($( set -- ${fam[@]}; shift;
for par; do tpar=`map $par`; [[ $tpar != $par ]] &&
git rev-parse -q --verify $tpar:"$subdir"
done
))
git rm -rq --cached --ignore-unmatch "$subdir"
if (( ${#pathcheck[@]} == 1 && ${#fam[@]} > 1 && ${#subfam[@]} > 0)); then
git update-index --add --cacheinfo 160000,$subfam,"$subdir"
else
subnew=`git cat-file -p $GIT_COMMIT | sed 1,/^$/d \
| git commit-tree $GIT_COMMIT:"$subdir" $(
${subfam:+printf ' -p %s' ${subfam[@]}}) 2>&-
` &&
git update-index --add --cacheinfo 160000,$subnew,"$subdir"
fi
}
${debug+set +x}
#!/bin/bash
# Git filter-branch to split a subdirectory into a submodule history.
# In each commit, the subdirectory tree is replaced in the index with an
# appropriate submodule commit.
# * If the subdirectory tree has changed from any parent, or there are
# no parents, a new submodule commit is made for the subdirectory (with
# the current commit's message, which should presumably say something
# about the change). The new submodule commit's parents are the
# submodule commits in any rewrites of the current commit's parents.
# * Otherwise, the submodule commit is copied from a parent.
# Since the new history includes references to the new submodule
# history, the new submodule history isn't dangling, it's incorporated.
# Branches for any part of it can be made casually and pushed into any
# other repo as desired, so hooking up the `git submodule` helper
# command's conveniences is easy, e.g.
# subdir=utils git split-submodule master
# git branch utils $(git rev-parse master:utils)
# git clone -sb utils . ../utilsrepo
# and you can then submodule add from there in other repos, but really,
# for small utility libraries and such, just fetching the submodule
# histories into your own repo is easiest. Setup on cloning a
# project using "incorporated" submodules like this is:
# setup: utils/.git
#
# utils/.git:
# @if _=`git rev-parse -q --verify utils`; then \
# git config submodule.utils.active true \
# && git config submodule.utils.url "`pwd -P`" \
# && git clone -s . utils -nb utils \
# && git submodule absorbgitdirs utils \
# && git -C utils checkout $$(git rev-parse :utils); \
# fi
# with `git config -f .gitmodules submodule.utils.path utils` and
# `git config -f .gitmodules submodule.utils.url ./`; cloners don't
# have to do anything but `make setup`, and `setup` should be a prereq
# on most things anyway.
# You can test that a commit and its rewrite put the same tree in the
# same place with this function:
# testit ()
# {
# tree=($(git rev-parse `git rev-parse $1`: refs/original/refs/heads/$1));
# echo $tree `test $tree != ${tree[1]} && echo ${tree[1]}`
# }
# so e.g. `testit make~95^2:t` will print the `t` tree there and if
# the `t` tree at ~95^2 from the original differs it'll print that too.
# To run it, say `subdir=path/to/it git split-submodule` with whatever
# filter-branch args you want.
# $GIT_COMMIT is set if we're already in filter-branch, if not, get there:
${GIT_COMMIT-exec git filter-branch --index-filter "subdir=$subdir; ${debug+debug=$debug;} $(sed 1,/SNIP/d "$0")" "$@"}
${debug+set -x}
fam=(`git rev-list --no-walk --parents $GIT_COMMIT`)
pathcheck=(`printf "%s:$subdir\\n" ${fam[@]} \
| git cat-file --batch-check='%(objectname)' | uniq`)
[[ $pathcheck = *:* ]] || {
subfam=($( set -- ${fam[@]}; shift;
for par; do tpar=`map $par`; [[ $tpar != $par ]] &&
git rev-parse -q --verify $tpar:"$subdir"
done
))
git rm -rq --cached --ignore-unmatch "$subdir"
if (( ${#pathcheck[@]} == 1 && ${#fam[@]} > 1 && ${#subfam[@]} > 0)); then
# one id same for all entries, copy mapped mom's submod commit
git update-index --add --cacheinfo 160000,$subfam,"$subdir"
else
# no mapped parents or something changed somewhere, make new
# submod commit for current subdir content. The new submod
# commit has all mapped parents' submodule commits as parents:
subnew=`git cat-file -p $GIT_COMMIT | sed 1,/^$/d \
| git commit-tree $GIT_COMMIT:"$subdir" $(
${subfam:+printf ' -p %s' ${subfam[@]}}) 2>&-
` &&
git update-index --add --cacheinfo 160000,$subnew,"$subdir"
fi
}
${debug+set +x}
Примечание: запись субмодуля создается только тогда, когда вы
git submodule init
git submodule update
Вам не нужны эти команды в вашем rewrite-submodule-tree-filter
сценарий, так как речь идет только о правильной настройке .gitmodules
содержимое файла.
Ты бы их выполнилgit submodule
msgstr "команды только при первом использовании родительского репо: см." Клонирование проекта с субмодулями".
Вот обновленный ответ, который работает для меня на MacOSX. Основным изменением является использование pushd/popd для изменения каталогов, так что подмодуль может быть чем-то вроде module/glop, а не просто glop.
#!/bin/bash
set -eu
if [ $# -eq 0 ]
then
echo "Usage: $0 submodules-to-split"
fi
export _tmp=$(mktemp -d /tmp/git-submodule-split.XXXXXX)
export _libs="$@"
for i in $_libs
do
mkdir -p $_tmp/$i
done
git filter-branch --commit-filter '
function gitCommit()
{
git add -A
if [ -n "$(git diff --cached --name-only)" ]
then
git commit -F $_msg
fi
} >/dev/null
# from git-filter-branch
git checkout-index -f -u -a || die "Could not checkout the index"
# files that $commit removed are now still in the working tree;
# remove them, else they would be added again
git clean -d -q -f -x >&2
_git_dir=$GIT_DIR
_git_work_tree=$GIT_WORK_TREE
_git_index_file=$GIT_INDEX_FILE
unset GIT_DIR
unset GIT_WORK_TREE
unset GIT_INDEX_FILE
_msg=$(mktemp /tmp/git-submodule-split-msg.XXXXXX)
cat /dev/stdin > $_msg
for i in $_libs
do
if [ -d "$i" ]
then
unset GIT_DIR
unset GIT_WORK_TREE
unset GIT_INDEX_FILE
pushd $i > /dev/null
if [ -d ".git" ]
then
gitCommit
else
git init >/dev/null
gitCommit
fi
popd > /dev/null
mkdir -p $_tmp/$i
rsync -a -rtu $i/.git/ $_tmp/$i/.git/
export GIT_DIR=$_git_dir
export GIT_WORK_TREE=$_git_work_tree
export GIT_INDEX_FILE=$_git_index_file
git rm -q -r --cached $i >&2
git submodule add ./$i $i >&2
git add $i >&2
fi
done
export GIT_DIR=$_git_dir
export GIT_WORK_TREE=$_git_work_tree
export GIT_INDEX_FILE=$_git_index_file
if [ -f ".gitmodules" ]
then
git add .gitmodules >&2
fi
_new_rev=$(git write-tree)
shift
git commit-tree -F $_msg "$_new_rev" $@;
rm -f $_msg
' --tag-name-filter cat -- --all
for i in $_libs
do
if [ -d "$_tmp/$i/.git" ]
then
rsync -a -i -rtu $_tmp/$i/.git/ $i/.git/
pushd $i
git reset --hard
popd
fi
done
rm -rf $_tmp
git for-each-ref refs/original --format="%(refname)" | while read i; do git update-ref -d $i; done
git reflog expire --expire=now --all
git gc --aggressive --prune=now