Por que eu iria querer um estágio antes de me comprometer no Git?

102

Sou novo no controle de versão e entendo que "confirmar" é essencialmente criar um backup enquanto atualiza a nova versão 'atual' do que você está trabalhando.

O que eu não entendo é para que serve a encenação de uma perspectiva prática. Encenar é algo que existe apenas no nome ou tem um propósito? Quando você se compromete, vai comprometer tudo de qualquer maneira, certo?

Edit: Eu acho que posso estar confundindo a terminologia. Um arquivo 'testado' é a mesma coisa que um arquivo 'rastreado'?

Cidadão
fonte
6
Não. Um arquivo monitorado é aquele que é conhecido no repositório (normalmente de um commit anterior). Um arquivo testado é aquele que foi adicionado ao índice, que mais tarde será usado para confirmar.
Mark Peters

Respostas:

82

Quando você confirma, ele apenas confirma as mudanças no índice (os arquivos "testados"). Há muitos usos para isso, mas o mais óbvio é dividir suas mudanças de trabalho em partes menores e independentes. Talvez você tenha corrigido um bug enquanto implementava um recurso. Você pode git addapenas esse arquivo (ou git add -padicionar apenas parte de um arquivo!) E, em seguida, confirmar a correção do bug antes de confirmar todo o resto. Se você estiver usando, git commit -aentão você está apenas forçando um addde tudo antes do commit. Não use -ase quiser tirar proveito dos arquivos de teste.

Você também pode tratar os arquivos testados como uma cópia de trabalho intermediária com --cachedmuitos comandos. Por exemplo, git diff --cachedmostrará como o estágio difere HEADpara que você possa ver o que está prestes a comprometer sem misturar suas outras alterações de trabalho.

Ben Jackson
fonte
25
O outro uso realmente comum é quando algumas de suas alterações nunca devem ser confirmadas; por exemplo, você pode encenar as coisas boas, confirmá-las e, em seguida, acabar com as coisas ruins git reset --hard.
Cascabel
3
@BenJackson em seu exemplo, qual é a diferença entre stage + commit e commit seletivo? Eu não vejo nenhuma diferença.
Eugenio
9
Eu, pessoalmente, não consegui obter uma resposta satisfatória para "Qual é o objetivo de encenar?" questão. Francamente, é simplesmente desnecessário. Já estou usando um branch local, então não há risco de quebrar a compilação. Não vou publicar e fazer uma solicitação de pull até estar completamente satisfeito com minhas alterações. Já posso dividir meus commits logicamente. Simplesmente não há necessidade de uma etapa intermediária. Como tal, nunca o uso.
mrplainswalker 01 de
3
Eu não acho que você realmente respondeu à pergunta. "mas o mais óbvio é quebrar suas mudanças de trabalho em pedaços menores e independentes." Por que alguém iria querer dividir suas mudanças em outras menores? Se você vai adicionar e cometer uma única correção de bug antes de consertar o código que você originalmente pretendia alterar, por que não apenas enviaria essa correção de bug em vez de adicioná-la e, em seguida, confirmá-la?
kiwicomb123 01 de
1
@ kiwicomb123 Normalmente porque você encontrou esse bug enquanto trabalhava em outra coisa, e deseja ter essa correção em seu próprio commit com sua própria mensagem de log e a flexibilidade de mesclar / selecionar / rebase essa correção em outro lugar.
Ben Jackson
26
  • A área de teste dá o controle para diminuir o commit. Basta fazer uma alteração lógica no código, adicionar os arquivos alterados à área de teste e, finalmente, se as alterações forem ruins, faça check-out para o commit anterior ou de outra forma confirme as alterações. Dá a flexibilidade de dividir a tarefa em tarefas menores e comprometer menores alterar. Com a área de teste, é mais fácil se concentrar em pequenas tarefas.
  • Ele também oferece a você a oferta de fazer uma pausa e esquecer quanto trabalho você fez antes da pausa. Suponha que você precise alterar três arquivos para fazer uma alteração lógica e alterou o primeiro arquivo e precisa de uma longa pausa até começar a fazer as outras alterações. Neste momento, você não pode enviar e deseja rastrear quais arquivos já terminou, para que, depois de voltar, não precise se lembrar de quanto trabalho foi feito. Portanto, adicione o arquivo à área de teste e ele salvará seu trabalho. Quando você voltar, basta fazer git diff --stagede verificar quais arquivos você alterou e onde e começar a fazer outras alterações.
