un-submodule un submódulo git


378

¿Cómo deshago el submódulo de un submódulo git (devuelvo todo el código al núcleo)?

Como en "debería" I, como en "Mejor procedimiento" ...


55
Nota: con git1.8.3, ahora puede probar un git submodule deinit, vea mi respuesta a continuación
VonC

66
Puedo entender mal, pero git submodule deinit parece eliminar el código.
Joe Germuska

2
Desde git 1.8.5 (noviembre de 2013), un simple git submodule deinit asubmodule ; git rm asubmodulees suficiente, como se ilustra en mi respuesta a continuación
VonC

considere usar git subtree
HiB

Respuestas:


527

Si todo lo que desea es poner su código de submódulo en el repositorio principal, solo necesita eliminar el submódulo y volver a agregar los archivos en el repositorio principal:

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"

Si también desea preservar el historial del submódulo, puede hacer un pequeño truco: "fusionar" el submódulo en el repositorio principal para que el resultado sea el mismo que antes, excepto que los archivos del submódulo ahora están en el repositorio principal.

En el módulo principal, deberá hacer lo siguiente:

# 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

El repositorio resultante se verá un poco extraño: habrá más de una confirmación inicial. Pero no causará ningún problema para git.

En esta segunda solución, tendrá la gran ventaja de que aún puede ejecutar git blame o git log en los archivos que originalmente estaban en submódulos. De hecho, lo que hizo aquí fue cambiar el nombre de muchos archivos dentro de un repositorio, y git debería detectarlo automáticamente. Si todavía tiene problemas con el registro de git, pruebe algunas opciones (--follow, -M, -C) que mejoran la detección de cambio de nombre / copia.


3
Creo que necesito hacer tu segundo método (preservar el historial) en algunos repositorios git que tengo. ¿Podría explicar qué parte de los comandos anteriores hace que los archivos del submódulo terminen en el subdirectorio? ¿Es usted cuando hace la fusión git trae el archivo en el directorio de nivel superior (con su historial) pero cuando hace git agrega submodule_path, la implicidad hace un git mv para cada archivo?
Bowie Owens

55
Básicamente sí. El truco es que git no almacena operaciones de cambio de nombre: en su lugar, las detecta mirando las confirmaciones principales. Si hay un contenido de archivo que estaba presente en la confirmación anterior, pero con un nombre de archivo diferente, se considera un cambio de nombre (o copia). En los pasos anteriores, git mergegarantiza que habrá un "compromiso previo" para cada archivo (en uno de los dos "lados" de la fusión).
gyim

66
Gracias Gyim, comencé un proyecto donde pensé que tenía sentido dividir las cosas en un par de repositorios y vincularlos nuevamente con submódulos. Pero ahora parece haber sido diseñado y quiero combinarlos de nuevo sin perder mi historia.
Bowie Owens

44
@theduke También tuve este problema. Se puede solucionar, antes de seguir estos pasos, moviendo todos los archivos del repositorio de submódulos a una estructura de directorio con la misma ruta que el repositorio en el que está a punto de fusionarse: es decir. si su submódulo en el repositorio principal está en foo /, en el submódulo, realice mkdir foo && git mv !(foo) foo && git commit.
Chris Down

35
Necesitaba agregar --allow-unrelated-historiespara forzar la fusión en la fusión falsa que estaba obteniendo fatal: refusing to merge unrelated histories, más aquí: github.com/git/git/blob/master/Documentation/RelNotes/…
vaskort

72

Desde git 1.8.5 (noviembre de 2013 ) ( sin mantener el historial del submódulo ):

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

Esa voluntad:

  • anular el registro y descargar (es decir, eliminar el contenido de ) el submódulo (de deinitahí el mv primero ),
  • limpia el .gitmodulespara ti ( rm),
  • y elimine la entrada especial que representa ese submódulo SHA1 en el índice del repositorio principal ( rm).

Una vez que se completa la eliminación del submódulo ( deinity git rm), puede cambiar el nombre de la carpeta a su nombre original y agregarlo al repositorio de git como una carpeta normal.

Nota: si el submódulo fue creado por un antiguo Git (<1.8), puede que tenga que quitar el anidado .gitcarpeta dentro de la misma sub-módulo, como se ha comentado por Simon Medio


