2010-05-09 9 views
17

Lo que quiero es similar a this question. Sin embargo, quiero que el directorio que se divide en un acuerdo de recompra por separado siga siendo un subdirectorio en el que repo:¿Cómo dividir un repositorio de git mientras se conservan los subdirectorios?

tengo esto:

foo/ 
    .git/ 
    bar/ 
    baz/ 
    qux/ 

Y quiero dividirlo en dos repositorios totalmente independientes:

foo/ 
    .git/ 
    bar/ 
    baz/ 

quux/ 
    .git/ 
    qux/ # Note: still a subdirectory 

¿Cómo hacer esto en git?

Podría usar el método desde this answer si hay alguna forma de mover todos los contenidos del repositorio nuevo a un subdirectorio, a lo largo de la historia.

Respuesta

16

De hecho, podría utilizar el filtro de subdirectorio seguido de un filtro de índice para volver a poner el contenido en un subdirectorio, pero ¿por qué molestarse, cuando podría simplemente usar el filtro de índice por sí mismo?

He aquí un ejemplo de la página man:

git filter-branch --index-filter 'git rm --cached --ignore-unmatch filename' HEAD 

Esto sólo elimina un nombre de archivo; lo que quiere hacer es eliminar todo menos un subdirectorio determinado. Si quieres ser cauteloso, usted podría enumerar explícitamente cada ruta a eliminar, pero si sólo quiere ir all-in, sólo puede hacer algo como esto:

git filter-branch --index-filter 'git ls-tree -z --name-only --full-tree $GIT_COMMIT | grep -zv "^directory-to-keep$" | xargs -0 git rm --cached -r' -- --all 

espero es probable que haya una forma más elegante ; si alguien tiene algo, por favor sugiéralo!

Algunas notas sobre ese comando:

  • filter-branch establece internamente GIT_COMMIT a la corriente cometer SHA1
  • yo no habría esperado --full-tree ser necesario, pero al parecer filter-branch corre el índice -filtro del directorio .git-rewrite/t en lugar del nivel superior del repositorio.
  • grep es probablemente exagerado, pero no creo que sea un problema de velocidad.
  • --all aplica esto a todas las referencias; Me imagino que realmente quieres eso. (-- lo separa de las opciones de la rama de filtro)
  • -z y -0 indique a ls-tree, grep y xargs que usen la terminación NUL para manejar espacios en nombres de archivo.

Editar, mucho después: Thomas amablemente sugirió una forma de eliminar las confirmaciones ahora vacías, pero ahora está desactualizada. Mirar el historial de edición si tienes una versión antigua de git, pero con git moderna, todo lo que necesita hacer es añadir esta opción:

--prune-empty 

Eso va a quitar todos los envíos que están vacías después de la aplicación del filtro de índice.

+0

Aparte de las comillas simples anidadas (que me tomé la libertad de reemplazar), esto funcionó casi a la perfección. El único problema era que los commits vacíos para los directorios ahora inexistentes permanecían en el registro. Eliminé estos usando 'git filter-branch -f --commit-filter 'si [z $ 1 = z \' git rev-parse $ 3^{tree} \ ']; luego skip_commit "$ @"; else git commit-tree "$ @"; fi '"$ @" 'que encontré en http://github.com/jwiegley/git-scripts/blob/master/git-remove-empty-commits – Thomas

+0

@Thomas: ¡Gracias por arreglar mi descuidado error! Además, debería poder usar el filtro de compromiso en el mismo comando que el filtro de índice. Los filtros se ejecutan en el orden que se muestra en la documentación; commit-filter es, naturalmente, después de los filtros que modifican el contenido de la confirmación. Probablemente también desee usar '--remap-to-ancestor', lo que hará que los refs que apuntan a commits omitidos se muevan al ancestro más cercano en lugar de excluirlos. – Cascabel

+0

@Jefromi: el argumento 'index-filter' debería ser más fácil de expresar como 'git rm -r -f --cached --ignore-unmatch $ (ls! (Directory-to-keep))', vea mis respuestas http : //stackoverflow.com/a/8079852/396967 y http://stackoverflow.com/a/7849648/396967 – kynan

3