Tapashee Tabassum Urmi
fonte
13

Um propósito prático da preparação é a separação lógica de confirmações de arquivo.

Como o teste permite que você continue fazendo edições nos arquivos / diretório de trabalho e faça commits em partes quando achar que tudo está pronto, você pode usar estágios separados para edições logicamente não relacionadas.

Suponha que você tem 4 arquivos fileA.html, fileB.html, fileC.htmle fileD.html. Você faz alterações em todos os 4 arquivos e está pronto para confirmar, mas as alterações em fileA.htmle fileB.htmlestão logicamente relacionadas (por exemplo, a mesma implementação de novo recurso em ambos os arquivos), enquanto as alterações em fileC.htmle fileD.htmlsão separadas e logicamente não relacionadas aos arquivos anteriores. Pode primeiros arquivos de estágio fileA.htmle fileB.htmle comprometer aqueles.

git add fileA.html
git add fileB.html
git commit -m "Implemented new feature XYZ"

Então, na próxima etapa, você prepara e confirma as alterações nos dois arquivos restantes.

git add fileC.html
git add fileD.html
git commit -m "Implemented another feature EFG"
DarthWader
fonte
6
Neste exemplo, não tenho certeza se o teste é realmente necessário. Depois de editar todos os 4 arquivos, se eu quiser apenas enviar fileA.html e fileB.html, ainda posso confirmar sem prepará-lo. O comando: git commit -m "Implemented new feature XYZ" fileA.html fileB.html funcionaria bem sem a necessidade dos comandos git add. Eu venho de um mundo de subversão, onde o staging não é um conceito, então, não estou convencido sobre a utilidade do git staging
Pavan
5

É mais fácil entender o uso dos comandos git adde commitse você imaginar um arquivo de log sendo mantido em seu repositório no Github. O arquivo de registro de um projeto típico para mim pode ser assim:

---------------- Day 1 --------------------
Message: Complete Task A
Index of files changed: File1, File2

Message: Complete Task B
Index of files changed: File2, File3
-------------------------------------------

---------------- Day 2 --------------------
Message: Correct typos
Index of files changed: File3, File1
-------------------------------------------
...
...
...and so on

Normalmente começo meu dia com um git pullpedido e termino com um git pushpedido. Portanto, tudo dentro do registro de um dia corresponde ao que ocorre entre eles. Durante cada dia, há uma ou mais tarefas lógicas que eu concluo que exigem a alteração de alguns arquivos. Os arquivos editados durante essa tarefa são listados em um índice.

Cada uma dessas subtarefas (Tarefa A e Tarefa B aqui) são confirmações individuais. O git addcomando adiciona arquivos à lista 'Índice de arquivos alterados'. Esse processo também é chamado de teste. O git commitcomando registra / finaliza as alterações e a lista de índice correspondente junto com uma mensagem personalizada.

Lembre-se de que você ainda está alterando apenas a cópia local do seu repositório e não a do Github. Depois disso, somente quando você fizer um 'git push', faça todas essas mudanças registradas, junto com seus arquivos de índice para cada commit, logado no repositório principal (no Github).

Por exemplo, para obter a segunda entrada nesse arquivo de log imaginário, eu teria feito:

git pull
# Make changes to these files
git add File3 File4
# Verify changes, run tests etc..
git commit -m 'Correct typos'
git push

Resumindo, git adde git commitpermite que você divida uma alteração no repositório principal em sub-alterações lógicas sistemáticas. Como outras respostas e comentários apontaram, é claro que há muitos outros usos para eles. No entanto, este é um dos usos mais comuns e um princípio orientador por trás do Git ser um sistema de controle de revisão de vários estágios, ao contrário de outros populares como o Svn.

