Existe-t-il un moyen d'ajouter un sous-répertoire d'un référentiel distant dans un sous-répertoire de mon référentiel avec git-subtree?
Supposons que j'ai ce dépôt main:
/
dir1
dir2
Et ce bibliothèque référentiel:
/
libdir
some-file
some-file-to-be-ignored
Je veux importer bibliothèque/libdir dans principal/dir1 pour qu'il ressemble à ceci:
/
dir1
some-file
dir2
En utilisant git-subtree, je peux spécifier d'importer dans dir1 avec le --prefix
argument, mais puis-je également spécifier de ne prendre que le contenu d'un répertoire spécifique dans le sous-arbre?
La raison d'utiliser git-subtree est que je peux plus tard synchroniser les deux référentiels.
J'ai expérimenté cela et trouvé des solutions partielles, bien qu'aucune ne soit tout à fait parfaite.
Pour ces exemples, je vais envisager de fusionner les quatre fichiers de contrib/completion/
de https://github.com/git/git.git dans third_party/git_completion/
du référentiel local.
C'est probablement le meilleur moyen que j'ai trouvé. J'ai seulement testé la fusion unidirectionnelle; Je n'ai pas essayé de renvoyer les modifications au référentiel en amont.
# 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
Comme il est difficile de se souvenir du dernier SHA1 de validation que vous avez fusionné à partir du référentiel en amont, j'ai écrit cette fonction Bash qui fait tout le travail pour vous (en la récupérant dans le journal 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
}
Utilisez-le comme ceci:
# 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.
Si vous n'allez jamais apporter de modifications locales aux fichiers fusionnés, c'est-à-dire que vous êtes toujours heureux de remplacer le sous-répertoire local avec la dernière version en amont, alors une approche similaire mais plus simple consiste à utiliser 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
J'ai trouvé un article de blog qui prétendait pouvoir fusionner (sans écraser) en utilisant une technique similaire, mais cela n'a pas fonctionné lorsque je l'ai essayé.
J'ai trouvé une solution qui utilise git subtree
, grâce à http://jrsmith3.github.io/merging-a-subdirectory-from-another-repo-via-git-subtree.html , mais c'est incroyablement lent (chaque git subtree split
la commande ci-dessous me prend 9 minutes pour un dépôt de 28 Mo avec 39000 commits sur un double Xeon X5675, alors que les autres solutions que j'ai trouvées prennent moins d'une seconde).
Si vous pouvez vivre avec la lenteur, cela devrait être réalisable:
# 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
Notez que je passe --squash
pour éviter de polluer le référentiel local avec de nombreuses validations, mais vous pouvez supprimer --squash
si vous préférez conserver l'historique des validations.
Il est possible que les divisions ultérieures puissent être effectuées plus rapidement en utilisant --rejoin
(voir https://stackoverflow.com/a/16139361/691281 ) - Je n'ai pas testé cela.
Le PO a clairement indiqué qu'il souhaitait fusionner un sous-répertoire d'un référentiel en amont dans un sous-répertoire du référentiel local. Si toutefois vous souhaitez fusionner tout un référentiel en amont dans un sous-répertoire de votre référentiel local, il existe une alternative plus simple, plus propre et mieux prise en charge:
# 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
Ou si vous préférez éviter de répéter l'URL du référentiel, vous pouvez l'ajouter en tant que télécommande:
# 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
Voir également:
Une technique connexe est git submodules , mais ils comportent des mises en garde ennuyeuses (par exemple, les personnes qui clonent votre référentiel ne cloneront pas les sous-modules à moins d'appeler git clone --recursive
), donc je n'ai pas cherché à savoir s'ils pouvaient prendre en charge les sous-chemins.
Edit: git-subtrac (de l'auteur du git-subtree précédent) semble résoudre certains des problèmes avec les sous-modules git. Donc, cela pourrait être une bonne option pour fusionner un référentiel en amont entier dans un sous-répertoire, mais il ne semble toujours pas prendre en charge l'inclusion uniquement d'un sous-répertoire du référentiel en amont.
J'ai pu faire quelque chose comme ça en ajoutant :dirname
à la commande read-tree. (Notez que j'essaie juste d'apprendre git et git-subtrees moi-même cette semaine, et d'essayer de configurer un environnement similaire à la façon dont j'avais mes projets dans Subversion en utilisant svn: externals - mon point de vue étant qu'il pourrait y avoir une meilleure ou plus simple que les commandes que je montre ici ...)
Ainsi, par exemple, en utilisant votre exemple de structure ci-dessus:
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