Mesclar, atualizar e puxar ramificações do Git sem usar caixas


629

Trabalho em um projeto que possui 2 ramificações, A e B. Normalmente, trabalho na ramificação A e mesclo as coisas da ramificação B. Para a mesclagem, normalmente:

git merge origin/branchB

No entanto, eu também gostaria de manter uma cópia local da ramificação B, pois ocasionalmente posso verificar a ramificação sem antes mesclar com a ramificação A. Para isso, eu faria:

git checkout branchB
git pull
git checkout branchA

Existe uma maneira de fazer o acima em um comando e sem precisar alternar entre ramificações? Devo estar usando git update-refpara isso? Quão?



1
A resposta de Jakub à primeira pergunta vinculada explica por que isso é geralmente impossível. Outra explicação (a posteriori) é que você não pode mesclar em um repositório simples, tão claramente que requer a árvore de trabalho.
Cascabel

3
@ Eric: Os motivos mais comuns são que os checkouts demoram para repositórios grandes e atualizam os carimbos de data e hora, mesmo se você retornar à mesma versão, portanto, pense que tudo precisa ser reconstruído.
Cascabel

A segunda pergunta que eu vinculei está perguntando sobre um caso incomum - fusões que podem ser encaminhadas rapidamente, mas que o OP queria mesclar usando a --no-ffopção, o que faz com que um commit de mesclagem seja registrado de qualquer maneira. Se você estiver interessado nisso, minha resposta mostra como você pode fazer isso - não tão robusto quanto minha resposta postada aqui, mas os pontos fortes dos dois certamente poderiam ser combinados.
Cascabel

Respostas:


973

A resposta curta

Desde que você faça uma mesclagem de avanço rápido , você pode simplesmente usar

git fetch <remote> <sourceBranch>:<destinationBranch>

Exemplos:

# 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

Embora a resposta de Amber também funcione em casos de avanço rápido, usar git fetchdessa maneira é um pouco mais seguro do que apenas mover a referência de ramificação com força, pois git fetchevitará automaticamente avanços acidentais não rápidos desde que você não use +o refspec.

A resposta longa

Você não pode mesclar uma ramificação B na ramificação A sem fazer check-out de A primeiro se isso resultar em uma mesclagem de avanço rápido. Isso ocorre porque é necessária uma cópia de trabalho para resolver possíveis conflitos.

No entanto, no caso de mesclagens de avanço rápido, isso é possível , porque essas mesclas nunca podem resultar em conflitos, por definição. Para fazer isso sem verificar primeiro uma ramificação, você pode usar git fetchcom um refspec.

Aqui está um exemplo de atualização master(não permitindo alterações sem avanço rápido) se você tiver outra ramificação em featurecheck-out:

git fetch upstream master:master

Esse caso de uso é tão comum que você provavelmente desejará criar um alias para ele no seu arquivo de configuração git, como este:

[alias]
    sync = !sh -c 'git checkout --quiet HEAD; git fetch upstream master:master; git checkout --quiet -'

O que esse alias faz é o seguinte:

  1. git checkout HEAD: coloca sua cópia de trabalho em um estado de desanexação. Isso é útil se você deseja atualizar masterenquanto faz o check-out. Eu acho que era necessário fazer isso, porque, caso contrário, a referência de ramificação para masternão se moverá, mas não me lembro se isso é realmente certo.

  2. git fetch upstream master:master: encaminha o local rapidamente masterpara o mesmo local que upstream/master.

  3. git checkout -faz check-out de sua filial com check-out anterior (é o que -faz neste caso).

A sintaxe de git fetchmesclagens de avanço rápido (não)

Se você deseja que o fetchcomando falhe se a atualização não for um avanço rápido, basta usar um refspec do formulário

git fetch <remote> <remoteBranch>:<localBranch>

Se você deseja permitir atualizações sem avanço rápido, adicione um +à frente do refspec:

git fetch <remote> +<remoteBranch>:<localBranch>

Observe que você pode passar seu repositório local como o parâmetro "remote" usando .:

git fetch . <sourceBranch>:<destinationBranch>

A documentação

A partir da git fetchdocumentação que explica esta sintaxe (ênfase minha):

<refspec>

O formato de um <refspec>parâmetro é uma vantagem opcional +, seguida pela referência de origem <src>, seguida de dois pontos :, seguida pela ref de destino <dst>.

A ref remota que corresponde <src>é buscada e, se <dst>não for uma cadeia vazia, a ref local que corresponde a ela é encaminhada rapidamente usando<src> . Se o plus opcional+for usado, a referência local será atualizada mesmo que não resulte em uma atualização de avanço rápido.

