6 pontos por GN⁺ 2024-05-27 | 1 comentários | Compartilhar no WhatsApp
  • Um PDF que organiza tarefas em que as pessoas costumam travar no Git para consulta rápida, como criação de commits, troca de branches, comparação de alterações, sincronização remota e comandos para desfazer alterações
  • O fluxo de trabalho local gira em torno de git status, git add, git commit e git diff, verificando o estado antes e depois do staging e criando commits
  • O trabalho com branches usa git switch, git checkout, git branch, git merge, git rebase e git cherry-pick para lidar com criação, troca, merge e rebase
  • Para explorar o histórico e recuperar alterações, inclui git log, git blame, git show, git reflog, git reset e git restore; branches, tags, HEAD e branches remotas também podem ser usados como referências de commits
  • Operações com repositórios remotos cobrem o processo de buscar e enviar alterações com git remote add, git fetch, git pull, git push, git push --force-with-lease e git push --tags

Iniciar um repositório e verificar o estado atual

  • Iniciar um novo repositório:
    • git init
  • Clonar um repositório existente:
    • git clone $URL
  • Verificar a localização e o estado atuais:
    • git status

Preparar e criar commits

  • Adicionar um arquivo não rastreado:
    • git add $FILE
  • Adicionar todos os arquivos não rastreados e alterações não staged:
    • git add .
  • Escolher apenas algumas alterações de um arquivo para staging:
    • git add -p
  • Excluir ou mover um arquivo:
    • git rm $FILE
    • git mv $OLD $NEW
  • Remover um arquivo do rastreamento do Git sem excluí-lo:
    • git rm --cached $FILE
  • Desfazer todo o staging:
    • git reset HEAD
  • Criar um commit:
    • git commit
    • Abre um editor de texto para escrever a mensagem
  • Criar um commit especificando a mensagem diretamente:
    • git commit -m 'message'
  • Criar um commit com mensagem incluindo também alterações não staged:
    • git commit -am 'message'

Trocar e gerenciar branches

  • Trocar de branch:
    • git checkout $NAME
    • git switch $NAME
  • Criar uma branch:
    • git checkout -b $NAME
    • git switch -c $NAME
  • Ver a lista de branches:
    • git branch
  • Ordenar branches pelo commit mais recente:
    • git branch --sort=-committerdate
  • Excluir uma branch:
    • git branch -d $NAME
  • Excluir uma branch à força:
    • git branch -D $NAME

Explorar histórico e rastrear código

  • Ver o histórico de uma branch:
    • git log main
  • Ver em grafo a relação entre duas branches:
    • git log --graph a b
  • Ver o log em uma linha:
    • git log --oneline
  • Ver quem alterou por último cada linha de um arquivo:
    • git blame $FILENAME
  • Ver todos os commits que modificaram um arquivo:
    • git log $FILENAME
  • Encontrar commits que adicionaram ou removeram um texto específico:
    • git log -S banana

Comparar alterações

  • Ver a diferença entre um commit e seu commit pai:
    • git show $COMMIT_ID
  • Ver a diferença entre um commit de merge e os pais mesclados:
    • git show --remerge-diff $COMMIT_ID
  • Ver um resumo do diff:
    • git diff $COMMIT_ID --stat
    • git show $COMMIT_ID --stat
  • Comparar dois commits:
    • git diff $COMMIT_ID $COMMIT_ID
  • Comparar apenas um arquivo específico com um commit:
    • git diff $COMMIT_ID $FILENAME
  • Comparar todas as alterações staged e não staged:
    • git diff HEAD
  • Comparar apenas alterações staged:
    • git diff --staged
  • Comparar apenas alterações não staged:
    • git diff

Arquivos de configuração e opções

  • Arquivo de configuração local do Git:
    • .git/config
  • Arquivo de configuração global do Git:
    • ~/.gitconfig
  • Lista de arquivos a ignorar:
    • .gitignore
  • Definir uma opção de configuração:
    • git config user.name 'Julia'
  • Definir uma opção global:
    • git config --global ...
  • Adicionar alias:
    • git config alias.st status
  • Ver opções de configuração disponíveis:
    • man git-config

Editar histórico e restaurar arquivos

  • Desfazer o commit mais recente, mantendo o diretório de trabalho como está:
    • git reset HEAD^
  • Desfazer um rebase que falhou:
    • git reflog BRANCHNAME
    • git reset --hard $COMMIT_ID
  • Unificar os últimos 5 commits em um só:
    • git rebase -i HEAD^^^^^^
    • Mude pick para fixup nos commits a serem unificados
  • Trazer a versão de um arquivo de outra branch ou commit:
    • git checkout $COMMIT_ID $FILE
    • git restore $FILE --source $COMMIT_ID
  • Alterar a mensagem do commit ou adicionar um arquivo esquecido:
    • git commit --amend

