Separar (mover) subdirectorio en repositorio Git separado

1758

Tengo un repositorio Git que contiene varios subdirectorios. Ahora he descubierto que uno de los subdirectorios no está relacionado con el otro y debe separarse a un repositorio separado.

¿Cómo puedo hacer esto mientras mantengo el historial de los archivos dentro del subdirectorio?

Supongo que podría hacer un clon y eliminar las partes no deseadas de cada clon, pero supongo que esto me daría el árbol completo al revisar una revisión anterior, etc. Esto podría ser aceptable, pero preferiría poder fingir que dos repositorios no tienen un historial compartido.

Solo para dejarlo claro, tengo la siguiente estructura:

XYZ/
    .git/
    XY1/
    ABC/
    XY2/

Pero me gustaría esto en su lugar:

XYZ/
    .git/
    XY1/
    XY2/
ABC/
    .git/
    ABC/
matli
fuente
77
Esto es trivial ahora con git filter-branchver mi respuesta a continuación.
jeremyjjbrown
8
@jeremyjjbrown tiene razón. Esto ya no es difícil de hacer, pero es difícil encontrar la respuesta correcta en Google porque todas las respuestas antiguas dominan los resultados.
Agnel Kurian

Respuestas:

1228

Actualización : Este proceso es tan común, que el equipo de git hizo que fuera mucho más sencillo con una nueva herramienta, git subtree. Ver aquí: Separar (mover) subdirectorio en repositorio Git separado


Desea clonar su repositorio y luego usarlo git filter-branchpara marcar todo menos el subdirectorio que desea en su nuevo repositorio para recolectar basura.

  1. Para clonar su repositorio local:

    git clone /XYZ /ABC
    

    (Nota: el repositorio se clonará mediante enlaces duros, pero eso no es un problema ya que los archivos enlazados no se modificarán en sí mismos; se crearán nuevos).

  2. Ahora, conservemos las ramas interesantes que queremos reescribir también, y luego eliminemos el origen para evitar presionar allí y asegurarnos de que el origen no haga referencia a las confirmaciones antiguas:

    cd /ABC
    for i in branch1 br2 br3; do git branch -t $i origin/$i; done
    git remote rm origin
    

    o para todas las sucursales remotas:

    cd /ABC
    for i in $(git branch -r | sed "s/.*origin\///"); do git branch -t $i origin/$i; done
    git remote rm origin
    
  3. Ahora es posible que también desee eliminar etiquetas que no tienen relación con el subproyecto; También puede hacerlo más tarde, pero es posible que necesite recortar su repositorio nuevamente. No lo hice y obtuve un WARNING: Ref 'refs/tags/v0.1' is unchangedpara todas las etiquetas (ya que no estaban relacionadas con el subproyecto); Además, después de eliminar tales etiquetas, se recuperará más espacio. Aparentemente git filter-branchdebería poder reescribir otras etiquetas, pero no pude verificar esto. Si desea eliminar todas las etiquetas, use git tag -l | xargs git tag -d.

  4. Luego use filter-branch y reset para excluir los otros archivos, para que puedan ser eliminados. Agreguemos también --tag-name-filter cat --prune-emptypara eliminar confirmaciones vacías y reescribir etiquetas (tenga en cuenta que esto tendrá que quitar su firma):

    git filter-branch --tag-name-filter cat --prune-empty --subdirectory-filter ABC -- --all
    

    o, alternativamente, para reescribir solo la rama HEAD e ignorar las etiquetas y otras ramas:

    git filter-branch --tag-name-filter cat --prune-empty --subdirectory-filter ABC HEAD
    
  5. Luego, elimine los reflogs de respaldo para que el espacio pueda ser verdaderamente recuperado (aunque ahora la operación es destructiva)

    git reset --hard
    git for-each-ref --format="%(refname)" refs/original/ | xargs -n 1 git update-ref -d
    git reflog expire --expire=now --all
    git gc --aggressive --prune=now
    

    y ahora tiene un repositorio git local del subdirectorio ABC con todo su historial preservado.

Nota: Para la mayoría de los usos, git filter-branchdebería tener el parámetro agregado -- --all. Si, eso es realmente --space-- all. Estos deben ser los últimos parámetros para el comando. Como Matli descubrió, esto mantiene las ramas y etiquetas del proyecto incluidas en el nuevo repositorio.

Editar: se incorporaron varias sugerencias de los comentarios a continuación para garantizar, por ejemplo, que el repositorio se reduzca realmente (que no siempre fue el caso antes).

Paul
fuente
29
Muy buena respuesta. ¡Gracias! Y para obtener exactamente lo que quería, agregué "- --all" al comando filter-branch.
matli
12
¿Por qué lo necesitas --no-hardlinks? Eliminar un enlace duro no afectará al otro archivo. Los objetos Git también son inmutables. Solo si cambia los permisos de propietario / archivo que necesita --no-hardlinks.
vdboor 01 de
67
Un paso adicional que recomendaría sería "git remote rm origin". Esto evitaría que los empujes regresen al repositorio original, si no me equivoco.
Tom
13
Otro comando para agregar filter-branches --prune-emptyeliminar las confirmaciones ahora vacías.
Seth Johnson, el
8
Al igual que Paul, no quería etiquetas de proyecto en mi nuevo repositorio, por lo que no las usé -- --all. También corrí git remote rm origin, y git tag -l | xargs git tag -dantes del git filter-branchcomando. Esto redujo mi .gitdirectorio de 60M a ~ 300K. Tenga en cuenta que necesitaba ejecutar ambos comandos para obtener la reducción de tamaño.
saltycrane
1321

