programing

Git 폴더를 하위 모듈로 소급하여 변환하시겠습니까?

iphone6s 2023. 9. 4. 19:37
반응형

Git 폴더를 하위 모듈로 소급하여 변환하시겠습니까?

어떤 종류의 프로젝트를 작성하는 경우가 많은데, 잠시 후에는 프로젝트의 일부 구성 요소가 독립 실행형 구성 요소(라이브러리 등)로 실제로 유용하다는 것이 분명해집니다.만약 당신이 일찍부터 그 아이디어를 가지고 있었다면, 그 코드의 대부분이 그 자체의 폴더에 있을 가능성이 상당히 높습니다.

Git 프로젝트의 하위 디렉터리 중 하나를 하위 모듈로 변환할 수 있는 방법이 있습니까?

이상적으로는 디렉터리의 모든 코드가 상위 프로젝트에서 제거되고 하위 모듈 프로젝트가 모든 적절한 기록과 함께 해당 위치에 추가되며 모든 상위 프로젝트 커밋이 올바른 하위 모듈 커밋을 가리킵니다.

하려면 하디렉리자체리로리분사리다다면용니합음을려를 합니다.filter-branch원래 저장소의 복제본에서:

git clone <your_project> <your_submodule>
cd <your_submodule>
git filter-branch --subdirectory-filter 'path/to/your/submodule' --prune-empty -- --all

그러면 원래 디렉터리를 삭제하고 하위 모듈을 상위 프로젝트에 추가하는 것에 불과합니다.

먼저 dir를 하위 모듈이 될 폴더로 변경합니다.그러면:

git init
git remote add origin <repourl>
git add .
git commit -am 'first commit in submodule'
git push -u origin master
cd ..
rm -rf <folder> # the folder which will be a submodule
git commit -am 'deleting folder'
git submodule add <repourl> <folder> # add the submodule
git commit -am 'adding submodule'

이것이 오래된 스레드라는 것을 알지만, 여기에 있는 답변은 다른 분기의 관련 커밋을 모두 삭제합니다.

모든 추가 분기 및 커밋을 복제하고 유지하는 간단한 방법:

1 - 이 Git 별칭이 있는지 확인합니다.

git config --global alias.clone-branches '! git branch -a | sed -n "/\/HEAD /d; /\/master$/d; /remotes/p;" | xargs -L1 git checkout -t'

2 - 원격 복제, 모든 분기 풀, 원격 변경, 디렉토리 필터링, 푸시

git clone git@github.com:user/existing-repo.git new-repo
cd new-repo
git clone-branches
git remote rm origin
git remote add origin git@github.com:user/new-repo.git
git remote -v
git filter-branch --subdirectory-filter my_directory/ -- --all
git push --all
git push --tags

현상 유지

가 있다고 가정해 repo-old하위 디렉토리를 포함하는 sub우리는 자체 레포를 가진 서브모듈로 변환하고 싶습니다.repo-sub.

원래의 레포는 더 의도된 것입니다.repo-old 수로변야합니다로 .repo-new의 서브디렉토리인 여서커밋이이전존에재던하다터니치합를리를 터치합니다.sub 커밋을 .repo-sub.

바꾸자꾸나

의 도움으로 이를 달성하는 것이 가능합니다.git filter-branch 2단계 프로세스:

  1. 하디렉추위에서 하위 repo-oldrepo-sub(승인된 답변에 언급된 내용)
  2. 하디 대리위에서 대체repo-oldrepo-new 매핑 사용자 정의)

비고: 저는 이 질문이 오래된 것으로 알고 있으며 이미 언급되었습니다.git filter-branch좀 더 사용하지 않고 위험할 수도 있습니다.그러나 다른 한편으로는 변환 후 검증하기 쉬운 개인 저장소를 사용하여 다른 사용자에게 도움이 될 수도 있습니다.그러니 경고해요!그리고 같은 일을 하지 않고 안전하게 사용할 수 있는 다른 도구가 있다면 알려주세요!

아래의 git 버전 2.26.2로 Linux에서 두 단계를 어떻게 실현했는지 설명하겠습니다.이전 버전은 어느 정도까지 작동할 수 있지만 이를 테스트해야 합니다.

단순함을 위해 나는 단지 한 가지 경우에만 나 자신을 제한할 것입니다.master과 가와가지지가.origin원판에서 멀리 떨어진.repo-old가 " 또접두가있임시는깃사태합점주니다도의야해는다용한한"인 임시 깃 태그에 한다는 점도 .temp_그 과정에서 제거될 것입니다.따라서 비슷한 이름의 태그가 이미 있는 경우 아래 접두사를 조정할 수 있습니다.그리고 마지막으로 제가 이것을 광범위하게 테스트하지 않았고 레시피가 실패하는 코너 사례가 있을 수 있다는 것을 알아두시기 바랍니다.계속하기 전에 모든 내용을 백업하십시오!

의 큰 될 수 , 이 스크립트는 음 bash 니의수있으며될결로, 이립트repo합다니야실어되행서에더와 같은 .repo-org모든 것을 복사하여 명령 창에 직접 붙여넣는 것은 권장되지 않습니다. (이것을 성공적으로 테스트했음에도 불구하고)

