Skip to content

02 Hands On (Git workflow)

Fábio Gaspar edited this page Nov 14, 2018 · 3 revisions

Git workflow

O segundo hands on consiste em:

  1. Criar repositórios
  2. Adicionar ficheiros à staging area
  3. Experimentar o comando git status
  4. Fazer commit
  5. Usar o comando git diff
  6. Remover snapshots da staging area
  7. Reverter um commit

Criar um repositório

Para criar um repositório vazio usamos o comando $git init. Este comando deverá ser executado na pasta onde se querer inicializar o repositório. Essa pasta pode estar vazia, ou pode já ter ficheiros e sub-diretórios.

O que o comando faz é criar um sub-diretório oculto, .git, (para ser correto, em alguns sistemas operativos o .git é um ficheiro, também oculto!) e é aqui onde o git armazena os dados, ou seja, é o repositório em si.

O repositório começa sempre vazio! Mesmo que o diretório onde o comando é executado contenha ficheiros, o git não irá adicioná-los automaticamente ao repositório, tudo é considerado inicialmente como untracked.

$mkdir workshop # cria uma pasta vazia
$git init # inicializa o repositório

Colocar ficheiros na staging area

Estando o repositório criado, experimenta criar um ficheiro, como um simples ficheiro de texto, e experimenta adicioná-lo à staging area.

Para colocar ficheiros na staging area, usa-se o comando $git add (vê a nota abaixo antes de executar).

💡 Experimenta também usar o comando $git status que lista os ficheiros untracked, modificados e que fazem parte do snapshot para o próximo commit (estão atualmente pendentes na staging area). Para te familiarizares com este comando, experimenta correr o comando sempre que adicionas novos ficheiros na working directory, quando crias o snapshot na staging area com $git add e quando faz commit.

Existem diversas formas de usar o comando $git add que podem tornar a tarefa mais eficiente. Podes especificar os ficheiros que quer adicionar, podes usar patterns, ou usar parametros que automaticamente consideram ficheiros num estado especifico. Eis alguns exemplos:

$git add filename.txt # adiciona um ficheiro especifico
$git add *.c # adiciona ficheiros com extensão .c
$git add . # adiciona TUDO (new/modified/deleted) 
$git add -A # adiciona TUDO (new/modified/deleted) 
$git add -u # adiciona apenas ficheiros conhecidos, ou seja, aqueles que já surgiram em algum commit (tracked files -> modified/deleted)

💡 Se um ficheiro é colocado na staging area e posteriormente modificado, o novo conteudo não será considerado. Ou seja, após usar o add com um ficheiro exemplo.txt, o snapshot é criado com o estado do ficheiro naquele mesmo instante, e irá ser adicionado como tal ao repositório com o $git commit. Por isso, o ficheiro até pode ser apagado e o commit irá funcionar na mesma. Mais à frente, iremos analizar como o git armazena os dados e isto ficará mais claro.

Fazer commit

Agora que temos a primeira versão do ficheiro pronta e com o seu snapshot na staging area, podemos fazer commit. Fazer commit consiste em mover o snapshot criado na staging area para o repositório.

Os commits têm uma mensagem que o descreve, e é especificado no parametro -m.

$git commit -m "First commit"

💡 Se te esqueceres da mensagem do commit, o git automaticamente abre um editor de texto na consola (nano, vim, ou outros, é configurável) que permite descrever o commit. Isto também é útil quando os commits merecem mensagens detalhadas. Podemos usar uma abordagem de no primeiro pagrafo dar um titulo, curto e que sumarize bem o que foi alterado, e depois noutro paragrafo descrever em detalhes as modificações, o porquê, etc.