Cibin Joseph
fonte
2

A área de teste nos ajuda a criar os commits com maior flexibilidade. Por crafting, quero dizer dividir os commits em unidades lógicas. Isso é muito importante se você deseja um software sustentável. A maneira mais óbvia de conseguir isso:

Você pode trabalhar em vários recursos / bugs em um único diretório de trabalho e ainda criar commits significativos. Ter um único diretório de trabalho que contém todo o nosso trabalho ativo também é muito conveniente. (Isso pode ser feito sem uma área de teste, apenas contanto que as alterações nunca se sobreponham a um arquivo. E você também tem a responsabilidade adicional de rastrear manualmente se elas se sobrepõem)

Você pode encontrar mais exemplos aqui: Usos do índice

E o melhor é que as vantagens não param nesta lista de workflows. Se um fluxo de trabalho exclusivo surgir, você pode ter quase certeza de que a área de preparação o ajudará.

Andrew Nessin
fonte
2

Para expandir a resposta de Ben Jackson , o que é bom, vamos examinar a pergunta original de perto. (Veja sua resposta para perguntas do tipo por que incomodar ; isso é mais sobre o que está acontecendo .)

Sou novo no controle de versão e entendo que "confirmar" é essencialmente criar um backup enquanto atualiza a nova versão 'atual' do que você está trabalhando.

Isso não está certo. Backups e controle de versão estão certamente relacionados - exatamente quão fortemente depende de algumas coisas que são, até certo ponto, questões de opinião - mas certamente existem algumas diferenças, mesmo que apenas na intenção: Backups são normalmente projetados para recuperação de desastres (falha de máquina, destruição de fogo edifício inteiro, incluindo todos os meios de armazenamento, etc.). O controle de versão é normalmente projetado para interações mais refinadas e oferece recursos que os backups não oferecem. Normalmente, os backups são armazenados por algum tempo e, em seguida, descartados como "muito antigos": um backup mais recente é tudo o que importa. O controle de versão normalmente salva cada versão confirmada para sempre.

O que eu não entendo é para que serve a encenação de uma perspectiva prática. Encenar é algo que existe apenas no nome ou tem um propósito? Quando você se compromete, vai comprometer tudo de qualquer maneira, certo?

Sim e não. O design do Git aqui é um tanto peculiar. Existem sistemas de controle de versão que não requerem uma etapa de teste separada. Por exemplo, o Mercurial, que é muito parecido com o Git em termos de uso, não requer uma hg addetapa separada , além da primeira que apresenta um arquivo totalmente novo. Com o Mercurial, você usa o hgcomando que seleciona alguns commit, então você faz seu trabalho, então você executa hg commite pronto. Com o Git, você usa git checkout, 1 então você faz seu trabalho, então você executa git add, e então git commit. Por que a git addetapa extra ?

O segredo aqui é o que o Git chama, de forma variada, de índice ou área de teste ou, às vezes - raramente hoje em dia - de cache . Todos esses são nomes para a mesma coisa.

Edit: Eu acho que posso estar confundindo a terminologia. Um arquivo 'testado' é a mesma coisa que um arquivo 'rastreado'?

Não, mas eles estão relacionados. Um arquivo rastreado é aquele que existe no índice do Git. Para entender o índice corretamente, é bom começar entendendo os commits.


Desde Git versão 2.23, você pode usar em git switchvez de git checkout. Para este caso específico, esses dois comandos fazem exatamente a mesma coisa. O novo comando existe porque git checkoutficou sobrecarregado com muitas coisas; eles foram divididos em dois comandos separados git switche git restore, para tornar mais fácil e seguro o uso do Git.


Compromissos

No Git, um commit salva um instantâneo completo de todos os arquivos que o Git conhece. (Quais arquivos o Git conhece? Veremos isso na próxima seção.) Esses instantâneos são armazenados em um formato especial, somente leitura, somente Git, compactado e desduplicado, que em geral apenas o próprio Git pode ler . (Há mais coisas em cada commit do que apenas este instantâneo, mas isso é tudo que cobriremos aqui.)

