🇵🇹 Versão em português desse texto aqui!
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).
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:
- Print some error message and exit non zero code if not in a git repository
- Find out if either main or master branch is available
- Execute
git log main^/master^..HEAD
if possible - 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:
#!/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 *
.
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.
To solve this problem, we can use a better regular expression than just main
. The pattern we are looking for is:
- Start of the line
- Possibly a
*
, but could not have it - Whitespaces (the amount depends if a * was there or not)
- The word main (or master) exactly
- End of the line
Let's break down how to do each part:
- The regex start of the line token is
^
- 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\*?
- 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*
- The words
main
ormaster
can be matched with the literal words - 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:
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:
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:
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:
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:
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:
alias gl='~/scripts/git-log-main-master --oneline'
Summed up script
#!/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! 😊
Top comments (0)