Maximo Martinez Soria
Published on

Mastering Git

Git is an atomic version control system that allows us to keep a very specific tracking of our code or whatever plain text documents you want.

Awesome, but why should I use git?

When you have some code and you need to change it, how do you save the last version?

You might use something like this:

  • myCode.js
  • myCode_v2.js
  • myCode_v3.js

Terrible!

That horrible way of organization has a ton of problems:

  • Saves all the code again, when you only need the new changes.
  • You can't go back easily.
  • You can't track changes between versions.
  • You can't work with others.

Git solve all these problems and many more. Let's take it a look.

Table of Contents

Getting started

Installation

The installation is kind of different across operating systems. So we will take care of each one separately.

Mac OS

It's quite simple in mac. You only need to go to the git page, download the .pkg installler, open it and follow the instructions.

Windows

Again, go to the git page, download the .exe installer, open it, and follow the instructions.

It should be configured by default, but doble check the settings in the installer. You need to install git bash and open ssl in order to get git to work.

Linux

If you are using linux, you might know more than me.

First, it is a good practice to update and upgrade your package manager. Then, you can install git and you shouldn't have any problem.

# Example with apt-get, which is the package manager for ubuntu, debian and others distributions based on those.
$ sudo apt-get update
$ sudo apt-get upgrade
$ sudo apt-get install git

Configuration

Git works almost out of the box. The only thing that you need to do, is to tell it what's your name and email.

From now on, we will use the terminal for everything.

If you are a windows user, you might want to use git bash.

$ git config --global user.name "John Doe"
$ git config --global user.email johndoe@example.com

First steps

First of all, you need to initialize a git repository inside the folder that you want to track.

$ git init

That command creates two different areas: staging and repository.

Staging

It is a RAM space and a bridge between our non-tracked code and our repository.

Repository

Here is where our tracked code lives.

Tracking code

When you have a new change in your code, and you want to track it, you need to add it to the staging area.

$ git add webpack.config.js

Finally, you need to commit it to the repository with a descriptive message of what you have changed.

$ git commit -m "Webpack configuration"

Branches

Each branch is a different timeline of the code.

Imagine you are working on frontend features and your partner on backend features. So you might have two branches: frontend and backend.

By default, all your changes are going to a branch called master.

# Create branch
$ git checkout -b my-new-branch

# Delete branch locally
# -d will delete the branch only if it has already been pushed and merged with the remote branch.
# Use -D instead if you want to force the branch to be deleted, even if it hasn't been pushed and merged yet.
$ git checkout -d my-new-branch

# Delete branch remotely
$ git push origin --delete my-new-branch

Take a look at the changes

You can see the whole history of a file with the command show

$ git show webpack.config.js

If you need to compare two specific commits you can do it with the command diff

$ git diff id-commit-x id-commit-y

In order to see the id of each commit, take a look at the log.

$ git log

Go back to a previous version

Git gives us a couple of possibilities to go back in time.

# Go back but keep the added changes (This will erase all the changes made after the selected commit)
git reset id-commit --soft

# Go back and erase added changes (This will erase all the changes made after the selected commit)
git reset id-commit --hard

# Go back without erasing anything
git checkout id-commit

# Go back a specific file
git checkout id-commit webpack.config.js

Git WorkFlow

Usually you are going to have two main branches: master and develop.

Master is where production code lives and develop where staging code does.

Remember that staging as environment isn’t the same as the git area called with the same name. As environment, it’s the middle place between production and local. Where we can test all the features as a user before doing the final deploy.

On the other hand, the project could have hundreds of branches. Let’s see how we can deal with them in an organized way.

New features

In order to keep things tidy and simple, you might want to create a new branch for each new feature that you need to code.

# Create a new branch called Feature/Auth
$ git checkout -b Feature/Auth

Once you’ve finished coding. You might want to see those changes in staging or production. Therefore you need to merge it with either master, develop or whatever the deploy branch is called in your project.

# Move to master
$ git checkout master

# Merge branch
$ git merge Feature/Auth

Fix

We usually call hotfix to the branch where we work on killing those bugs that need immediate attention.

$ git checkout -b hotfix

Again, once you are ready, merge changes with master or develop.

# Move to master
$ git checkout master

# Merge branch
$ git merge hotfix