Veja também

  1. Git checkout e mesclar sem tocar na árvore de trabalho

  2. Mesclando sem alterar o diretório de trabalho


3
git checkout --quiet HEADé a git checkout --quiet --detachpartir do Git 1.7.5.
245 Rafa

6
Eu descobri que eu tinha que fazer: git fetch . origin/foo:foopara atualizar minha foo local para o meu local de origem / foo
Weston

3
Existe uma razão para as partes "git checkout HEAD --quiet" e "git checkout --quiet -" estarem incluídas na resposta longa, mas não na resposta curta? Eu acho que é porque o script pode ser executado quando você faz o check-out mestre, mesmo que você pudesse fazer um git pull?
Sean

8
por que 'buscar' o comando para fazer 'mesclagem' aqui ... simplesmente não faz sentido; Se 'pull' for 'buscar' seguido de 'mesclar', deve haver um equivalente mais lógico 'mesclar -ff' que atualizaria 'ramificação' de 'origem / ramificação' localmente, considerando que uma 'busca' foi já foi executado.
Ed Randall

1
Obrigado pelo git checkout -truque! Tão fácil quanto cd -.
Steed

84

Não, não há. É necessário fazer um checkout do ramo de destino para permitir a resolução de conflitos, entre outras coisas (se o Git não conseguir mesclá-los automaticamente).

No entanto, se a mesclagem for um avanço rápido, você não precisará verificar a ramificação de destino, porque na verdade não precisa mesclar nada - tudo o que você precisa fazer é atualizar a ramificação para apontar para o nova cabeça ref. Você pode fazer isso com git branch -f:

git branch -f branch-b branch-a

Será atualizado branch-bpara apontar para o cabeçalho do branch-a.

A -fopção significa --force, o que significa que você deve ter cuidado ao usá-lo.

Não use a menos que tenha certeza absoluta de que a mesclagem será rápida.


46
Apenas tome muito cuidado para não fazer isso, a menos que você tenha certeza absoluta de que a fusão seria um avanço rápido! Não quero perceber depois que você perdeu os commits.
Cascabel

@FuadSaud Não exatamente. git resetsó funciona na filial com check-out atualmente.
Amber

9
O mesmo resultado (avanço rápido) é alcançado por git fetch upstream branch-b:branch-b(retirado desta resposta ).
613 Oliver Oliver

6
Para expandir o comentário de @ Oliver, você também pode fazer git fetch <remote> B:A, onde B e A são ramificações completamente diferentes, mas B pode ser incorporado rapidamente em A. Você também pode passar seu repositório local como o "remoto" usando .como alias remoto: git fetch . B:A.

8
A pergunta do OP deixa bem claro que a fusão será realmente rápida. Independentemente disso, branch -fpode ser perigoso, como você aponta. Então não use! Use fetch origin branchB:branchB, que falhará com segurança se a mesclagem não for avançar rapidamente.
Bennett McElwee

30

Como Amber disse, as fusões de avanço rápido são o único caso em que você poderia fazer isso. É possível que qualquer outra mesclagem precise passar por toda a mescla de três vias, aplicando patches, resolvendo conflitos - e isso significa que precisa haver arquivos por aí.

Por acaso, tenho um script usado para exatamente isso: fazer mesclagens de avanço rápido sem tocar na árvore de trabalho (a menos que você esteja mesclando no HEAD). É um pouco longo, porque é pelo menos um pouco robusto - ele verifica se a mesclagem seria um avanço rápido e, em seguida, executa-o sem verificar a ramificação, mas produzindo os mesmos resultados que se você tivesse - você vê o diff --statresumo das alterações e a entrada no reflog é exatamente como uma mesclagem de avanço rápido, em vez da "redefinição" que você obtém se usar branch -f. Se você nomeá-lo git-merge-ffe soltá-lo em seu diretório bin, você pode chamá-lo como um comando 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

PS Se alguém vir algum problema com esse script, comente! Era um trabalho de escrever e esquecer, mas ficaria feliz em melhorá-lo.


você pode estar interessado em stackoverflow.com/a/5148202/717355 para comparação
Philip Oakley

@PhilipOakley De uma olhada rápida, isso no núcleo faz exatamente a mesma coisa que a minha. Mas o meu não é codificado para um único par de ramificações, ele tem muito mais manipulação de erros, simula a saída do git-merge e faz o que você quer dizer se você o chamar enquanto estiver na ramificação.
Cascabel