준비

변수

# Root directory where repo-org lives
# and a temporary location for git filter-branch
root="$PWD"
temp='/dev/shm/tmp'

# The old repository and the subdirectory we'd like to extract
repo_old="$root/repo-old"
repo_old_directory='sub'

# The new submodule repository, its url
# and a hash map folder which will be populated
# and later used in the filter script below
repo_sub="$root/repo-sub"
repo_sub_url='https://github.com/somewhere/repo-sub.git'
repo_sub_hashmap="$root/repo-sub.map"

# The new modified repository, its url
# and a filter script which is created as heredoc below
repo_new="$root/repo-new"
repo_new_url='https://github.com/somewhere/repo-new.git'
repo_new_filter="$root/repo-new.sh"

필터 스크립트

# The index filter script which converts our subdirectory into a submodule
cat << EOF > "$repo_new_filter"
#!/bin/bash

# Submodule hash map function
sub ()
{
    local old_commit=\$(git rev-list -1 \$1 -- '$repo_old_directory')

    if [ ! -z "\$old_commit" ]
    then
        echo \$(cat "$repo_sub_hashmap/\$old_commit")
    fi
}

# Submodule config
SUB_COMMIT=\$(sub \$GIT_COMMIT)
SUB_DIR='$repo_old_directory'
SUB_URL='$repo_sub_url'

# Submodule replacement
if [ ! -z "\$SUB_COMMIT" ]
then
    touch '.gitmodules'
    git config --file='.gitmodules' "submodule.\$SUB_DIR.path" "\$SUB_DIR"
    git config --file='.gitmodules' "submodule.\$SUB_DIR.url" "\$SUB_URL"
    git config --file='.gitmodules' "submodule.\$SUB_DIR.branch" 'master'
    git add '.gitmodules'

    git rm --cached -qrf "\$SUB_DIR"
    git update-index --add --cacheinfo 160000 \$SUB_COMMIT "\$SUB_DIR"
fi
EOF
chmod +x "$repo_new_filter"

하위 디렉터리 추출

cd "$root"

# Create a new clone for our new submodule repo
git clone "$repo_old" "$repo_sub"

# Enter the new submodule repo
cd "$repo_sub"

# Remove the old origin remote
git remote remove origin

# Loop over all commits and create temporary tags
for commit in $(git rev-list --all)
do
    git tag "temp_$commit" $commit
done

# Extract the subdirectory and slice commits
mkdir -p "$temp"
git filter-branch --subdirectory-filter "$repo_old_directory" \
                  --tag-name-filter 'cat' \
                  --prune-empty --force -d "$temp" -- --all

