un-submodule a git submodule


378

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

Як у тому, як "повинен" я, як у "Кращій процедурі" ...


5
Примітка: з git1.8.3 тепер ви можете спробувати git submodule deinit, дивіться мою відповідь нижче
VonC

6
Я можу неправильно зрозуміти, але deitit підмодулю git видає код.
Джо Гермуска

2
Оскільки git 1.8.5 (листопад 2013 р.), git submodule deinit asubmodule ; git rm asubmoduleДостатньо простого , як показано у моїй відповіді нижче
VonC

розглянути питання про використання Git поддерево
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.

У цьому другому рішенні ви матимете велику перевагу в тому, що ви все ще можете запускати git blame або git log на файли, які спочатку були в підмодулях. Насправді те, що ви тут зробили, - це перейменувати багато файлів у одному сховищі, і git повинен автоматично виявити це. Якщо у вас все ще виникають проблеми з журналом git, спробуйте кілька варіантів (- нижче, -M, -C), які краще перейменувати / виявити копію.


3
Я думаю, що мені потрібно зайнятись вашим другим методом (збереження історії) на деяких git repos. Чи можете ви пояснити, яка частина вищезазначених команд призводить до того, що файли з підмодуля потрапляють у підкаталог? Це те, що ви при виконанні об'єднання git приносить файл у каталозі верхнього рівня (з його історією), але коли ви додаєте git, додайте submodule_path, це неявність робить git mv для кожного файлу?
Боуї Оуенс

5
В основному, так. Хитрість полягає в тому, що git не зберігає операції з перейменуванням: натомість він виявляє їх, переглядаючи батьківські коміти. Якщо є вміст файлу, який був у попередній комісії, але з іншим ім'ям файлу, він вважається перейменуванням (або копією). На етапах вище, git mergeгарантується, що для кожного файлу (на одній із двох "сторін" об'єднання) буде "попередня фіксація".
gyim

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.


5
Це насправді видаляє його з робочого дерева в 1.8.4 (весь мій підмодуль був очищений).
Кріс Даун

@ChrisDown ви маєте на увазі, що 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>

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

Підтримка @qznc Ubuntu 16.04 об'єднана, а відповідь оновлено.
jsears

2
Це найкраща відповідь, зберігає всю історію. Дуже хороший!
ЧарльзB

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.shand./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працювати над папкою підмодуля
Карсон Еванс

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 за замовчуванням не слідує за переміщеними файлами, проте, запустивши, 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

1
Найкраща відповідь для збереження історії підмодуля. Дякую @mschuett
Антон Темченко

У наведеному прикладі чи є спосіб отримати файли вище в потоці до childкаталогу, тож вам не доведеться переміщувати їх пізніше? У мене однакові імена файлів у підмодулі та в основному репо ... тому я просто отримую конфлікт злиття, оскільки він намагається об'єднати два файли разом.
Скітерм

Можливо, але я цього не знаю напевно. Я особисто просто
взяв

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

Тут ми надсилаємо вміст master у підмодулі до каталогу, де він був до префіксації імені каталогу

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

Тут ми завершуємо процедуру, виконуючи фіксацію змін у об'єднанні.

Закінчивши це, ви можете натиснути і почати знову з будь-якої іншої гілки для злиття, просто замовте гілку у вашому сховищі, яка отримає зміни та змінить гілку, яку ви вводите в операціях злиття та читання дерева.


це, здається, не зберегло історію файлів підмодулю, я просто бачу одну фіксацію в журналі git для файлів, доданих підdirectory_of_submodule
Anentropic

@Anentropic Вибачте за затримку з відповіддю. Я просто зробив повну процедуру ще раз (з невеликим виправленням). Процедура зберігає всю історію, але вона має точку злиття, можливо, саме тому ви її не знаходите. Якщо ви хочете побачити історію підмодулю, просто зробіть "журнал git", знайдіть комісію злиття (у прикладі є повідомлення з повідомленням "submodule_name тепер є частиною головного проекту"). Він матиме 2 батьківських комітету (Злиття: sdasda asdasd), git журнал другого комітету, і ви отримали там весь свій підмодуль / головну історію.
dvicino

моя пам'ять зараз туманна, але я думаю, що мені вдалося отримати історію об'єднаних файлів підмодулів, зробивши, git log original_path_of_file_in_submoduleтобто шлях, зареєстрований у git repo для файлу (який більше не існує у файловій системі), хоча файл підмодуля зараз живе за адресоюsubmodule_path/new_path_of_file
Анентропік

Це не дуже добре зберігає історію, а також шляхи неправильні. Я відчуваю, що потрібне щось на кшталт дерева-фільтра, але я вийшов з глибини ... пробуючи те, що я тут знайшов: x3ro.de/2013/09/01/…
Лука 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 вже містить каталог, названий subrepoіз вмістом у ньому матеріалів?
detly

На останньому кроці я отримую таку помилку: 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

Мені було зручніше (також?) Отримувати місцеві фіксації даних з підмодуля, тому що в іншому випадку я б втратив їх. (Не вдалося натиснути їх, оскільки я не маю доступу до цього пульта). Тож я додав підмодуль / .git як remote_origin2, виніс його і здійснив об'єднання з цією гілкою. Не впевнений, чи мені ще потрібен дистанційний підмодуль як джерело, оскільки я ще недостатньо знайомий з git.


0

Ось що я вважаю найкращим та найпростішим.

У підмодулі repo від 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

бум зроблено

історії збереглися

ніяких турбот


Зауважте, це майже ідентично з деякими іншими відповідями. Але це передбачає, що у вас є підмодуль repo. Крім того, це полегшує отримання майбутніх змін для підмодуля.

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.