Juntar branches

  • Fazer um fast-forward merge para atualizar a branch atual com outra branch:
    • git switch main
    • git merge banana
  • Copiar um commit para outra branch:
    • git cherry-pick $COMMIT_ID
  • Juntar branches divergentes com rebase:
    • git switch banana
    • git rebase main
  • Juntar branches divergentes com merge:
    • git switch main
    • git merge banana
    • git commit
  • Juntar branches divergentes com squash merge:
    • git switch main
    • git merge --squash banana
    • git commit

Repositórios remotos e sincronização

  • Adicionar um repositório remoto:
    • git remote add $NAME $URL
  • Fazer push da branch main para o remoto origin:
    • git push origin main
  • Enviar uma branch pela primeira vez para origin e configurar a branch de rastreamento:
    • git push -u origin $NAME
  • Fazer push da branch atual para a branch remota rastreada:
    • git push
  • Force push:
    • git push --force-with-lease
  • Fazer push de tags:
    • git push --tags
  • Buscar alterações sem modificar a branch local:
    • git fetch origin main
  • Fazer fetch de todas as branches:
    • git fetch --all
  • Após o fetch, fazer rebase na branch atual:
    • git pull --rebase
  • Após o fetch, fazer merge na branch atual:
    • git pull origin main
    • git pull

Formas de apontar para commits

  • No lugar de $COMMIT_ID, é possível usar várias referências:
    • Branch: main
    • Tag: v0.1
    • ID do commit: 3e887ab
    • Branch remota: origin/main
    • Commit atual: HEAD
    • Commit de 3 versões atrás: HEAD^^^
    • Commit de 3 versões atrás: HEAD~3

Descartar alterações

  • Excluir tanto as alterações staged quanto as não staged de um arquivo:
    • git checkout HEAD $FILE
  • Excluir alterações não staged de um arquivo:
    • git checkout $FILE
  • Excluir todas as alterações staged e não staged:
    • git reset --hard
  • Colocar alterações staged e não staged no stash:
    • git stash
  • Excluir arquivos não rastreados:
    • git clean