Si necesita mantener el historial del submódulo, consulte la respuesta de jsears , que utiliza .git filter-branch


55
En realidad, esto lo elimina del árbol de trabajo en 1.8.4 (se borró todo mi directorio de submódulos).
Chris Down

@ChrisDown quieres decir, ¿el deinitsolo limpió el árbol de trabajo de tu submódulo?
VonC

Sí, elimina todo el contenido del directorio de submódulos.
Chris Down

2
@mschuett no, no te falta nada: un submódulo no tiene un .git en primer lugar. Si ese fuera el caso para usted, era un repositorio anidado, no un submódulo. Eso explica por qué esta respuesta anterior no se aplicaría en su caso. Para la diferencia entre los dos, consulte stackoverflow.com/a/34410102/6309 .
VonC

1
@VonC Actualmente estoy en 2.9.0.windows.1, sin embargo, los submódulos pueden haber sido creados hace varios años en una versión mucho anterior de git, no estoy seguro. Creo que los pasos parecen funcionar siempre que elimine ese archivo antes de hacer el add + commit final.
Simon East

67

He creado una secuencia de comandos que traducirá un submódulo a un directorio simple, mientras conserva todo el historial de archivos. No sufre los git log --follow <file>problemas que sufren las otras soluciones. También es una invocación de una línea muy fácil que hace todo el trabajo por usted. G'luck.

Se basa en el excelente trabajo de Lucas Jenß, descrito en su publicación de blog " Integrando un submódulo en el repositorio principal ", pero automatiza todo el proceso y limpia algunos otros casos de esquina.

El último código se mantendrá con correcciones de errores en github en https://github.com/jeremysears/scripts/blob/master/bin/git-submodule-rewrite , pero en aras del protocolo de respuesta de stackoverflow adecuado, he incluido el solución en su totalidad a continuación.

Uso:

$ 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

No funciona en Ubuntu 16.04. Envié una solicitud de extracción al repositorio de Github.
qznc

1
Buena captura, @qznc. Esto fue probado en OSX. Felizmente lo combinaré cuando pase en ambas plataformas.
jsears

El soporte de @qznc Ubuntu 16.04 se fusionó y la respuesta se actualizó.
jsears

2
Esta es la mejor respuesta, mantiene toda la historia. ¡Muy agradable!
CharlesB

1
Haga todo el trabajo sin errores en Git Bash 2.20.1.1 en Windows 10 con la última versión de github: curl https://raw.githubusercontent.com/jeremysears/scripts/master/bin/git-submodule-rewrite > git-submodule-rewrite.shy./git-submodule-rewrite.sh <submodule-name>
Alexey

32
  1. git rm --cached the_submodule_path
  2. elimine la sección del submódulo del .gitmodulesarchivo, o si es el único submódulo, elimine el archivo.
  3. hacer un commit "submódulo eliminado xyz"
  4. git add the_submodule_path
  5. otro commit "base de código agregada de xyz"

No encontré ninguna manera más fácil todavía. Puede comprimir 3-5 en un solo paso, por git commit -acuestión de gustos.


66
¿No debería ser en .gitmoduleslugar de .submodules?
imz - Ivan Zakharyaschev

1
Debe ser .gitmodulesno.submodules
tecla m

1
Tuve que eliminar el .gitdirectorio del submódulo antes de git addque funcionara en la carpeta del submódulo
Carson Evans

16

Muchas respuestas aquí, pero todas parecen ser demasiado complejas y probablemente no hacen lo que quieres. Estoy seguro de que la mayoría de la gente quiere mantener su historia.

Para este ejemplo, el repositorio principal será git@site.com:main/main.gity el repositorio de submódulo será git@site.com:main/child.git. Esto supone que el submódulo está ubicado en el directorio raíz del repositorio principal. Ajuste las instrucciones según sea necesario.

Comience clonando el repositorio principal y eliminando el submódulo anterior.

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

Ahora agregaremos los repositorios secundarios aguas arriba al repositorio principal.

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

El siguiente paso supone que desea mover los archivos de la rama merge-prep a la misma ubicación que el submódulo anterior, aunque puede cambiar fácilmente la ubicación cambiando la ruta del archivo.