The Easy Way ™

Resulta que esta es una práctica tan común y útil que los señores superiores de Git lo hicieron realmente fácil, pero debe tener una versión más nueva de Git (> = 1.7.11 de mayo de 2012). Consulte el apéndice sobre cómo instalar el último Git. Además, hay un ejemplo del mundo real en el tutorial a continuación.

  1. Prepara el viejo repositorio

    cd <big-repo>
    git subtree split -P <name-of-folder> -b <name-of-new-branch>
    

    Nota: <name-of-folder> NO debe contener caracteres iniciales o finales. Por ejemplo, la carpeta llamada subprojectDEBE pasar como subproject, NO./subproject/

    Nota para usuarios de Windows: cuando la profundidad de la carpeta es> 1, <name-of-folder>debe tener un separador de carpeta de estilo * nix (/). Por ejemplo, la carpeta llamada path1\path2\subprojectDEBE pasar comopath1/path2/subproject

  2. Crea el nuevo repositorio

    mkdir ~/<new-repo> && cd ~/<new-repo>
    git init
    git pull </path/to/big-repo> <name-of-new-branch>
    
  3. Enlace el nuevo repositorio a GitHub o donde sea

    git remote add origin <[email protected]:user/new-repo.git>
    git push -u origin master
    
  4. Limpieza interior <big-repo>, si lo desea

    git rm -rf <name-of-folder>
    

    Nota : Esto deja todas las referencias históricas en el repositorio. Consulte el Apéndice a continuación si realmente le preocupa haber cometido una contraseña o si necesita disminuir el tamaño del archivo de su .gitcarpeta.

...

Tutorial

Estos son los mismos pasos que los anteriores , pero seguir mis pasos exactos para mi repositorio en lugar de usarlos <meta-named-things>.

Aquí hay un proyecto que tengo para implementar módulos de navegador JavaScript en el nodo:

tree ~/node-browser-compat

node-browser-compat
├── ArrayBuffer
├── Audio
├── Blob
├── FormData
├── atob
├── btoa
├── location
└── navigator

Quiero dividir una sola carpeta btoa, en un repositorio Git separado

cd ~/node-browser-compat/
git subtree split -P btoa -b btoa-only

Ahora tengo una nueva rama, btoa-onlyque solo tiene commits btoay quiero crear un nuevo repositorio.

mkdir ~/btoa/ && cd ~/btoa/
git init
git pull ~/node-browser-compat btoa-only

A continuación, creo un nuevo repositorio en GitHub o Bitbucket, o lo que sea, y lo agrego como origin

git remote add origin [email protected]:node-browser-compat/btoa.git
git push -u origin master

¡Día feliz!

Nota: Si creó un repositorio con a README.md, .gitignorey LICENSE, primero deberá extraer:

git pull origin master
git push origin master

Por último, querré eliminar la carpeta del repositorio más grande

git rm -rf btoa

...

Apéndice

Latest Git en macOS

Para obtener la última versión de Git con Homebrew :

brew install git

Último Git en Ubuntu

sudo apt-get update
sudo apt-get install git
git --version

Si eso no funciona (tiene una versión muy antigua de Ubuntu), intente

sudo add-apt-repository ppa:git-core/ppa
sudo apt-get update
sudo apt-get install git

Si eso todavía no funciona, intente

sudo chmod +x /usr/share/doc/git/contrib/subtree/git-subtree.sh
sudo ln -s \
/usr/share/doc/git/contrib/subtree/git-subtree.sh \
/usr/lib/git-core/git-subtree

Gracias a rui.araujo por los comentarios.

Limpiando tu historia

De forma predeterminada, eliminar archivos de Git en realidad no los elimina, solo confirma que ya no están allí. Si realmente desea eliminar las referencias históricas (es decir, tiene una contraseña confirmada), debe hacer esto:

git filter-branch --prune-empty --tree-filter 'rm -rf <name-of-folder>' HEAD

Después de eso, puede verificar que su archivo o carpeta ya no aparezca en el historial de Git

git log -- <name-of-folder> # should show nothing

Sin embargo, no puede "empujar" eliminaciones a GitHub y similares. Si lo intentas, obtendrás un error y tendrás que hacerlo git pullantes de que puedas git push, y luego volverás a tener todo en tu historial.

Por lo tanto, si desea eliminar el historial del "origen", es decir, eliminarlo de GitHub, Bitbucket, etc., deberá eliminar el repositorio y volver a enviar una copia podada del repositorio. Pero espera, ¡ hay más ! - Si realmente le preocupa deshacerse de una contraseña o algo así, deberá podar la copia de seguridad (consulte a continuación).

Haciendo .gitmás pequeño

El comando eliminar historial mencionado anteriormente todavía deja atrás un montón de archivos de copia de seguridad, porque Git es muy amable al ayudarlo a no arruinar su repositorio por accidente. Eventualmente eliminará archivos huérfanos a lo largo de los días y meses, pero los dejará allí por un tiempo en caso de que se dé cuenta de que eliminó accidentalmente algo que no quería.

Entonces, si realmente desea vaciar la basura para reducir el tamaño de clon de un repositorio de inmediato, debe hacer todo esto realmente extraño:

rm -rf .git/refs/original/ && \
git reflog expire --all && \
git gc --aggressive --prune=now