Save the repository in an online server

Let’s use GitHub for this example, but keep in mind that it's sort of similar in the other platforms.

I’m pretty sure that you can create an account on your own so let’s skip that step.

Once you have an account, you need to create a repository clicking the green button and filling the required fields.

Don’t worry about .gitignore and Readme files right now. We will get there soon.

Asymmetric encryption

The best and most secure way to connect our computers with GitHub is with asymmetric encryption.

In summary, you need two keys. A public one and a private one. Github will have your public key and your computer will have the private one.

Every piece of information that travels across the internet will be encrypted with those keys. The only way to decode it, is having them both.

Don’t worry about the difficult words right now. Just follow my steps in order to configure the whole environment.

First of all, we will create a set of keys running a command in the terminal. Keys, will be called id_rsa(Private) and id_rsa.pub(Public) and will live in a new folder called .ssh in the home of your computer. Also, the command will ask you a passphrase.

Second of all, we will turn on the ssh keys agent, which is a process that do all the magic behind the scenes.

Then, we will add our private key to that agent.

Finally, we will add the public key on GitHub.

Let’s do it.

Things are kind of different in Mac. So let’s start with windows and linux:

# Generate ssh keys
$ ssh-keygen -t rsa -b 4096 -C "your@email.com"

# Turn on ssh keys agent in your machine
$ eval $(ssh-agent -s)

# Add keys to the agent
$ ssh-add ~/.ssh/id_rsa

Now for mac users:

# Generate ssh keys
$ ssh-keygen -t rsa -b 4096 -C "your@email.com"

# Turn on ssh keys agent in your machine
$ eval "$(ssh-agent -s)"

# Create a config file inside the new .ssh folder
$ touch ~/.ssh/config

# Add this content to the file.
Host *
  AddKeysToAgent yes
  UseKeychain yes
  IdentityFile ~/.ssh/id_rsa

# Add keys to the agent
$ ssh-add -K ~/.ssh/id_rsa

Now, let’s add the public key on GitHub. First, copy the content of the public key. You can see it with cat.

$ cat ~/.ssh/id_rsa.pub

There is a specific menu for ssh into Github settings: https://github.com/settings/keys.

Add a SSH key, put a title and paste your public key.

Title usually is the model of the computer.

Pulling and pushing code

Going back to the terminal and in order to be able to push your committed changes, you have to set up an origin.

The origin, tells git where to find the remote repository.

$ git remote add origin https://github.com/maximomartinezsoria/app.git

Remember to use your own repository’s link

Finally, let’s push our code.

The first time that you push a repository, you will need to tell git which origin to use.

$ git push -u origin master

From now on, you can keep it simple and just push without specifying anything.

$ git push

If the repository already had some code before your first push, git would throw an error. You will need to allow git to merge the local history with the upcoming history.

$ git pull origin master —allow-unrelated-histories

If you’re working with a team, they might push their code to the same repository.

Next time that you need to push, git will tell you that there is code that you don’t have in your local repository. So you will need to pull it down.

# Download changes and merge them with local code automatically
$ git pull

Gitignore

Sometimes, we need git to avoid tracking some files. For instance, we don’t want to track compiled files or node_modules folder.

.gitignore, is a file that tells git what not to track.

Let’s create an example:

Create a .gitignore file in the root of your project and paste this content:

# env files and node_modules folder should not be tracked by git
.env
/node_modules

Now, add everything with git add -A and you'll notice that the files specified in .gitignore are not included.

Readme

Readme files are the ones that you can see at the end of each GitHub repository. They usually contain highlights of the documentation or how to set up the project.

Here you can see an example: https://github.com/maximomartinezsoria/minimalist-spa-router/blob/master/Readme.md

Tags

Tags are what we usually use to save a reference in some part of the repository timeline.

For instance is the way that we have to reference versions.

# Create a tag
$ git tag -a tag-name commit-id

# Delete a tag locally
$ git tag -d tag-name

# Delete a tag from the remote repository
$ git tag -d tag-name
$ git push origin :refs/tags/tag-name.

# Show tags
$ git tag

# Show tags with more info
$ git show-ref --tags

# Push tags
$ git push origin --tags

Rebase

Previously in this article, we used merge to join two branches. If you take a look at the log, you will see both branches, the commits on them and the point where they were merged.

