name - ¿Cómo dividir un repositorio git preservando subdirectorios?
git clone tag (5)
De hecho, podría usar el filtro de subdirectorio seguido de un filtro de índice para volver a colocar el contenido en un subdirectorio, pero ¿por qué molestarse, cuando podría usar el filtro de índice solo?
Aquí hay un ejemplo de la página del manual:
git filter-branch --index-filter ''git rm --cached --ignore-unmatch filename'' HEAD
Esto solo elimina un nombre de archivo; lo que quieres hacer es eliminar todo menos un subdirectorio dado. Si quieres ser cauteloso, puedes enumerar explícitamente cada ruta para eliminar, pero si solo quieres ir con todo incluido, puedes 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
Supongo que probablemente hay una manera más elegante; Si alguien tiene algo, por favor sugiérelo!
Algunas notas sobre ese comando:
- filter-branch establece internamente GIT_COMMIT en el commit actual SHA1
- No hubiera esperado que
--full-tree
fuera necesario, pero aparentemente filter-branch ejecuta el filtro de índice desde el.git-rewrite/t
lugar del nivel superior del repositorio. - grep es probablemente una exageración, pero no creo que sea un problema de velocidad.
-
--all
esto se aplica a todas las referencias; Me imagino que realmente quieres eso. (el--
separa de las opciones de filtro-rama) -
-z
y-0
dicen a ls-tree, grep y xargs que usen la terminación NUL para manejar espacios en los nombres de archivo.
Edite, mucho más tarde: Thomas sugirió amablemente una manera de eliminar las confirmaciones ahora vacías, pero ahora está desactualizada. Mire el historial de edición si tiene una versión antigua de git, pero con git moderno, todo lo que necesita hacer es agregar esta opción:
--prune-empty
Eso eliminará todas las confirmaciones que estén vacías después de la aplicación del filtro de índice.
Lo que quiero es similar a esta pregunta . Sin embargo, quiero que el directorio que se divide en un repositorio separado siga siendo un subdirectorio en ese repositorio:
Tengo esto:
foo/
.git/
bar/
baz/
qux/
Y quiero dividirlo en dos repositorios completamente independientes:
foo/
.git/
bar/
baz/
quux/
.git/
qux/ # Note: still a subdirectory
¿Cómo hacer esto en git?
Podría usar el método de esta respuesta si hay alguna forma de mover todo el contenido del nuevo repositorio a un subdirectorio, a lo largo de la historia.
Esto es lo que terminé haciendo para resolver este problema cuando lo tuve:
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 el subdirectorio Detach (move) en un repositorio Git separado, además de muchos comentarios aquí sobre SO.
La razón por la que la solución de Jefromi no me funcionó fue que tenía archivos y carpetas en mi repositorio cuyos nombres contenían caracteres especiales (principalmente espacios). Además, git rm
quejó de archivos no --ignore-unmatch
(resueltos con --ignore-unmatch
).
Puede mantener el filtrado en el directorio al no estar en la raíz del repo o moverse:
grep --invert-match "^.*directory-to-keep$"
Y finalmente, 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 puedes usar 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
Quería hacer algo similar, pero como la lista de archivos que quería guardar era bastante larga, no tenía sentido hacerlo con innumerables vicios. Escribí un script 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
Sorprendentemente, esto resultó ser mucho más trabajo del que inicialmente esperaba, así que decidí publicarlo aquí.
Si desea dividir un solo directorio como repositorio git separado
git-filter-branch tiene la opción --subdirectory-filter
y es mucho más simple que las soluciones mencionadas anteriormente, solo:
git filter-branch --subdirectory-filter foodir -- --all
Además, cambia la ruta y coloca el contenido del directorio sobre el nuevo repositorio, no solo filtra y elimina otro contenido.
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 para seguir solo con los comandos principales, sub en git read-tree --prefix=path/to/dir/ $GIT_COMMIT:path/to/dir
para el restablecimiento.
Especificar la path/to/dir
en el rev-list args hace la poda antes, con un filtro tan barato que no importa mucho, pero es bueno evitar el esfuerzo perdido de todos modos.