mkdir child

mueve todas las carpetas y archivos excepto la carpeta .git a la carpeta secundaria.

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

Ahora puede simplemente fusionar sus archivos nuevamente en la rama maestra.

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

Mire a su alrededor y asegúrese de que todo se vea bien antes de correr git push

Lo único que debe recordar ahora es que git log no sigue de forma predeterminada los archivos movidos; sin embargo, al ejecutarlos git log --follow filenamepuede ver el historial completo de sus archivos.


2
Llegué hasta la final git merge merge-prepy recibí el error fatal: refusing to merge unrelated histories. Solución alternativa es la siguiente: git merge --allow-unrelated-histories merge-prep.
humblehacker

@humblehacker gracias, agregué un pequeño comentario en caso de que otros también se encuentren con esto.
mschuett

1
La mejor respuesta para mantener el historial del submódulo. Gracias @mschuett
Anton Temchenko

En el ejemplo aquí, ¿hay alguna forma de recuperar los archivos del flujo ascendente en el childdirectorio, para que no tenga que moverlos más tarde? Tengo los mismos nombres de archivo en un submódulo y el repositorio principal ... así que solo aparece un conflicto de fusión ya que está tratando de fusionar los dos archivos juntos.
Skitterm

Posiblemente pero no lo sé de improviso. Yo, personalmente, simplemente hacer un commit mover los archivos en el repositorio que está tratando de moverse en lo que residen en el directorio que desee antes de tirar de ellos en.
mschuett

12

Nos sucedió que creamos 2 repositorios para 2 proyectos que estaban tan acoplados que no tenía sentido separarlos, por lo que los fusionamos.

Primero mostraré cómo fusionar las ramas maestras en cada una y luego explicaré cómo puede extender esto a todas las ramas que tenga, espero que le sirva de ayuda.

Si tiene el submódulo funcionando y desea convertirlo a un directorio en su lugar, puede hacer lo siguiente:

git clone project_uri project_name

Aquí hacemos un clon limpio para trabajar. Para este proceso no necesita inicializar o actualizar los submódulos, así que simplemente omítalo.

cd project_name
vim .gitmodules

Edite .gitmodulescon su editor favorito (o Vim) para eliminar el submódulo que planea reemplazar. Las líneas que necesita eliminar deberían verse así:

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

Después de guardar el archivo,

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

Aquí eliminamos la relación de submódulo por completo para que podamos crear el otro repositorio al proyecto en el lugar.

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

Aquí buscamos el repositorio de submódulos para fusionar.

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

Aquí comenzamos una operación de fusión de los 2 repositorios, pero nos detenemos antes de confirmar.

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

Aquí enviamos el contenido de master en el submódulo al directorio donde estaba antes de prefijar un nombre de directorio

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

Aquí completamos el procedimiento haciendo un commit de los cambios en la fusión.

Después de terminar esto, puede presionar y comenzar de nuevo con cualquier otra rama para fusionar, solo revise la rama en su repositorio que recibirá los cambios y cambie la rama que trae en las operaciones de fusión y árbol de lectura.


esto no parece haber conservado el historial de los archivos de submódulos, solo veo un solo commit en el registro de git para los archivos agregados bajodirectory_of_submodule
Anentropic

@Anentropic Perdón por el retraso en responder. Acabo de hacer el procedimiento completo nuevamente (con una pequeña corrección). El procedimiento mantiene todo el historial, pero tiene un punto de fusión, quizás es por eso que no lo encuentra. Si desea ver el historial de submódulos, simplemente haga un "registro de git", busque la confirmación de fusión (en el ejemplo es el que tiene el mensaje "submodule_name ahora es parte del proyecto principal"). Tendrá 2 confirmaciones principales (Combinar: sdasda asdasd), registre la segunda confirmación y obtendrá todo su historial de submódulo / maestro allí.
dvicino

mi memoria está borrosa ahora, pero creo que pude obtener el historial de los archivos de submódulos fusionados, git log original_path_of_file_in_submodulees decir, la ruta registrada en el repositorio de git para el archivo (que ya no existe en el sistema de archivos) a pesar de que el archivo de submódulo ahora vive ensubmodule_path/new_path_of_file
Anentropic

