un-submodule git submodule

378

Как мне удалить субмодуль подмодуля git (вернуть весь код обратно в ядро)?

Как в «как» я, как в «Лучшая процедура» ...

Quickredfox
источник
5
Примечание: теперь с git1.8.3 вы можете попробовать git submodule deinit, см. Мой ответ ниже
VonC
6
Я могу неправильно понять, но git submodule deinit, кажется, удаляет код.
Джо Гермуска
2
Начиная с git 1.8.5 (ноябрь 2013), достаточно простого git submodule deinit asubmodule ; git rm asubmodule, как показано в моем ответе ниже
VonC
рассмотрите возможность использования поддерева
HiB

Ответы:

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 blame или git log для файлов, которые изначально были в подмодулях. Фактически, вы сделали это для переименования множества файлов в одном репозитории, и git должен автоматически определить это. Если у вас все еще есть проблемы с git log, попробуйте некоторые опции (--follow, -M, -C), которые лучше обнаруживают переименование / копирование.

gyim
источник
3
Я думаю, что мне нужно сделать ваш второй метод (сохранение истории) на некоторых git-репозиториях, которые у меня есть. Не могли бы вы объяснить, какая часть вышеуказанных команд приводит к тому, что файлы из подмодуля попадают в подкаталог? Это то, что вы, когда вы делаете git слияния, вводите файл в каталог верхнего уровня (с его историей), но когда вы делаете git add submodule_path, это просто делает git mv для каждого файла?
Боуи Оуэнс
5
В основном да. Хитрость в том, что git не хранит операции переименования: вместо этого он обнаруживает их, просматривая родительские коммиты. Если в предыдущем коммите есть содержимое файла, но с другим именем файла, оно считается переименованным (или копируемым). В описанных выше шагах git mergeгарантирует, что будет «предыдущий коммит» для каждого файла (на одной из двух «сторон» слияния).
Гим
6
Благодаря gyim, я начал проект, где я думал, что имеет смысл разделить вещи на несколько репозиториев и связать их вместе с подмодулями. Но теперь это кажется чрезмерным, и я хочу объединить их вместе, не теряя своей истории.
Боуи Оуэнс
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 ) ( без учета истории субмодуля ):

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

Что будет:

  • отменить регистрацию и выгрузить (т.е. удалить содержимое ) подмодуль ( deinitотсюда mv первый ),
  • навести порядок .gitmodulesдля вас ( rm),
  • и удалите специальную запись, представляющую этот подмодуль SHA1 в индексе родительского репо ( rm).

Когда удаление субмодуля завершено ( deinitи git rm), вы можете переименовать папку обратно в ее первоначальное имя и добавить ее в git repo как обычную папку.

Примечание: если подмодуль был создан старой Git (<1.8), возможно , потребуется удалить вложенную .gitпапку внутри самого подмодуль, так прокомментировал на Саймона Востоке


Если вам нужно сохранить историю субмодуля см jsears «ы ответ , который использует git filter-branch.

VonC
источник
5
Это на самом деле удаляет его из рабочего дерева в 1.8.4 (весь мой подмодуль dir был очищен).
Крис Даун
@ChrisВы имеете в виду, что deinitодин очистил рабочее дерево от вашего подмодуля?
VonC
Да, он удаляет весь контент в каталоге подмодулей.
Крис Даун
2
@mschuett нет, вы ничего не упускаете: в подмодуле нет .git в первую очередь. Если это так, то это был вложенный репозиторий, а не подмодуль. Это объясняет, почему этот ответ выше не будет применяться в вашем случае. О разнице между ними см. Stackoverflow.com/a/34410102/6309 .
VonC
1
@VonC Я в настоящее время на 2.9.0.windows.1, однако, подмодули могли быть созданы несколько лет назад на более ранней версии git, я не уверен. Я думаю, что шаги, кажется, работают, пока я удаляю этот файл перед выполнением финального добавления + фиксации.
Саймон Ист
67

Я создал скрипт, который будет переводить подмодуль в простой каталог, сохраняя всю историю файлов. Он не страдает от git log --follow <file>проблем, от которых страдают другие решения. Это также очень простой однострочный вызов, который сделает всю работу за вас. G'luck.

Он основан на превосходной работе Лукаса Йенса, описанной в его посте в блоге « Интеграция подмодуля в родительский репозиторий », но автоматизирует весь процесс и очищает несколько других угловых случаев.

Последний код будет поддерживаться с исправлениями ошибок на github по адресу https://github.com/jeremysears/scripts/blob/master/bin/git-submodule-rewrite , но для правильного протокола ответа stackoverflow я включил Решение во всей полноте ниже.

Применение:

$ git-submodule-rewrite <submodule-name>

ГИТ-подмодуль-переписать:

#!/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
jsears
источник
Не работает на Ubuntu 16.04. Я отправил запрос на выгрузку в репозиторий Github.
qznc
1
Хороший улов, @qznc. Это было проверено на OSX. Я с радостью сливаю это, когда это пройдет на обеих платформах.
jsears
@qznc Поддержка Ubuntu 16.04 объединена и ответ обновлен.
jsears
2
Это лучший ответ, хранит всю историю. Очень хорошо!
CharlesB
1
Все работают без ошибок в Git Bash 2.20.1.1 на Windows 10 с последней версией от github: curl https://raw.githubusercontent.com/jeremysears/scripts/master/bin/git-submodule-rewrite > git-submodule-rewrite.shи./git-submodule-rewrite.sh <submodule-name>
Алексей
32
  1. git rm --cached the_submodule_path
  2. удалите секцию подмодуля из .gitmodulesфайла, или, если это единственный подмодуль, удалите файл.
  3. сделать коммит "удаленный подмодуль xyz"
  4. git add the_submodule_path
  5. другой коммит "добавлена ​​кодовая база xyz"

Я не нашел более простой способ. Вы можете сжать 3-5 в один шаг git commit -a- вопрос вкуса.

Марсель Джекверт
источник
6
Разве это не должно быть .gitmodulesвместо .submodules?
imz - Иван Захарящев
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

Следующий шаг предполагает, что вы хотите переместить файлы в ветке подготовки к слиянию в то же место, что и субмодуль, который был выше, хотя вы можете легко изменить местоположение, изменив путь к файлу.

mkdir child

переместите все папки и файлы, кроме папки .git, в дочернюю папку.

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

Теперь вы можете просто объединить ваши файлы обратно в главную ветку.

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

Посмотрите вокруг и убедитесь, что все выглядит хорошо, прежде чем бежать git push

Единственное, что вы должны помнить сейчас, это то, что git log по умолчанию не отслеживает перемещенные файлы, однако, запустив, git log --follow filenameвы можете увидеть полную историю ваших файлов.

mschuett
источник
2
Я прошел весь путь до финала git merge merge-prepи получил ошибку fatal: refusing to merge unrelated histories. Обходной путь заключается в следующем: git merge --allow-unrelated-histories merge-prep.
Humblehacker
@humblehacker спасибо, я добавил небольшой комментарий на случай, если другие столкнутся с этим.
mschuett
1
Лучший ответ, чтобы сохранить историю субмодуля. Спасибо @mschuett
Антон Темченко
В приведенном здесь примере, есть ли способ извлечь файлы восходящего потока в childкаталог, чтобы вам не пришлось их перемещать позже? У меня одинаковые имена файлов в подмодуле и главном репо ... поэтому я просто получаю конфликт слияния, так как он пытается объединить два файла вместе.
Skitterm
Возможно, но я не знаю это случайно. Лично я бы сделал коммит, перемещая файлы в репозитории, в который вы пытаетесь переместиться, чтобы они находились в каталоге, который вы хотите, прежде чем
извлекать
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

Здесь мы начинаем операцию слияния двух репозиториев, но останавливаемся перед фиксацией.

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

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

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

Здесь мы завершаем процедуру, делая фиксацию изменений в слиянии.

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

dvicino
источник
казалось, что это не сохранило историю файлов субмодулей, я просто вижу один коммит в журнале git для файлов, добавленных вdirectory_of_submodule
Anentropic
@Anentropic Извините за задержку с ответом. Я просто сделал всю процедуру снова (с небольшим исправлением). Процедура хранит всю историю, но в ней есть точка слияния, может быть, поэтому вы ее не найдете. Если вы хотите просмотреть историю подмодулей, просто выполните «git log», найдите коммит слияния (в примере показан с сообщением «submodule_name теперь является частью основного проекта»). У него будет 2 родительских коммита (Merge: sdasda asdasd), git зарегистрирует второй коммит, и вы получите всю свою историю субмодуля / мастера.
Двичино
моя память сейчас мутная, но я думаю, что смог получить историю объединенных файлов субмодулей, выполнив, git log original_path_of_file_in_submoduleнапример, путь, зарегистрированный в git-репозитории для файла (которого больше нет в файловой системе), даже если файл субмодуля сейчас живет вsubmodule_path/new_path_of_file
Anentropic
Это не очень хорошо сохраняет историю, а также неправильные пути. Я чувствую, что нужно что-то вроде древовидного фильтра, но я не в своей тарелке
Luke H
Этот ответ устарел, stackoverflow.com/a/16162228/11343 (ответ VonC) делает то же самое, но лучше
CharlesB
6

Вот немного улучшенная версия (IMHO) ответа @ gyim. Он вносит ряд опасных изменений в основную рабочую копию, и я думаю, что гораздо проще работать с отдельными клонами, а затем объединять их вместе в конце.

В отдельном каталоге (чтобы было легче исправить ошибки и повторить попытку) проверьте верхний репозиторий и вложенный репозиторий.

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"

Запишите ГОЛОВУ

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 ответ обновлен. но главный ответ по-прежнему является лучшим ответом с отрывом в 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"

Обратите внимание, что это не сохраняет историю.

brandones
источник
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/**"
void.pointer
источник
0

Я считаю более удобным (также?) Извлекать данные локальной фиксации из подмодуля, потому что в противном случае я бы их потерял. (Не могу их подтолкнуть, так как у меня нет доступа к этому пульту). Поэтому я добавил субмодуль / .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

бум сделан

истории сохранены

не беспокойся


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

CommaToast
источник