取消子模块的git子模块


378

如何取消git子模块的子模块(将所有代码带回内核)?

正如我“应该”如何,如“最佳程序”一样...


5
注意:使用git1.8.3,您现在可以尝试使用a git submodule deinit,请参阅下面的答案
VonC

6
我可能会误会,但是git submodule deinit似乎删除了代码。
Joe Germuska 2013年

2
自git 1.8.5(2013年11月)以来,简单git submodule deinit asubmodule ; git rm asubmodule就足够了,如下面我的回答所示
VonC 2014年

考虑使用git subtree
HiB

Answers:


527

如果只需要将子模块代码放入主存储库,则只需删除子模块,然后将文件重新添加到主存储库中即可:

git rm --cached submodule_path # delete reference to submodule HEAD (no trailing slash)
git rm .gitmodules             # if you have more than one submodules,
                               # you need to edit this file instead of deleting!
rm -rf submodule_path/.git     # make sure you have backup!!
git add submodule_path         # will add files instead of commit reference
git commit -m "remove submodule"

如果您还想保留子模块的历史记录,可以做一个小技巧:将子模块“合并”到主存储库中,以便结果与以前相同,只是子模块文件现在位于主存储库。

在主模块中,您需要执行以下操作:

# Fetch the submodule commits into the main repository
git remote add submodule_origin git://url/to/submodule/origin
git fetch submodule_origin

# Start a fake merge (won't change any files, won't commit anything)
git merge -s ours --no-commit submodule_origin/master

# Do the same as in the first solution
git rm --cached submodule_path # delete reference to submodule HEAD
git rm .gitmodules             # if you have more than one submodules,
                               # you need to edit this file instead of deleting!
rm -rf submodule_path/.git     # make sure you have backup!!
git add submodule_path         # will add files instead of commit reference

# Commit and cleanup
git commit -m "removed submodule"
git remote rm submodule_origin

最终的存储库看起来有些奇怪:将有多个初始提交。但这不会对git造成任何问题。

在第二种解决方案中,您将具有很大的优势,即您仍然可以在最初位于子模块中的文件上运行git blame或git log。实际上,您在这里所做的是在一个存储库中重命名许多文件,而git应该自动检测到这一点。如果git log仍然有问题,请尝试一些选项(--follow,-M,-C),这些选项可以更好地重命名/复制检测。


3
我想我需要对我拥有的一些git仓库做第二种方法(历史保存)。您能否解释一下以上命令的哪一部分导致子模块中的文件最终位于子目录中?是您执行合并git时将文件引入顶级目录(及其历史记录),但是当您执行git add submodule_path时,它隐式地为每个文件执行git mv吗?
Bowie Owens

5
基本上是。诀窍是git不存储重命名操作:而是通过查看父提交来检测到它们。如果上一次提交中存在文件内容,但文件名不同,则将其视为重命名(或副本)。在上述步骤中,请git merge确保每个文件都有一个“先前的提交”(在合并的两个“边”之一)。
gyim

6
谢谢gyim,我启动了一个项目,在该项目中,我认为将东西分成几个存储库并将它们与子模块链接回去是有意义的。但是现在看来,它的设计过度了,我想将它们重新组合在一起而又不失去我的历史。
Bowie Owens,

4
@theduke我也有这个问题。在执行以下步骤之前,可以通过以下方法解决该问题:将所有文件从子模块存储库移至目录结构,目录结构与要合并到的存储库的路径相同:即。如果主存储库中的子模块位于foo /中,请在子模块中执行mkdir foo && git mv !(foo) foo && git commit
克里斯·

35
需要增加--allow-unrelated-histories强制合并在合并假,因为我渐渐fatal: refusing to merge unrelated histories,更多在这里:github.com/git/git/blob/master/Documentation/RelNotes/...
vaskort

72

git 1.8.5(2013年11月)开始(不保留子模块的历史记录):

mv yoursubmodule yoursubmodule_tmp
git submodule deinit yourSubmodule
git rm yourSubmodule
mv yoursubmodule_tmp yoursubmodule
git add yoursubmodule

那将:

  • 取消注册并卸载(即删除)子模块(deinit因此是mv 第一个)的内容,
  • .gitmodules为您清理(rm),
  • 并在父存储库()的索引中删除表示该子模块SHA1 的特殊条目rm

子模块的删除完成(deinitgit rm)后,您可以将文件夹重命名为其原始名称,并将其作为常规文件夹添加到git repo中。

