2 Pregunta: Añadir subdirectorio de repositorio remoto con git-subárbol

pregunta creada en Thu, May 29, 2014 12:00 AM

¿Hay alguna manera de agregar un subdirectorio de un repositorio remoto a un subdirectorio de mi repositorio con git-subárbol?

Supongamos que tengo este repositorio principal :

 
/
    dir1
    dir2

Y este biblioteca repositorio:

 
/
    libdir
        some-file
    some-file-to-be-ignored

Quiero importar biblioteca /libdir a main /dir1 para que se vea así:

 
/
    dir1
        some-file
    dir2

Al usar git-subárbol, puedo especificar importar en dir1 con el argumento --prefix, pero también puedo especificar que solo se tome el contenido de un directorio específico en el subárbol?

La razón para usar git-subárbol es que luego puedo sincronizar los dos repositorios.

    
36
2 Respuestas                              2                         

He estado experimentando con esto y encontré algunas soluciones parciales, aunque ninguna es del todo perfecta.

Para estos ejemplos, consideraré fusionar los cuatro archivos de contrib/completion/ de https://github.com /git/git.git en third_party/git_completion/ del repositorio local.

1. git diff | git aplicar

Esta es probablemente la mejor manera que he encontrado. Sólo probé la fusión unidireccional; No he intentado enviar los cambios de nuevo al repositorio anterior.

 
# Do this the first time:
$ git remote add -f -t master --no-tags gitgit https://github.com/git/git.git
# The next line is optional. Without it, the upstream commits get
# squashed; with it they will be included in your local history.
$ git merge -s ours --no-commit gitgit/master
# The trailing slash is important here!
$ git read-tree --prefix=third_party/git-completion/ -u gitgit/master:contrib/completion
$ git commit

# In future, you can merge in additional changes as follows:
# The next line is optional. Without it, the upstream commits get
# squashed; with it they will be included in your local history.
$ git merge -s ours --no-commit gitgit/master
# Replace the SHA1 below with the commit hash that you most recently
# merged in using this technique (i.e. the most recent commit on
# gitgit/master at the time).
$ git diff --color=never 53e53c7c81ce2c7c4cd45f95bc095b274cb28b76:contrib/completion gitgit/master:contrib/completion | git apply -3 --directory=third_party/git-completion
# Now fix any conflicts if you'd modified third_party/git-completion.
$ git commit

