Git Sweet Git

Jul 19, 2020 (8 minutes read)


Your Git repository is like your home. You spent time there. You move things. You want it to be a nice and comfortable place for you. And you want to keep it clean and ready for guests. Yes, sometimes it’s a mess after a party but still, you know how to clean everything and get back a shiny house.

README

When guests arrive to you what is the first thing they see? Door and a rug with “WELCOME” word printed on it.

This rug is a README.md file in your Git repository. This is a first place where users try to familiarize with your project and start to look around. Like in real life.

Usually your guests will take off shoes on this rug while inspecting what they can see.

In README.md you want to answer following main questions:

  • What is this?
  • How to install this?
  • How to use this?

If you would like your guests to contribute to today’s dinner, then additional questions should be answered:

  • How to contribute?
  • What help is needed?

There are different homes. But Git repository should be a minimalist house.

What does it mean — “minimalist house”? My definition is following:

Minimalist house is a house that has only what is needed.

.gitignore

You know, when guests enter your room you don’t want them to see socks scattered all around. You need to put everything in a locker. In the Git repository “socks” can be hidden with help of .gitignore.

What are those “socks”? Something that adds mess to your repository and should not be shown to your guests. Something like node_modules, vendor, .DS_Store, .vscode, etc.

In Git there are two types of .gitignore files. Global and local. Global is used to ignore files that expose your working environment. For example:

  • Files that are generated by the OS, e.g. .DS_Store
  • Files that somehow show which editor you are using, e.g. .idea, .vscode

Local is used to hide irrelevant parts of your project. For example:

  • node_modules
  • *.log
  • .env
  • vendor

Those files and folders that are not mandatory to save in the repository. node_modules are populated after npm install, thus are not needed. *.log files are added when you debug something. .env file is your specific environment configuration that should not be shared with others. I think you’ve got an idea.

Good collection of global and local .gitignore files can be found in github/gitignore repository.

Good repository should only consist of things that are needed for the project.

.gitattributes

While you cook guests wait for you in the main room and start to walk around touch things and then somebody asks a question: “Is this a picture or a book?” pointing their finger on a shelf. “This is a picture”, you answer. “Looks so real”, one of your friends whispers.

Sometimes you need to help Git the same way as you helped your friend to recognize what it is in front. With this helps .giattributes file. This file helps Git to understand the contents of other files to better diff / render them.

Nice collection of .gitattributes files can be found in alexkaratarakis/gitattributes.

Some web project .gitattributes file might look as follows:

# Graphics
*.gif    binary
*.jpg    binary
*.png    binary

You want to help Git to identify images as images and not text, for example.

At this point repository looks like this:

README.md
.gitignore
.gitattributes

This is a good start for any project that should go with an initial commit.

Commit message

Somebody found your family photo album. They start to look through, year by year, event by event. Childhood, youth, adulthood. What were the times… What is good about this album is that on each page there is a title and some description of what happened.

Git commit history is similar to this photo album. Event by event you can go through it. You can go from beginning till the end viewing commits with title and description.

Yes, commits have title and description. And it is nice to have them both. For example:

feat: add view counter to each post

Added view counters to each post
to understand what posts are popular
and which are not.

Closes: 906

When somebody in the future will open this commit, they can understand why this was done. In the commit description you can have some meta information to help find related content. In this case you see Closes trailer which point to an issue in some ticket system that can be closed by this commit. This also might be a link to some internal wiki, etc.

There are many different conventions on how to write commits and it is mandatory to agree on the approach within your team. Otherwise a beautiful story might be lost and you will need to do a lot of explanation to your guests. For the convention example you can take a look at Git Wiki and Conventional Commits Specification.

Treat Git commit as emails. There is a good summary on Git Wiki about how to write nice Git commits:

The “one-line summary plus body of the message” has a strong correlation with how we communicate [..] via e-mail. You do not start a sentence on the “Subject: " header and continue on to the body of the message, starting the body halfway of the sentence. Instead, you try to make sure you write something sensible by itself on the “Subject: " header to help the recipient when later scanning for it among bunch of messages, and you write a full paragraph that you can understand without reading the subject line first.

Commit history

Now you enter the room with 12 plates in your hands. Everyone looks at you shocked (because you handled so many). And then they fall…

It’s better to serve plates one by one or have a few in hands. Stable and more control.

Same applies for Git commits. Smaller and granular ones are better.

Personally I would recommend to have commits on each move while you fix bugs or develop features. This will help reviewers to understand thinking behind final implementation. Your commit history might look like this in a feature branch:

* c911ba2 (HEAD) simplified method according to provided feedback
* 4692d22 added integration tests
* 7b84532 added unit tests
* 47b8452 implemented message parsing logic

When everything is reviewed and you are ready to merge — squash commits into one:

* c911ba2 (HEAD) feat: add message parser

With body having context of work done:

feat: add message parser

Implemented message parser according to #42 document.
Unit and integration tests cover all cases described
in the linked document.

Reviewed-By: D
Closes: 21

Rebase and Merge

While you make a beautiful dessert in the kitchen a lot of things are flying around. Many ingredients lie on a table, jars are open, everything smells tasty. Process is going on. When you are almost finished, you clean everything up and add final touch with a cherry on top.

It is good to do a rebase when you are almost finished and would like to present your beautiful work. Merge — to signal that work is completed.

Use both. The approach I prefer is:

  • Rebase on feature / fix branches
  • Merge to main branch when everything is finished

Rebase is a nice helper for cleanup after work. Highly recommend to read about “The Golden Rule of Rebasing” before using it.

Merge is a completion indicator. When work is finished and a milestone is achieved, it can be marked with a merge commit.

.gitconfig

When conversations are finished, you and your friend go toward the corner with piano and guitar and start jamming, having fun and singing songs together.

It is possible to start playing straight away because all instruments are in tune and fingers know how to play these instruments. To play even better on guitar some time ago you gave it to master which made it even better to play. Less movement for hands, more precise sound because strings were changed.

Git can also be treated as an instrument. You can play on it right now or you can tune it up a little bit to play even better.

Such tuning is done with help of configuration and aliases. For example, you would like to list tags and branches in your repository:

git tags
git branches

# Without configuration
# git tag -l
# git branch -a

Aliases hide complexity. Better to read and less possible mistakes to make.

Push branch and added release tag:

git push

# Without configuration:
#  git push --follow-tags or git push && git push --tags

With help of .gitconfig you can set this as default behavior (push tags on git push).

My recommendation is to share the same .gitconfig across your team. Good configuration will ensure that team members are on the same page.

Nice configuration can simplify work with Git and make it more predictable.

For a reference you can take a look at my .gitconfig in skibish/dotfiles.

rm -rf . && git init

If you did some home repairs you know that sometimes it is better to start from scratch.

Sometimes this happens to the Git repositories too. Even though ideally Git should store all the history of your repo, sometimes it makes sense to drop everything and start recording history of changes from scratch. When this might happen:

  • You build an MVP or PoC of a product. It would be better to start the history of a “real” production product fresh
  • Repository is not in a good shape. A lot of artifacts are not in their places, some burden around, not easy to follow history, etc.

Don’t be afraid to throw things away. It just means that you are growing and freeing up space for future endeavors.

Final words

These little things helped me to simplify my Git workflow. Hope that something from suggestions will work for you too.

What are your approaches to keep Git repository clean and work with it easier?

Next post → ← Previous post
comments powered by Disqus