注意:如果子模块是由旧的Git(<1.8)创建的,则可能需要删除.git子模块自身内的嵌套文件夹,如Simon East评论的那样


如果需要保留子模块的历史记录,请参阅jsears答案,该答案使用git filter-branch


5
实际上,这确实从1.8.4中的工作树中将其删除(我的整个子模块目录都已清除)。
克里斯·

@ChrisDown,您的意思是,一个deinit人从子模块中清除了工作树?
VonC

是的,它删除了子模块目录中的所有内容。
克里斯·

2
@mschuett不,您没有丢失任何内容:子模块中首先没有.git。如果您的情况如此,那就是嵌套的回购协议,而不是子模块。这就解释了为什么上述答案不适用于您的情况。有关两者之间的区别,请参见stackoverflow.com/a/34410102/6309
VonC

1
@VonC我当前在2.9.0.windows.1上,但是我不确定子模块可能是几年前在git的早期版本上创建的。我认为只要在执行最后的add + commit之前删除该文件,这些步骤就似乎可行。
西蒙东

67

我创建了一个脚本,该脚本将子模块转换为简单目录,同时保留所有文件历史记录。它不会遭受git log --follow <file>其他解决方案所遭受的问题的困扰。这也是一个非常简单的单行调用,可以为您完成所有工作。祝你好运。

它以LucasJenß的出色工作为基础,在他的博客文章“ 将子模块集成到父存储库中 ”中进行了描述,但是使整个过程自动化并清理了其他一些极端情况。

最新的代码将通过github上的错误修复程序进行维护,网址https://github.com/jeremysears/scripts/blob/master/bin/git-submodule-rewrite,但是为了适当的stackoverflow应答协议,我包括了完整解决方案如下。

用法:

$ git-submodule-rewrite <submodule-name>

git-submodule-rewrite:

#!/usr/bin/env bash

# This script builds on the excellent work by Lucas Jenß, described in his blog
# post "Integrating a submodule into the parent repository", but automates the
# entire process and cleans up a few other corner cases.
# https://x3ro.de/2013/09/01/Integrating-a-submodule-into-the-parent-repository.html

function usage(){
  echo "Merge a submodule into a repo, retaining file history."
  echo "Usage: $0 <submodule-name>"
  echo ""
  echo "options:"
  echo "  -h, --help                Print this message"
  echo "  -v, --verbose             Display verbose output"
}

function abort {
    echo "$(tput setaf 1)$1$(tput sgr0)"
    exit 1
}

function request_confirmation {
    read -p "$(tput setaf 4)$1 (y/n) $(tput sgr0)"
    [ "$REPLY" == "y" ] || abort "Aborted!"
}

function warn() {
  cat << EOF
    This script will convert your "${sub}" git submodule into
    a simple subdirectory in the parent repository while retaining all
    contents and file history.

    The script will:
      * delete the ${sub} submodule configuration from .gitmodules and
        .git/config and commit it.
      * rewrite the entire history of the ${sub} submodule so that all
        paths are prefixed by ${path}.
        This ensures that git log will correctly follow the original file
        history.
      * merge the submodule into its parent repository and commit it.

    NOTE: This script might completely garble your repository, so PLEASE apply
    this only to a fresh clone of the repository where it does not matter if
    the repo is destroyed.  It would be wise to keep a backup clone of your
    repository, so that you can reconstitute it if need be.  You have been
    warned.  Use at your own risk.

EOF

  request_confirmation "Do you want to proceed?"
}

function git_version_lte() {
  OP_VERSION=$(printf "%03d%03d%03d%03d" $(echo "$1" | tr '.' '\n' | head -n 4))
  GIT_VERSION=$(git version)
  GIT_VERSION=$(printf "%03d%03d%03d%03d" $(echo "${GIT_VERSION#git version}" | tr '.' '\n' | head -n 4))
  echo -e "${GIT_VERSION}\n${OP_VERSION}" | sort | head -n1
  [ ${OP_VERSION} -le ${GIT_VERSION} ]
}