Esto es lo que terminé haciendo para resolver este problema cuando lo tenía a mí mismo:

git filter-branch --index-filter \ 
'git ls-tree --name-only --full-tree $GIT_COMMIT | \ 
grep -v "^directory-to-keep$" | \ 
sed -e "s/^/\"/g" -e "s/$/\"/g" | \ 
xargs git rm --cached -r -f --ignore-unmatch \ 
' \ 
--prune-empty -- --all 

La solución se basa en la respuesta de Jefromi y en Detach (move) subdirectory into separate Git repository además de muchos comentarios aquí en la SO.

La razón por la que la solución de Jefromi no funcionó fue porque tenía archivos y carpetas en mi repositorio cuyos nombres contenían caracteres especiales (principalmente espacios). Además, git rm se quejó de los archivos no coincidentes (resuelto con --ignore-unmatch).

Puede mantener el filtrado agnóstico al directorio no estar en la raíz de la cesión temporal o ser movido en torno a:

grep --invert-match "^.*directory-to-keep$" 

Y, por último, se puede usar esto para filtrar un subconjunto fijo de archivos o directorios:

egrep --invert-match "^(.*file-or-directory-to-keep-1$|.*file-or-directory-to-keep-2$|…)" 

Para limpiar después usted puede utilizar estos comandos:

$ git reset --hard 
$ git show-ref refs/original/* --hash | xargs -n 1 git update-ref -d 
$ git reflog expire --expire=now --all 
$ git gc --aggressive --prune=now 
3

Quería hacer algo similar, pero dado que la lista de archivos que quería mantener era bastante larga, no tenía sentido hacer esto usando innumerables greps. Escribí un guión que lee la lista de archivos de un archivo:

#!/bin/bash 

# usage: 
# git filter-branch --prune-empty --index-filter \ 
# 'this-script file-with-list-of-files-to-be-kept' -- --all 

if [ -z $1 ]; then 
    echo "Too few arguments." 
    echo "Please specify an absolute path to the file" 
    echo "which contains the list of files that should" 
    echo "remain in the repository after filtering." 
    exit 1 
fi 

# save a list of files present in the commit 
# which is currently being modified. 
git ls-tree -r --name-only --full-tree $GIT_COMMIT > files.txt 

# delete all files that shouldn't be removed 
while read string; do 
    grep -v "$string" files.txt > files.txt.temp 
    mv -f files.txt.temp files.txt 
done < $1 

# remove unwanted files (i.e. everything that remained in the list). 
# warning: 'git rm' will exit with non-zero status if it gets 
# an invalid (non-existent) filename OR if it gets no arguments. 
# If something exits with non-zero status, filter-branch will abort. 
# That's why we have to check carefully what is passed to git rm. 
if [ "$(cat files.txt)" != "" ]; then 
    cat files.txt | \ 
    # enclose filenames in "" in case they contain spaces 
    sed -e 's/^/"/g' -e 's/$/"/g' | \ 
    xargs git rm --cached --quiet 
fi 

Bastante sorprendentemente, esto resultó ser mucho más trabajo de lo que inicialmente esperaba, así que decidí publicar aquí.

+1

¡Muchas gracias por compartir! Eso funcionó para mí en un informe de prueba. También agregué 'if [" $ (cat $ 1) "==" "]; luego echo "Sin contenido en el archivo de exclusión" exit 1 fi' para comprobar si el archivo proporcionado está allí. También parece que es necesario proporcionar una ruta completa al archivo excluyente. – Denis

+0

p.s. también, el archivo de exclusión debe tener la última línea vacía/basura. – Denis

1

Un método más limpio:

git filter-branch --index-filter ' 
       git read-tree --empty 
       git reset $GIT_COMMIT path/to/dir 
     ' \ 
     -- --all -- path/to/dir 

o pegarse con sólo los comandos básicos, sub en git read-tree --prefix=path/to/dir/ $GIT_COMMIT:path/to/dir para el restablecimiento.

Especificando path/to/dir en la lista de revisión args hace la poda temprano, con un filtro tan barato que no importa mucho pero es bueno evitar el esfuerzo desperdiciado de todos modos.

Cuestiones relacionadas