Eu tenho o seu no meu diretório \ bin ;-) Eu tinha esquecido disso e estava olhando em volta, vi o script e ele me fez lembrar! Minha cópia tem esse link e # or git branch -f localbranch remote/remotebranchpara me lembrar a fonte e as opções. Deu seu comentário no outro link a +1.
Philip Oakley

3
+1, também pode usar "$branch@{u}"como committish a fusão para obter a ramificação upstream (em kernel.org/pub/software/scm/git/docs/gitrevisions.html )
orip

Obrigado! Alguma chance de você dar um exemplo simples na resposta?
nmr

20

Você só pode fazer isso se a mesclagem for um avanço rápido. Caso contrário, o git precisa fazer o check-out dos arquivos para que possa mesclá-los!

Para fazer isso apenas para avanço rápido :

git fetch <branch that would be pulled for branchB>
git update-ref -m "merge <commit>: Fast forward" refs/heads/<branch> <commit>

onde <commit>está o commit buscado, aquele para o qual você deseja avançar rapidamente. Isso é basicamente como usar git branch -fpara mover a ramificação, exceto que também a registra no reflog como se você realmente fizesse a mesclagem.

Por favor, por favor, não faça isso por algo que não seja um avanço rápido, ou você apenas redefinirá sua ramificação para o outro commit. (Para verificar, veja se git merge-base <branch> <commit>fornece o SHA1 da ramificação.)


4
Existe uma maneira de fazê-lo falhar se não puder avançar rapidamente?
precisa

2
@ gman você pode usar git merge-base --is-ancestor <A> <B>. "B" é o que precisa ser mesclado em "A". O exemplo seria A = mestre e B = desenvolver, garantindo que o desenvolvimento seja rápido no mestre. Nota: Existe com 0 se não for possível ff, existe com 1 se for.
Eddemoya

1
Não documentado no git-scm, mas está no kernal.org. kernel.org/pub/software/scm/git/docs/git-merge-base.html
eddiemoya

Fiz um resumo rápido para encerrar o que estou falando (na verdade não o testou, mas deve levá-lo até lá). gist.github.com/eddiemoya/ad4285b2d8a6bdabf432 ---- como uma observação lateral, eu tinha a maior parte disso útil, tenho um script que verifica ff e, se não, permite que você refaça primeiro o ramo desejado - então ff se funde - tudo sem verificar nada.
Eddemoya

12

No seu caso, você pode usar

git fetch origin branchB:branchB

que faz o que você deseja (assumindo que a mesclagem é um avanço rápido). Se a ramificação não puder ser atualizada porque requer uma mesclagem de avanço rápido, isso falhará com segurança em uma mensagem.

Essa forma de busca também tem algumas opções mais úteis:

git fetch <remote> <sourceBranch>:<destinationBranch>

Observe que <remote> pode ser um repositório local e <sourceBranch>uma ramificação de rastreamento. Assim, você pode atualizar uma filial local, mesmo que não tenha feito check-out, sem acessar a rede .

Atualmente, o acesso ao servidor upstream é feito por meio de uma VPN lenta, então eu me conecto periodicamente git fetchpara atualizar todos os controles remotos e depois desconectar. Então, se, digamos, o mestre remoto mudou, eu posso fazer

git fetch . remotes/origin/master:master

para atualizar meu mestre local com segurança, mesmo que atualmente eu tenha alguma outra ramificação em dia. Não é necessário acesso à rede.


11

Outra maneira reconhecidamente bastante bruta é apenas recriar o ramo:

git fetch remote
git branch -f localbranch remote/remotebranch

Isso descarta a ramificação local desatualizada e recria uma com o mesmo nome, portanto, use com cuidado ...


Acabei de ver agora que a resposta original já menciona o ramo -f ... Ainda assim, não vejo vantagem em ter a mesclagem no reflog para o caso de uso descrito originalmente.
precisa saber é o seguinte

7

Você pode clonar o repositório e fazer a mesclagem no novo repositório. No mesmo sistema de arquivos, isso será vinculado em vez de copiar a maioria dos dados. Termine puxando os resultados para o repositório original.


4

Digite git-forward-merge :

Sem precisar fazer o checkout do destino, git-forward-merge <source> <destination>mescla a origem à ramificação de destino.

https://github.com/schuyler1d/git-forward-merge

Funciona apenas para mesclagens automáticas, se houver conflitos, você precisará usar a mesclagem regular.


