How to only show commits of current branch
Tags: git, shell, cli
Published: 2021-10-04 18:33:14

🇵🇹 Versão em português desse texto [_aqui_](https://dev.to/lucassperez/shell-script-para-mostrar-um-range-de-commits-13ah)!

Every now and then I make aliases that execute a command with a few flags that I use often. One of them being an alias for `git log --oneline`.

The problem is that when I executed this command, it would show all the commits untill my screen is filled, and the last line shows that it has more to come. If I press `q`, I will return to my prompt, but the most recent commit will be pushed out of my screen (because my prompt has two lines).
![image of git commits filling up entirely a terminal screen](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/s1q5dgtf6qsxiinqefsh.png)

Other than this specific problem to my big prompt, I usually just want the commits on the branch I am right now. A simple way that crossed my mind was `git log --oneline main..HEAD` (or maybe use `main^`). This looked like a simple new alias for my collection, but then I remembered that I have projects with a `main` branch and also other projects with a `master` branch.

I decided, then, to practice my shell scripting and make a script that would decide between main or master automatically.

## First things first

Before starting, let me plan what will be done beforehand.

If I would write some pseudo code of this script or summarize it in steps, how would that be?
Also, what should happen if I am not in a git repository?
Let's think about these things while writing down a plan:

### We have to:
1. Print some error message and exit non zero code if not in a git repository
2. Find out if either main or master branch is available
3. Execute `git log main^/master^..HEAD` if possible
4. Print some error message and exit non zero code otherwise

### 1 - Check if in a git repository

A simple, and not very good, way would be to just execute `ls .git`. If it exits 0, we are in a git repo. If not, then we are not.

This is not a very good solution because it will not work if we are inside a subdirectory of a git repository. We have to think about something else, but hopefully git itself already has some quick way to check this.

And surely it has, since git is so nice.

A quick google search showed me that the command `git rev-parse --git-dir` can be used for this task. `man git rev-parse` shows more details, and the `--git-dir` option indeed looks like a good option. When we are not in a git repo, it prints an error message and exits "non zero", according to the manual.

I'm not really interested in the error message, just the exit code (zero or non zero).

So to check if we are in a git repository while also hiding possible error messages, I did this:

```sh
#!/bin/sh

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

The `[ ... ]` syntax is a shorthand for the `test` command.
The `!` will invert the check, if the command (in this case, `git rev-parse --git-dir`) returns 0, then this will be considered "false", or a "failure".
The `2> /dev/null` is redirecting the error message to `/dev/null`. This is a simple way of not showing error messages.

So basically what this is doing is checking if the command failed. If it did fail, it means we are not in a git repo, so an error message is printed and the script returns 1. If the command succeeds, nothing happens, and we can proceed to our next steps.

### 2 - Find out if main or master exists

We can do this using the `git branch` command and some grepping, but `git rev-parse` can also do it.

The second option is very simple, `git rev-parse --verify <branch-name>` will do the trick (pass `-q` to hide error messages):

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

Just for a change, though, I'll use the simple (maybe naive) grep way. By doing so, we can learn more about regexes!

The command `git branch` shows all the branches you have avaiable in your local machine, with the current branch being preceded by a `*`.
![image of git branch showing a main branch and another-branch](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/ygliwcc1z74ec054iug2.png)
Basically we can grep for `main` or `master`.

There is a danger, though. Simply executing:
```
git branch | grep main
```
will return successfully if any branch containing the word main exists.
![image of git branch showing main branch but also not-main branch](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/0aaeyfx44vbnpa8pg61u.png)

To solve this problem, we can use a better regular expression than just `main`. The pattern we are looking for is:

1. Start of the line
2. Possibly a `*`, but could not have it
3. Whitespaces (the amount depends if a * was there or not)
4. The word main (or master) exactly
5. End of the line

Let's break down how to do each part:

1. The regex start of the line token is `^`
2. In regex, a `*` is a quantifier, meaning _"the thing before me between 0 and unlimited times"_, so to match the `*` literal, we have to escape it: `\*`, and to make it optional, we use the `?` quantifier (it means _"the thing before me between 0 and 1 times"_). So to match a possible `*`, the regex would be `\*?`
3. Whitespaces can be simply a space or `\s`, the latter matching things like tabs and new lines also. The amount of whitespaces can vary, so we can use the `*` quantifier, leaving us with `\s*`
4. The words `main` or `master` can be matched with the literal words
5. The end of the line is the token `$`

So our regex would be `^\*?\s*main$` for the main branch. For the master, of course, is extremely similar.

_An important note, to use some special regex characters, grep have to be invoked with the `-E` flag (the **extended regex** flag)!_

The command for the main branch could then be:

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

This should work for us right now.

### 3 - Execute git log for the correct branch

We can do this with a sequence of `if/elif` commands. If the main exists, use it. If not, but the master exists, use master instead.

We could store the result of the last step in a variable and check it, or we could just check it directly:

```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
```

This snippet is fairly simple. It checks if main exists using the regex we defined. If it does, it git logs the range from the main (inclusive, since we added `^`) to HEAD. It then exits 0 to finish the script (this exit is probably not needed and also not very good, because if there is some bug in our logic/regex, it can fail silently, exiting 0!).

### 4 - Print error message and exit non zero if main/master not found

We can simply add an `else` statement to the snippet above:
```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
```

## Does it work?

We can test our script now! (:

I saved a file named `git-log-main-master` with our script and gave it execution permission. Let's try it out:
![image showing only commits from main to HEAD](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/f0hbkd3pwdhhck40a5pi.png)

Now that's nice. But can we make it **better**?

What if we could pass the standard git log flags to it?

### Flags!
 
This is fairly simple to do. In a shell script, the special variable `$@` contains all the arguments passed to the script, so we can simply append it to our "git log" lines:

```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
```

Now if we pass a bunch of flags, it is going to execute git log with those options:
![image showing the script being run with --oneline and --graph flags correctly](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/wpdp0w752ndb5ddfpgsi.png)
 
Is there a way to make it even nicer to use? What if we passed a branch name to use instead of either main or master?

We could definitely do that. Maybe we could assume the first argument is either a branch name or a flag and use the positional special arguments `$1`, `$2` etc.

Maybe the command could be `git log $1^..HEAD $FLAGS`, where `$FLAGS` would be all the arguments passed but the first.

This would involve some better treatment and checks to see if all the arguments are just flags (in this case we could keep main/master and `$@`) or if a branch name was passed, and is definitely doable, but I'll leave for possibly another post for now... 😅

#### Aliases...?

Like I said, I usually make aliases for simple commands with common flags. `--oneline` is definitely one of my most used options for git log, and sure enough, I made an alias:

```sh
alias gl='~/scripts/git-log-main-master --oneline'
```

#### Summed up script
```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
```

### That's it for today

I'm still learning the very basics of shell scripting, and there are probably better ways of doing this. Any comment and advice is more than welcome! 😊