🤔 Mas, se o comando commit apenas move o snapshot da staging area para o repositório, qual a necessidade destes dois passos (stage .. commit)? Podemos ver o ato de staging como uma preparação daquilo que realmente queremos fazer. Sem a staging area, seria muito fácil fazer commit de coisas por engano, o que implicaria reverter commits e outras operações que podem ser perigosas no sentido de haver perda de dados. Assim, primeiro trabalhamos nesta área intermédia, staging area, e quando tivermos a certeza que queremos registar o novo snapshot no repositório, fazemos commit (havendo sempre a possibilidade de reverter, mas há certos cuidados a ter!). Além disso, na staging area temos a flexibilidade de apenas criar snapshots de ficheiros em particular, ou até ir ao detalhe de para cada ficheiro apenas fazer stage a alguns blocos desse ficheiro 🔥 Outro uso frequente, em contextos como programação, é o facto de termos a segurança de que uma vez criado o snapshot, ele não será apagado/modificado a menos que se volte a correr $git add. Isto dá a flexibilidade ao programador de fazer experiências no código. Se chegar a also que funciona, faz $git add. Se eventualmente as coisas ficarem partidas, pode não só fazer commit do que funcionava, como repôr o estado da working directory. Quantas vezes quiseste fazer undo no teu editor de texto e não conseguiste chegar à versão que querias?! Além disso, sejamos sinceros, fazer Ctrl+Z 153 vezes, com alterações em vários ficheiros às quais temos que estar atentos para não exceder os Undos, não é propriamente divertido.

Após esta motivação para a existência da staging area, ainda assim haverão casos em que estamos a fazer coisas muito simples, e não queremos perder tempo a executar dois comandos. Pois bem, o comando commit permite fazer os dois passos, adicionar à staging area e commit, num só comando. Contudo, este comando apenas considera ficheiros já conhecidos (novos ficheiros/untracked files são ignorados).

$git commit -a -m "commit description"

💡 Experimenta usar o comando $git log antes e após fazer commits. Este comando permite ver todos os commits (alcançaveis através do último commit. Quando aprenderes o conceito de branch, ficará claro em que situações um commit não pode alcançar outro commit).

Demo (exemplo)

Uma pequena ilustração do que é suposto fazer neste hands on, usando somente o terminal, num ambiente Linux!

  1. Inicializar um repositório
$mkdir workshop # creates an empty folder 'workshop'
$cd workshop # changes the current folder
$git init
Initialized empty Git repository in /mnt/c/Users/fabio/Documents/workshop/.git/
  1. Criar um ficheiro de examplo, explorar o $git status e criar um snapshot.
$touch f1.txt # creates empty file

$ls
f1.txt

$git status
On branch master

Initial commit

Untracked files:
  (use "git add <file>..." to include in what will be committed)

        f1.txt

nothing added to commit but untracked files present (use "git add" to track)

$git add f1.txt
  1. Fazer commit, continuar a explorar o $git status e usar $git log.
$git status
On branch master

Initial commit

Changes to be committed:
  (use "git rm --cached <file>..." to unstage)

        new file:   f1.txt

$git commit -m "Added first empty file"
[master (root-commit) cfbb95e] Added first empty file
 1 file changed, 0 insertions(+), 0 deletions(-)
 create mode 100644 f1.txt

$git log
commit cfbb95e5cef2aca23774cd59e2a20d48d6fca69a
Author: Fabio Gaspar <[email protected]>
Date:   Sun Mar 18 12:01:10 2018 +0000

    Added first empty file
  1. Modificar o ficheiro, voltar a fazer commit. Uma vez mais, continuando a experimentar os comandos status e log.
# Let's modify the file and had the most original phrase you ever seen
$cat > f1.txt
This is the first paragraph!

$git status
On branch master
Changes not staged for commit:
  (use "git add <file>..." to update what will be committed)
  (use "git checkout -- <file>..." to discard changes in working directory)

        modified:   f1.txt

no changes added to commit (use "git add" and/or "git commit -a")

# Ok, I KNOW WHAT I AM DOING! I will skip manually staging by using `commit -a`
$git commit -a -m "Wrote first line"
[master bdaded3] Wrote first line
 1 file changed, 1 insertion(+)

$ git log
commit bdaded3d1aa1fc919d237073db5fd8c2078aae48
Author: Fabio Gaspar <[email protected]>
Date:   Sun Mar 18 12:02:13 2018 +0000

    Wrote first line

commit cfbb95e5cef2aca23774cd59e2a20d48d6fca69a
Author: Fabio Gaspar <[email protected]>
Date:   Sun Mar 18 12:01:10 2018 +0000

    Added first empty file

git diff

