desmódulo um submódulo git

378

Como desmódulo um submódulo git (trago todo o código de volta ao núcleo)?

Como em "deveria" eu, como em "Melhor procedimento" ...

Quickredfox
fonte
5
Nota: com git1.8.3, agora você pode tentar a git submodule deinit, veja minha resposta abaixo
VonC:
6
Posso entender mal, mas o submódulo git deinit parece remover o código.
Joe Germuska
2
Desde o git 1.8.5 (novembro de 2013), git submodule deinit asubmodule ; git rm asubmodulebasta um simples , como ilustrado na minha resposta abaixo
VonC 2/14/14
considere usar a subárvore
HiB

Respostas:

527

Se tudo o que você deseja é colocar o código do submódulo no repositório principal, basta remover o submódulo e adicionar novamente os arquivos ao repositório 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"

Se você também deseja preservar o histórico do submódulo, pode fazer um pequeno truque: "mescle" o submódulo no repositório principal para que o resultado seja o mesmo de antes, exceto que os arquivos do submódulo estão agora no diretório repositório principal.

No módulo principal, você precisará fazer o seguinte:

# 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

O repositório resultante parecerá um pouco estranho: haverá mais de um commit inicial. Mas isso não causará problemas para o git.

Nesta segunda solução, você terá a grande vantagem de ainda poder executar o git blame ou git log nos arquivos que estavam originalmente nos submódulos. De fato, o que você fez aqui é renomear muitos arquivos dentro de um repositório, e o git deve detectar isso automaticamente. Se você ainda tiver problemas com o git log, tente algumas opções (--follow, -M, -C) que melhor renomear / detecção de cópia.

Gyim
fonte
3
Eu acho que preciso fazer o seu segundo método (preservação da história) em alguns repositórios git que eu tenho. Você poderia explicar qual parte dos comandos acima faz com que os arquivos do submódulo acabem no subdiretório? É quando você mescla o git que o arquivo traz no diretório de nível superior (com seu histórico), mas quando o git adiciona submodule_path, isso implica em um git mv para cada arquivo?
Bowie Owens
5
Basicamente sim. O truque é que o git não armazena operações de renomeação: em vez disso, ele as detecta observando as confirmações pai. Se houver um conteúdo de arquivo presente no commit anterior, mas com um nome de arquivo diferente, ele será considerado uma renomeação (ou cópia). Nas etapas acima, git mergeassegura que haverá uma "confirmação anterior" para cada arquivo (em um dos dois "lados" da mesclagem).
gyim
6
Obrigado gyim, iniciei um projeto em que pensava que fazia sentido dividir as coisas em alguns repositórios e vinculá-los novamente aos submódulos. Mas agora parece superdimensionado e quero combiná-los novamente sem perder minha história.
Bowie Owens
4
@ theduke Eu também tive esse problema. Pode ser corrigido antes de seguir estas etapas, movendo todos os arquivos do repositório de submódulos para uma estrutura de diretórios com o mesmo caminho que o repositório no qual você está prestes a mesclar: ie. se o seu submódulo no repositório principal estiver em foo /, no submódulo, execute mkdir foo && git mv !(foo) foo && git commit.
Chris Baixo
35
Necessário para adicionar --allow-unrelated-historiespara forçar a fusão no falso merge como eu estava ficando fatal: refusing to merge unrelated histories, mais aqui: github.com/git/git/blob/master/Documentation/RelNotes/...
vaskort
72

Desde o git 1.8.5 (novembro de 2013 ) ( sem manter o histórico do submódulo ):

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

Aquilo vai:

  • cancelar o registro e descarregar (ou seja, excluir o conteúdo ) do submódulo ( deinitdaí o mv primeiro ),
  • limpe o .gitmodulespara você ( rm),
  • e remova a entrada especial que representa esse submódulo SHA1 no índice do repositório pai ( rm).

Depois que a remoção do submódulo estiver concluída ( deinite git rm), você poderá renomear a pasta novamente para seu nome original e adicioná-la ao repositório git como uma pasta comum.

Nota: se o submódulo foi criado por um antigo Git (<1.8), pode ser necessário remover a .gitpasta aninhada dentro do próprio submódulo, conforme comentado por Simon East


