g - a wrapper around git with additional feature extension
Tags: git, cli, zsh
Published: 2020-08-02 13:52:07

I am a longtime CLI fan. I love how the basic Unix commands can be piped through each other to create very robust automation with a few lines of codes.

I also love [git][1], and I use it almost exclusively from command line. But after the learning curve (which was almost 10 years ago as my colleague introduced it to me), I realised I spend too much time on repetitive commands, and it would be nice to extend it with some handy features too!

{% github https://github.com/pcdevil/g %}

On that notion, I created a small tool called `g`! It's a long-term project of mine, and contains lot of subjective decisions based on how I use **git**. I've recently made it public and added better documentation to help anyone who would like to use it :)

In this post, I'll go through the functionalities to give a high level overview of it!

## The entry-point
The [`git status`][2] shows information about the current working tree and it's one of the most used **git** commands. There are several good practices how to make it more accessible with `git st`, `g st` or `gst` aliases, but I found these solutions less intuitive, and made it even simpler: just run the `g` command and you'll get the same result while in non-git directories it calls the [`git help`][3].

The entry-point is not only an alias for the `git status`, but it's also a wrapper around **git** and when it is called with argument, it acts according to them. Other functionalities are based on this behaviour.

## Aliases
If we talk about _how to remove the repetitiveness of **git** usage_, most of the time the solution is the [aliases][4]. `g` provides aliases for common git commands, and some more advanced ones including [`git commit`][5] and [`git merge`][6]!

See the following table for some hints of the available aliases, while the full list is [in the documentation][7]:

| Alias command | **git** command                |
| ------------- | ------------------------------ |
| `g br`        | `git branch`                   |
| `g cl`        | `git clone`                    |
| `g ci-a`      | `git commit --amend`           |
| `g ci-as`     | `git commit --amend --no-edit` |
| `g d`         | `git diff`                     |
| `g me`        | `git merge`                    |
| `g me-ms`     | `git merge --no-ff --no-edit`  |
| `g sh`        | `git show`                     |
| `g sw`        | `git switch`                   |
| `g sw-m`      | `git switch master`            |

Moreover, there are also [angular commit][8] and [advanced][9] aliases: the first gives shortcut for a below discussed `g` feature, while the second consist of a one liner log (`g l`), set origin and push the branch to it (`g ph-o`), and alternate way to commit where only the message can be specified (`g ci`).

## Added functionalities
While having shortcuts to existing commands is good, `g` gives several small utilities to enhance **git** usage - there are mostly related to bootstrap a repository.

### Setting the git user
If you are working in an environment where custom e-mail domain is used, and you want to make sure your commits are bound to you nicely, you can use [`git config`][10] to set that up.   
To achieve the effect you either have to do it locally in every single repository, or only one time globally. The first is troublesome (declaring the name and e-mail in numerous repositories), while the second option eliminates the portability of the config because and you can't put the `.gitconfig` in your _dotfiles_ repository.

To resolve this, `g set-user` (or `g su` for short) comes in handy. You only set up the username, email and potentially signing key once as a url-matched configuration, then running the `g su` command sets it on the current repository with no hustle!

**Example setup**:
```zsh
$ git config --global 'user.https://github.com.email' 'jakab@gipsz.eu'
$ git config --global 'user.https://example.org.email' 'jakab.gipsz@example.org'
$ git config --global 'user.https://example.org.name' 'jakab.gipsz'
$ git config --global 'user.https://example.org.signingkey' '0000000000000042'
```

Thanks to the url-matched keys, this won't have any effect on your standard config, allowing to _symlink_ the same config file on several computers.

**Example usage**:
```zsh
$ cd my-project
$ g set-user example.org
$ git config user.name
jakap.gipsz
$ git config user.email
jakab.gipsz@example.org
$ git config user.signingkey
0000000000000042
```

Here the when the command is called, the author changes accordingly to the config without stating your user information! Full details about how the matching config is found or how the signing key is set is [in the documentation][11].

### More delicate cloning
[Oh My Zsh][12] provides a [`take`][13] command which is basically `mkdir && cd`. The assumption behind the command is after creating a directory you usually want to use too. For the same reason this can be useful for **git** too, and `g` is equipped a command which mimics the original one: `g take` (`g ta` as alias) clones a repository then changes the current directory into it.

Running `g take` has one more benefit: it runs the `g set-user` command after the clone, which makes `g take` an end-to-end solution for cloning and immediate usage of a repository.