A eliminação da duplicação ajuda com espaço: normalmente mudamos apenas alguns arquivos e, em seguida, fazemos um novo commit. Portanto, a maioria dos arquivos em um commit são basicamente os mesmos que os arquivos no commit anterior. Simplesmente reutilizando esses arquivos diretamente, o Git economiza muito espaço: se tocarmos em apenas um arquivo, o novo commit ocupará apenas espaço para uma nova cópia. Mesmo assim, ele é compactado - às vezes muito compactado, embora isso realmente aconteça mais tarde - de forma que um .gitdiretório pode realmente ser menor do que os arquivos que contém, uma vez que são expandidos para arquivos normais do dia a dia. A eliminação da duplicação é segura porque os arquivos confirmados ficam congelados para sempre. Ninguém pode mudar um, então é seguro que os commits dependam das cópias uns dos outros.

Como os arquivos armazenados estão neste formato especial, congelado para sempre, somente Git, o Git precisa expandir cada arquivo em uma cópia comum do dia a dia. Esta cópia comum não é a cópia do Git : é a sua cópia, para fazer o que quiser. O Git apenas escreverá para eles quando você disser para fazer isso, para que você tenha suas cópias para trabalhar. Essas cópias utilizáveis ​​estão em sua árvore de trabalho ou árvore de trabalho .

O que isso significa é que quando você verifica algum commit em particular, há automaticamente duas cópias de cada arquivo:

  • Git tem uma cópia congelada para sempre, Git-ified no commit atual . Você não pode mudar esta cópia (embora você possa, é claro, selecionar um commit diferente, ou fazer um novo commit).

  • Você tem, em sua árvore de trabalho, uma cópia em formato normal. Você pode fazer o que quiser, usando qualquer um dos comandos do seu computador.

Outros sistemas de controle de versão (incluindo Mercurial como mencionado acima) param aqui, com essas duas cópias. Você apenas modifica sua cópia da árvore de trabalho e depois efetua o commit. Git ... não.

O índice

Entre essas duas cópias, o Git armazena uma terceira cópia 2 de cada arquivo. Esta terceira cópia está no formato congelado , mas ao contrário da cópia congelada no commit, você pode alterá-la. Para mudar, você usa git add.

O git addcomando significa fazer com que a cópia do índice do arquivo corresponda à cópia da árvore de trabalho . Isto é, você está dizendo ao Git: Substitua a cópia congelada e não duplicada que está no índice agora, compactando minha cópia da árvore de trabalho atualizada, deduplicando-a e preparando-a para ser congelada em um novo commit. Se você não usar git add, o índice ainda mantém a cópia em formato congelado do commit atual.

Quando você executa git commit, Git pacotes até tudo o que está no índice direito, em seguida, para o uso como o novo snapshot. Como já está no formato congelado e pré-desduplicado, o Git não precisa fazer muito trabalho extra.

Isso também explica o que são os arquivos não rastreados . Um arquivo não rastreado é um arquivo que está em sua árvore de trabalho, mas não está no índice do Git agora . Não importa como o arquivo terminou neste estado. Talvez você tenha copiado de algum outro lugar em seu computador, em sua árvore de trabalho. Talvez você o tenha criado fresco aqui. Talvez haja era uma cópia no índice do Git, mas você removeu essa cópia com git rm --cached. De uma forma ou de outra, existe uma cópia aqui na sua árvore de trabalho, mas não existe uma cópia no índice do Git. Se você fizer um novo commit agora, esse arquivo não estará no novo commit.

Note que git checkoutinicialmente preenche o índice do Git a partir do commit que você verificou. Portanto, o índice começa correspondendo ao commit. Git também preenche sua árvore de trabalho a partir desta mesma fonte. Então, inicialmente, todos os três combinam. Quando você altera arquivos em sua árvore de trabalho e git addeles, bem, agora o índice e sua árvore de trabalho combinam. Então você executa git commite o Git faz um novo commit do índice, e agora todos os três se casam novamente.