With rebase, we are able to merge two branches and erase the history. What rebase actually does is to show the commits that were made in a branch as if they were made in another.

It's quite simple to get that result. Just make two rebases, one from the branch that you don't want anymore and another from the branch that is going to keep all the changes.

$ git checkout experiment
$ git reabase master

$ git checkout master
$ git rebase experiment

Keep in mind that we use Git in order to have a complete history of all our project's changes. Using this kind of things might be considered as a bad practice, because you are changing that history and making it not completely real.

Stash

Imagine this situation:

You are working in a feature. You coded a lot. Eventually, you need to take a look at something in another branch, but you are not ready to commit your changes. So, what can you do?

Stash allows us to save current not commited changes in a temporary place.

# Save not commited changes in a temporary place
$ git stash

Now, you can go to another branch and do whatever you want.

When you want to bring those changes back, you just pop them.

# Bring stashed changes back
$ git stash pop

You can also see the list of all your stashed code and erase it if you don't need that code anymore.

# List stashs
$ git stash list

# Erase stashs
$ git stash drop

Another cool thing about stash, is that you can take your current changes and go to another branch without a commit.

# Create a new branch called 'new-feature' with the current changes.
$ git stash branch new-feature

Removing files

Git clean, delete those new files that are not tracked yet.

# See what files will be deleted but don't erase anything
$ git clean --dry-run

# Delete the files listed by the previous command
$ git clean -f

Git rm, allows us to delete tracked files.

# Delete file from staging area but keep it in your computer
$ git rm --cached

# Delete file from staging area and from your computer
$ git rm --force

Git cherry-pick

Imagine that you are working in that awesome new feature. As a good practice, you are coding in a new branch. Eventually, you need to deploy one commit of that branch. But you haven’t finished your new feature yet. So you need to merge your feature branch with master but want only one commit.

It's kind of a weird situation, but it could happen. Git, solves this problem with something called cherry-pick

# Look for the id of a commit. Remember that you should run this command into the branch where the commit has been done.
$ git log

# Go to master branch
$ git checkout master

# Merge only one commit of a branch to master
# That strange string is the id of a commit 🙂
$ git cherry-pick 4fd534

There you go. Now you should see that commit in the master's log.

Just like with rebase, be carefull with this command and remember that you are changing the history.

Amending commits

Amend a commit, means that you commit a change to the very last commit that you did.

Let's say that you wrote lots of console.logs but forgot to erase them before the commit.

Ok, it's not a problem. Go back to the code, erase them and commit again:

# add changes
$ git add -A

# amend last commit
$ git commit --amend

Take a look at the log and you'll see only one commit.

Clone

Git has been made for teams.

If you want to get into a project that has already been started, you will need to clone it.

# Example with SSH
$ git clone git@github.com:user/repository.git

# Example with HTTPS
$ git clone https://github.com/user/repository.git

Note: you will find the link in GitHub, GitLab, Bitbucket or the service where the project is hosted.

Git reflog

There is a command that remembers everything. Even though you have used rebase, cherry pick or another of those commands that changes the history shown in git log; git rebase knows that you've done all of that crazy things.

$ git reflog

Git allows us to search throughout our project with 2 commands.

For searching in the files: git grep.

For searching in commits: git log -S.

# Look for all p tags
$ git grep "<p>"

# Look for the commit where we change colors
$ git log -S "change colors"

Some interesting flags for grep command:

  • -n: returns the specific line where the word is.
  • -c: counts how many times was the word written.

Complete log

We've already talked about git log, but it is much more powerful than expected.

Let's see some flags:

  • --oneline: shows each result in one line. Useful to see more information in less space.
  • --all: shows the logs of all the references: branches, tags.
  • --graph: draws lines to illustrate the relationship of the different branches.
  • --decorate: a little bit of colors for the graph.

Now, let's put it all together.

$ git log --all --graph --decorate --oneline

Aliases

Git allows us to create an alias for a command.

If you know about linux aliases, it's the same idea.

$ git config --global alias.name "command"

# Example
$ git config --global alias.tree "log --all --graph --decorate --oneline"

# Trying it
$ git tree

Git is really big

You have now a really good understanding of git and how to use it. But Git is really big. I encourage you to read the docs to learn a lot more about it.