Esto no conserva muy bien la historia, y también los caminos están equivocados. Siento que se necesita algo así como un filtro de árbol, pero estoy fuera de mi alcance ... probando lo que he encontrado aquí: x3ro.de/2013/09/01/…
Luke H

Esta respuesta es obsoleta, stackoverflow.com/a/16162228/11343 (respuesta de VonC) hace lo mismo pero mejor
CharlesB


6

Aquí hay una versión ligeramente mejorada (en mi humilde opinión) de la respuesta de @ gyim. Está haciendo un montón de cambios peligrosos en la copia de trabajo principal, donde creo que es mucho más fácil operar en clones separados y luego fusionarlos al final.

En un directorio separado (para hacer que los errores sean más fáciles de limpiar e intentar nuevamente), consulte tanto el repositorio superior como el subrepo.

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

Primero edite el subrepo para mover todos los archivos al subdirectorio deseado

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"

Tome nota de la CABEZA

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

Ahora elimine el subrepo del repositorio principal

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"

Y finalmente, solo fusionarlos

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

¡Y hecho! Con seguridad y sin ningún tipo de magia.


... que respuesta es esa? Es posible que desee hacer referencia al nombre de usuario y la respuesta principal puede cambiar con el tiempo.
Contango

Respuesta de @Contango actualizada. pero la respuesta principal sigue siendo la respuesta principal con una ventaja de 400 puntos ;-)
datos el

¿Funciona esto si el subrepo ya contiene un directorio llamado subrepocon cosas en él?
desvío

En el último paso me sale el siguiente error: git merge $SUBREPO_HEAD fatal: refusing to merge unrelated histories¿Debo usar git merge $SUBREPO_HEAD --allow-unrelated-historiesen este caso? ¿O debería funcionar sin y cometí un error?
Ti-m

1
@ Ti-m Sí, este es exactamente el caso de fusionar dos historias que no comparten ninguna confirmación. La protección contra historias no relacionadas parece ser nueva en git desde que escribí esto por primera vez; Actualizaré mi respuesta.
datos

3

Para cuando

git rm [-r] --cached submodule_path

devoluciones

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

Contexto: lo hice rm -r .git*en mis carpetas de submódulos antes de darme cuenta de que necesitaban ser sub-modulados en el proyecto principal al que los acababa de agregar. Obtuve el error anterior al desmodular algunos, pero no todos. De todos modos, los arreglé corriendo (después, por supuesto, el 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"

Tenga en cuenta que esto no preserva la historia.


3

Basado en la respuesta de VonC , he creado un script bash simple que hace esto. Al addfinal tiene que usar comodines; de lo contrario, deshacerá el anterior rmpara el submódulo mismo. Es importante agregar el contenido del directorio de submódulos y no nombrar el directorio en síadd comando.

En un archivo llamado git-integrate-submodule:

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

0

Me pareció más conveniente (¿también?) Recuperar datos de confirmación local del submódulo, porque de lo contrario los perdería. (No pude empujarlos ya que no tengo acceso a ese control remoto). Así que agregué submodule / .git como remote_origin2, busqué commits y me fusioné desde esa rama. No estoy seguro si todavía necesito el submódulo remoto como origen, ya que todavía no estoy lo suficientemente familiarizado con git.


0

Esto es lo que encontré mejor y más simple.

En el repositorio de submódulos, desde HEAD desea fusionarse con el repositorio principal:

  • git checkout -b "mergeMe"
  • mkdir "foo/bar/myLib/" (ruta idéntica a donde desea los archivos en el repositorio principal)
  • git mv * "foo/bar/myLib/" (mover todo al camino)
  • git commit -m "ready to merge into main"

De vuelta en el repositorio principal después de eliminar el submódulo y borrar la ruta "foo / bar / myLib":

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

boom hecho

historias preservadas

sin preocupaciones


Tenga en cuenta esto casi idéntico a algunas otras respuestas. Pero esto supone que tienes un repositorio de submódulos. Además, esto facilita obtener futuros cambios ascendentes para el submódulo.

Al usar nuestro sitio, usted reconoce que ha leído y comprende nuestra Política de Cookies y Política de Privacidad.
Licensed under cc by-sa 3.0 with attribution required.