Pregunta Separar (mover) el subdirectorio en un repositorio separado de Git


tengo un Git repositorio que contiene una cantidad de subdirectorios. Ahora he encontrado que uno de los subdirectorios no está relacionado con el otro y debe separarse en un repositorio separado.

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

Creo 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 pretender que el dos repositorios no tienen un historial compartido.

Para dejarlo en 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/

1595
2017-12-11 13:57


origen


Respuestas:


Actualizar: Este proceso es tan común, que el equipo de git lo hizo mucho más simple con una nueva herramienta, git subtree. Mira aquí: Separar (mover) el subdirectorio en un repositorio separado de Git


Quieres clonar tu repositorio y luego usar git filter-branch para marcar todo excepto el subdirectorio que desea en su nuevo repositorio para que se recoja basura.

  1. Para clonar su repositorio local:

    git clone /XYZ /ABC
    

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

  2. Ahora, preservemos 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 anteriores:

    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 podar su repositorio nuevamente. No lo hice y obtuve un WARNING: Ref 'refs/tags/v0.1' is unchanged para todas las etiquetas (ya que no estaban relacionadas con el subproyecto); adicionalmente, después de eliminar tales etiquetas, se recuperará más espacio. Aparentemente git filter-branch debería ser capaz de 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 reinicie para excluir los otros archivos, para que puedan ser eliminados. Agreguemos también --tag-name-filter cat --prune-empty para eliminar las confirmaciones vacías y reescribir las 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, solo reescriba la rama HEAD e ignore las etiquetas y otras ramas:

    git filter-branch --tag-name-filter cat --prune-empty --subdirectory-filter ABC HEAD
    
  5. A continuación, elimine los reflogs de copia de seguridad para que el espacio pueda recuperarse realmente (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 toda su historia preservada.

Nota: Para la mayoría de los usos, git filter-branch debería tener el parámetro agregado -- --all. Sí, eso es realmente --espacio--  all. Esto debe ser el último parámetro para el comando. Como Matli descubrió, esto mantiene las ramas del proyecto y las etiquetas incluidas en el nuevo repositorio.

Editar: se incorporaron varias sugerencias de los comentarios a continuación para garantizar, por ejemplo, que el repositorio en realidad se redujo (lo que no siempre era el caso antes).


1155
2017-07-25 17:10



The Easy Way ™

Resulta que esta es una práctica tan común y útil que los jefes supremos de git lo hicieron realmente fácil, pero debes tener una versión más nueva de git (> = 1.7.11 de mayo de 2012). Ver el apéndice para saber cómo instalar el último git. Además, hay un ejemplo del mundo real en el tutorial abajo.

  1. Prepare el viejo repos

    pushd <big-repo>
    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 nombrada subproject DEBE pasarse como subprojectNO ./subproject/

    Nota para los usuarios de Windows: cuando la profundidad de su carpeta es> 1, <name-of-folder> debe tener separador de carpeta de estilo * nix (/). Por ejemplo, la carpeta nombrada path1\path2\subproject DEBE pasarse como path1/path2/subproject

  2. Crea el nuevo repositorio

    mkdir <new-repo>
    pushd <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 <git@github.com:my-user/new-repo.git>
    git push origin -u master
    
  4. Limpiar, Si es deseado

    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. Vea el Apéndice a continuación si realmente le preocupa haber ingresado una contraseña o si necesita disminuir el tamaño del archivo de su .git carpeta.

...

Tutorial

Estos son los los mismos pasos que arriba, pero siguiendo mis pasos exactos para mi repositorio en lugar de usar <meta-named-things>.

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

tree ~/Code/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

pushd ~/Code/node-browser-compat/
git subtree split -P btoa -b btoa-only
popd

Ahora tengo una nueva rama, btoa-only, que solo tiene compromisos para btoa y quiero crear un nuevo repositorio

mkdir ~/Code/btoa/
pushd ~/Code/btoa/
git init
git pull ~/Code/node-browser-compat btoa-only

A continuación, creo un nuevo repositorio en Github o bitbucket, o lo que sea, y lo agrego es el origin (Por cierto, "origen" es solo una convención, no parte del comando, se podría llamar "servidor remoto" o lo que quieras)

git remote add origin git@github.com:node-browser-compat/btoa.git
git push origin -u master

¡Día feliz!

Nota: Si creó un repositorio con un README.md, .gitignore y LICENSE, deberás tirar primero:

git pull origin -u master
git push origin -u master

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

git rm -rf btoa

...

Apéndice

Último git en OS X

Para obtener la última versión de git:

brew install git

Para obtener brew para OS X:

http://brew.sh

Último git en Ubuntu

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

Si eso no funciona (tienes una versión muy antigua de ubuntu), prueba

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

Si eso todavía no funciona, prueba

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

Por defecto, eliminar archivos de git no los elimina de git, solo confirma que ya no están allí. Si realmente desea eliminar las referencias históricas (es decir, si 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, puedes verificar que tu archivo o carpeta ya no aparezca en el historial de git.

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

Sin embargo, tu no puede "presionar" elimina a github y similares. Si lo intenta, obtendrá un error y tendrá que git pull antes de que puedas git push - y luego has vuelto a tener todo en tu historia.

Por lo tanto, si desea eliminar el historial del "origen", es decir, eliminarlo de github, bitbucket, etc., deberá eliminar el repositorio y volver a insertar una copia del repositorio. Pero espera - hay más! - Si realmente está preocupado por deshacerse de una contraseña o algo así, deberá podar la copia de seguridad (ver a continuación).

fabricación .git menor

El comando delete history mencionado anteriormente aún deja atrás un montón de archivos de respaldo, porque git es muy amable al ayudarlo a no arruinar su repositorio por accidente. Con el tiempo eliminará los archivos huérfanos durante los días y meses, pero los deja allí por un tiempo en caso de que se dé cuenta de que accidentalmente borró algo que no quería.

Entonces, si realmente quieres vaciar la basura a reducir el tamaño de clon de un repo inmediatamente tienes que 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, recomiendo no realizar estos pasos a menos que sepa que debe hacerlo, en caso de que haya podado el subdirectorio incorrecto, ¿sabe? Los archivos de copia de seguridad no deberían clonarse cuando envía el repositorio, sino que estarán en su copia local.

Crédito


1122
2018-06-05 13:15



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, primero pruébelo en un repositorio 'clone --no-hardlinks', y sígalo con los comandos reset, gc y poune que Paul enumera.


131
2017-10-19 21:10



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

  1. Hacer 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 anterior. "Origen" fue el seguimiento de su clon, y "original" es donde filtrar-rama 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 archivo de paquete que fsck no tocará. Desgarro en fragmentos, creando un nuevo archivo de paquete y eliminando los objetos no utilizados:

    git repack -ad
    

Ahi esta una explicación de esto en el manual para filtrar-ramificar.


94
2018-06-09 15:41



Editar: secuencia de comandos Bash agregado.

Las respuestas dadas aquí funcionaron solo parcialmente para mí; Muchos de los 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. Esto lo bajó a 1.7 MB. Quizás 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

38
2017-08-20 14:11



Esto ya no es tan complejo, solo puedes usar el git filter-branch comando en un clon de usted repo para sacrificar los subdirectorios que no desea y luego presione para el nuevo control remoto.

git filter-branch --prune-empty --subdirectory-filter <YOUR_SUBDIR_TO_KEEP> master
git push <MY_NEW_REMOTE_URL> -f .

21
2018-03-22 20:55



Actualizar: El módulo git-subárbol fue tan útil que el equipo de git lo tiró al núcleo y lo hizo git subtree. Mira aquí: Separar (mover) el subdirectorio en un repositorio separado de Git

git-subárbol puede ser útil para esto

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

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


19
2017-08-06 15:26



Aquí hay una pequeña modificación a CoolAJ86es La respuesta "The Easy Way ™" para dividir múltiples subcarpetas (digamos sub1y sub2) en un nuevo repositorio git.

The Easy Way ™ (múltiples subcarpetas)

  1. Prepare el viejo repos

    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 nombrada subproject DEBE pasarse como subprojectNO ./subproject/

    Nota para los usuarios de Windows: cuando la profundidad de su carpeta es> 1, <name-of-folder> debe tener separador de carpeta de estilo * nix (/). Por ejemplo, la carpeta nombrada path1\path2\subproject DEBE pasarse como path1/path2/subproject. Por otra parte no use mvcomando pero move.

    Nota final: la única y gran diferencia 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. Enlace el nuevo repositorio a Github o donde sea

    git remote add origin <git@github.com:my-user/new-repo.git>
    git push origin -u master
    
  4. Limpiar, Si es deseado

    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. Vea el Apéndice en la respuesta original si realmente le preocupa haber ingresado una contraseña o si necesita disminuir el tamaño del archivo de su .git carpeta.


13
2018-04-17 05:12



La pregunta original quiere que XYZ / ABC / (* archivos) se conviertan en ABC / ABC / (* archivos). Después de implementar la respuesta aceptada para mi propio código, noté que realmente cambia XYZ / ABC / (* archivos) en ABC / (* archivos). La página man de la rama de filtro incluso dice:

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

En otras palabras, promueve que la carpeta de nivel superior "suba" un nivel. Esa es una distinción importante porque, por ejemplo, en mi historia he cambiado el nombre a una carpeta de nivel superior. Al promocionar las carpetas "arriba" de un nivel, git pierde continuidad en el compromiso donde hice el cambio de nombre.

I lost contiuity after filter-branch

Mi respuesta a la pregunta es hacer 2 copias del repositorio y eliminar manualmente la (s) carpeta (s) que desea guardar en cada una. La página man me respalda con esto:

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


11
2017-07-25 10:01