O comando git diff permite comparar secções do git (working directory, staging area), commits, branches 🔜 etc.

# compara working directory com a staging area. Ilustra o que iria ser adicionado à staging area com o comando add
$git diff 

# compara o conteúdo da staging area com um commit (se omitido, considera o ultimo commit)
$git diff --cached <commit>

# compara dois commits
$git diff <commit> <commit>

# compara o conteúdo na working directory com um commit
$git diff <commit>

Temos dois commits, com base na demo anterior, um com SHA1 bdad... e outro com SHA1 cfbb9...

Normalmente usar-se-ia o SHA1 completo, mas basta usar o suficiente para que o git consiga distinguir objetos (objetos? Wait for it... 🔜).

$git diff bdad cfbb9
diff --git a/f1.txt b/f1.txt
index 77d7734..e69de29 100644
--- a/f1.txt
+++ b/f1.txt
@@ -1 +0,0 @@
-This is the first paragraph!

$git diff cfbb9 bdad
diff --git a/f1.txt b/f1.txt
index e69de29..77d7734 100644
--- a/f1.txt
+++ b/f1.txt
@@ -0,0 +1 @@
+This is the first paragraph!

Repara como inverter a ordem dos commits nos argumentos produz o resultado inverso. No primerio comando, $git diff bdad cfbb9, estamos a comparar o segundo commit, onde adicionei a linha This is the first paragraph!, com o primeiro commit, onde simplesmente adicionei o ficheiro f1.txt, vazio. Portanto, a forma de interpretar o output é, se eu me posiconar no commit bdad, e caminhar para o commit cfbb9, o que é que muda? Efetivamente, incialmente tenho uma linha no ficheiro f1.txt, mas ao avançar para o commit cfbb9 ele estava vazio, então a linha é apagada. Se inverter o sentido, e seguir a ordem temporal, ou seja, indo do primeiro commit para o segundo, então eu comecei com um ficheiro vazio, e depois adicionei conteúdo. Então no output vemos o +, indicando o que foi adicionado.

Remover ficheiros da staging area

Por vezes adicionamos ficheiros à staging area acidentalmente, ou reparamos num erro do conteúdo, e como tal, não queremos fazer commit do snapshot criado.

Existem dois cenários possíveis:

  1. Apagar o snapshot de um ou mais ficheiros da staging area, mas não discartar as modificações na working directory.
  2. Apagar o snapshot, e repôr o(s) ficheiros (discarta as alterações na working directory)

Para o primeiro caso, usa-se o comando $git reset <file>. Podemos especificar um ou mais ficheiros, mas se não indicarmos nada, o git faz unstage de todos os ficheiros.

Para o segundo caso, usa-se $git checkout -- <file>. Aqui o parametro <file> é orbigatório.

💡 -- ??? ಠ_ಠ O -- é tipicamente uma expressão da shell, para indicar o fim de comandos de opção, normalmente começados por -. No exemplo do checkout, imaginemos que temos um branch chamado master e um ficheiro master. O checkout vai interpretar master como uma branch, ou como um ficheiro? O -- poderá ser usado para remover esta ambiguidade. Quando se tratam de branches, nunca se usa o --, mas se for um ficheiro, deve ser usado. No geral, é uma regra válida para diversos comandos, mas nada melhor que consultar o manual para ter a certeza, já que há comandos que ou apenas lidam com ficheiros ou apenas branches, não havendo margem para ambiguidades.

Reverter um commit

Para reverter um commit, usa-se o comando $git revert <commit>. O commit pode ser identificado pelo seu SHA1 (ou parte dele), podendo este ser obtido através do $git log. Existe ainda uma notação simbólica HEAD mas que ainda não foi abordada. Por agora, apenas interessa que o HEAD referencia sempre o último commit, apesar de não ser uma verdade absoluta.

Ao contrário do que se possa pensar, o git não vai apagar o commit. Vai criar um novo commit que reverte o commit especificado. E por sua vez podemos reverter o commit que reverteu o commit que............. (╯°□°)╯︵ ┻━┻

💡 Antes de usar revert, é necessário garantir que não existem alterações na working directory. O $git stash é um comando útil aqui.