Como o Git faz novos commits do índice, podemos colocar as coisas desta forma: o índice do Git contém o próximo commit que você planeja fazer. Isso ignora a função expandida que o índice do Git assume durante uma mesclagem em conflito, mas gostaríamos de ignorar isso por enquanto. :-)

É só isso - mas ainda é muito complicado! É particularmente complicado porque não há uma maneira fácil de ver exatamente o que está no índice do Git. 3 Mas não é um comando Git que lhe diz o que está acontecendo, de uma maneira que é muito útil, e que comando é git status.


2 Tecnicamente, isso não é realmente uma cópia . Em vez disso, é uma referência ao arquivo Git-ified, pré-deduplicado e tudo. Há mais coisas aqui também, como o modo, nome do arquivo, um número de teste e alguns dados de cache para tornar o Git mais rápido. Mas, a menos que você comece a trabalhar com alguns dos comandos de baixo nível do Git - git ls-files --stagee git update-indexem particular - você pode apenas pensar nisso como uma cópia.

3 O git ls-files --stagecomando mostrará os nomes e números de teste de cada arquivo no índice do Git, mas geralmente isso não é muito útil de qualquer maneira.


git status

O git statuscomando realmente funciona executando dois git diffcomandos separados para você (e também fazendo algumas outras coisas úteis, como informar em qual branch você está).

O primeiro git diffcompara o commit atual - que, lembre-se, está congelado para sempre - com o que quer que esteja no índice do Git. Para arquivos iguais , o Git não dirá nada. Para arquivos que são diferentes , Git irá dizer-lhe que este arquivo é encenado para cometer . Isso inclui todos os novos arquivos-se a cometer não tem sub.pynele, mas o índice não têm sub.pynele, então é acrescentado e esse arquivo quaisquer arquivos removidos, que eram (e são) no cometer, mas não estão em o índice mais ( git rm, talvez).

O segundo git diffcompara todos os arquivos no índice do Git com os arquivos em sua árvore de trabalho. Para arquivos que são iguais , o Git não diz nada. Para arquivos que são diferentes , o Git dirá que esse arquivo não foi testado para confirmação . Ao contrário do primeiro diff, esta lista particular não inclui arquivos que são todos novos: se o arquivo untrackedexiste em sua árvore de trabalho, mas não no índice do Git, Git apenas o adiciona à lista de arquivos não rastreados . 4

No final, tendo acumulado esses arquivos não rastreados em uma lista, git statusirá anunciar os nomes desses arquivos também, mas há uma exceção especial: se o nome de um arquivo estiver listado em um .gitignorearquivo, isso suprime esta última listagem. Observe que listar um arquivo rastreado - aquele que está no índice do Git - em um .gitignorenão tem efeito aqui : o arquivo está no índice, então ele é comparado e confirmado, mesmo se estiver listado em .gitignore. O arquivo para ignorar apenas suprime as reclamações de "arquivo não rastreado". 5


4 Ao usar a versão curta de git status- git status -s—os arquivos não rastreados não são separados, mas o princípio é o mesmo. Acumular os arquivos dessa forma também permite git statusresumir vários nomes de arquivos não rastreados, apenas imprimindo um nome de diretório, às vezes. Para obter a lista completa, use git status -uallou git status -u.

5 Listar um arquivo também faz com que em massa adicione muitas operações de arquivo , como git add .ou git add *pule o arquivo não rastreado. Esta parte fica um pouco mais complicada, pois você pode usar git add --forcepara adicionar um arquivo que normalmente seria ignorado. Existem alguns outros casos especiais normalmente menores, todos os quais se somam a isso: o arquivo .gitignorepode ser chamado mais apropriadamente .git-do-not-complain-about-these-untracked-files-and-do-not-auto-add-themou algo igualmente difícil de manejar. Mas isso é muito ridículo, .gitignoreé.


git add -u, git commit -aetc