function main() {

  warn

  if [ "${verbose}" == "true" ]; then
    set -x
  fi

  # Remove submodule and commit
  git config -f .gitmodules --remove-section "submodule.${sub}"
  if git config -f .git/config --get "submodule.${sub}.url"; then
    git config -f .git/config --remove-section "submodule.${sub}"
  fi
  rm -rf "${path}"
  git add -A .
  git commit -m "Remove submodule ${sub}"
  rm -rf ".git/modules/${sub}"

  # Rewrite submodule history
  local tmpdir="$(mktemp -d -t submodule-rewrite-XXXXXX)"
  git clone "${url}" "${tmpdir}"
  pushd "${tmpdir}"
  local tab="$(printf '\t')"
  local filter="git ls-files -s | sed \"s/${tab}/${tab}${path}\//\" | GIT_INDEX_FILE=\${GIT_INDEX_FILE}.new git update-index --index-info && mv \${GIT_INDEX_FILE}.new \${GIT_INDEX_FILE}"
  git filter-branch --index-filter "${filter}" HEAD
  popd

  # Merge in rewritten submodule history
  git remote add "${sub}" "${tmpdir}"
  git fetch "${sub}"

  if git_version_lte 2.8.4
  then
    # Previous to git 2.9.0 the parameter would yield an error
    ALLOW_UNRELATED_HISTORIES=""
  else
    # From git 2.9.0 this parameter is required
    ALLOW_UNRELATED_HISTORIES="--allow-unrelated-histories"
  fi

  git merge -s ours --no-commit ${ALLOW_UNRELATED_HISTORIES} "${sub}/master"
  rm -rf tmpdir

  # Add submodule content
  git clone "${url}" "${path}"
  rm -rf "${path}/.git"
  git add "${path}"
  git commit -m "Merge submodule contents for ${sub}"
  git config -f .git/config --remove-section "remote.${sub}"

  set +x
  echo "$(tput setaf 2)Submodule merge complete. Push changes after review.$(tput sgr0)"
}

set -euo pipefail

