Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Introduction

Git is a distributed version control system used to manage the source code of Digger. We can use apt-get to install Git:

$ sudo apt-get install git

Before using Git, let’s do a few initial configurations:

  • The initial branch name to use in all new repositories:

     $ git config --global init.DefaultBranch main
    
  • Identify the author of the commits:

     $ git config --global user.email "you@example.com"
     $ git config --global user.name "Your Name"
    

Configuring Git to Simplify Authentication

For the moment, every time we push code to GitHub the prompt asks for a username and password. We can bypass this step by registering a SSH key. To do that, we first check whether there is already an existing SSH key we can reuse:

$ ls -al ~/.ssh

If files with the extension .pub are listed then one of them can be reused to authenticate to GitHub. If not, then we can create one:

$ ssh-keygen -t rsa -b 4096 -C "[firstname.lastname]@domain.com"
  Enter file in which to save the key (/Users/[user]/.ssh/id_rsa): [Press enter]
  Enter passphrase (empty for no passphrase): [Type a passphrase]
  Enter same passphrase again: [Type passphrase again]

The generated keys need to be protected with the right permissions otherwise the access won’t work:

$ chmod 700 ~/.ssh
$ chmod 644 ~/.ssh/id_rsa.pub
$ chmod 600 ~/.ssh/id_rsa

The next step is to add the new key - or an existing one - to the ssh-agent. This program runs the duration of a local login session, stores unencrypted keys in memory, and communicates with SSH clients using a Unix domain socket. Everyone who is able to connect to this socket also has access to the ssh-agent. First, we have to enable the ssh-agent:

$ eval "$(ssh-agent -s)"

And add key to it:

$ ssh-add ~/.ssh/id_rsa

The next step is to make GitHub aware of the key. For that, we have to copy the exact content of the file id_rsa.pub and paste into GitHub. To make no mistake about the copy, install a program called xclip:

$ sudo apt-get install xclip

And then copy the content of the file id_rsa.pub in the clipboard:

$ xclip -sel clip < ~/.ssh/id_rsa.pub

The command above is the equivalent of opening the file ~/.ssh/id_rsa.pub, selecting the whole content and pressing Ctrl+C. This way, you can paste the content on GitHub when required in the next steps. On the GitHub side:

  1. Login at https://github.com

  2. In the top right corner of the page, click on the profile photo and select Settings

  3. In the user settings sidebar, click SSH keys

  4. Then click Add SSH key

  5. In the form, define a friendly title for the new key and paste the key in the Key field

  6. Click Add Key to finish with GitHub

To make sure everything is working, lets test the connection:

$ ssh -T git@github.com
  The authenticity of host 'github.com (207.97.227.239)' can't be established.
  RSA key fingerprint is 16:27:ac:a5:76:28:2d:36:63:1b:56:4d:eb:df:a6:48.
  Are you sure you want to continue connecting (yes/no)? yes
  _
  Hi [username]! You've successfully authenticated, but GitHub does not
  provide shell access.

Repository

We can test the installation by cloning the Digger repository:

$ mkdir -p ~/java/projects/digger
$ cd ~/java/projects/digger
$ git clone git@github.com:htmfilho/digger.git .

This configuration works only when we use a ssh connection to GitHub. To verify that, go to one of your local GitHub projects and check the url pointing to the server:

$ cd ~/java/projects/digger
$ git remote -v

If the url starts with https:// then you are using https instead of ssh. In this case, you should change the url to the ssh one:

$ git remote set-url origin git@github.com:htmfilho/digger.git

The automatic authentication should work after that.

By the way, when the repository is too big, with a long versioning history, we may consider cloning only the latest version to kick-off our day faster. We do it using the --depth parameter:

$ git clone --depth 1 -b master git@github.com:htmfilho/digger.git

When we have our configuration done and everything is working, before stopping for the day, run:

$ git fetch --unshallow

to download the rest of the repo. We want this to enable the “blame” feature in our IDE.

Changing The Author To The One Recognizable by GitHub

In case your default Git author is not the same as GitHub, configure the author of the repository:

$ git config --local user.name "John Doe"
$ git config --local user.email "john@doe.org"

It can also be done to a specific commit:

$ git commit --author#"John Doe <john@doe.org>"

Setting Pull Behaviour

The git pull command merges the remote branch into the local branch with a merge commit, but we don’t think this commit is useful. We want to make sure our commits represent changes made by developers only. So, we would like to ask you to use rebase to merge remote branches locally. You can do it at every pull with:

