Je travaille sur un projet comportant 2 branches, A et B. Je travaille généralement sur la branche A et fusionne des éléments de la branche B. Pour la fusion, je procéderais généralement comme suit:
git merge Origin/branchB
Cependant, je voudrais aussi conserver une copie locale de la branche B, car je peux parfois vérifier la branche sans fusionner d'abord avec ma branche A. Pour ce faire, je ferais ce qui suit:
git checkout branchB
git pull
git checkout branchA
Existe-t-il un moyen de faire ce qui précède en une seule commande, sans avoir à changer de branche? Devrais-je utiliser git update-ref
pour cela? Comment?
Tant que vous faites une fusion fast-forward, vous pouvez simplement utiliser
git fetch <remote> <sourceBranch>:<destinationBranch>
Exemples:
# Merge local branch foo into local branch master,
# without having to checkout master first.
# Here `.` means to use the local repository as the "remote":
git fetch . foo:master
# Merge remote branch Origin/foo into local branch foo,
# without having to checkout foo first:
git fetch Origin foo:foo
Bien que réponse d'Amber fonctionne également dans les cas d'avance rapide, utiliser git fetch
de cette manière est un peu plus sûr qu'un simple déplacement forcé de la référence de branche, puisque git fetch
empêchera automatiquement les transferts non rapides par avance. tant que vous n'utilisez pas +
dans le refspec.
Vous ne pouvez pas fusionner une branche B dans la branche A sans vérifier d'abord A si cela entraînerait une fusion sans avance rapide. En effet, une copie de travail est nécessaire pour résoudre tout conflit potentiel.
Cependant, dans le cas de fusions à avance rapide, cela est possible, car de telles fusions ne peuvent jamais donner lieu à des conflits, par définition. Pour ce faire sans extraire d’abord une branche, vous pouvez utiliser git fetch
avec un refspec.
Voici un exemple de mise à jour de master
(interdisant les modifications sans avance rapide) si vous avez une autre branche feature
extraite:
git fetch upstream master:master
Ce cas d'utilisation est si courant que vous voudrez probablement lui attribuer un alias dans votre fichier de configuration git, comme celui-ci:
[alias]
sync = !sh -c 'git checkout --quiet HEAD; git fetch upstream master:master; git checkout --quiet -'
Ce que cet alias fait est le suivant:
git checkout HEAD
: ceci place votre copie de travail dans un état détaché. Ceci est utile si vous souhaitez mettre à jour master
pendant que vous l'avez extrait. Je pense que c'était nécessaire, car sinon la référence de branche pour master
ne bougerait pas, mais je ne me souviens pas si c'est vraiment ce qui m'est arrivé.
git fetch upstream master:master
: ceci accélère votre master
locale au même endroit que upstream/master
.
git checkout -
extrait votre branche précédemment extraite (c'est ce que fait le -
dans ce cas).
git fetch
pour les fusions (non) rapidesSi vous voulez que la commande fetch
échoue si la mise à jour n'est pas une avance rapide, utilisez simplement un refspec de la forme
git fetch <remote> <remoteBranch>:<localBranch>
Si vous souhaitez autoriser les mises à jour sans avance rapide, vous devez ajouter un +
au début du refspec:
git fetch <remote> +<remoteBranch>:<localBranch>
Notez que vous pouvez transmettre votre référentiel local en tant que paramètre "remote" à l'aide de .
:
git fetch . <sourceBranch>:<destinationBranch>
De la git fetch
documentation qui explique cette syntaxe (souligné par moi):
<refspec>
Le format d'un paramètre
<refspec>
est facultatif, plus+
, suivi de la référence source<src>
, suivi de deux points:
, suivi de la référence de destination<dst>
.La référence distante correspondant à
<src>
est extraite et, si<dst>
n'est pas une chaîne vide, la référence locale correspondante correspond à une avance rapide à l'aide de<src>
. Si le plus facultatif+
est utilisé, la référence locale est mise à jour même si cela ne donne pas lieu à une mise à jour rapide.
Non, il n'y en a pas. Une extraction de la branche cible est nécessaire pour vous permettre de résoudre les conflits, entre autres choses (si Git ne peut pas les fusionner automatiquement).
Cependant, si la fusion est une fusion rapide, vous n'avez pas besoin de vérifier la branche cible, car vous n'avez pas besoin de fusionner quoi que ce soit - tout ce que vous avez à faire est de mettre à jour la branche pour qu'elle pointe vers la nouvelle tête réf. Vous pouvez le faire avec git branch -f
:
git branch -f branch-b branch-a
Mettra à jour branch-b
pour qu'il pointe vers la tête de branch-a
.
L'option -f
signifie --force
, ce qui signifie que vous devez être prudent lorsque vous l'utilisez. Ne l'utilisez pas, sauf si vous êtes sûr que la fusion sera rapide.
Comme Amber l'a dit, les fusions rapides sont le seul cas dans lequel vous pourriez le faire. Il est concevable que toute autre fusion doive passer par toute la fusion à trois voies, appliquer des correctifs, résoudre des conflits, ce qui signifie qu'il doit y avoir des fichiers autour.
Il se trouve que j’utilise un script que j’utilise exactement pour cela: faire des fusions d’avance rapide sans toucher à l’arbre de travail (à moins que vous ne fusionniez dans HEAD). C'est un peu long, car il est au moins un peu robuste - il vérifie que la fusion est un transfert rapide, puis l'exécute sans vérifier la branche, mais en produisant les mêmes résultats que si vous aviez - vous voyez le résultat. diff --stat
récapitulatif des modifications, et l'entrée dans le fichier de refouillement est exactement comme une fusion rapide, au lieu de la "réinitialisation" que vous obtenez si vous utilisez branch -f
. Si vous l'appelez git-merge-ff
et le déposez dans votre répertoire bin, vous pouvez l'appeler en tant que commande git: git merge-ff
.
#!/bin/bash
_usage() {
echo "Usage: git merge-ff <branch> <committish-to-merge>" 1>&2
exit 1
}
_merge_ff() {
branch="$1"
commit="$2"
branch_orig_hash="$(git show-ref -s --verify refs/heads/$branch 2> /dev/null)"
if [ $? -ne 0 ]; then
echo "Error: unknown branch $branch" 1>&2
_usage
fi
commit_orig_hash="$(git rev-parse --verify $commit 2> /dev/null)"
if [ $? -ne 0 ]; then
echo "Error: unknown revision $commit" 1>&2
_usage
fi
if [ "$(git symbolic-ref HEAD)" = "refs/heads/$branch" ]; then
git merge $quiet --ff-only "$commit"
else
if [ "$(git merge-base $branch_orig_hash $commit_orig_hash)" != "$branch_orig_hash" ]; then
echo "Error: merging $commit into $branch would not be a fast-forward" 1>&2
exit 1
fi
echo "Updating ${branch_orig_hash:0:7}..${commit_orig_hash:0:7}"
if git update-ref -m "merge $commit: Fast forward" "refs/heads/$branch" "$commit_orig_hash" "$branch_orig_hash"; then
if [ -z $quiet ]; then
echo "Fast forward"
git diff --stat "$branch@{1}" "$branch"
fi
else
echo "Error: fast forward using update-ref failed" 1>&2
fi
fi
}
while getopts "q" opt; do
case $opt in
q ) quiet="-q";;
* ) ;;
esac
done
shift $((OPTIND-1))
case $# in
2 ) _merge_ff "$1" "$2";;
* ) _usage
esac
P.S. Si quelqu'un voit des problèmes avec ce script, veuillez commenter! C'était un travail d'écriture et d'oubli, mais je serais heureux de l'améliorer.
Vous ne pouvez le faire que si la fusion est une avance rapide. Si ce n'est pas le cas, git doit alors extraire les fichiers pour pouvoir les fusionner!
Pour le faire pour une avance rapide seulement:
git fetch <branch that would be pulled for branchB>
git update-ref -m "merge <commit>: Fast forward" refs/heads/<branch> <commit>
où <commit>
est la validation extraite, celle à laquelle vous souhaitez effectuer une avance rapide. En gros, c'est comme si vous utilisiez git branch -f
pour déplacer la branche, sauf qu'il l'enregistre également dans le reflog comme si vous l'aviez réellement fusionnée.
S'il vous plaît, s'il vous plaît, s'il vous plaît ne faites pas ceci pour quelque chose qui n'est pas une avance rapide, ou vous allez simplement réinitialiser votre branche sur l'autre commit. (Pour vérifier, voyez si git merge-base <branch> <commit>
donne le SHA1 de la branche.)
Une autre façon, certes assez brute, est de simplement recréer la branche:
git fetch remote
git branch -f localbranch remote/remotebranch
Cela jette la branche obsolète locale et en crée une du même nom, alors utilisez-le avec précaution ...
Dans votre cas, vous pouvez utiliser
git fetch Origin branchB:branchB
qui fait ce que vous voulez (en supposant que la fusion est rapide). Si la branche ne peut pas être mise à jour car elle nécessite une fusion sans avance rapide, elle échoue en toute sécurité avec un message.
Cette forme de récupération contient également des options plus utiles:
git fetch <remote> <sourceBranch>:<destinationBranch>
Notez que <remote>
peut être un référentiel local, et <sourceBranch>
peut être une branche de suivi. Ainsi, vous pouvez mettre à jour une branche locale, même si elle n’est pas extraite, sans accéder au résea.
Actuellement, l'accès au serveur en amont se fait via un VPN lent. Je me connecte donc périodiquement, git fetch
pour mettre à jour toutes les télécommandes, puis me déconnecter. Ensuite, si, par exemple, le maître distant a changé, je peux le faire.
git fetch . remotes/Origin/master:master
pour mettre mon maître local à jour en toute sécurité, même si ma succursale est actuellement fermée. Aucun accès réseau requis.
Vous pouvez cloner le référentiel et le fusionner dans le nouveau référentiel. Sur le même système de fichiers, cela créera un lien physique plutôt que de copier la plupart des données. Terminez en tirant les résultats dans le rapport initial.
Dans de nombreux cas (tels que la fusion), vous pouvez simplement utiliser la branche distante sans avoir à mettre à jour la branche de suivi locale. Ajouter un message dans le reflog ressemble à une overkill et l’empêchera d’être plus rapide. Pour faciliter la récupération, ajoutez ce qui suit dans votre configuration git
[core]
logallrefupdates=true
Puis tapez
git reflog show mybranch
pour voir l'historique récent de votre branche
Entrez git-forward-merge :
Sans avoir à vérifier la destination,
git-forward-merge <source> <destination>
fusionne la source dans la branche de destination.
https://github.com/schuyler1d/git-forward-merge
Ne fonctionne que pour les fusions automatiques. S'il existe des conflits, vous devez utiliser la fusion normale.
J'ai écrit une fonction Shell pour un cas d'utilisation similaire que je rencontre quotidiennement sur des projets. Il s’agit essentiellement d’un raccourci pour tenir à jour les succursales locales avec une succursale commune, comme développer avant d’ouvrir un PR, etc.
Publier ceci même si vous ne voulez pas utiliser
checkout
, au cas où cela ne dérangerait pas cette contrainte.
glmh
("git pull and fusionner here") va automatiquement _checkout branchB
_, pull
la dernière, re -_checkout branchA
_ et _merge branchB
_.
Ne répond pas à la nécessité de conserver une copie locale de branchA, mais pourrait facilement être modifié pour ce faire en ajoutant une étape avant de vérifier branchB. Quelque chose comme...
_git branch ${branchA}-no-branchB ${branchA}
_
Pour les fusions rapides simples, ceci passe à l'invite du message de validation.
Pour les fusions non rapides, cela place votre branche dans l'état de résolution du conflit (vous devrez probablement intervenir).
.bashrc
_ ou _.zshrc
_, etc.:_glmh() {
branchB=$1
[ $# -eq 0 ] && { branchB="develop" }
branchA="$(git branch | grep '*' | sed 's/* //g')"
git checkout ${branchB} && git pull
git checkout ${branchA} && git merge ${branchB}
}
_
_# No argument given, will assume "develop"
> glmh
# Pass an argument to pull and merge a specific branch
> glmh your-other-branch
_
Remarque: Ceci est non assez robuste pour permettre le transfert d'arguments au-delà du nom de la branche vers _
git merge
_
Une autre façon de le faire efficacement est:
git fetch
git branch -d branchB
git branch -t branchB Origin/branchB
Comme il s'agit d'une minuscule -d
, elle ne sera supprimée que si les données existent encore quelque part. C'est similaire à la réponse de @ kkoehne, sauf qu'elle ne force pas. En raison du -t
, il configurera à nouveau la télécommande.
J'avais un besoin légèrement différent de OP, qui consistait à créer une nouvelle branche de fonction develop
(ou master
), après la fusion d'une demande d'extraction. Cela peut être accompli dans une ligne sans force, mais cela ne met pas à jour la branche locale develop
. Il s’agit simplement de vérifier une nouvelle branche et de la fonder sur Origin/develop
:
git checkout -b new-feature Origin/develop
juste pour tirer le maître sans vérifier le maître que j'utilise
git fetch Origin master:master
_git worktree add [-f] [--detach] [--checkout] [--lock] [-b <new-branch>] <path> [<commit-ish>]
_
Vous pouvez essayer git worktree
d'avoir deux branches ouvertes côte à côte, cela ressemble à ce que vous pourriez être, mais très différent de certaines des autres réponses que j'ai vues ici.
De cette façon, vous pouvez avoir deux branches distinctes dans le même dépôt git, il vous suffit donc de chercher une fois pour obtenir les mises à jour dans les deux arbres de travail (plutôt que de devoir cloner deux fois et de tirer sur chacune d'elles).
Worktree créera un nouveau répertoire de travail pour votre code dans lequel vous pourrez extraire simultanément une branche différente au lieu d’échanger les branches à la place.
Lorsque vous souhaitez le supprimer, vous pouvez nettoyer avec
_git worktree remove [-f] <worktree>
_