Notas preliminares
A observação aqui é que, depois de começar a trabalhar branch1
(esquecendo ou não percebendo que seria bom mudar para um ramo diferente branch2
primeiro), você executa:
git checkout branch2
Às vezes, o Git diz "OK, você está no branch2 agora!" Às vezes, Git diz: "Não posso fazer isso, perderia algumas de suas alterações".
Se o Git não permitir, você deve confirmar suas alterações, salvá-las em algum lugar permanente. Você pode usar git stash
para salvá-los; essa é uma das coisas para as quais foi projetada. Observe que git stash save
ou git stash push
realmente significa "Confirme todas as alterações, mas em nenhuma ramificação, em seguida, remova-as de onde estou agora". Isso possibilita a troca: agora você não tem alterações em andamento. Você pode então git stash apply
eles depois de mudar.
Barra lateral: git stash save
é a sintaxe antiga; git stash push
foi introduzido no Git versão 2.13, para corrigir alguns problemas com os argumentos git stash
e permitir novas opções. Ambos fazem a mesma coisa, quando usados de maneira básica.
Você pode parar de ler aqui, se quiser!
Se o Git não permitir que você alterne, você já tem um remédio: use git stash
or git commit
; ou, se suas alterações forem triviais para recriar, use git checkout -f
para forçá-las. Esta resposta é sobre quando o Git permitirá git checkout branch2
que você comece a fazer algumas alterações. Por que funciona algumas vezes , e não outras ?
A regra aqui é simples de uma maneira e complicada / difícil de explicar de outra:
Você pode alternar ramificações com alterações não confirmadas na árvore de trabalho se e somente se a referida alternância não exigir que essas alterações sejam prejudicadas.
Ou seja, e observe que isso ainda é simplificado; existem alguns casos de canto extra-difíceis com git add
s, se git rm
e tal - suponha que você esteja branch1
. A git checkout branch2
teria que fazer o seguinte:
- Para cada arquivo que está no
branch1
e não em branch2
, 1 remove o arquivo.
- Para cada arquivo que está no
branch2
e não em branch1
, criar esse arquivo (com conteúdos apropriados).
- Para cada arquivo que esteja nos dois ramos, se a versão
branch2
for diferente, atualize a versão da árvore de trabalho.
Cada uma dessas etapas pode prejudicar algo em sua árvore de trabalho:
- A remoção de um arquivo é "segura" se a versão na árvore de trabalho for igual à versão confirmada
branch1
; é "inseguro" se você fez alterações.
- Criar um arquivo como ele aparece
branch2
é "seguro" se ele não existir agora. 2 É "inseguro" se ele existe agora, mas tem o conteúdo "errado".
- E, é claro, substituir a versão da árvore de trabalho de um arquivo por uma versão diferente é "seguro" se a versão da árvore de trabalho já estiver comprometida
branch1
.
Criar uma nova ramificação ( git checkout -b newbranch
) é sempre considerado "seguro": nenhum arquivo será adicionado, removido ou alterado na árvore de trabalho como parte desse processo, e a área de indexação / preparação também é intocada. (Advertência: é seguro ao criar uma nova ramificação sem alterar o ponto de partida da nova ramificação; mas se você adicionar outro argumento, por exemplo git checkout -b newbranch different-start-point
, isso pode ter que mudar as coisas, para mudar para different-start-point
. O Git aplicará as regras de segurança do checkout normalmente. .)
1 Isso requer que definamos o que significa um arquivo estar em uma ramificação, o que, por sua vez, exige a definição correta da palavra ramificação . (Veja também o que exatamente queremos dizer com "ramo"? ) Aqui, o que eu realmente quero dizer é a comprometer-se a que os resolve-nome de ramo: um arquivo cujo caminho é é em se produz um hash. Esse arquivo não está em se você receber uma mensagem de erro. A existência de caminho no seu índice ou árvore de trabalho não é relevante ao responder a essa pergunta em particular. Assim, o segredo aqui é examinar o resultado de cadaP
branch1
git rev-parse branch1:P
branch1
P
git rev-parse
branch-name:path
. Isso falha porque o arquivo está "no" no máximo uma ramificação ou nos fornece dois IDs de hash. Se os dois IDs de hash forem iguais , o arquivo será o mesmo nas duas ramificações. Nenhuma mudança é necessária. Se os IDs de hash forem diferentes, o arquivo será diferente nas duas ramificações e deverá ser alterado para alternar as ramificações.
A noção principal aqui é que os arquivos em confirmações são congelados para sempre. Os arquivos que você editará obviamente não estão congelados. Estamos, pelo menos inicialmente, olhando apenas para as diferenças entre dois commits congelados. Infelizmente, nós - ou o Git - também temos que lidar com arquivos que não estão no commit do qual você vai mudar e estão no commit que você vai mudar. Isso leva às complicações restantes, pois os arquivos também podem existir no índice e / ou na árvore de trabalho, sem a necessidade de existir esses dois commits congelados específicos com os quais estamos trabalhando.
2 Pode ser considerado "seguro" se já existir com o "conteúdo correto", para que o Git não precise criá-lo, afinal. Lembro-me de pelo menos algumas versões do Git permitindo isso, mas o teste agora mostra que ele é considerado "inseguro" no Git 1.8.5.4. O mesmo argumento se aplicaria a um arquivo modificado que passou a ser modificado para corresponder à ramificação a ser alternada. Novamente, o 1.8.5.4 apenas diz que "seria substituído", no entanto. Veja também o final das notas técnicas: minha memória pode estar com defeito, pois não acho que as regras da árvore de leitura tenham sido alteradas desde que comecei a usar o Git na versão 1.5.
Importa se as alterações são preparadas ou não?
Sim, de certa forma. Em particular, você pode preparar uma alteração e "desmodificar" o arquivo da árvore de trabalho. Aqui está um arquivo em dois ramos, que é diferente em branch1
e branch2
:
$ git show branch1:inboth
this file is in both branches
$ git show branch2:inboth
this file is in both branches
but it has more stuff in branch2 now
$ git checkout branch1
Switched to branch 'branch1'
$ echo 'but it has more stuff in branch2 now' >> inboth
Neste ponto, o arquivo da árvore de trabalho inboth
corresponde ao arquivo branch2
, mesmo que estejamos ativando branch1
. Essa mudança não é preparada para confirmação, que é o que git status --short
mostra aqui:
$ git status --short
M inboth
O espaço então M significa "modificado, mas não preparado" (ou mais precisamente, a cópia da árvore de trabalho difere da cópia preparada / indexada).
$ git checkout branch2
error: Your local changes ...
OK, agora vamos preparar a cópia da árvore de trabalho, que já sabemos que também corresponde à cópia branch2
.
$ git add inboth
$ git status --short
M inboth
$ git checkout branch2
Switched to branch 'branch2'
Aqui, as cópias encenadas e em trabalho coincidiam com o que estava dentro branch2
, então o check-out era permitido.
Vamos tentar outro passo:
$ git checkout branch1
Switched to branch 'branch1'
$ cat inboth
this file is in both branches
A alteração que fiz foi perdida na área de preparação agora (porque o checkout grava na área de preparação). Este é um caso de esquina. A mudança não se foi, mas o fato de que eu tinha encenado isso, está desaparecido.
Vamos preparar uma terceira variante do arquivo, diferente da cópia de ramificação, e definir a cópia de trabalho para corresponder à versão atual da ramificação:
$ echo 'staged version different from all' > inboth
$ git add inboth
$ git show branch1:inboth > inboth
$ git status --short
MM inboth
Os dois M
aqui significam: o arquivo intermediário difere do HEAD
arquivo e o arquivo da árvore de trabalho difere do arquivo intermediário. A versão da árvore de trabalho corresponde à versão branch1
(aka HEAD
):
$ git diff HEAD
$
Mas git checkout
não permitirá o checkout:
$ git checkout branch2
error: Your local changes ...
Vamos definir a branch2
versão como a versão de trabalho:
$ git show branch2:inboth > inboth
$ git status --short
MM inboth
$ git diff HEAD
diff --git a/inboth b/inboth
index ecb07f7..aee20fb 100644
--- a/inboth
+++ b/inboth
@@ -1 +1,2 @@
this file is in both branches
+but it has more stuff in branch2 now
$ git diff branch2 -- inboth
$ git checkout branch2
error: Your local changes ...
Mesmo que a cópia de trabalho atual corresponda à cópia inserida branch2
, o arquivo temporário não corresponde, portanto, a git checkout
perderia a cópia e ela git checkout
será rejeitada.
Notas técnicas - apenas para os insanamente curiosos :-)
O mecanismo de implementação subjacente a tudo isso é o índice do Git . O índice, também chamado de "área de preparação", é onde você cria a próxima confirmação: ela começa a corresponder à confirmação atual, ou seja, o que você fez o check-out agora e, a cada vez que você cria git add
um arquivo, substitui a versão do índice com o que você tem em sua árvore de trabalho.
Lembre -se de que a árvore de trabalho é onde você trabalha em seus arquivos. Aqui, eles têm sua forma normal, em vez de alguma forma especial útil apenas para o Git, como eles fazem nos commits e no índice. Então você extrai um arquivo de uma consolidação, através do índice, e depois na árvore de trabalho. Depois de alterá-lo, você git add
é o índice. Portanto, existem três locais para cada arquivo: a confirmação atual, o índice e a árvore de trabalho.
Quando você executa git checkout branch2
, o que o Git faz embaixo das capas é comparar a confirmação da dicabranch2
com o que estiver na confirmação atual e no índice agora. Qualquer arquivo que corresponda ao que existe agora, o Git pode deixar em paz. Tudo intocado. Qualquer arquivo que seja o mesmo nos dois commits , o Git também pode ser deixado em paz - e esses são os que permitem alternar ramificações.
Grande parte do Git, incluindo a troca de commit, é relativamente rápida por causa desse índice. O que realmente está no índice não é cada arquivo em si, mas o hash de cada arquivo . A cópia do arquivo em si é armazenada como o que o Git chama de objeto blob , no repositório. É semelhante à maneira como os arquivos são armazenados nas confirmações: confirmações não contêm os arquivos , eles apenas levam o Git ao ID de hash de cada arquivo. Portanto, o Git pode comparar IDs de hash - atualmente cadeias de caracteres de 160 bits - para decidir se as confirmações X e Y têm o mesmo arquivo ou não. Em seguida, ele também pode comparar esses IDs de hash com o ID de hash no índice.
É isso que leva a todos os casos de canto excêntricos acima. Temos confirmações X e Y que possuem arquivo path/to/name.txt
e temos uma entrada de índice para path/to/name.txt
. Talvez todos os três hashes correspondam. Talvez dois deles combinem e um não. Talvez todos os três sejam diferentes. E também podemos ter another/file.txt
isso apenas em X ou apenas em Y e está ou não está no índice agora. Cada um desses vários casos exige sua própria consideração separada: o Git precisa copiar o arquivo do commit para o índice, ou removê-lo do índice, para alternar de X para Y ? Nesse caso, também devecopie o arquivo para a árvore de trabalho ou remova-o da árvore de trabalho. E se for esse o caso, as versões do índice e da árvore de trabalho terão melhor correspondência com pelo menos uma das versões confirmadas; caso contrário, o Git estará bloqueando alguns dados.
(As regras completas para tudo isso estão descritas, não na git checkout
documentação que você pode esperar, mas na git read-tree
documentação, na seção intitulada "Mesclagem de duas árvores" .)
git checkout -m
, que mescla sua árvore de trabalho e as alterações de índice no novo checkout.Você tem duas opções: esconda suas alterações:
depois, para recuperá-los:
ou coloque suas alterações em uma ramificação para que você possa obter a ramificação remota e depois mesclar suas alterações. Essa é uma das melhores coisas do git: você pode criar um ramo, se comprometer com ele e buscar outras alterações no ramo em que estava.
Você diz que não faz sentido, mas só o faz para poder mesclá-los à vontade depois de fazer o puxão. Obviamente, sua outra opção é confirmar sua cópia do ramo e, em seguida, fazer o pull. A presunção é de que você não quer fazer isso (nesse caso, fico intrigado por não querer um ramo) ou tem medo de conflitos.
fonte
git stash apply
? aqui os documentos.git stash pop
-lo e ele eliminará o stash da sua lista se for aplicado com êxito.git stash pop
, a menos que você pretende manter um registro de esconderijos em sua história repoSe a nova ramificação contiver edições diferentes da ramificação atual para esse arquivo alterado em particular, não será possível alternar ramificações até que a alteração seja confirmada ou oculta. Se o arquivo alterado for o mesmo nos dois ramos (ou seja, a versão confirmada desse arquivo), você poderá alternar livremente.
Exemplo:
Isso vale para arquivos não rastreados, bem como arquivos rastreados. Aqui está um exemplo para um arquivo não rastreado.
Exemplo:
Um bom exemplo de por que você desejaria mover-se entre os ramos enquanto fazia alterações seria se estivesse realizando algumas experiências no mestre, quisesse confirmá-las, mas ainda não o dominar ainda ...
fonte
A resposta correta é
git checkout -m origin/master
Ele mescla alterações da ramificação principal de origem com as alterações não confirmadas locais.
fonte
Caso você não queira que essas alterações sejam confirmadas, faça
git reset --hard
.Em seguida, você pode fazer o checkout no ramo desejado, mas lembre-se de que as alterações não confirmadas serão perdidas.
fonte