git reflog expire --all --expire-unreachable=0
git repack -A -d
git prune

Dicho esto, recomendaría no realizar estos pasos a menos que sepa que necesita hacerlo, en caso de que haya eliminado el subdirectorio incorrecto, ¿sabe? Los archivos de copia de seguridad no deben clonarse cuando presiona el repositorio, solo estarán en su copia local.

Crédito

CoolAJ86
fuente
16
git subtreesigue siendo parte de la carpeta 'contrib' y no está instalado de forma predeterminada en todas las distribuciones. github.com/git/git/blob/master/contrib/subtree
onionjake
11
@krlmlr sudo chmod + x /usr/share/doc/git/contrib/subtree/git-subtree.sh sudo ln -s /usr/share/doc/git/contrib/subtree/git-subtree.sh / usr / lib / git-core / git-subtree Para activar en Ubuntu 13.04
rui.araujo
41
Si ha introducido una contraseña en un repositorio público, debe cambiar la contraseña, no intentar eliminarla del repositorio público y esperar que nadie la haya visto.
Miles Rout
8
Esta solución no conserva la historia.
Cœur
18
El comando popdy pushdhace que esto sea bastante implícito y más difícil de asimilar lo que pretende hacer ...
jones77
133

La respuesta de Paul crea un nuevo repositorio que contiene / ABC, pero no elimina / ABC de / XYZ. El siguiente comando eliminará / ABC de / XYZ:

git filter-branch --tree-filter "rm -rf ABC" --prune-empty HEAD

Por supuesto, pruébelo primero en un repositorio 'clone --no-hardlinks', y sígalo con los comandos reset, gc y podar que Paul enumera.

pgs
fuente
53
hacer eso git filter-branch --index-filter "git rm -r -f --cached --ignore-unmatch ABC" --prune-empty HEADy será mucho más rápido. index-filter funciona en el índice, mientras que tree-filter tiene que pagar y organizar todo para cada confirmación .
fmarc
51
en algunos casos, estropear la historia del repositorio XYZ es excesivo ... solo un simple "rm -rf ABC; git rm -r ABC; git commit -m'extracted ABC en su propio repositorio" funcionaría mejor para la mayoría de las personas.
Evgeny
2
Probablemente desee utilizar -f (force) en este comando si lo hace más de una vez, por ejemplo, para eliminar dos directorios después de que se hayan separado. De lo contrario, obtendrá "No se puede crear una nueva copia de seguridad".
Brian Carlton
44
Si está haciendo el --index-filtermétodo, es posible que también desee hacerlo git rm -q -r -f, de modo que cada invocación no imprima una línea para cada archivo que elimine.
Eric Naeseth
1
Sugeriría editar la respuesta de Paul, solo porque la de Paul es muy completa.
Erik Aronesty
96

Descubrí que para eliminar correctamente el historial anterior del nuevo repositorio, debe realizar un poco más de trabajo después del filter-branchpaso.

  1. Haz el clon y el filtro:

    git clone --no-hardlinks foo bar; cd bar
    git filter-branch --subdirectory-filter subdir/you/want
    
  2. Elimine todas las referencias a la historia antigua. "Origen" estaba haciendo un seguimiento de su clon, y "original" es donde filter-branch guarda las cosas viejas:

    git remote rm origin
    git update-ref -d refs/original/refs/heads/master
    git reflog expire --expire=now --all
    
  3. Incluso ahora, su historial podría estar atascado en un paquete que fsck no tocará. Rómpelo en pedazos, creando un nuevo paquete de archivos y eliminando los objetos no utilizados:

    git repack -ad
    

Hay una explicación de esto en el manual para filter-branch .

Josh Lee
fuente
3
Creo que git gc --aggressive --prune=nowtodavía falta algo como , ¿no?
Albert
1
@Albert El comando repack se encarga de eso, y no habría objetos sueltos.
Josh Lee
sí, git gc --aggressive --prune=nowredujo gran parte del nuevo repositorio
Tomek Wyderka
Simple y elegante ¡Gracias!
Marco Pelegrini
40

Editar: Bash script agregado.

Las respuestas dadas aquí funcionaron solo parcialmente para mí; Muchos archivos grandes permanecieron en el caché. Lo que finalmente funcionó (después de horas en #git en freenode):

git clone --no-hardlinks file:///SOURCE /tmp/blubb
cd blubb
git filter-branch --subdirectory-filter ./PATH_TO_EXTRACT  --prune-empty --tag-name-filter cat -- --all
git clone file:///tmp/blubb/ /tmp/blooh
cd /tmp/blooh
git reflog expire --expire=now --all
git repack -ad
git gc --prune=now

Con las soluciones anteriores, el tamaño del repositorio era de alrededor de 100 MB. Este lo redujo a 1,7 MB. Tal vez ayude a alguien :)


El siguiente script bash automatiza la tarea:

!/bin/bash