Se você precisar manter o histórico do submódulo, consulte a resposta de jsears , que usa .git filter-branch

VonC
fonte
5
Na verdade, isso o exclui da árvore de trabalho na 1.8.4 (todo o diretório do submódulo foi limpo).
Chris Baixo
@ ChrisDown você quer dizer, o deinitsozinho limpou a árvore de trabalho do seu submódulo?
VonC
Sim, ele remove todo o conteúdo no diretório do submódulo.
Chris Baixo
2
@ mschuett não, você não está perdendo nada: um submódulo não possui um .git nele. Se esse era o seu caso, era um repositório aninhado, não um submódulo. Isso explica por que essa resposta acima não se aplicaria no seu caso. Para a diferença entre os dois, consulte stackoverflow.com/a/34410102/6309 .
VonC 13/03/16
11
@VonC Atualmente, estou no 2.9.0.windows.1, no entanto, os submódulos podem ter sido criados há vários anos em uma versão muito anterior do git, não tenho certeza. Acho que as etapas parecem funcionar desde que eu remova esse arquivo antes de fazer o add + commit final.
Simon Médio
67

Eu criei um script que traduzirá um submódulo para um diretório simples, mantendo todo o histórico do arquivo. Não sofre com os git log --follow <file>problemas que as outras soluções sofrem. Também é uma chamada de uma linha muito fácil que faz todo o trabalho para você. Boa sorte.

Ele se baseia no excelente trabalho de Lucas Jenß, descrito em sua postagem no blog " Integrando um submódulo no repositório pai ", mas automatiza todo o processo e limpa alguns outros casos de canto.

O código mais recente será mantido com correções de bugs no github em https://github.com/jeremysears/scripts/blob/master/bin/git-submodule-rewrite , mas por uma questão de protocolo de resposta de fluxo de pilha adequado, incluí o solução em sua totalidade abaixo.

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
jsears
fonte
Não funciona no Ubuntu 16.04. Enviei uma solicitação de recebimento ao repositório do Github.
Qznc 3/05
11
Boa captura, @qznc. Isso foi testado no OSX. Felizmente, mesclarei isso quando passar nas duas plataformas.
jsears
@qznc Suporte ao Ubuntu 16.04 mesclado e resposta atualizada.
jsears
2
Esta é a melhor resposta, mantém toda a história. Muito agradável!
charlesb
11
Faça todo o trabalho sem erros no Git Bash 2.20.1.1 no Windows 10 com a versão mais recente do github: curl https://raw.githubusercontent.com/jeremysears/scripts/master/bin/git-submodule-rewrite > git-submodule-rewrite.she./git-submodule-rewrite.sh <submodule-name>
Alexey
32
  1. git rm --cached the_submodule_path
  2. remova a seção submódulo do .gitmodulesarquivo ou, se for o único submódulo, remova o arquivo.
  3. faça um commit "submódulo removido xyz"
  4. git add the_submodule_path
  5. outro commit "adicionado código base de xyz"

Ainda não encontrei nenhuma maneira mais fácil. Você pode comprimir 3-5 em uma etapa por git commit -aquestão de gosto.

Marcel Jackwerth
fonte
6
Não deveria ser em .gitmodulesvez de .submodules?
imz - Ivan Zakharyaschev
11
Deveria .gitmodulesnão ser.submodules
Mkey
11
Eu tinha que remover o .gitdiretório do submodule antes git addiria trabalhar na pasta submodule
Carson Evans
16

Muitas respostas aqui, mas todas elas parecem muito complexas e provavelmente não fazem o que você deseja. Tenho certeza que a maioria das pessoas quer manter sua história.

Neste exemplo, o repositório principal será [email protected]:main/main.gite o repositório do submódulo será [email protected]:main/child.git. Isso pressupõe que o submódulo esteja localizado no diretório raiz do repositório pai. Ajuste as instruções conforme necessário.

Comece clonando o repositório pai e removendo o submódulo antigo.

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

Agora, adicionaremos os repositórios derivados a montante ao repositório principal.

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