$ git pull --rebase origin master

or change a local configuration to make it the default pull behavior:

$ git config --local pull.rebase true

Note: you don’t need to run this local configuration if you already have it globally.

Pruning Deleted Remote Branches

When branches are removed from origin, this change is not automatically reflected in the local clone when doing a git pull or git fetch. To have deleted remote branches automatically pruned from the local repo, set the following config:

$ git config --global fetch.prune true

Workflow

Let’s first clarify that, when discussing the workflow, we use the term “Issue” to refer to a change request, not a technical issue that could happen when we are dealing with version control.

Before

Before doing the work.

During

While doing the work.

Dealing with Unexpected Issues

Imagine a scenario where you are busy, working on an issue, then a more urgent issue comes up, so you have to pause what you are doing to shift context. It happens all the time. Git is our best friend in this kind of situation, but we have to use it properly.

The dev Branch Must Be Immutable

I was working on an issue and, in the spirit of saving time, I have commited changes directly to the branch main. In this project, we could not push changes to the remote branch main, so we needed to create a separate branch from main and create a pull request. I thought I could simply do that and move on, which normally works when everything goes well.

However, I may need to test something in main before the pull request is accepted, but since it contains changes then it doesn´t behave the same way. I would need to reset my local dev branch to be able to reproduce the issue. It can be done running:

$ git reset --hard origin/main

Change the Most Recent Commit Message

The command below will open the text editor where we can change the commit message:

$ git commit --amend

Adding a File to the Most Recent Commit

$ git add missed-file.txt
$ git commit --amend

Undo the Most Recent Commit

$ git reset HEAD~

After

Once the work is done, many situations can happen. Here is how to deal with them.

Changing Several Commits in Bulk

If commits were done with a wrong author, use Git Rebase to fix the authors of the commits:

$ git rebase -i -p <commit-id>
$ git commit --amend --author="John Doe <john@doe.org>"
$ git rebase --continue
$ git push -f origin master

The rebase starts from the commit after the informed <commit-id>. It wouldn’t work if the rebase needs to consider the very first commit. To include the first commit, start an interactive rebase of all commits using git rebase -i --root.

Undo One or More Commits Pushed to Remote

Update the working branch to have it as a backup:

$ cd ~/java/projects/project
$ git pull origin master

Create a new clone to use as experimentation:

$ cd ..
$ git clone git@github.com:htmfilho/project.git project-temp
$ cd project-temp

You can also clone a specific branch:

$ git clone --branch bugfix git@github.com:htmfilho/project.git project-temp

Look at the log to see the id of the latest valid commit:

$ git log

Force the head of the tree to point to the latest valid commit:

$ git reset –hard 73d48037

Force the new head into the remote branch (origin):

$ git push –force origin master

The clients that still have the old commits should update their local branches accordingly before the next push:

$ git reset –hard origin/master

Remove a File From the Repository Without Deleting It

For a single file:

$ git rm --cached mylogfile.log

For a single directory:

$ git rm --cached -r logs

Restore a Deleted Branch

The follow commands recover a branch that was deleted locally with the command git branch -D issue-52. Use reflog to figure out the of the deleted branch:

$ git reflog

Take note of the and jump into it:

$ git checkout -b issue-52 dc4b3ff

Look at the log to see if it contains what you are looking for:

$ git log

Finally, move to the master branch and merge the recovered branch into it:

$ git checkout master
$ git merge issue-52

Cherry Pick a Commit from One Branch to Another

In some projects, we may have release branches. The ones that are deteched from the main branch when all the features planned for the release are done. This release branch allows development work to continue in the main branch without affecting what is about to be released.

However, when the release branch is deployed to a staging environment, we may find some bug that needs to be fixed before deploying to production. In this scenario, we have to push the same fix to the release branch as well as to the main branch, to ensure the fix is carried on to future releases.

To save some effort, start working on the release branch, commiting the fix and creating a pull request. Then follow these steps to add the fix to the main branch:

  1. Get the hash or the hard range of all commits related to the fix. You can see the hashes using:

    $ git log
    
  2. Move to the main branch:

    $ git checkout main
    $ git pull
    
  3. Create a branch for the change:

    $ git checkout -b fix-123
    
  4. Cherry-pick the fix from the release branch using the hashes:

    $ git cherry-pick <hash1> <hash2> <hash3>
    

After this, you may have to fix code conflicts, making sure the fix is adapted to recent changes in the main branch.