Existem vários atalhos úteis que você deve conhecer aqui:

  • git add .irá adicionar todos os arquivos atualizados no diretório atual e qualquer subdiretório. Isso respeita .gitignore, portanto, se um arquivo que não está sendo rastreado git statusnão receber uma reclamação de , ele não será adicionado automaticamente.

  • git add -uirá adicionar automaticamente todos os arquivos atualizados em qualquer lugar em sua árvore de trabalho . 6 Isso afeta apenas os arquivos rastreados . Observe que se você removeu a cópia da árvore de trabalho, isso removerá a cópia do índice também ( git addfaz isso como parte de fazer com que o índice corresponda à árvore de trabalho ).

  • git add -Aé como correr git add .do nível superior de sua árvore de trabalho (mas veja a nota de rodapé 6).

Além disso, você pode correr git commit -a, o que equivale a aproximadamente 7 a correr git add -ue então git commit. Ou seja, você obtém o mesmo comportamento conveniente no Mercurial.

Em geral, desaconselho o git commit -apadrão: acho que é melhor usar com git statusfrequência, observe atentamente a saída e, se o status não for o que você esperava, descubra por que é esse o caso. Usando git commit -a, é muito fácil modificar acidentalmente um arquivo e enviar uma alteração que você não pretendia. Mas isso é principalmente uma questão de gosto / opinião.


6 Se a sua versão do Git for anterior ao Git 2.0, tome cuidado aqui: git add -usó funciona no diretório e subdiretórios atuais, então você deve subir ao nível superior da sua árvore de trabalho primeiro. A git add -Aopção tem um problema semelhante.

7 Digo aproximadamente equivalente porque git commit -ana verdade funciona criando um índice extra e usando esse outro índice para fazer o commit. Se o commit funcionar , você terá o mesmo efeito que fazer git add -u && git commit. Se o commit não funcionar - se você fizer o Git pular o commit de qualquer uma das muitas maneiras que você pode fazer isso - então nenhum arquivo será git addexecutado depois, porque o Git joga fora o índice extra temporário e volta a usar o índice principal .

Existem complicações adicionais que surgem se você usar git commit --onlyaqui. Nesse caso, o Git cria um terceiro índice, e as coisas ficam muito complicadas, especialmente se você usar ganchos de pré-confirmação. Este é outro motivo para usar git addoperações separadas .

Torek
fonte
1

Eu vejo o ponto de usar o stage para fazer commits menores, conforme mencionado por @Ben Jackson e @Tapashee Tabassum Urmi e às vezes eu o uso para esse propósito, mas eu principalmente o uso para tornar meus commits maiores! aqui está o meu ponto:

Digamos que eu queira adicionar um pequeno recurso que requer várias etapas menores. Não vejo sentido em ter um commit separado para etapas menores e inundar minha linha do tempo. No entanto, quero salvar cada etapa e voltar, se necessário,

Simplesmente enceno os passos menores um em cima do outro e quando sinto que vale a pena me comprometer, eu me comprometo. Desta forma, eu removo os commits desnecessários da linha do tempo, mas capaz de desfazer (checkout) a última etapa.

Vejo outras maneiras de fazer isso (simplificando o histórico do git) que você pode usar de acordo com sua preferência:

  1. git alteração (que muda seu último commit) que não é algo que você deseja para este propósito específico (eu vejo isso principalmente como fazer um commit ruim e então corrigi-lo)
  2. git rebase, que é uma reflexão tardia e pode causar sérios problemas para você e outras pessoas que usam seu repositório.
  3. criar um branch temporário, mesclar e excluí-lo depois (o que também é uma boa opção, requer mais etapas, mas oferece mais controle)
Ali80
fonte
0

É como uma caixa de seleção que permite escolher quais arquivos enviar.

por exemplo, se eu editei fileA.txte fileB.txt.Mas quero confirmar as alterações de fileA.txtapenas. porque ainda não terminei fileB.txt.

Posso simplesmente usar git add fileA.txte comprometer usando git commit -m "changed fileA.txt"e continuar trabalhando com fileB.txte depois de terminar posso comprometer fileB.txtfacilmente

Ramoun
fonte