A próxima etapa pressupõe que você deseja mover os arquivos na ramificação de preparação para mesclagem para o mesmo local que o submódulo acima, embora seja possível alterar facilmente o local alterando o caminho do arquivo.

mkdir child

mova todas as pastas e arquivos, exceto a pasta .git, para a pasta filho.

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

Agora você pode simplesmente mesclar seus arquivos de volta ao ramo principal.

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

Olhe em volta e verifique se tudo está bem antes de executar git push

A única coisa que você deve se lembrar agora é que o log do git não segue os arquivos movidos por padrão; no entanto, ao executar, git log --follow filenamevocê pode ver o histórico completo dos seus arquivos.

mschuett
fonte
2
Cheguei à final git merge merge-prepe recebi o erro fatal: refusing to merge unrelated histories. Solução alternativa é esta: git merge --allow-unrelated-histories merge-prep.
Humblehacker
@humblehacker obrigado Eu adicionei um pequeno comentário caso outros se deparem com isso também.
mschuett
11
A melhor resposta para manter o histórico do submódulo. Obrigado @mschuett
Anton Temchenko
No exemplo aqui, existe alguma maneira de buscar os arquivos do upstream no childdiretório, para que você não precise movê-los mais tarde? Eu tenho o mesmo nome de arquivo em um submódulo e o repositório principal ... então, acabei de ter um conflito de mesclagem, pois ele está tentando mesclar os dois arquivos.
Skitterm
Possivelmente, mas não sei de imediato. Eu pessoalmente apenas fazer um commit mover os arquivos no repo que você está tentando mover-se para que eles residem no diretório que você deseja antes de puxar-los.
mschuett
12

Aconteceu-nos que criamos 2 repositórios para 2 projetos que eram tão acoplados que não faziam sentido separá-los, por isso os fundimos.

Vou mostrar como mesclar as ramificações principais em cada uma delas primeiro e depois explicarei como você pode estender isso para todas as ramificações que você obteve, espero que ajude.

Se você tem o submódulo funcionando e deseja convertê-lo em um diretório, você pode:

git clone project_uri project_name

Aqui fazemos um clone limpo para trabalhar. Para esse processo, você não precisa inicializar ou atualizar os submódulos; basta pular.

cd project_name
vim .gitmodules

Edite .gitmodulescom seu editor favorito (ou Vim) para remover o submódulo que você planeja substituir. As linhas que você precisa remover devem ter a seguinte aparência:

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

Depois de salvar o arquivo,

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

Aqui, removemos completamente a relação do submódulo para que possamos criar o outro repositório para o projeto no local.

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

Aqui, buscamos o repositório do submódulo para mesclar.

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

Aqui começamos uma operação de mesclagem dos 2 repositórios, mas paramos antes de confirmar.

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

Aqui enviamos o conteúdo do master no submódulo para o diretório em que estava antes de prefixar o nome do diretório

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

Aqui, concluímos o procedimento realizando as alterações na mesclagem.

Após concluir isso, você pode enviar por push e iniciar novamente com qualquer outra ramificação para mesclar, basta fazer check-out da ramificação em seu repositório que receberá as alterações e alterará a ramificação que você está trazendo nas operações de mesclagem e árvore de leitura.

dvicino
fonte
isso não parecia ter preservado a história dos arquivos submódulo, eu só vejo uma única confirmação no log de git para os arquivos adicionados sobdirectory_of_submodule
Anentropic
@ Anentropic Desculpe pela demora em responder. Acabei de fazer o procedimento completo novamente (com uma pequena correção). O procedimento mantém toda a história, mas possui um ponto de fusão, talvez seja por isso que você não a encontra. Se você quiser ver o histórico do sub-módulo, basta fazer um "log git", procure o commit de mesclagem (no exemplo, é aquele com a mensagem "submodule_name agora faz parte do projeto principal"). Ele terá 2 confirmações pai (Mesclar: sdasda asdasd), git registrará a segunda confirmação e você terá todo o seu histórico de submódulo / mestre lá.
Dvicino
minha memória está turva agora, mas acho que consegui obter o histórico dos arquivos do submódulo mesclado, fazendo git log original_path_of_file_in_submoduleo caminho registrado no repositório git para o arquivo (que não existe mais no sistema de arquivos), mesmo que o arquivo do submódulo agora vive emsubmodule_path/new_path_of_file
Anentropic
Isso não preserva muito bem a história e também os caminhos estão errados. Eu sinto que algo como um filtro de árvore é necessário, mas estou fora de minha profundidade ... tentando o que encontrei aqui: x3ro.de/2013/09/01/…
Luke H
Esta resposta é obsoleta, stackoverflow.com/a/16162228/11343 (resposta do VonC) faz o mesmo, mas melhor #
801 CharlesB
6