# Populate hash map folder from our previously created tag names
mkdir -p "$repo_sub_hashmap"
for tag in $(git tag | grep "^temp_")
do
    old_commit=${tag#'temp_'}
    sub_commit=$(git rev-list -1 $tag)

    echo $sub_commit > "$repo_sub_hashmap/$old_commit"
done
git tag | grep "^temp_" | xargs -d '\n' git tag -d 2>&1 > /dev/null

# Add the new url for this repository (and e.g. push)
git remote add origin "$repo_sub_url"
# git push -u origin master

하위 디렉터리 교체

cd "$root"

# Create a clone for our modified repo
git clone "$repo_old" "$repo_new"

# Enter the new modified repo
cd "$repo_new"

# Remove the old origin remote
git remote remove origin

# Replace the subdirectory and map all sliced submodule commits using
# the filter script from above
mkdir -p "$temp"
git filter-branch --index-filter "$repo_new_filter" \
                  --tag-name-filter 'cat' --force -d "$temp" -- --all

# Add the new url for this repository (and e.g. push)
git remote add origin "$repo_new_url"
# git push -u origin master

# Cleanup (commented for safety reasons)
# rm -rf "$repo_sub_hashmap"
# rm -f "$repo_new_filter"

비고: 새로 생성된 repo인 경우repo-new다음 시간 동안git submodule update --init그런 다음 저장소를 한 번씩 재귀적으로 다시 복제해 보십시오.

cd "$root"

# Clone the new modified repo recursively
git clone --recursive "$repo_new" "$repo_new-tmp"

# Now use the newly cloned one
mv "$repo_new" "$repo_new-bak"
mv "$repo_new-tmp" "$repo_new"

# Cleanup (commented for safety reasons)
# rm -rf "$repo_new-bak"

이제 공식 git 프로젝트에서 git-filter-repo를 사용할 것을 권장합니다.

# install git-filter-repo, see [1] for install via pip, or other OS's.
sudo apt-get install git-filter-repo 

# copy your repo; everything EXCEPT the subdir will be deleted, and the subdir will become root.
# --no-local is required to prevent git from hard linking to files in the original, and is checked by `filter-branch`
git clone working-dir/.git working-dir-copy --no-local
cd working-dir-copy

# extract the desired subdirectory and its history.
git filter-repo --subdirectory-filter foodir

# foodir is now its own directory. Push it to github/gitlab etc
git remote add origin user@hosting/project.git
git push -u origin --all
git push -u origin --tags

요지 덕분이기도 합니다.

편집: LFS 사용자(가난한 사람)의 경우 GitClone은 이미지의 전체 LFS 기록을 가져오지 않으므로 Git 푸시가 실패합니다.

// Original branch needs to get history of all images
git lfs fetch --all

// clone needs to copy the history
git lfs install --skip-smudge
git lfs pull working-dir --all

https://github.com/newren/git-filter-repo/blob/main/INSTALL.md

@knittl의 현재 답변은 다음을 사용합니다.filter-branch우리가 원하는 효과에 꽤 근접하게 해주지만, 시도했을 때, Git은 나에게 경고를 던졌습니다.

WARNING: git-filter-branch has a glut of gotchas generating mangled history
         rewrites.  Hit Ctrl-C before proceeding to abort, then use an
         alternative filtering tool such as 'git filter-repo'
         (https://github.com/newren/git-filter-repo/) instead.  See the
         filter-branch manual page for more details; to squelch this warning,
         set FILTER_BRANCH_SQUELCH_WARNING=1.

으로 질문되고된 지 이 지난 , 질이처년음질로대문답지지금지 9이난문이된으고,filter-branch을 위해 더 이상 사용하지 않습니다.git filter-repo사실, 내가 내 기트 이력을 봤을 때는git log --all --oneline --graph관련 없는 커밋으로 가득 차 있었습니다.

그럼 어떻게 사용하나요?Github에는 Git과는 별도로 설치해야 하는 꽤 좋은 기사가 있습니다.파이썬 버전을 사용했습니다.pip3 install git-filter-repo)

그들이 기사를 이동/삭제하기로 결정한 경우, 나는 아래에서 그들의 절차를 요약하고 일반화할 것입니다.

git clone <your_old_project_remote> <your_submodule>
cd <your_submodule>
git filter-repo --path path/to/your/submodule
git remote set-url origin <your_new_submodule_remote>
git push -u origin <branch_name>

여기서 새 리포지토리를 원하는 하위 모듈로 등록하기만 하면 됩니다.

cd <path/to/your/parent/module>
git submodule add <your_new_submodule_remote>
git submodule update
git commit

그것은 할 수 있지만, 간단하지 않습니다.를 하면.git filter-branch,subdirectory그리고.submodule그 과정에 대한 괜찮은 글들이 있습니다.는 기본적으로을 두 개 합니다.git filter-branch하나의 하위 디렉터리를 제외한 모든 항목을 제거하고 다른 하나의 하위 디렉터리만 제거합니다.그런 다음 두 번째 리포지토리를 첫 번째 리포지토리의 하위 모듈로 설정할 수 있습니다.

이 인플레이스로되므로 필터 분기와 할 수 는 인플레이스(in-place)를 합니다.됩니다. 필터 분기와 마찬가지로 변환을 백업할 수 있습니다(사용)git fetch . +refs/original/*:*).

저는 프로젝트가 있습니다.utils다른 프로젝트에서 유용하게 사용되기 시작했고, 역사를 하위 모듈로 나누고자 했던 라이브러리. 볼 했는데, 진행됩니다.명령의 할 수 . 로컬에서 기록을 구축하므로 훨씬 더 빨라집니다. 그 후에 원한다면 도우미 명령어를 설정할 수 있습니다..gitmodules파일 등을 작성하고 하위 모듈 기록 자체를 원하는 곳으로 밀어넣습니다.

제거된 명령어 자체가 여기에 있고, 의사는 주석에 있고, 다음에 이어지는 제거되지 않은 명령어에 있습니다. 실행니다합로으령명과 함께 합니다.subdir를 들어 트세, 예를 들어subdir=utils git split-submodule분할하는 경우utils디렉토리입니다.일회성이라 해크하지만 Git 내역에 있는 Documentation 하위 디렉토리에서 테스트했습니다.

#!/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}

이전 기록을 상위 폴더에만 보관할 수 있는 경우 간단한 해결 방법은 인덱스에서 하위 폴더를 제거하고 동일한 경로로 새 리포지토리 또는 하위 모듈을 시작하는 것입니다.예:

  1. 더하다subdir.gitignore
  2. rm -r --cached subdir
  3. git add .gitignore && git commit
  4. cd subdir && git init && git add .
  5. 새파서초커에서 subdir

git help rm:

--선택:인덱스에서 경로만 스테이징하고 제거하려면 이 옵션을 사용합니다.수정 여부에 관계없이 작업 트리 파일은 그대로 유지됩니다.

생산 코드에 하위 모듈을 사용한 적이 있기 때문에, 특히 프로젝트의 종속성을 문서화하기 때문에 좋은 솔루션이라고 말할 수 있습니다.

단순한 프로젝트의 경우, 또는 다른 개발자가 없거나 강한 의존성이 없고 폴더 구조가 더 편리한 경우, 하위 모듈은 약간 너무 많을 수 있습니다.그러나 해당 경로를 선택한 경우 1단계를 건너뛰고 그에 따라 진행합니다.

언급URL : https://stackoverflow.com/questions/12514197/convert-a-git-folder-to-a-submodule-retrospectively

반응형