web-dev-qa-db-fra.com

Fusionner, mettre à jour et extraire des branches Git sans utiliser de checkout

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?

559
charles

La réponse courte

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.

La longue réponse

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:

  1. 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é.

  2. git fetch upstream master:master: ceci accélère votre master locale au même endroit que upstream/master.

  3. git checkout - extrait votre branche précédemment extraite (c'est ce que fait le - dans ce cas).

La syntaxe de git fetch pour les fusions (non) rapides

Si 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>

La documentation

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.

Voir également

  1. Git checkout et fusion sans toucher à l’arbre de travail

  2. Fusion sans changer le répertoire de travail

873
user456814

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.

80
Amber

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.

29
Cascabel

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>

<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.)

19
Cascabel

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 ...

10
kkoehne

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.

10
Bennett McElwee

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.

6
wnoise

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

3
Casebash

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.

3
lkraider

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).

Pour configurer, ajoutez à _.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} 
}
_

Usage:

_# 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_

2
rkd

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
0
Benjamin Atkin

juste pour tirer le maître sans vérifier le maître que j'utilise

git fetch Origin master:master

0
Sodhi saab
_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>
_
0
grego