非子模Git子模

un-submodule a git submodule

如何取消Git子模块的子模块(将所有代码放回核心)?

至于"应该"如何,如"最佳程序"…


如果您只想将子模块代码放入主存储库,只需删除子模块并将文件重新添加到主repo中:

1
2
3
4
5
6
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"

如果您还想保留子模块的历史,您可以做一个小技巧:"合并"子模块到主存储库中,这样结果将与以前一样,只是子模块文件现在在主存储库中。

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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
# 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责备或git日志。实际上,您在这里所做的是重命名一个存储库中的许多文件,而Git应该自动检测到这一点。如果Git日志仍然有问题,请尝试一些选项(--follow,-m,-c),它们可以更好地重命名/复制检测。


自Git 1.8.5(2013年11月)起(不保留子模块历史):

1
2
3
4
5
mv yoursubmodule yoursubmodule_tmp
git submodule deinit yourSubmodule
git rm yourSubmodule
mv yoursubmodule_tmp yoursubmodule
git add yoursubmodule

那将:

  • 注销并卸载(即删除)子模块(deinit的内容,因此先卸载mv
  • 为你清理.gitmodules(rm)
  • 并在母公司回购索引(rm中删除表示该子模块sha1的特殊条目。

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

注意:如果子模块是由一个旧的git(<1.8)创建的,您可能需要删除子模块本身中嵌套的EDOCX1[7]文件夹,正如Simon East所评论的那样。

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


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

这是建立在卢卡斯·珍出色的作品之上的?在他的博客文章"将子模块集成到父存储库"中进行了描述,但是自动化了整个过程并清理了其他一些角落的案例。

最新的代码将通过在github上的错误修复进行维护,网址为https://github.com/jeremysers/scripts/blob/master/bin/git-submodule-rewrite,但是为了正确的stackoverflow应答协议,我已经在下面完整地包含了解决方案。

用途:

1
$ git-submodule-rewrite <submodule-name>

Git子模块重写:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
#!/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 '.' '
' | head -n 4))
  GIT_VERSION=$(git version)
  GIT_VERSION=$(printf"%03d%03d%03d%03d" $(echo"${GIT_VERSION#git version}" | tr '.' '
' | head -n 4))
  echo -e"${GIT_VERSION}
${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


  • git rm --cached the_submodule_path
  • .gitmodules文件中删除子模块部分,或者如果它是唯一的子模块,则删除该文件。
  • 执行"删除的子模块XYZ"
  • git add the_submodule_path
  • 另一个提交"添加了XYZ的代码库"
  • 我还没找到更容易的方法。你可以通过git commit -a味觉物质将3-5压缩成一个步骤。


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

    在本例中,主回购为[email protected]:main/main.git,子模块回购为[email protected]:main/child.git。这假设子模块位于父repo的根目录中。根据需要调整说明。

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

    1
    2
    3
    4
    5
    git clone [email protected]:main/main.git
    git submodule deinit child
    git rm child
    git add --all
    git commit -m"remove child submodule"

    现在我们将把子回购协议添加到主回购协议的上游。

    1
    2
    3
    git remote add upstream [email protected]:main/child.git
    git fetch upstream
    git checkout -b merge-prep upstream/master

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

    1
    mkdir child

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

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

    现在您可以简单地将文件合并回主分支。

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

    在运行git push之前,四处看看,确保一切正常。

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


    我们碰巧为两个项目创建了两个存储库,它们是如此地耦合以至于分离它们毫无意义,所以我们合并了它们。

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

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

    1
    git clone project_uri project_name

    在这里我们做一个干净的克隆工作。对于这个过程,您不需要初始化或更新子模块,所以跳过它。

    1
    2
    cd project_name
    vim .gitmodules

    用您最喜欢的编辑器(或vim)编辑.gitmodules以删除要替换的子模块。需要删除的行应如下所示:

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

    保存文件后,

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

    在这里,我们完全删除子模块关系,这样我们就可以创建一个将另一个repo带到项目中。

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

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

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

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

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

    在这里,我们将子模块中master的内容发送到它在前缀目录名之前所在的目录。

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

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

    完成此操作后,您可以推送并重新启动要合并的任何其他分支,只需签出存储库中接收更改的分支,然后更改引入合并和读取树操作的分支。


    我找到的最好的答案是:

    http://x3ro.de/2013/09/01/integrating-a-submodule-into-the-parent-repository.html

    本文很好地解释了这个过程。


    下面是当前热门答案的稍微改进版本(imho):

    在一个单独的目录中(为了使错误更容易清理并重试),检查顶部回购和子报告。

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

    首先编辑子报告,将所有文件移动到所需的子目录中。

    1
    2
    3
    4
    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"

    记下头部

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

    现在从主回购中移除子回购

    1
    2
    3
    4
    5
    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"

    最后,合并它们

    1
    2
    git fetch ../sub.tmp
    git merge $SUBREPO_HEAD

    完成了!安全,没有任何魔力。


    因为什么时候

    1
    git rm [-r] --cached submodule_path

    收益率

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

    上下文:我在子模块文件夹中执行了rm -r .git*,然后才意识到它们需要在我刚添加到的主项目中进行子模块化。当对其中一些进行去模时,我得到了上面的错误,但不是全部。不管怎么说,我是通过跑步来解决的,(当然,在rm -r .git*之后)

    1
    2
    3
    4
    5
    6
    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"

    请注意,这并不能保留历史。


    基于VONC的答案,我创建了一个简单的bash脚本来完成这个任务。末尾的add必须使用通配符,否则它将为子模块本身撤消先前的rm。重要的是添加子模块目录的内容,而不是在add命令中命名目录本身。

    在名为git-integrate-submodule的文件中:

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

    我发现(也是?)更方便从子模块获取本地提交数据,否则我将释放它们。(无法推送它们,因为我无法访问该遥控器)。所以我添加了子模块/.git作为远程的origin2,获取了它的提交并从该分支中合并。不知道我是否还需要子模块远程作为源,因为我还不太熟悉git。