if (( $# < 3 ))
then
    echo "Usage:   $0 </path/to/repo/> <directory/to/extract/> <newName>"
    echo
    echo "Example: $0 /Projects/42.git first/answer/ firstAnswer"
    exit 1
fi


clone=/tmp/${3}Clone
newN=/tmp/${3}

git clone --no-hardlinks file://$1 ${clone}
cd ${clone}

git filter-branch --subdirectory-filter $2  --prune-empty --tag-name-filter cat -- --all

git clone file://${clone} ${newN}
cd ${newN}

git reflog expire --expire=now --all
git repack -ad
git gc --prune=now
Simon A. Eugster
fuente
26

Esto ya no es tan complejo que solo puede usar el comando git filter-branch en un clon de su repositorio para eliminar los subdirectorios que no desea y luego pasar al nuevo control remoto.

git filter-branch --prune-empty --subdirectory-filter <YOUR_SUBDIR_TO_KEEP> master
git push <MY_NEW_REMOTE_URL> -f .
jeremyjjbrown
fuente
3
Esto funcionó a las mil maravillas. YOUR_SUBDIR en el ejemplo anterior es el subdirectorio que desea MANTENER, todo lo demás se eliminará
JT Taylor
1
Actualizaciones basadas en tu comentario.
jeremyjjbrown
2
Esto no responde la pregunta. De los documentos dice The result will contain that directory (and only that) as its project root.y, de hecho, esto es lo que obtendrá, es decir, la estructura original del proyecto no se conserva.
NicBright
2
@NicBright ¿Puede ilustrar su problema con XYZ y ABC como en la pregunta, para mostrar lo que está mal?
Adam
@jeremyjjbrown es posible reutilizar el repositorio clonado y no usar un nuevo repositorio, es decir, mi pregunta aquí stackoverflow.com/questions/49269602/…
Qiulang
19

Actualización : El módulo git-subtree fue tan útil que el equipo git lo incorporó al núcleo y lo creó git subtree. Ver aquí: Separar (mover) subdirectorio en repositorio Git separado

git-subtree puede ser útil para esto

http://github.com/apenwarr/git-subtree/blob/master/git-subtree.txt (en desuso)

http://psionides.jogger.pl/2010/02/04/sharing-code-between-projects-with-git-subtree/

DW
fuente
1
git-subtree ahora es parte de Git, aunque está en el árbol contrib, por lo que no siempre se instala por defecto. Sé que está instalado por la fórmula git de Homebrew, pero sin su página de manual. Apenwarr llama así a su versión obsoleta.
echristopherson
19

He aquí una pequeña modificación en CoolAJ86 's 'La manera más fácil ™' respuesta con el fin de dividir múltiples subcarpetas (digamos sub1y sub2) en un nuevo repositorio git.

The Easy Way ™ (múltiples subcarpetas)

  1. Prepara el viejo repositorio

    pushd <big-repo>
    git filter-branch --tree-filter "mkdir <name-of-folder>; mv <sub1> <sub2> <name-of-folder>/" HEAD
    git subtree split -P <name-of-folder> -b <name-of-new-branch>
    popd
    

    Nota: <name-of-folder> NO debe contener caracteres iniciales o finales. Por ejemplo, la carpeta llamada subprojectDEBE pasar como subproject, NO./subproject/

    Nota para usuarios de Windows: cuando la profundidad de su carpeta es> 1, <name-of-folder>debe tener un separador de carpeta de estilo * nix (/). Por ejemplo, la carpeta llamada path1\path2\subprojectDEBE pasar como path1/path2/subproject. Además, no use el mvcomando peromove .

    Nota final: la diferencia única y grande con la respuesta base es la segunda línea del guión " git filter-branch..."

  2. Crea el nuevo repositorio

    mkdir <new-repo>
    pushd <new-repo>
    
    git init
    git pull </path/to/big-repo> <name-of-new-branch>
    
  3. Vincula el nuevo repositorio a Github o donde sea

    git remote add origin <[email protected]:my-user/new-repo.git>
    git push origin -u master
    
  4. Limpieza, si lo desea

    popd # get out of <new-repo>
    pushd <big-repo>
    
    git rm -rf <name-of-folder>
    

    Nota : Esto deja todas las referencias históricas en el repositorio. Consulte el Apéndice en la respuesta original si realmente le preocupa haber cometido una contraseña o si necesita disminuir el tamaño del archivo de su .gitcarpeta.

Anthony O.
fuente
1
Esto funcionó para mí con una ligera modificación. Porque misub1 y sub2carpetas no existían con la versión inicial, tuve que modificar mi --tree-filterscript de la siguiente manera: "mkdir <name-of-folder>; if [ -d sub1 ]; then mv <sub1> <name-of-folder>/; fi". Para el segundo filter-branchcomando, reemplacé <sub1> con <sub2>, omití la creación de <name-of-folder> e incluí -fdespués filter-branchpara anular la advertencia de una copia de seguridad existente.
pglezen
Esto no funciona si alguno de los subdirectores ha cambiado durante el historial en git. ¿Cómo se puede resolver esto?
nietras
@nietras ve la respuesta de rogerdpack. Me tomó un tiempo encontrarlo después de leer y absorber toda la información en estas otras respuestas.
Adam
12

La pregunta original quiere que XYZ / ABC / (* archivos) se convierta en ABC / ABC / (* archivos). Después de implementar la respuesta aceptada para mi propio código, noté que en realidad cambia XYZ / ABC / (* files) en ABC / (* files). La página man de filter-branch incluso dice:

El resultado contendrá ese directorio (y solo eso) como raíz del proyecto ".

En otras palabras, promueve la carpeta de nivel superior "arriba" un nivel. Esa es una distinción importante porque, por ejemplo, en mi historia había renombrado una carpeta de nivel superior. Al promover las carpetas "arriba" en un nivel, git pierde continuidad en el commit donde hice el cambio de nombre.

Perdí la continuidad después de la rama de filtro

Mi respuesta a la pregunta es hacer 2 copias del repositorio y eliminar manualmente las carpetas que desea mantener en cada una. La página del manual me respalda con esto:

[...] evite usar [este comando] si una simple confirmación simple fuera suficiente para solucionar su problema

MM.
fuente
1
Me gusta el estilo de ese gráfico. ¿Puedo preguntar qué herramienta estás usando?
Slipp D. Thompson
3
Torre para Mac. Me gusta mucho. Casi vale la pena cambiar a Mac por sí mismo.
MM.
2
Sí, aunque en mi caso, mi subcarpeta targetdirhabía cambiado de nombre en algún momento y git filter-branchsimplemente lo había llamado un día, ¡eliminando todas las confirmaciones realizadas antes del cambio de nombre! ¡Impactante, teniendo en cuenta cuán experto es Git en el seguimiento de tales cosas e incluso la migración de fragmentos de contenido individuales!
Jay Allen
1
Ah, también, si alguien se encuentra en el mismo barco, aquí está el comando que usé. No olvide que git rmrequiere múltiples argumentos, por lo que no hay razón para ejecutarlo para cada archivo / carpeta: BYEBYE="dir/subdir2 dir2 file1 dir/file2"; git filter-branch -f --index-filter "git rm -q -r -f --cached --ignore-unmatch $BYEBYE" --prune-empty -- --all
Jay Allen
7

Para añadir a la respuesta de Paul , descubrí que, en última instancia, para recuperar espacio, tengo que empujar HEAD a un repositorio limpio y que reduce el tamaño del directorio .git / objects / pack.

es decir

$ mkdir ... ABC.git
$ cd ... ABC.git
$ git init --bare

Después de la ciruela pasa gc, también haz:

$ git push ... ABC.git HEAD

Entonces puedes hacer

$ git clone ... ABC.git

y el tamaño de ABC / .git se reduce

En realidad, algunos de los pasos que requieren mucho tiempo (por ejemplo, git gc) no son necesarios con el empuje para limpiar el repositorio, es decir:

$ git clone --no-hardlinks / XYZ / ABC
$ git filter-branch --subdirectory-filter ABC HEAD
$ git reset --hard
$ git push ... ABC.git HEAD
Case Larsen
fuente
6

La forma correcta ahora es la siguiente:

git filter-branch --prune-empty --subdirectory-filter FOLDER_NAME [first_branch] [another_branch]

GitHub ahora incluso tiene un pequeño artículo sobre tales casos.

Pero asegúrese de clonar su repositorio original para separar el directorio primero (ya que eliminaría todos los archivos y otros directorios y probablemente necesite trabajar con ellos).

Entonces su algoritmo debería ser:

  1. clone su repositorio remoto a otro directorio
  2. usando git filter-brancharchivos de solo izquierda debajo de algún subdirectorio, empuje a un nuevo control remoto
  3. create commit para eliminar este subdirectorio de su repositorio remoto original
Olexandr Shapovalov
fuente
6

Parece que la mayoría (¿todas?) De las respuestas aquí se basan en alguna forma de git filter-branch --subdirectory-filtery su tipo. Esto puede funcionar "la mayoría de las veces", sin embargo, en algunos casos, por ejemplo, cuando cambiaste el nombre de la carpeta, por ejemplo:

 ABC/
    /move_this_dir # did some work here, then renamed it to

ABC/
    /move_this_dir_renamed

Si realiza un estilo de filtro git normal para extraer "move_me_renamed", perderá el historial de cambios de archivos que se produjo desde atrás cuando inicialmente se movía move_this_dir ( ref ).

Por lo tanto, parece que la única forma de mantener realmente todo el historial de cambios (si el suyo es un caso como este), es, en esencia, copiar el repositorio (crear un nuevo repositorio, establecer que sea el origen), luego destruir todo lo demás y cambie el nombre del subdirectorio al padre así:

  1. Clonar el proyecto de varios módulos localmente
  2. Ramas: compruebe lo que hay allí: git branch -a
  3. Haga un pago a cada sucursal que se incluirá en la división para obtener una copia local en su estación de trabajo: git checkout --track origin/branchABC
  4. Haga una copia en un nuevo directorio: cp -r oldmultimod simple
  5. Entra en la nueva copia del proyecto: cd simple
  6. Deshágase de los otros módulos que no son necesarios en este proyecto:
  7. git rm otherModule1 other2 other3
  8. Ahora solo queda el subdirectorio del módulo de destino
  9. Deshágase del subdirectorio del módulo para que la raíz del módulo se convierta en la nueva raíz del proyecto
  10. git mv moduleSubdir1/* .
  11. Eliminar el subdirectorio de reliquias: rmdir moduleSubdir1
  12. Verifique los cambios en cualquier momento: git status
  13. Cree el nuevo repositorio de git y copie su URL para apuntar este proyecto en él:
  14. git remote set-url origin http://mygithost:8080/git/our-splitted-module-repo
  15. Verifica que esto sea bueno: git remote -v
  16. Empuje los cambios hasta el repositorio remoto: git push
  17. Vaya al repositorio remoto y verifique que esté todo allí.
  18. Repítalo para cualquier otra rama necesaria: git checkout branch2

Esto sigue al documento de github "Dividir una subcarpeta en un nuevo repositorio" pasos 6-11 de para llevar el módulo a un nuevo repositorio.

Esto no le ahorrará espacio en su carpeta .git, pero conservará todo su historial de cambios para esos archivos, incluso en todos los cambios de nombre. Y esto puede no valer la pena si no hay "mucha" historia perdida, etc. ¡Pero al menos se garantiza que no perderá compromisos más antiguos!

rogerdpack
fuente
1
¡Encontré la aguja en el pajar de git! Ahora puedo mantener TODO mi historial de confirmaciones.
Adam
5

Recomiendo la guía de GitHub para dividir subcarpetas en un nuevo repositorio . Los pasos son similares a la respuesta de Paul , pero encontré sus instrucciones más fáciles de entender.

Modifiqué las instrucciones para que soliciten un repositorio local, en lugar de uno alojado en GitHub.


Dividir una subcarpeta en un nuevo repositorio

  1. Abre Git Bash.

  2. Cambie el directorio de trabajo actual a la ubicación donde desea crear su nuevo repositorio.

  3. Clone el repositorio que contiene la subcarpeta.

git clone OLD-REPOSITORY-FOLDER NEW-REPOSITORY-FOLDER
  1. Cambie el directorio de trabajo actual a su repositorio clonado.

cd REPOSITORY-NAME
  1. Para filtrar la subcarpeta del resto de los archivos en el repositorio, ejecute git filter-branch , proporcionando esta información:
    • FOLDER-NAME: La carpeta dentro de su proyecto desde la que desea crear un repositorio separado.
      • Consejo: los usuarios de Windows deben usar /para delimitar carpetas.
    • BRANCH-NAME: La rama predeterminada para su proyecto actual, por ejemplo, mastero gh-pages.

git filter-branch --prune-empty --subdirectory-filter FOLDER-NAME  BRANCH-NAME 
# Filter the specified branch in your directory and remove empty commits
Rewrite 48dc599c80e20527ed902928085e7861e6b3cbe6 (89/89)
Ref 'refs/heads/BRANCH-NAME' was rewritten
Stevoisiak
fuente
Buena publicación, pero noto que el primer párrafo del documento que vinculó dice If you create a new clone of the repository, you won't lose any of your Git history or changes when you split a folder into a separate repository.Sin embargo, de acuerdo con los comentarios sobre todas las respuestas aquí, tanto filter-branchy el subtreeguión resultan en la pérdida de la historia donde sea que se haya cambiado el nombre de un subdirectorio. ¿Hay algo que se pueda hacer para abordar esto?
Adam
Encontré la solución para preservar todos los commits, incluidos los cambios de nombre / movimientos de directorio anteriores: es la respuesta de rogerdpack a esta misma pregunta.
Adam
El único problema es que ya no puedo usar el repositorio clonado
Qiulang
5

Cuando se ejecuta git filter-branchcon una versión más nueva de git( 2.22+¿tal vez?), Dice usar esta nueva herramienta git-filter-repo . Esta herramienta ciertamente simplificó las cosas para mí.

Filtrado con filtro-repo

Comandos para crear el XYZrepositorio a partir de la pregunta original:

# create local clone of original repo in directory XYZ
tmp $ git clone [email protected]:user/original.git XYZ

# switch to working in XYZ
tmp $ cd XYZ

# keep subdirectories XY1 and XY2 (dropping ABC)
XYZ $ git filter-repo --path XY1 --path XY2

# note: original remote origin was dropped
# (protecting against accidental pushes overwriting original repo data)

# XYZ $ ls -1
# XY1
# XY2

# XYZ $ git log --oneline
# last commit modifying ./XY1 or ./XY2
# first commit modifying ./XY1 or ./XY2

# point at new hosted, dedicated repo
XYZ $ git remote add origin [email protected]:user/XYZ.git

# push (and track) remote master
XYZ $ git push -u origin master

supuestos: * el repositorio XYZ remoto era nuevo y estaba vacío antes del envío

Filtrando y moviendo

En mi caso, también quería mover un par de directorios para una estructura más consistente. Inicialmente, ejecuté ese filter-repocomando simple seguido de git mv dir-to-rename, pero descubrí que podía obtener un historial ligeramente "mejor" usando la --path-renameopción. En lugar de ver la última modificación 5 hours agoen archivos movidos en el nuevo repositorio, ahora veolast year (en la interfaz de usuario de GitHub), que coincide con los tiempos modificados en el repositorio original.

En vez de...

git filter-repo --path XY1 --path XY2 --path inconsistent
git mv inconsistent XY3  # which updates last modification time

Finalmente corrí ...

git filter-repo --path XY1 --path XY2 --path inconsistent --path-rename inconsistent:XY3
Notas:
  • Pensé que la publicación del blog Git Rev News explicaba bien el razonamiento detrás de la creación de otra herramienta de filtro de repositorio.
  • Inicialmente probé la ruta de crear un subdirectorio que coincida con el nombre del repositorio de destino en el repositorio original y luego filtrar (usar git filter-repo --subdirectory-filter dir-matching-new-repo-name). Ese comando convirtió correctamente ese subdirectorio a la raíz del repositorio local copiado, pero también resultó en un historial de solo las tres confirmaciones que tomó para crear el subdirectorio. (No me había dado cuenta de que --pathpodría especificarse varias veces; por lo tanto, obviando la necesidad de crear un subdirectorio en el repositorio fuente.) Dado que alguien se había comprometido con el repositorio fuente en el momento en que noté que había fallado en llevar adelante el historial, simplemente utilicé git reset commit-before-subdir-move --harddespués del clonecomando y agregué --forceel filter-repocomando para que funcione en el clon local ligeramente modificado.
git clone ...
git reset HEAD~7 --hard      # roll back before mistake
git filter-repo ... --force  # tell filter-repo the alterations are expected
  • Me quedé perplejo con la instalación ya que no conocía el patrón de extensión git, pero finalmente cloné git-filter-repo y lo vinculé a $(git --exec-path):
ln -s ~/github/newren/git-filter-repo/git-filter-repo $(git --exec-path)
lpearson
fuente
1
Votado por recomendar la nueva filter-repoherramienta (que presenté el mes pasado en stackoverflow.com/a/58251653/6309 )
VonC
Usar git-filter-repodefinitivamente debería ser el enfoque preferido en este punto. Es mucho, mucho más rápido y seguro que git-filter-branch, y protege contra muchas de las trampas con las que uno puede toparse al reescribir el historial de git. Esperemos que esta respuesta reciba más atención, ya que es la que se debe abordar git-filter-repo.
Jeremy Caney
4

Tuve exactamente este problema, pero todas las soluciones estándar basadas en git filter-branch fueron extremadamente lentas. Si tiene un repositorio pequeño, entonces esto puede no ser un problema, fue para mí. Escribí otro programa de filtrado de git basado en libgit2 que, como primer paso, crea ramas para cada filtrado del repositorio primario y luego los empuja para limpiar los repositorios como el siguiente paso. En mi repositorio (500Mb 100000 commits) los métodos estándar de ramificación de filtro git tomaron días. Mi programa tarda minutos en hacer el mismo filtrado.

Tiene el fabuloso nombre de git_filter y vive aquí:

https://github.com/slobobaby/git_filter

en GitHub.

Espero que sea útil para alguien.

slobobaby
fuente
4

Use este comando de filtro para eliminar un subdirectorio, al tiempo que conserva sus etiquetas y ramas:

git filter-branch --index-filter \
"git rm -r -f --cached --ignore-unmatch DIR" --prune-empty \
--tag-name-filter cat -- --all
cmcginty
fuente
¿Qué es el gato aquí?
rogerdpack
4

Por lo que vale, así es cómo se usa GitHub en una máquina con Windows. Digamos que tiene un repositorio clonado en residir en C:\dir1. La estructura de directorios es el siguiente: C:\dir1\dir2\dir3. El dir3directorio es el que quiero que sea un nuevo repositorio separado.

Github:

  1. Crea tu nuevo repositorio: MyTeam/mynewrepo

Aviso de golpe:

  1. $ cd c:/Dir1
  2. $ git filter-branch --prune-empty --subdirectory-filter dir2/dir3 HEAD
    Devuelto: Ref 'refs/heads/master' was rewritten(para su información: dir2 / dir3 distingue entre mayúsculas y minúsculas).

  3. $ git remote add some_name [email protected]:MyTeam/mynewrepo.git
    git remote add origin etc. no funcionó, regresó " remote origin already exists"

  4. $ git push --progress some_name master

James Lawruk
fuente
3

Como mencioné anteriormente , tuve que usar la solución inversa (eliminar todas las confirmaciones sin tocar midir/subdir/targetdir ) que parecía funcionar bastante bien, eliminando aproximadamente el 95% de las confirmaciones (según lo deseado). Sin embargo, quedan dos pequeños problemas.

PRIMERO , filter-branchhizo un trabajo explosivo al eliminar los commits que introducen o modifican el código, pero aparentemente, los commits de fusión están debajo de su estación en el Gitiverse.

Este es un problema cosmético con el que probablemente pueda vivir (dice ... retrocediendo lentamente con los ojos apartados) .

SEGUNDO: ¡ los pocos commits que quedan están casi TODOS duplicados! Parece que he adquirido una segunda línea de tiempo redundante que abarca casi toda la historia del proyecto. Lo interesante (que puede ver en la imagen a continuación), es que mis tres sucursales locales no están todas en la misma línea de tiempo (que es, ciertamente, por qué existe y no es solo basura recolectada).

Lo único que puedo imaginar es que una de las confirmaciones eliminadas fue, tal vez, la única confirmación de combinación que filter-branch realmente eliminó , y que creó la línea de tiempo paralela ya que cada hebra ahora no fusionada tomó su propia copia de las confirmaciones. ( encogiéndose de hombros ¿Dónde está mi TARDiS?) Estoy bastante seguro de que puedo solucionar este problema, aunque realmente me encantaría entender cómo sucedió.

En el caso del loco mergefest-O-RAMA, probablemente lo dejaré solo, ya que se ha arraigado firmemente en mi historial de compromisos, amenazándome cada vez que me acerco, no parece estar causando realmente cualquier problema no cosmético y porque es bastante bonito en Tower.app.

Jay Allen
fuente
3

La manera más fácil

  1. instalar git splits. Lo creé como una extensión git, basado en la solución de jkeating .
  2. Dividir los directorios en una sucursal local. #change into your repo's directory cd /path/to/repo #checkout the branch git checkout XYZ
    #split multiple directories into new branch XYZ git splits -b XYZ XY1 XY2

  3. Crea un repositorio vacío en alguna parte. Asumiremos que hemos creado un repositorio vacío llamado xyzen GitHub que tiene ruta:[email protected]:simpliwp/xyz.git

  4. Empuje al nuevo repositorio. #add a new remote origin for the empty repo so we can push to the empty repo on GitHub git remote add origin_xyz [email protected]:simpliwp/xyz.git #push the branch to the empty repo's master branch git push origin_xyz XYZ:master

  5. Clone el repositorio remoto recién creado en un nuevo directorio local
    #change current directory out of the old repo cd /path/to/where/you/want/the/new/local/repo #clone the remote repo you just pushed to git clone [email protected]:simpliwp/xyz.git

AndrewD
fuente
Una ventaja de este método en comparación con "The Easy Way" es que el control remoto ya está configurado para el nuevo repositorio, por lo que puede hacer una adición de subárbol de inmediato. De hecho, esta forma me parece más fácil (incluso sin git splits)
MM
Apoyos a AndrewD para publicar esta solución. Bifurqué su repositorio para que funcione en OSX ( github.com/ricardoespsanto/git-splits ) si eso es útil para cualquier otra persona
ricardoespsanto
2

Es posible que necesite algo como "git reflog expire --expire = now --all" antes de la recolección de basura para limpiar realmente los archivos. git filter-branch solo elimina referencias en el historial, pero no elimina las entradas de registro que contienen los datos. Por supuesto, prueba esto primero.

El uso de mi disco disminuyó drásticamente al hacer esto, aunque mis condiciones iniciales fueron algo diferentes. Quizás --subdirectory-filter niega esta necesidad, pero lo dudo.


fuente
2

Echa un vistazo al proyecto git_split en https://github.com/vangorra/git_split

Convierta los directorios de git en sus propios repositorios en su propia ubicación. No hay subárbol de negocios divertidos. Este script tomará un directorio existente en su repositorio git y lo convertirá en un repositorio independiente propio. En el camino, copiará todo el historial de cambios para el directorio que proporcionó.

./git_split.sh <src_repo> <src_branch> <relative_dir_path> <dest_repo>
        src_repo  - The source repo to pull from.
        src_branch - The branch of the source repo to pull from. (usually master)
        relative_dir_path   - Relative path of the directory in the source repo to split.
        dest_repo - The repo to push to.
vanguardia
fuente
1

Pon esto en tu gitconfig:

reduce-to-subfolder = !sh -c 'git filter-branch --tag-name-filter cat --prune-empty --subdirectory-filter cookbooks/unicorn HEAD && git reset --hard && git for-each-ref refs/original/ | cut -f 2 | xargs -n 1 git update-ref -d && git reflog expire --expire=now --all && git gc --aggressive --prune=now && git remote rm origin'
más asqueroso
fuente
1

Estoy seguro de que git subtree está bien y es maravilloso, pero mis subdirectorios de código administrado por git que quería mover estaban en eclipse. Entonces, si está usando egit, es dolorosamente fácil. Tome el proyecto que desea mover y haga equipo-> desconéctelo, y luego equipo-> compártalo en la nueva ubicación. De forma predeterminada, intentará usar la ubicación del repositorio anterior, pero puede desmarcar la selección de uso existente y elegir el nuevo lugar para moverla. Todos granizan egit.

stu
fuente
3
La parte "excelente y maravillosa" del subárbol es que la historia de su subdirectorio aparece durante el viaje. Si no necesita el historial, entonces su método dolorosamente fácil es el camino a seguir.
pglezen
0

Puedes probar fácilmente el https://help.github.com/enterprise/2.15/user/articles/splitting-a-subfolder-out-into-a-new-repository/

Esto funcionó para mí. Los problemas que enfrenté en los pasos anteriores son

  1. en este comando git filter-branch --prune-empty --subdirectory-filter FOLDER-NAME BRANCH-NAME El BRANCH-NAMEes maestro

  2. si el último paso falla al comprometerse debido a un problema de protección, siga: https://docs.gitlab.com/ee/user/project/protected_branches.html

Barath Ravichander
fuente
0

He encontrado una solución bastante sencilla, la idea es copiar el repositorio y luego simplemente eliminar la parte innecesaria. Así es como funciona:

1) Clona un repositorio que te gustaría dividir

git clone [email protected]:testrepo/test.git

2) Mover a la carpeta git

cd test/

2) Eliminar carpetas innecesarias y confirmarlo

rm -r ABC/
git add .
enter code here
git commit -m 'Remove ABC'

3) Eliminar el historial de formularios de carpetas innecesarias con BFG

cd ..
java -jar bfg.jar --delete-folders "{ABC}" test
cd test/
git reflog expire --expire=now --all && git gc --prune=now --aggressive

para multiplicar carpetas puedes usar comas

java -jar bfg.jar --delete-folders "{ABC1,ABC2}" metric.git

4) Verifique que el historial no contenga los archivos / carpetas que acaba de eliminar

git log --diff-filter=D --summary | grep delete

5) Ahora tiene un repositorio limpio sin ABC, así que simplemente empújelo a un nuevo origen

remote add origin [email protected]:username/new_repo
git push -u origin master

Eso es. Puede repetir los pasos para obtener otro repositorio,

simplemente elimine XY1, XY2 y cambie el nombre de XYZ -> ABC en el paso 3

Vladislav Troyan
fuente
Casi perfecto ... pero olvidó "git filter-branch --prune-empty" para eliminar todas las confirmaciones antiguas que ahora están vacías. Para hacer antes de empujar al maestro de origen!
ZettaCircl
Si cometió el error y aún quiere "repush" después de haber eliminado la confirmación vacía anterior, realice: "git push -u origin master --force-with-lease"
ZettaCircl