2
Eu acho que isso é melhor do que o git fetch <remote> <source>: <destination>, porque um avanço rápido é uma operação de mesclagem e não busca e é mais fácil escrever. Coisa ruim, porém, não está no git padrão.
Binarian 19/07

4

Em muitos casos (como mesclagem), você pode simplesmente usar a ramificação remota sem precisar atualizar a ramificação de rastreamento local. Adicionar uma mensagem no reflog soa como um exagero e impedirá que seja mais rápido. Para facilitar a recuperação, adicione o seguinte na sua configuração do git

[core]
    logallrefupdates=true

Então digite

git reflog show mybranch

para ver o histórico recente de sua filial


Eu acho que a seção [core]não deve ser [user]? (e é por padrão
ativado

3

Eu escrevi uma função shell para um caso de uso semelhante que encontro diariamente em projetos. Este é basicamente um atalho para manter as filiais locais atualizadas com uma filial comum, como desenvolver antes de abrir um PR, etc.

Postar isso mesmo que você não queira usá-lo checkout, caso outras pessoas não se importem com essa restrição.

glmh("git pull and merge here") automaticamente checkout branchB, pullo mais recente, re checkout branchA, e merge branchB.

Não trata da necessidade de manter uma cópia local da filial A, mas pode ser facilmente modificada para isso adicionando uma etapa antes de verificar a filial B. Algo como...

git branch ${branchA}-no-branchB ${branchA}

Para mesclagens de avanço rápido simples, isso pula para o prompt de mensagem de confirmação.

Para mesclagens sem avanço rápido, isso coloca sua ramificação no estado de resolução de conflitos (você provavelmente precisará intervir).

Para configurar, adicionar a .bashrcou .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} 
}

Uso:

# No argument given, will assume "develop"
> glmh

# Pass an argument to pull and merge a specific branch
> glmh your-other-branch

Nota: Isso não é robusto o suficiente para transferir os argumentos além do nome da ramificação paragit merge


2

Outra maneira de efetivamente fazer isso é:

git fetch
git branch -d branchB
git branch -t branchB origin/branchB

Por ser uma letra minúscula -d, ela será excluída apenas se os dados ainda existirem em algum lugar. É semelhante à resposta de @ kkoehne, exceto que não força. Por causa -tdisso, o controle remoto será configurado novamente.

Eu tinha uma necessidade ligeiramente diferente do OP, que era criar uma nova ramificação de recurso develop(ou master), depois de mesclar uma solicitação de recebimento. Isso pode ser feito em uma linha sem força, mas não atualiza a developramificação local . É apenas uma questão de verificar uma nova ramificação e basear-se nela origin/develop:

git checkout -b new-feature origin/develop

1

apenas para puxar o mestre sem verificar o mestre que eu uso

git fetch origin master:master


1

É absolutamente possível fazer qualquer mesclagem, mesmo mesclagens sem avanço rápido, sem git checkout. A worktreeresposta do @grego é uma boa dica. Para expandir isso:

cd local_repo
git worktree add _master_wt master
cd _master_wt
git pull origin master:master
git merge --no-ff -m "merging workbranch" my_work_branch
cd ..
git worktree remove _master_wt

Agora, você mesclou a ramificação local de trabalho à masterramificação local sem mudar sua compra.


1

Se você deseja manter a mesma árvore que uma das ramificações que deseja mesclar (ou seja, não é uma "mesclagem" real)), você pode fazer assim.

# Check if you can fast-forward
if git merge-base --is-ancestor a b; then
    git update-ref refs/heads/a refs/heads/b
    exit
fi

# Else, create a "merge" commit
commit="$(git commit-tree -p a -p b -m "merge b into a" "$(git show -s --pretty=format:%T b)")"
# And update the branch to point to that commit
git update-ref refs/heads/a "$commit"

1
git worktree add [-f] [--detach] [--checkout] [--lock] [-b <new-branch>] <path> [<commit-ish>]

Você pode tentar git worktreeabrir dois ramos lado a lado; isso pode parecer o que você deseja, mas muito diferente das respostas que já vi aqui.

Dessa forma, você pode ter dois desvios separados no mesmo repositório git, portanto, você só precisa buscar uma vez para obter atualizações nas duas árvores de trabalho (em vez de precisar clonar duas vezes e git pull em cada uma)

O Worktree criará um novo diretório de trabalho para o seu código, onde você poderá fazer check-out de uma ramificação diferente simultaneamente, em vez de trocar as ramificações no local.

Quando você deseja removê-lo, pode limpar com

git worktree remove [-f] <worktree>
Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.