### Change directory relative to the root
On bigger projects I often enter a deep sub-directory to perform an action, and afterwards I want to go back to the root directory.

For that, `cd -` can help you to change the current directory to the previous one, but if the current directory was a result of several changes, you need to remember how many changes were executed and pass it to it. **Oh My Zsh** helps a little bit (see [cheatsheet][14]), but the burden of remembering how much jumping occurred is still present.

The `g cd` command helps with this problem:
- With no argument, it acts like `cd` and jumps to the _home directory_ - which is interpreted as the root of the repository
- While passing an argument changes the active directory relative to the root

**Example for given argument**:
```zsh
$ cd ~/my-project
$ cd src/routes/api/categories
$ # do some actions
$ g cd test/routes
$ pwd
~/my-project/test/routes
```

### Robust, extensive repository initialisation
Creating a new repository can be tedious:
1. Init git
2. Set up the [gitignore][15] _(and then forget to include a crucial path by accident)_
3. Add readme and license files
4. Add temp directory
5. Create the first commit

To make it simpler, I created the `g super-init` (with `g si` as alias) command to automatise all these steps and make sure I don't forget anything!

The command has the same procedure as listed above with the following details:
- The **gitignore** will be populated with:
  - The output of [gitignore.io][16] with Linux, MacOS, Windows, IntelliJ (+all), Vim, and Visual Studio Code related ignores.
  - The ignores related to the specified programming language.
  - a `tmp` directory and a `.env` file.
- A `README.md` file will be created with the name of the project and the given description (if any).
- A `LICENSE.md` file will be created with [MIT][17] license.
- The commit will follow the [Angular's Contributing Commit Message Format][18], in form of `build(<projectname>): initial commit`.

Additionally, the `g super-init` acts as `g take`, meaning it will change the current directory to the new project too.

The programming language and the descriptions can be specified during invocation.

**Example usage**:
```zsh
$ g super-init my-project node "My awesome new project."
$ pwd
~/my-project

$ ls -1A
.git
.gitignore
LICENSE.md
README.md
tmp

$ tail -6 .gitignore
# End of https://www.toptal.com/developers/gitignore/api/linux,macos,windows,node,intellij+all,vim,visualstudiocode

### Project ###
/tmp/**
!/tmp/.gitkeep
.env

$ cat README.md
# my-project
My awesome new project.

## License
Available under the [MIT license](LICENSE.md).
```

### Switching to the ~~master~~ main branch
The `g switch-main` (with an alias of `g sw-m`) command helps you to change the current branch to the `main` branch.

In case of needing to change the default branch, you can specify it in the **git config** with the [`init.defaultBranch` variable][19] - this added to **git** very recently and `g switch-main` uses it to line up with the standard usage of **git**.

**Example to change the default branch locally**:
```zsh
$ cd my-project
$ git config --local branch.default gh-pages
$ git switch --create fix/footer-visibility
$ # work and commit
$ g switch-main
$ g # invoking git status
On branch gh-pages
Your branch is up-to-date with 'origin/gh-pages'.

nothing to commit, working tree clean
```

# Closing thoughts
If you made it this far thank you for reading my article, and I hope you find some of these features useful.

**Have a nice day!**

[1]: https://git-scm.com
[2]: https://git-scm.com/docs/git-status
[3]: https://git-scm.com/docs/git-help
[4]: https://git-scm.com/book/en/v2/Git-Basics-Git-Aliases
[5]: https://git-scm.com/docs/git-commit
[6]: https://git-scm.com/docs/git-merge
[7]: https://github.com/pcdevil/g#predefined-git-config
[8]: https://github.com/pcdevil/g#angular-commit-aliases
[9]: https://github.com/pcdevil/g#advanced-aliases
[10]: https://git-scm.com/docs/git-config
[11]: https://github.com/pcdevil/g#set-user
[12]: https://github.com/ohmyzsh/ohmyzsh
[13]: https://github.com/ohmyzsh/ohmyzsh/wiki/Cheatsheet#commands
[14]: https://github.com/ohmyzsh/ohmyzsh/wiki/Cheatsheet#directory
[15]: https://git-scm.com/docs/gitignore
[16]: https://www.toptal.com/developers/gitignore
[17]: https://mit-license.org/
[18]: https://github.com/angular/angular/blob/master/CONTRIBUTING.md#-commit-message-format
[19]: https://git-scm.com/docs/git-config#Documentation/git-config.txt-initdefaultBranch