Como mostrar apenas os commits da branch atual
Tags: git, shell, cli
Published: 2021-10-04 18:35:45

🏴󠁧󠁢󠁥󠁮󠁧󠁿 English version of this text [_here_](https://dev.to/lucassperez/shell-script-to-show-a-range-of-git-commits-44ne)!

Volta e meia eu crio aliases que executam comandos com algumas opções que eu uso com frequência. Um deles é o meu alias para `git log --oneline`.

O problema é que quando eu executo esse comando, vários commits aparecem na minha tela até que ela fique completamente cheia, enquanto que a última linha indica que existem ainda mais commits. Se eu apertar `q`, eu volto para a minha linha de comando, mas o commit mais recente de todos será empurrado para fora da tela (isso acontece comigo porque o meu prompt ocupa duas linhas).

![imagem de commits do git preenchendo completamente a tela de um terminal](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/s1q5dgtf6qsxiinqefsh.png)

Além desse problema específico do meu prompt grande, eu normalmente também quero apenas os commits na branch em que estou agora. Uma maneira simples que passou pela minha cabeça seria `git log --oneline main..HEAD` (ou talvez usando `main^`). Isso parecia apenas que seria mais um alias pra minha coleção, mas daí eu lembrei que tenho projetos com branch a `main` e projetos com branch a `master`.

Decidi, então, praticar minhas habilidades de shell script e fazer um programinha que decidiria automaticamente entre usar main ou master.

## Primeiramente

Antes de começar, deixe-me planejar tudo que será feito de antemão.

Se eu fosse escrever pseudo código desse script ou resumí-lo em passos, como seria?
Além disso, o que deveria acontecer se eu não estiver em um repositório git?
Vamos pensar sobre essas coisas enquanto escrevemos um plano:

### Temos que:
1. Imprimir alguma mesangem de erro e sair com código não zero caso não estejamos em um repo git
2. Descobrir se ou main ou master estão disponíveis
3. Executar `git log main^/master^..HEAD` se possível
4. Imprimir alguma mensagem de erro e sair com código não zero caso contrário

### 1 - Verificar se estamos em um repositório git

Uma maneira simples, e não muito boa, seria de apenas executar `ls .git`. Se sair 0, estamos num repositório git. Se não, não estamos.

Essa não é uma solução muito boa porque não vai funcionar se estivermos dentro de um subdiretório de um repositório git. Temos que pensar em outra forma. Torcemos para que o git já tenha uma maneira simples e fácil de verificar isso.

E certamente tem, já que o git é tão legal.

Uma pesquisa rápida no google me mostrou que o comando `git rev-parse --git-dir` pode ser usado para isso. `man git rev-parse` mostra mais detalhes, e a opção `--git-dir` realmente parece uma boa opção. Quando não estamos em um repositório git, um erro será impresso na tela e um código não zero será retornado, segundo o manual.

Eu não estou realmente interessado na mensagem de erro, apenas no código de saída (zero ou não zero).

Portanto, para verificar se estamos ou não em um repositório git ao mesmo tempo que escondemos mensagens de erro, eu fiz assim:

```sh
#!/bin/sh

if [ ! "$(git rev-parse --git-dir 2> /dev/null)" ]; then
  echo Not in a git repository
  exit 1
fi
```

A sintaxe `[ ... ]` é uma forma reduzida do comando `test`.
O `!` vai inverter a verificação, isto é, se o comando (nesse caso `git rev-parse --git-dir`) retornar 0, então ele será considerado "falso" ou que resultou em uma "falha".
O `2> /dev/null` está redirecionando a mensagem de erro para `/dev/null`. Isso é uma maneira simples de apenas não mostrar mensagens de erro na tela.

Então basicamente o que isso está fazendo é verificando se o comando falhou. Se sim, significa que não estamos num repositório git, então uma mensagem de erro é impressa e o script retorna 1. Do contrário, nada acontece e podemos seguir para os nossos próximos passos.

### 2 - Descobrir se main ou master existem

Podemos fazer isso usando o comando `git branch` e o comando `grep`, mas `git rev-parse` também pode fazê-lo.

A segunda opção é bastante simples, `git rev-parse --verify <nome-da-branch>` é o suficiente (passe `-q` para suprimir mensagens de erro):

```
git rev-parse -q --verify main
```

Pra mudar um pouco, porém, vou fazer com o grep. Dessa forma, podemos aprender um pouquinho mais sobre regexes!

O comando `git branch` mostra todas as branches disponíveis na sua máquina local, com a branch atual sendo precedida por um `*`.
![imagem do comando git branch mostrando mostrando as banches main e another-branch](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/ygliwcc1z74ec054iug2.png)
Basicamente podemos usar o `grep` passando `main` ou `master`.

Existe um perigo, porém. Simplesmente executar:
```
git branch | grep main
```
vai retornar com sucesso se alguma branch contendo a palavra main existir.
![imagem do comando git branch com grep mostrando a branch main, mas também a branch not-main](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/0aaeyfx44vbnpa8pg61u.png)

Para resolver esse problema, podemos usar uma expressão regular melhor do que apenas `main`. O padrão que buscamos é:

1. Começo da linha
2. Possivelmente um `*`, mas poderia não ter também
3. Espaços em branco (a quantidade depende se um * estava lá ou não)
4. A palavra main (ou master) exatamente
5. Final da linha

Vamos fazer por partes:

1. O regex para o começo da linha é `^`
2. Nos regexes, um `*` é um quantificador, que significa _"a coisa antes de mim entre 0 e infinitas vezes"_. Portanto, para encontrar literalmente um `*`, temos que escapá-lo: `\*`. Além disso, para tornálo opcional, podemos usar o quantificador `?`, que significa _"a coisa antes de mim entre 0 e 1 vezes"_. Portanto, o regex pra isso tudo seria `\*?`
3. Espaços em branco poderiam simplesmente ser um espaço ou `\s`. Este último também encontra coisas como tabulações e quebra de linhas. Como a quantidade de espaços pode variar, podemos usar o quantificador `*`, resultando em `\s*`
4. As palavras `main` ou `master` podem ser encontradas pelas palavras literais
5. O regex para o final de uma linha é `$`

Portanto, nosso regex para a branch main seria `^\*?\s*main$`. Para a master, é claro, é extremamente similar.

_Um ponto importante, apra usar alguns caracteres especiais no regex, o comando grep deve ser invocado com a flag `-E` (a opção de **regex extendido**)!_

O comando final para a branch main, então, poderia ser:

```
git branch | grep -E '^\*?\s*main$'
```

Isso deve funcionar para nós.

### 3 - Executar git log para a branch correta

Podemos fazer isso com uma sequência de comandos `if/elif`. Se main existir, usamos main. Se não, porém master existir, usamos master.

Até poderíamos guardar o resultado do comando acima (git branch com grep) em uma variável e verificá-la. Dessa forma não teremos que executar `git branch` duas vezes. Porém, também podemos passar diretamente o comando inteiro para o if, assim:

```sh
if [ "$(git branch | grep -E '^\*?\s*main$')" ]; then
  git log main^..HEAD
  exit 0
elif [ "$(git branch | grep -E '^\*?\s*master$')" ]; then
  git log master^..HEAD
  exit 0
fi
```

Esse trecho é razoavelmente simples. É verificado se main existe usando aquele regex que definimos. Se existir, usamos o git log no range saindo de main (incluída, pois adicionamos o `^`) até HEAD. Daí saímos 0 para finalizar o script (essa saída 0 provavelmente não é necessária e nem muito boa, pois se existir algum bug em nossa lógica/regex, o programa pode falhar silenciosamente, retornando 0 apesar de um erro!)

### 4 - Imprimir mensagem de erro e sair não zero caso main/master não sejam encontradas

Podemos simplesmente adicionar um comando `else` para o trecho acima:

```sh
if [ "$(git branch | grep -E '^\*?\s*main$')" ]; then
  git log main^..HEAD
  exit 0
elif [ "$(git branch | grep -E '^\*?\s*master$')" ]; then
  git log master^..HEAD
  exit 0
else
  echo 'Neither main nor master branch found'
  exit 2
fi
```

## Mas funciona?

Podemos testar nosso script agora! (:

Eu salvei um arquivo chamado `git-log-main-master` com nosso script e dei a ele permissão de execução. Vamos testá-lo:
![imagem mostrando apenas commits de main até HEAD](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/f0hbkd3pwdhhck40a5pi.png)

Isso foi legal. Mas será que não conseguimos fazê-lo **melhor**?

E se pudéssemos passar flags do git log para nosso script?

### Flags!

Isso é bastante simples de se fazer. Em um shell script, a variável especial `$@` contém todos os argumentos passados ao script, então podemos simplesmente adicioná-lo às nossas linhas com "git log": 

```sh
if [ "$(git branch | grep -E '^\*?\s*main$')" ]; then
  git log main^..HEAD "$@"
  exit 0
elif [ "$(git branch | grep -E '^\*?\s*master$')" ]; then
  git log master^..HEAD "$@"
  exit 0
else
  echo 'Neither main nor master branch found'
  exit 2
fi
```

Agora se passarmos um monte de flags, será executado o git log com essas opções:

![iamgem mostrando o script sendo executado com as flags --oneline e --graph corretamente](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/wpdp0w752ndb5ddfpgsi.png)
 
Mas será que não podemos tornar esse script ainda mais legal de usar? E se passássemos um nome de uma branch pra usar ao invés de `main` ou `master`?

Isso com certeza pode ser feito. Quem sabe pudéssemos assumir que o primeiro argumento é ou um nome de branch ou uma flag, e usar as variáveis posicionais especiais `$1`, `$2` etc.

Talvez o comando pudesse ser `git log $1^..HEAD $FLAGS`, onde `$FLAGS` seriam todos os argumentos passados menos o primeiro.

Isso envolveria um tratamento melhor e verificações pra ver se todos os argumentos passados são somente flags (daí poderíamos manter main/master e `$@`) ou se um nome de branch foi passado. É certamente factível, mas vou deixar a possibilidade pra outro post por enquanto... 😅

#### Aliases...?

Como eu disse, costumo fazer aliases para comandos simples com flags que uso comumente.  `--oneline` é definitivamente uma das minhas opções mais usadas com git log, e é claro, eu fiz um alias:

```sh
alias gl='~/scripts/git-log-main-master --oneline'
```
#### Script no final das contas

```sh
#!/bin/sh

##############################
# Non zero exit codes:
#   1 - If not in a git repository
#   2 - If neither git branch main nor master is found
##############################

if [ ! "$(git rev-parse --git-dir 2> /dev/null)" ]; then
  echo Not in a git repository

  exit 1
fi

if [ "$(git rev-parse -q --verify main)" ]; then
  git log main^..HEAD "$@"

  exit 0
elif [ "$(git rev-parse -q --verify master)" ]; then
  git log master^..HEAD "$@"

  exit 0
else
  echo 'Neither main nor master branch found'

  exit 2
fi
```

### Isso é tudo por hoje

Ainda estou aprendendo o básico de shell script, e deve haver maneiras melhores de se fazer isso. Qualquer comentário e sugestão é mais do que bem vindo! 😊