1 comentários

 
GN⁺ 2024-05-27
Opiniões no Hacker News
  • Há alguns comandos que são verdadeiras joias escondidas: git diff --staged mostra a diferença entre o último commit e o que está atualmente em staging, então é bom para verificar se só entrou o que você pretendia ao adicionar arquivos ou hunks com precisão
    git log <file> mostra apenas os commits em que aquele arquivo foi alterado, reduzindo a necessidade de vasculhar todo o git log, e é especialmente útil em arquivos que não mudam com frequência
    Ao usar git add --patch, se você quiser escolher com mais granularidade do que os hunks que o Git separou, vale experimentar a opção e. Ela explica como incluir/excluir linhas individuais, então não é preciso decorar

    • Só agora descobri que git diff --staged é um alias de git diff --cached. Uso cached há anos, mas staged é bem mais intuitivo. Só não sei se vou conseguir apagar essa memória muscular
    • Criar muitos aliases para comandos desse tipo economiza bastante tempo e também reduz a barreira para organizar melhor o trabalho
      Se tiver interesse, pode dar uma olhada ou sugerir aliases: https://gitlab.com/Falimonda/gitrc
    • Ao configurar git config --global commit.verbose true, o Git insere automaticamente o diff em staging na área de comentários do editor da mensagem de commit
  • Um dos meus comandos favoritos que não está na lista é git log -LstartLine,endLine:fileName
    Como um git blame estendido, ele mostra como as linhas especificadas mudaram ao longo do tempo e quem as alterou. Ele acompanha mesmo quando os números de linha mudam ou o arquivo é movido
    Responde a perguntas como “quem fez isso, quando e por quê?” ou “desde quando está assim?”

  • Isso me lembra a frase de Phil Karlton: “Há apenas duas coisas difíceis em ciência da computação: invalidação de cache e dar nomes às coisas.”
    Desse ponto de vista, o nome do comando Git ser blame sempre me pareceu bem estranho, e eu não fui o único a pensar assim
    What does 'git blame' do? https://stackoverflow.com/questions/31203001/what-does-git-blame-do
    Blame someone else for your bad code https://news.ycombinator.com/item?id=27963868
    Git blame should be called git credit https://dev.to/damcosset/git-blame-should-be-called-git-credit-27h5
    Does Git Blame sound too negative? https://www.reddit.com/r/ProgrammerHumor/comments/r5lzyo/does_git_blame_sound_too_negative_just_look_up/

    • Pensando no nome da ferramenta git, blame parece um nome alinhado à marca
    • Concordo. Na minha configuração, troquei blame por who. Na verdade, como sou usuário hardcore de Vim, uso simplesmente w
  • Gostei da forma como aprendi Git. No começo, comecei pelo Sourcetree, uma GUI Git de terceiros, que era muito fácil de usar e entender
    Depois passei para o git gui, mas essa etapa pode ser pulada. Mais tarde, fiquei com preguiça de abrir a UI e aprendi os comandos no terminal
    Recomendo para iniciantes em Git, mas é apenas minha experiência n=1

    • O que sei sobre Git é basicamente clone, pull, commit e push, e até agora isso foi suficiente. Git também pode ser simples assim. Claro, se precisar, dá para aprender usos mais complexos
    • Não quero voltar para comandos de terminal. Repositórios Git e fluxos de trabalho são muito mais fáceis de entender quando visualizados, então o Git é praticamente uma ferramenta que pede para ser usada via GUI
      No momento em que comecei a usar Magit, comecei a entender Git
    • Eu mudei do Sourcetree para o Fork. É muito parecido, mas tem desempenho muito melhor e uma UI mais fluida. A menos que o Sourcetree tenha melhorado nos últimos anos
    • Segui um caminho parecido, mas usei GitKraken e Sublime Merge, e mantive o Sublime Merge porque ele facilita até operações complexas
      Só tenho elogios ao Sublime Merge
  • Fico me perguntando se há razões históricas para o Git ser desnecessariamente complexo
    Ao ver esta cheat sheet, essa complexidade fica ainda mais evidente

    • Sim. É porque o Git foi uma ferramenta criada por Linus Torvalds para viabilizar e evoluir o desenvolvimento do kernel Linux
      O fato de ele ter passado a ser usado tão amplamente talvez tenha sido um “acidente feliz”, ou talvez uma infelicidade. O GitHub teve um grande mérito em popularizar o Git com “hospedagem Git gratuita”, mas não acho que o Git seja o sistema de controle de versão ideal para muitos projetos
      Como ele foi feito para Linus e para o Linux, a terminologia e os fluxos de trabalho às vezes parecem o inverso dos sistemas de controle de versão comuns. O nome Pull Request também vem do desenvolvimento do Linux, porque Linus ou um mantenedor puxa as alterações para a árvore do kernel. Em outros sistemas, costuma-se chamar de Merge Request, o que é muito mais lógico, já que as alterações são mescladas à árvore independentemente de onde vieram
      O Git era praticamente o único sistema de controle de versão distribuído open source capaz de lidar rapidamente com bases de código enormes, e por isso a inércia também cresceu nas grandes empresas
      Para projetos pessoais, prefiro muito mais Mercurial e de fato o uso, mas, ao usar Hg em uma base de código grande como o Android AOSP, ele engasga bastante. O Mercurial tem uma interface de usuário e um sistema de comandos melhores, e também tem um recurso que sobe um servidor web na hora para visualizar a árvore de alterações, mas, por ser escrito em Python, seu desempenho e escalabilidade não são ideais
    • Parte da complexidade vem do fato de que, nas versões iniciais, o custo de adicionar um novo subcomando era relativamente alto. Como resultado, funcionalidades continuaram sendo anexadas a subcomandos existentes, como git checkout, tornando a experiência do usuário mais confusa
      Nas versões mais recentes do Git, o custo de adicionar novos subcomandos diminuiu, e eles começaram a incluir comandos mais específicos, como git switch
    • Para a maioria dos fluxos de trabalho, acho que basta conhecer alguns comandos do Git, como log, add, commit, pull e push. Também existem ferramentas com GUI
      Sinceramente, não acho que o Git seja tão complexo para tarefas comuns. Ele só tem muitos recursos poderosos disponíveis quando você precisa deles
    • O Git não é desnecessariamente complexo. O fluxo de trabalho que o Git foi criado para suportar é que era excepcionalmente complexo, e os usuários-alvo, os desenvolvedores do kernel, eram muito técnicos, então a complexidade não era um grande problema
      Os desenvolvedores do kernel precisavam se comunicar de forma descentralizada e trabalhar em cima do trabalho uns dos outros. Como se trabalha em um projeto em que 15.000 pessoas escrevem código? O Git foi criado para esse propósito
      Ele é tão complexo quanto precisa ser para suportar o fluxo de trabalho para o qual foi projetado. É fácil criar uma ferramenta mais simples, mas aí ela não suportaria fluxos complexos como o desenvolvimento do kernel Linux. Portanto, ele não é desnecessariamente complexo, mas é verdade que é mais complexo do que o necessário para usos simples
      Se você precisa de um front-end menos complexo, há muitas ferramentas que tornam o Git mais fácil de usar. Se você não é desenvolvedor de software em tempo integral, eu não recomendaria usar Git diretamente. Mas, se você é desenvolvedor em tempo integral, vale a pena investir tempo para entender os conceitos internos; depois que você entende, o Git é bastante elegante pelo que faz
      Eu não diria que saber Git define se alguém é qualificado como desenvolvedor de software, mas, se entender por conta própria como sistemas fundamentais como esse funcionam não lhe dá dopamina, talvez esta área não seja do seu gosto. E tudo bem: há coisas que combinam ou não com cada pessoa
      Para aprender a se movimentar no Git, recomendo https://learngitbranching.js.org. Claro, https://xkcd.com/1597/ também se aplica
    • Porque um bom design de UI é uma habilidade separada e exige esforço adicional, então geralmente é simplesmente ignorado
  • Eu não conhecia git add -p, mas é realmente útil
    -p, --patch permite escolher interativamente hunks de patch entre o índice e a árvore de trabalho para adicioná-los ao índice. Isso dá a chance de revisar as diferenças antes de adicionar as alterações ao índice

    • Sempre que possível, escolho manualmente os hunks do trabalho atual e os monto em commits coesos. Isso ajuda a reorganizar mentalmente o que fiz e, quando preciso explicar a alguém, consigo seguir o fluxo dos commits para explicar
  • Algo que eu acrescentaria aqui é a combinação de git commit --fixup $COMMIT_ID com git rebase -i upstream/master --autosquash
    Virou parte essencial do meu fluxo de trabalho recente. O exemplo de Julia usa HEAD^^^^^ para fazer rebase dos 5 commits anteriores, mas até pouco tempo eu usava HEAD~5. Só que agora descobri que posso simplesmente fazer rebase de todos os commits até o HEAD do upstream

    • Preciso adotar git commit --fixup $COMMIT_ID imediatamente. Eu sempre criava pequenos commits de correção como “oops GET users/me” e, no próximo rebase interativo, movia manualmente para fazer fixup, mas esse jeito é muito melhor
    • Aprendi isso só recentemente, e é ótimo ao trabalhar com patches empilhados no Gerrit
  • “Juntar os últimos 5 commits em um só: git rebase -i HEAD^^^^^^” acho que deveria ser git rebase -i HEAD~5
    Partindo do pressuposto de que você quer fazer rebase apenas de um histórico linear
    https://stackoverflow.com/questions/2221658/whats-the-difference-between-head-and-head-in-git

  • A maior ficha caiu no momento em que entendi que todos os commits ainda permanecem internamente. Tags e branches apenas apontam para commits, e o commit para o qual um branch apontava antes também não desapareceu
    Muitos comandos — talvez quase todos — são não destrutivos, então é fácil referenciar o estado anterior de um branch
    Eu já tinha visto git reflog, mas usava principalmente para lembrar “qual era mesmo aquele branch em que eu tinha trabalhado antes?”
    Acabei desenvolvendo um medo meio saudável de operações que podiam me jogar no meio de uma resolução de conflitos indesejada. Normalmente os conflitos aconteciam porque eu referenciava o commit ou branch errado
    Esse medo não sumiu completamente, mas pelo menos fiquei bem mais confortável em recuperar trabalhos antigos no meio de operações como rebase

    • Jujutsu eliminou completamente esse medo
      Se não sei se alguma operação vai gerar conflito, simplesmente tento e vejo. Se der conflito, resolvo, reverto a operação que criou o conflito, ou crio um novo commit em outro lugar e deixo o conflito para lidar depois
      Se já sei que vai dar conflito, duplico o branch relacionado e tento ajustá-lo antes, ou executo a operação conflitante e passo pelo processo de resolução. Se a resolução ficar bagunçada, posso reverter só parte dela e continuar tentando, deixar para depois e fazer outra coisa, ou descartar a árvore inteira
  • Falando sem rodeios, é preciso deixar o Git de lado e olhar para o JJ
    https://martinvonz.github.io/jj/v0.17.1/
    Acho que até o Linus considera a UI do Git péssima. O fluxo de trabalho do JJ é excelente

    • É difícil eu concordar com a ausência de índice. Talvez isso torne alguns fluxos de trabalho mais simples, mas, para o bem ou para o mal, estou tão acostumado ao índice do Git que meu jeito de trabalhar provavelmente mudaria bastante
      Dito isso, além de ter mexido um pouco, ainda não usei a fundo em trabalho real, então é difícil cravar