Aqui está uma versão ligeiramente melhorada (IMHO) da resposta do @ gyim. Ele está fazendo várias alterações perigosas na cópia principal de trabalho, onde acho muito mais fácil operar em clones separados e depois juntá-los no final.

Em um diretório separado (para facilitar a limpeza de erros e tente novamente), verifique o repo superior e o subrepo.

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

Primeiro edite o subrepo para mover todos os arquivos para o subdiretório desejado

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"

Anote o HEAD

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

Agora remova o subrepo do repo 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"

E, finalmente, apenas mesclá-los

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

E feito! Com segurança e sem nenhuma mágica.

sem data
fonte
... qual resposta é essa? Pode querer referenciar o nome de usuário, assim como a resposta principal pode mudar com o tempo.
Contango 22/10/19
Resposta do @Contango atualizada. mas a resposta principal ainda é a resposta principal por uma vantagem de 400 pontos ;-) #
31819
Isso funciona se o subrepo já contiver um diretório nomeado subrepocom itens?
detly
Na última etapa, recebo o seguinte erro: git merge $SUBREPO_HEAD fatal: refusing to merge unrelated historiesDevo usar git merge $SUBREPO_HEAD --allow-unrelated-historiesneste caso? Ou deveria funcionar sem e eu cometi um erro?
Ti-m
11
@ Ti-m Sim, este é exatamente o caso da fusão de dois históricos que não compartilham nenhum commit. A proteção contra histórias não relacionadas parece ser nova no git desde que escrevi isso pela primeira vez; Vou atualizar minha resposta.
dataless 16/01
3

Para quando

git rm [-r] --cached submodule_path

retorna

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

Contexto: eu fiz rm -r .git*nas minhas pastas do submódulo antes de perceber que elas precisavam ser desmoduladas no projeto principal ao qual eu as adicionara. Eu recebi o erro acima ao desmodular alguns, mas não todos. Enfim, eu os consertei executando (depois, é claro, o 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"

Observe que isso não preserva o histórico.

brandones
fonte
3

Com base na resposta de VonC , criei um script bash simples que faz isso. O addfinal deve usar caracteres curinga, caso contrário, desfará o anterior rmpara o próprio submódulo. É importante adicionar o conteúdo do diretório do submódulo e não nomear o diretório no addcomando.

Em um arquivo chamado 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
fonte
0

Achei mais conveniente (também?) Buscar dados de confirmação local no submódulo, porque, caso contrário, eu os perderia. (Não foi possível enviá-los porque não tenho acesso ao controle remoto). Então, adicionei o submodule / .git como remote_origin2, busquei o commit e a mesclagem desse ramo. Não tenho certeza se ainda preciso do submódulo remoto como origem, pois ainda não estou familiarizado o suficiente com o git.

Rian Wouters
fonte
0

Aqui está o que eu achei melhor e mais simples.

No repositório do submódulo, a partir de HEAD, você deseja mesclar no repositório principal:

  • git checkout -b "mergeMe"
  • mkdir "foo/bar/myLib/" (caminho idêntico ao local em que você deseja os arquivos no repositório principal)
  • git mv * "foo/bar/myLib/" (mova tudo para o caminho)
  • git commit -m "ready to merge into main"

Volte ao repositório principal após remover o submódulo e limpar o caminho "foo / bar / myLib":

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

boom feito

histórias preservadas

não se preocupe


Observe isso quase idêntico a algumas outras respostas. Mas isso pressupõe que você possui um repositório de submódulo. Além disso, isso facilita a obtenção de futuras alterações upstream para o submódulo.

CommaToast
fonte