Ya que es incómodo tener que recordar el SHA1 de confirmación más reciente que fusionaste desde el repositorio ascendente, he escrito esta función Bash que hace todo el trabajo por ti (extrayéndolo del registro de git):

 
git-merge-subpath() {
    local SQUASH
    if [[ $1 == "--squash" ]]; then
        SQUASH=1
        shift
    fi
    if (( $# != 3 )); then
        local PARAMS="[--squash] SOURCE_COMMIT SOURCE_PREFIX DEST_PREFIX"
        echo "USAGE: ${FUNCNAME[0]} $PARAMS"
        return 1
    fi

    # Friendly parameter names; strip any trailing slashes from prefixes.
    local SOURCE_COMMIT="$1" SOURCE_PREFIX="${2%/}" DEST_PREFIX="${3%/}"

    local SOURCE_SHA1
    SOURCE_SHA1=$(git rev-parse --verify "$SOURCE_COMMIT^{commit}") || return 1

    local OLD_SHA1
    local GIT_ROOT=$(git rev-parse --show-toplevel)
    if [[ -n "$(ls -A "$GIT_ROOT/$DEST_PREFIX" 2> /dev/null)" ]]; then
        # OLD_SHA1 will remain empty if there is no match.
        local RE="^${FUNCNAME[0]}: [0-9a-f]{40} $SOURCE_PREFIX $DEST_PREFIX\$"
        OLD_SHA1=$(git log -1 --format=%b -E --grep="$RE" \
                   | grep --color=never -E "$RE" | tail -1 | awk '{print $2}')
    fi

    local OLD_TREEISH
    if [[ -n $OLD_SHA1 ]]; then
        OLD_TREEISH="$OLD_SHA1:$SOURCE_PREFIX"
    else
        # This is the first time git-merge-subpath is run, so diff against the
        # empty commit instead of the last commit created by git-merge-subpath.
        OLD_TREEISH=$(git hash-object -t tree /dev/null)
    fi &&

    if [[ -z $SQUASH ]]; then
        git merge -s ours --no-commit "$SOURCE_COMMIT"
    fi &&

    git diff --color=never "$OLD_TREEISH" "$SOURCE_COMMIT:$SOURCE_PREFIX" \
        | git apply -3 --directory="$DEST_PREFIX" || git mergetool

    if (( $? == 1 )); then
        echo "Uh-oh! Try cleaning up with |git reset --merge|."
    else
        git commit -em "Merge $SOURCE_COMMIT:$SOURCE_PREFIX/ to $DEST_PREFIX/

# Feel free to edit the title and body above, but make sure to keep the
# ${FUNCNAME[0]}: line below intact, so ${FUNCNAME[0]} can find it
# again when grepping git log.
${FUNCNAME[0]}: $SOURCE_SHA1 $SOURCE_PREFIX $DEST_PREFIX"
    fi
}

Úsalo así:

 
# Do this the first time:
$ git remote add -f -t master --no-tags gitgit https://github.com/git/git.git
$ git-merge-subpath gitgit/master contrib/completion third_party/git-completion

# In future, you can merge in additional changes as follows:
$ git fetch gitgit
$ git-merge-subpath gitgit/master contrib/completion third_party/git-completion
# Now fix any conflicts if you'd modified third_party/git-completion.

2. git read-tree

Si nunca va a realizar cambios locales en los archivos combinados, es decir, está contento de sobrescribir siempre el subdirectorio local con la última versión de upstream, entonces un enfoque similar pero más simple es usar git read-tree:

 
# Do this the first time:
$ git remote add -f -t master --no-tags gitgit https://github.com/git/git.git
# The next line is optional. Without it, the upstream commits get
# squashed; with it they will be included in your local history.
$ git merge -s ours --no-commit gitgit/master
$ git read-tree --prefix=third_party/git-completion/ -u gitgit/master:contrib/completion
$ git commit

# In future, you can *overwrite* with the latest changes as follows:
# As above, the next line is optional (affects squashing).
$ git merge -s ours --no-commit gitgit/master
$ git rm -rf third_party/git-completion
$ git read-tree --prefix=third_party/git-completion/ -u gitgit/master:contrib/completion
$ git commit

Encontré un publicación de blog que decía ser capaz de fusionarse (sin sobrescribir) usando una técnica similar, pero no funcionó cuando lo probé.

3. git subárbol

Realmente encontré una solución que usa git subtree, gracias a http://jrsmith3.github.io/merging-a-subdirectory-from-another-repo-via-git-subtree.html , pero es increíblemente lento (cada comando git subtree split a continuación toma 9 minutos para un repo de 28 MB con 39000 confirmaciones en un Xeon X5675 dual, mientras que las otras soluciones que encontré tardan menos de un segundo).

Si puedes vivir con la lentitud, debería ser viable:

 
# Do this the first time:
$ git remote add -f -t master --no-tags gitgit https://github.com/git/git.git
$ git checkout gitgit/master
$ git subtree split -P contrib/completion -b temporary-split-branch
$ git checkout master
$ git subtree add --squash -P third_party/git-completion temporary-split-branch
$ git branch -D temporary-split-branch

# In future, you can merge in additional changes as follows:
$ git checkout gitgit/master
$ git subtree split -P contrib/completion -b temporary-split-branch
$ git checkout master
$ git subtree merge --squash -P third_party/git-completion temporary-split-branch
# Now fix any conflicts if you'd modified third_party/git-completion.
$ git branch -D temporary-split-branch

Tenga en cuenta que paso --squash para evitar contaminar el repositorio local con muchas confirmaciones, pero puede eliminar --squash si prefiere conservar el historial de confirmación.

Es posible que las divisiones posteriores se puedan hacer más rápido usando --rejoin (consulte https://stackoverflow.com/a/16139361/691281 ) - No probé eso.

4. Todo el subárbol git repo

El OP declaró claramente que desea fusionar un subdirectorio de un repositorio ascendente en un subdirectorio del repositorio local. Sin embargo, si, en cambio, desea combinar todo un repositorio ascendente en un subdirectorio de su repositorio local, entonces hay una alternativa más simple, más limpia y mejor admitida:

 
# Do this the first time:
$ git subtree add --squash --prefix=third_party/git https://github.com/git/git.git master

# In future, you can merge in additional changes as follows:
$ git subtree pull --squash --prefix=third_party/git https://github.com/git/git.git master

O si prefiere evitar repetir la URL del repositorio, puede agregarla como un control remoto:

 
# Do this the first time:
$ git remote add -f -t master --no-tags gitgit https://github.com/git/git.git
$ git subtree add --squash --prefix=third_party/git gitgit/master

# In future, you can merge in additional changes as follows:
$ git subtree pull --squash --prefix=third_party/git gitgit/master

# And you can push changes back upstream as follows:
$ git subtree push --prefix=third_party/git gitgit/master
# Or possibly (not sure what the difference is):
$ git subtree push --squash --prefix=third_party/git gitgit/master

Ver también:

5. Todo el repositorio git submódulo

Una técnica relacionada es submódulos de git , pero vienen con advertencias molestas (por ejemplo, las personas que clonan su repositorio no clonarán los submódulos a menos que llamen al git clone --recursive), así queNo he investigado si pueden soportar subpaths.

    
44
2017-05-23 12: 26: 11Z
  1. Buen detalle, aunque esto es un poco confuso al tratar de aplicar estas soluciones a la pregunta del OP, ya que los controles remotos y las rutas locales no coinciden en todos sus ejemplos . ES DECIR. Es un poco difícil ver de dónde proviene algún archivo del repositorio de la biblioteca y cómo colocarlo en dir1 del repositorio principal.
    2017-02-28 13: 49: 19Z
  2. ¿Alguno de los métodos para combinar el subdirectorio de otro soporte de repo más tarde PULSE los cambios al repositorio original? Esperaba que 3: "git subárbol" pueda hacerlo, pero jrsmith3.github.io/… dijo, que "git-subárbol dará como resultado que el estado final del repositorio de destino sea tal que los cambios al repositorio de destino no sean rechazados en sentido ascendente hacia La fuente. "
    2017-04-01 00: 52: 36Z
  3. "es increíblemente lento": ¿es porque tienes demasiados confirmaciones (39000)? ¿Y git tiene que pasar por todo el historial de compromiso para dividir a los que pertenecen al subárbol?
    2017-12-06 02: 16: 24Z
  4. @ MichaelFreidgeim, buena pregunta. Me temo que no probé hacer cambios en el sentido ascendente, pero sería genial tener una solución que funcione para eso.
    2017-12-06 15: 42: 03Z
  5. @ BruceSun, seguro, sería más rápido para un repositorio más pequeño. Pero 39000 no son muchos; el kernel de Linux se acerca a un millón de confirmaciones :)
    2017-12-06 15: 42: 08Z

Pude hacer algo como esto agregando :dirname al comando read-tree. (tenga en cuenta que en realidad solo estoy tratando de aprender git y git-subtrees yo mismo esta semana, y estoy tratando de configurar un entorno similar al de mis proyectos en subversión usando svn: externals; mi punto es que podría haber una mejor o más fácil que los comandos que estoy mostrando aquí ...)

Por ejemplo, usando la estructura de ejemplo de arriba:

 
git remote add library_remote _URL_TO_LIBRARY_REPO_
git fetch library_remote
git checkout -b library_branch library_remote/master
git checkout master
git read-tree --prefix=dir1 -u library_branch:libdir
    
1
2014-06-04 18: 54: 19Z
  1. OK, acabo de leer que git-subárbol es diferente de la subárbol-fusión (que creo que es lo que mostré arriba). Lo mismo podría funcionar (agregando ": libdir" en el lugar apropiado), pero no puedo asegurarlo. Estoy leyendo los documentos del subárbol ahora mismo.
    2014-06-04 19: 05: 44Z
fuente colocada aquí