declare verbose=false
while [ $# -gt 0 ]; do
    case "$1" in
        (-h|--help)
            usage
            exit 0
            ;;
        (-v|--verbose)
            verbose=true
            ;;
        (*)
            break
            ;;
    esac
    shift
done

declare sub="${1:-}"

if [ -z "${sub}" ]; then
  >&2 echo "Error: No submodule specified"
  usage
  exit 1
fi

shift

if [ -n "${1:-}" ]; then
  >&2 echo "Error: Unknown option: ${1:-}"
  usage
  exit 1
fi

if ! [ -d ".git" ]; then
  >&2 echo "Error: No git repository found.  Must be run from the root of a git repository"
  usage
  exit 1
fi

declare path="$(git config -f .gitmodules --get "submodule.${sub}.path")"
declare url="$(git config -f .gitmodules --get "submodule.${sub}.url")"

if [ -z "${path}" ]; then
  >&2 echo "Error: Submodule not found: ${sub}"
  usage
  exit 1
fi

if ! [ -d "${path}" ]; then
  >&2 echo "Error: Submodule path not found: ${path}"
  usage
  exit 1
fi

main

在Ubuntu 16.04上不起作用。我向Github存储库发送了请求请求
qznc

1
好收获,@ qznc。这已在OSX上进行了测试。当它在两个平台上传递时,我都会很高兴地将其合并。
jsears '17

@qznc Ubuntu 16.04支持已合并,答案已更新。
jsears '17

2
这是最好的答案,保留了整个历史。非常好!
CharlesB

1
使用github的最新版本,在Windows 10上的Git Bash 2.20.1.1中进行所有工作,没有错误:curl https://raw.githubusercontent.com/jeremysears/scripts/master/bin/git-submodule-rewrite > git-submodule-rewrite.sh./git-submodule-rewrite.sh <submodule-name>
Alexey,

32
  1. git rm --cached the_submodule_path
  2. .gitmodules文件中删除子模块部分,或者如果它是唯一的子模块,则删除文件。
  3. 提交“已删除子模块xyz”
  4. git add the_submodule_path
  5. 另一个提交“添加了xyz的代码库”

我还没有找到更简单的方法。您可以通过git commit -a口味将3-5压缩为一个步骤。


6
它不应该是.gitmodules不是.submodules
imz-伊万·扎哈拉里舍夫(Ivan Zakharyaschev)2015年

1
它应该.gitmodules不是.submodules
Mkey

1
我必须先删除.git子模块的目录,然后git add才能在子模块文件夹上工作
Carson Evans

16

这里有很多答案,但所有答案似乎都过于复杂,可能无法满足您的要求。我相信大多数人都想保留自己的历史。

对于此示例,主仓库为git@site.com:main/main.git,子模块仓库为git@site.com:main/child.git。这假定子模块位于父仓库的根目录中。根据需要调整说明。

首先克隆父仓库并删除旧的子模块。

git clone git@site.com:main/main.git
git submodule deinit child
git rm child
git add --all
git commit -m "remove child submodule"

现在,我们将子存储库添加到主存储库的上游。

git remote add upstream git@site.com:main/child.git
git fetch upstream
git checkout -b merge-prep upstream/master

下一步假设您希望将merge-prep分支上的文件移动到与上面子模块相同的位置,尽管您可以通过更改文件路径轻松地更改位置。

mkdir child

将.git文件夹以外的所有文件夹和文件移动到子文件夹中。

git add --all
git commit -m "merge prep"

现在,您只需将文件合并回master分支即可。

git checkout master
git merge merge-prep # --allow-unrelated-histories merge-prep flag may be required 

环顾四周,确保运行前一切正常 git push

您现在必须记住的一件事是,默认情况下git log不会跟踪移动的文件,但是通过运行,git log --follow filename您可以查看文件的完整历史记录。


2
我一路进入决赛git merge merge-prep并收到错误fatal: refusing to merge unrelated histories。解决方法是这样的:git merge --allow-unrelated-histories merge-prep
humblehacker

@humblehacker谢谢,我还添加了一点评论,以防其他人也遇到这个问题。
mschuett '16

1
保留子模块历史的最佳答案。谢谢@mschuett
Anton

在此处的示例中,是否有任何方法可以将上游的文件提取到child目录中,因此您以后不必移动它们了吗?我在子模块和主仓库中有相同的文件名...所以我遇到合并冲突,因为它试图将两个文件合并在一起。
Skitterm '18年

可能,但是我不知道这是不是。我个人将提交一份文件,以将文件移入您要移入的存储库中,以便在将其拉入之前将其驻留在所需的目录中
。– mschuett

12

碰巧,我们为2个项目创建了2个存储库,这些存储库是如此耦合,以至于无法将它们分开,因此我们将它们合并。

我将首先展示如何合并每个主分支,然后说明如何将其扩展到您拥有的每个分支,希望对您有所帮助。

如果子模块正常工作,并且想将其转换为目录,则可以执行以下操作:

git clone project_uri project_name

在这里,我们进行了干净的克隆工作。对于此过程,您无需初始化或更新子模块,因此只需跳过它。

cd project_name
vim .gitmodules

.gitmodules使用您喜欢的编辑器(或Vim)进行编辑,以删除计划替换的子模块。您需要删除的行应如下所示:

[submodule "lib/asi-http-request"]
    path = lib/asi-http-request
    url = https://github.com/pokeb/asi-http-request.git

保存文件后,

git rm --cached directory_of_submodule
git commit -am "Removed submodule_name as submodule"
rm -rf directory_of_submodule

在这里,我们完全删除了子模块关系,因此我们可以将其他存储库创建到位。

git remote add -f submodule_origin submodule_uri
git fetch submodel_origin/master

在这里,我们获取要合并的子模块存储库。

git merge -s ours --no-commit submodule_origin/master

在这里,我们开始2个存储库的合并操作,但是在提交之前停止。

git read-tree --prefix=directory_of_submodule/ -u submodule_origin/master

在这里,我们将子模块中master的内容发送到目录之前,该目录之前加上目录名称

git commit -am "submodule_name is now part of main project"

在这里,我们完成了合并中所做更改的提交过程。

完成此操作后,您可以推送并重新开始与任何其他分支合并,只需在存储库中签出将接收更改的分支,并更改您进行合并和读取树操作的分支。


这似乎并没有保留子模块文件的历史记录,我只是在git日志中看到针对在directory_of_submodule
Anentropic 2013年

@Anentropic抱歉,回复延迟。我只是再次做了完整的过程(有一个小修正)。该过程保留了整个历史记录,但是它有一个合并点,也许这就是为什么您找不到它的原因。如果要查看子模块的历史记录,只需执行“ git log”,查找合并提交(在示例中为消息“ submodule_name现在是主项目的一部分”)。它将有2个父提交(合并:sdasda asdasd),git log第二个提交,您在那里就拥有了所有子模块/主历史记录。
dvicino

我的记忆现在很朦胧,但是我认为我可以通过执行以下操作来获取合并的子模块文件的历史记录:git log original_path_of_file_in_submodule即即使子模块文件,在git repo中为该文件注册的路径(该文件系统中不再存在该路径)现在居住于submodule_path/new_path_of_file
Anentropic 2013年

这不能很好地保留历史,而且路径也有误。我觉得需要像树过滤器之类的东西,但我的能力不强……尝试在这里找到的东西: x3ro.de/2013/09/01/…–
Luke H

此答案已过时,stackoverflow.com
a/16162228/11343


6

这是@gyim答案的稍微改进的版本(IMHO)。他在主要工作副本中进行了一系列危险的更改,我认为在其中操作单独的克隆,然后在最后将它们合并在一起要容易得多。

在一个单独的目录中(为了使错误更容易清除并重试),请同时检查顶级仓库和子仓库。

git clone ../main_repo main.tmp
git clone ../main_repo/sub_repo sub.tmp

首先编辑子仓库,将所有文件移动到所需的子目录中

cd sub.tmp
mkdir sub_repo_path
git mv `ls | grep -v sub_repo_path` sub_repo_path/
git commit -m "Moved entire subrepo into sub_repo_path"

记下HEAD

SUBREPO_HEAD=`git reflog | awk '{ print $1; exit; }'`

现在从主仓库中删除子仓库

cd ../main.tmp
rmdir sub_repo_path
vi .gitmodules  # remove config for submodule
git add -A
git commit -m "Removed submodule sub_repo_path in preparation for merge"

最后,将它们合并

git fetch ../sub.tmp
# remove --allow-unrelated-histories if using git older than 2.9.0
git merge --allow-unrelated-histories $SUBREPO_HEAD

并做了!安全无任何魔法。


...那是什么答案?可能希望引用用户名以及最佳答案可能会随时间而变化。
Contango

@Contango答案已更新。但最佳答案仍然是领先400点的最佳答案;-)
无数据,

如果子仓库中已经包含一个subrepo用东西命名的目录,这行得通吗?

在最后一步中,我得到以下错误:在这种情况下git merge $SUBREPO_HEAD fatal: refusing to merge unrelated histories应该使用git merge $SUBREPO_HEAD --allow-unrelated-histories吗?还是应该没有我就弄错了?
钛米

1
@ Ti-m是的,这确实是合并两个不共享任何提交的历史的情况。自从我第一次写这篇文章以来,对无关历史的防范似乎是git中的新内容。我将更新我的答案。
无数据

3

对于什么时候

git rm [-r] --cached submodule_path

退货

fatal: pathspec 'emr/normalizers/' did not match any files

上下文:我rm -r .git*在子模块文件夹中进行操作,然后才意识到需要在刚刚添加了它们的主项目中对它们进行反调制。当对一些(而非全部)子信号进行非子调制时,出现上述错误。无论如何,我通过运行(当然是在之后rm -r .git*)修复了它们

mv submodule_path submodule_path.temp
git add -A .
git commit -m "De-submodulization phase 1/2"
mv submodule_path.temp submodule_path
git add -A .
git commit -m "De-submodulization phase 2/2"

请注意,这不会保留历史记录。


3

基于VonC的答案,我创建了一个简单的bash脚本来执行此操作。将add在年底必须使用通配符否则将取消先前rm的子模块本身。重要的是添加子模块目录的内容,而不要在目录中命名目录本身。add命令中。

在一个名为的文件中git-integrate-submodule

#!/usr/bin/env bash
mv "$1" "${1}_"
git submodule deinit "$1"
git rm "$1"
mv "${1}_" "$1"
git add "$1/**"

0

我发现从子模块获取本地提交数据也更加方便,因为否则我将丢失它们。(由于我无法访问该遥控器,因此无法推送它们)。因此,我将submodule / .git添加为remote_origin2,获取了该提交并从该分支合并。不知道我是否仍需要将远程子模块作为源,因为我对git还不熟悉。


0

这是我发现的最佳和最简单的方法。

在子模块仓库中,您要从HEAD合并到主仓库中:

  • git checkout -b "mergeMe"
  • mkdir "foo/bar/myLib/" (与您希望主仓库中的文件的位置相同的路径)
  • git mv * "foo/bar/myLib/" (全部进入路径)
  • git commit -m "ready to merge into main"

删除子模块并清除路径“ foo / bar / myLib”后,返回主仓库:

  • git merge --allow-unrelated-histories SubmoduleOriginRemote/mergeMe

繁荣完成

保存的历史

别担心


请注意,这几乎与其他答案完全相同。但这假定您拥有子模块存储库。同样,这使得子模块将来获得上游更改变得容易。

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.