Pocket Git Guide

A short entry-level approach to Git

Reasons to use Git

Git will help you doing the following things with a computer:

Personal

Team

Get Git

Git is free. You can get Git for any platform at git-scm.com/downloads.

No matter what platform you are using, all of the examples in this document are to be run from inside a command shell with access to Git.

Get Git on your Mac

I find it a little bit faster and more comfortable to use the GitHub Desktop Installer for setting up Git on either Mac or Windows.

On a Mac, after installation, fire up GitHub Desktop, open the Preferences screen, go to the advanced tab and activate Install Command Line Tools to have the Git shell on your Mac. If you are using GitHub Desktop the first time, you can either install the command line tools from the welcome screen of GitHub Desktop.

Though not mandatory, for an improved Git experience in the shell, I like to have the following configured:

export GIT_PS1_SHOWDIRTYSTATE=true
export GIT_PS1_SHOWUNTRACKEDFILES=true
PS1='\u@\h \W$(__git_ps1 " (%s)")\$ '

Get Git on Windows

On Windows, the installation of GitHub Desktop will bring Git and the GitHub Desktop Client on your machine. This all with minimal configuration effort for yourself. I think it´s a good starting point for your Git experience on Windows.

The Git shell

You can use a slick GUI client on top of Git, like for example Tower on a Mac or the reduced GitHub Desktop. Sometimes things go faster and easier with a GUI client. Anyway, an advantage of the command shell is, it works the same on all platforms. What you learned here can be used anywhere. I assume it´s not so bad to start at the bottom to understand the tool right.

Tell Git who you are

git config [--global] user.name ["your name or user name"]
git config [--global] user.email ["your email address"]

Use the --global option to tell Git that the given configuration will be the default for all of your projects on your computer. After these settings have been made, your user data will be added to your commits. When you then push commits to a shared server, your name and email address will also appear on that server.

Detect your configuration settings with git config --list.

Get help from Git

You can type git help <command> to get access to user documentation about the specified <command>. To only get short info about the command syntax, use git <command> -h.

Create a new local repository

Move to the folder which should contain your project cd /path/to/your/prj/ , then initialize the Git repository for the project with git init.

Status of your repository

When inside of a local Git repository git status will tell you what branch you are currently working on and give you an overview of untracked changes and outstanding commits.

Working, staging and committing

Versioning in Git is achieved by creating snapshots of your entire working directory. A snapshot is created with a commit. For files without changes, Git will maintain a reference to the previous snapshot. This means, each commit represents your entire working directory at that time, but stores only the files which have been changed. Technically speaking, Git maintains a directed acyclic graph of snapshots of your work.

Figure: Git produces snapshots of your entire project

While you are working, Git divides your content into three main sections. Your working directory, your staging area (often referred to as index) and your committed work.

Figure: Working – Staging – Committing

Stage to tell Git what to refer to

To prepare a snapshot of your current work, which will be stored in the Git repository, call git add <pathspec>.

<pathspec> specifies the files to be put into the staging area (the index). Wildcards are allowed.

If a version of a file is not staged, Git doesn´t know how to refer to that version of the file and therefore you can´t commit it later on. Staged but uncommitted content remains only on your local computer and will not be sent to a remote repository somewhere else.

If you omit the <pathspec> , use git add --all which will ensure all untracked files in your current project are being added to your staging area.

The opposite of git add is git reset <pathspec>. With git reset you can un-stage contents.

If you created files or directories without staging them, and you want to get rid of them, use git clean [-ifd].

-i show what would be done and clean files interactively.

-f if the git config clean.requireForce false is not set, this option is needed to proceed with the command.

-d will also remove directories in addition to files.

Commit to make a snapshot of your work

You will commit very often, multiple times a day, sometimes within in minutes. It is the action you do most of the time when using Git.

Do not commit half-done, not-functioning work. Any commit should be a small solution for its own. As an example, a commit can contain the refactoring of a single method in your code or the rephrasing of a paragraph in a text document.

Contents which have been staged must be committed to produce a snapshot of your current work in the Git repository. Any commit is self-contained, it does not only reference your current changes but everything which makes up the state of your current project at the time you are committing. This is because each commit contains a pointer to its direct predecessor, the parent commit. Beginning at the most recent commit, the tip, the list of commits is a sequence pointing to the past, defining your entire project at the current time.

Commit with git commit [-a] [-m "your commit message"].

-a is a nice shorthand option even to stage content which has been modified or deleted without a previous git add command. New contents still need to be staged with the git add command. With -m "your commit message" you tell your co-workers and probably yourself why you made the commit.

An even shorter form of committing, in that case, is git commit -am "your commit message".

Here the option to stage modified and deleted contents and the option to provide a commit message are combined in one option -am. You can combine multiple options in a single one like here, the only restriction is that only the last option can take an argument, like the commit message.

If you don´t specify a commit message when firing the commit command, an editor will be opened where you have to provide the commit message. You can configure the editor of your preference with git config --global core.editor <editor-name>.

help.github.com/articles/associating-text-editors-with-git provides a brief guide to setting up several editors. To see the currently configured editor, type git config [--global] core.editor.

Once you configured your preferred editor, you can not only write your commit messages by using it, you can also edit all of your configurations easily with the command git config [--global] --edit.

To see the history of commits use git log. The output of a git log can be filtered and formatted with git log [--oneline] [--pretty] [<branch-name>].

For example git log --oneline will display most recent commits organized into one single line per commit.

git log --oneline <branch-name> will display most recent commits in the specified branch, organized into one single line per commit.

A free formatting of the git log output can be achieved with git log --pretty="<your format string>"

A list of available formatting options is on git-scm.com/docs/pretty-formats.

The commit message

A git log of commit messages should give an idea of how the project has evolved. Each message explains what has been accomplished or changed with the commit. The message should describe a whole idea of completed work [Westby 2014]. Don´t describe how the change was accomplished – that´s in the code.

A properly formed commit subject line should complete the following sentence:

If applied, this commit will <your commit subject line>

The commit messages reveal whether a committer is a good collaborator or not.

Because in the output of a git log is not much space to display text and we do not have time to read through many sentences to understand what a commit was about, as a rule of thumb, the subject of the commit message should contain one line and 50 or fewer characters. Start the subject with a capital letter and do not end with a period.

Use the imperative mood, which means „spoken or written as if giving a command or instruction.“ Examples:

If more explanation is needed, let a blank line follow the subject and then write the body of the commit message, wrapped at 72 characters per line, as Git will not wrap text automatically. Explain why the commit was made, again, not how.

In other words, follow the seven rules of a great Git commit message
[Beams 2014]:

  1. Use the imperative mood in the subject line
  2. Limit the subject to 50 characters
  3. Capitalize the subject line
  4. Do not end the subject line with a period
  5. Separate the subject from the body with a blank line
  6. Wrap the body at 72 characters
  7. Use the body to explain what and why vs. how

Removing files

To delete a file from the working copy and the staging index, use git rm <pathspec>.

<pathspec> describes the file or even files (wildcards allowed), which should be deleted. git rm will remove the file in your working copy and will stage the remove so that the removal can be committed in a subsequent commit. If you forgot to use git rm at first hand and instead removed the file with your usual remove command in the shell, the file will be removed from the working copy, but not from the staging index. In that case, you can even call git rm <pathspec> afterward, to have the file be removed from the index, as it is already removed from the file system.

Renaming files

If you rename a file in your command shell with mv a.txt b.txt , it will produce a similar situation as if you would remove a file with just the shell command rm a.txt and creating a new file b.txt. Git would still try to keep track of a.txt. To fix this, you would have to

git rm a.txt
git add b.txt

Or, use the suitable Git command right from the start: git mv a.txt b.txt which can be generalized to git mv <current-path> <new-path>

Ignoring files

If Git should ignore some files in your project, for example, because

you can specify patterns inside of the .gitignore file to exclude these from Git version control. Each pattern is one line in the .gitignore file. The .gitignore usually is in the root of your Git project.

An example of the patterns your .gitignore may contain

*.a
build/
doc/*.txt
doc/**/*.txt
!doc/todo.txt

The meaning of the patterns:

The .gitignore file itself should be under version control – so be aware of not putting .gitignore as a pattern into the .gitignore file.

Branch to isolate

Any contents in Git must be in a branch. The first branch of a Git repository is the master branch. Technically it is a branch like all other branches, but conceptually it is the primary, stable version of whatever is stored in the repository.

A commit will always be done inside of a particular branch. But while commits point to the past, a branch is a concept for the future. A branch is a virtual copy of your project, where commits can be made freely in isolation from whatever else may happen in the repository. You would create a branch to experiment with some new feature inside of your project, to fix a bug or to do other things which you want to have separated from everything else until you have indeed found what you are after in your branch.

While you can have multiple branches in your repository, there is always exactly one working branch in your local repository, which is the one you are currently working on. This is what is in your working directory or working copy. Any commit you make will be against the working branch.

git branch <branch-name> [<commit>] will create a new branch for you. Choose a short descriptive branch name. The optional <commit> specifies a commit to start from. If you don´t give the <commit> , the branch will be created from the latest commit in the current branch.

Figure: Creating a new branch with the name 'lazyload'

git branch <branch-name> will not make the newly created branch your current working copy; therefore your next commit would not be against the new branch. To make the new branch the active working copy, you need to git checkout <branch-name> after you have created your branch. Whatever you commit from that point on will be inside of your new branch and nowhere else.

Again, there is a shorthand command for creating a branch and making it the current working copy all at once: git checkout -b <branch-name> will create a new branch and make it the current working copy.

Figure: First commit in the 'lazyload' branch
Figure: Alternate picture for the first commit in the 'lazyload' branch
Figure: Multiple commits in the 'lazyload' branch

git branch [-a|-r] without any option this command will show you the current list of local branches with a * in front of the currently active branch. Local branches are the branches you are working on and where your commits go against. With -r the remote tracking branches will be shown. -a will show all local branches and all remote tracking branches.

Remote tracking branches are all branches from your remote server (please refer to Working with remotes to share with a team) which came with your most recent git fetch or git pull into your local repository (so they are called remote tracking, but indeed they are stored in your local repository). They are used to connect your work with a remote repository. Whenever you call get status and get a result like

Your branch is ahead of 'origin/<branch-name>' by 1 commit.
(use "git push" to publish your local commits)

Git has figured out a difference between your local branch and its counterpart, the remote tracking branch.

Remote tracking branches are named origin/. Don´t checkout such a branch via git checkout origin/<branch-name> – instead, do git checkout <branch-name> to make the remote tracking branch a local branch.

To see the history of commits in a branch-oriented tree format, use git log --graph --oneline.

The --graph option will produce the branch tree, and the --oneline option leads to each commit being displayed in a single line of the tree structure.

Compare your working directory

git diff will indicate the differences between your working directory and your staging area.

git diff [<branch-name or commit] compares your working directory with <branch-name or commit>.

git diff <older-commit> <newer-commit> will indicate the differences between the two commits.

I find myself now and then using one of the two options --stat or --color-words in conjunction with any of the above git diff commands.

--stat will display a little statistic of changed files with numbers of lines added and removed.

--color-words will indicate changes in files word by word, instead of line by line, which is the default.

Merge to include

Sometimes the work which has been done in a branch will be thrown away. You delete the branch, and everything is as if the branch never existed. If you don´t want to throw away your work, you probably have to bring the contents of your branch into the master branch. That´s what merge is for. All commits that have been made in your source branch have to be merged into your master branch.

To merge any branch into your master branch, you have to

git checkout master
git merge <source-branch-name>

The first command will bring you into the master branch; the second command will pull in the changes from the source branch into the master branch. The principle is always the same – make the branch into which you want to merge the working copy and then pull changes from any other branch into your working copy by

git checkout <destination-branch-name>
git merge <source-branch-name>

To be more precise, all commits from your source branch will be merged into your working copy, which is the checked out branch.

The simplest kind of merge is if nothing had been changed in the destination branch while you were working inside of the source branch.

Figure: Fast-forward merge of 'lazyload' into 'master' branch

In this case, any changes made in the source branch will entirely be added to the destination branch, which is called fast-forward. The tip (last commit, or head commit) of the destination branch and the tip of the source branch will point to the same commit then, which is the last commit that was made in the source branch. After that, both branches, the source branch and the destination branch, are identical except in their branch names.

A true merge is something different. That´s when both, the source branch and the destination branch, have been modified before merging.

Figure: 'master' and 'lazyload' have been modified – a true merge is needed

A fast-forward then is no longer possible, and Git has to figure out the combined state of the content, which will lead to a so-called merge commit.

Figure: After a true merge of 'master' and 'lazyload' a merge commit was created

Each changed file in each branch is compared. When Git identifies a line that has changed in either branch, that line is carried forward for inclusion in the destination merge.

Figure: A true merge goes line-by-line

As long as the branches don´t both contain changes to the same line, Git will merge and commit automatically with a generated commit message: Merge branch '<source-branch-name>' into '<destination-branch-name>'.

Unlike a regular commit, which has one parent commit, a merge commit has two parent commits.

Now, when two modified lines of the same file are overlapping during a merge, a merge conflict occurs. Git can not automatically solve this conflict. Instead, Git indicate the conflict in the console

CONFLICT (content): Merge conflict in <conflicting-file-name-in-destination-branch>
Automatic merge failed; fix conflicts and then commit the result.

and put a conflict marker into the file of the destination branch, indicating the conflicting lines.

<<<<<<< HEAD
<conflicting content line in destination-branch (the current working copy)>
=======
<conflicting content line in source-branch>

>>>>>>> <destination-branch-name>

To resolve the conflict, this entire section, including the angle brackets, needs to be edited and refactored into the final version you want to see in the file. After that, you can commit the merge with commit -am "your merge commit message".

Remove or rename a branch

Sometimes you want to remove a branch, e.g., after all work is done and the branch has been merged into the master. In order to go ahead, use git branch -d <branch-to-delete> to remove your local branch. To remove a remote branch (refer to Working with remotes to share with a team), use git push origin --delete <branch-to-delete>.

A rename of the current local branch can be achieved by git branch -m <new-branch-name>.

To have the renamed branch on the server, do

git push origin <new-branch-name>
git push origin --delete <old-branch-name>

the second push with the --delete option will remove the old branch from the remote.

Rewind your work

The git checkout can not only be used to checkout an entire branch to drive further the work in this separate branch. git checkout is also a way to go back to the history of your work.

git checkout <commit hash> will set your working directory into the state of the commit referred to with the commit hash (the code you see for each entry in the git log , something like c04ff32). You are working then in the so-called detached HEAD state. Git will inform you about detached HEAD with the following output:

git checkout c04ff32
Note: checking out 'c04ff32'.

You are in 'detached HEAD' state. You can look around, make experimental
changes and commit them, and you can discard any commits you make in this
state without impacting any branches by performing another checkout.

If you want to create a new branch to retain commits you create, you may
do so (now or later) by using -b with the checkout command again. Example:

git checkout -b <new-branch-name>

HEAD is now at c04ff32... New image for git committing over time

Another way to use git checkout is to use it for files.

git checkout <pathspec> will bring the file which is described by <pathspec> from the head of your current branch back into your working directory. This is useful to revert changes you did on a file while keeping the changes of other files you just modified in your working directory.

Working with remotes to share with a team

The Git working mode is offline by default. This is nice because you don´t need to have an account to sign in somewhere, you can start working with your repository.

A remote in the Git world is a physical copy of a repository. It may be on the same computer as the repository it was copied from (copying is named cloning in Git) or on a different computer far away. The essential part is, you can exchange data between the two repositories and keep them synchronized. Usually, a remote repository is placed on a server which is accessible by your team. Any new teammate initially will clone the repository from the server to his or her local machine. The Git name for this remote repository is usually origin. The origin has nothing special and is not distinct from the local repositories on the machines of each team member, except only by convention it is used as a hub to which the team members connect to synchronize their work with all others. This will happen in a way that team members are working locally on their own computers and eventually sync with the remote repository on the hub server by pulling and pushing committed content.

Figure: Origin is a hub by convention

Remote repositories are usually bare repositories, they have no staging area and no working copy like local repositories because no one is working directly on the remote repository. To indicate a bare repository usually .git is being appended to the name of the repository.

A remote may be on GitHub or on a self-hosted system you want to use as a hub.

Clone a remote repository to local

On your local machine move to the folder under which the remote repository should be cloned cd /path/to/parent/. Then clone the remote repository into the parent git clone <repo> [<new-folder-name>].

<new-folder-name> is the optional folder name of the cloned project on your computer.

<repo> is the path to the remote repository and has one of the following structures, each standing for a different protocol:

/path/to/repo.git
ssh://[user@]host.xz[:port]/path/to/repo.git
git://host.xz[:port]/path/to/repo.git
http[s]://host.xz[:port]/path/to/repo.git

The first protocol is the local protocol. This is useful if the remote repository will reside on a shared filesystem to which every team member has access. The other protocols are obviously ssh, git and http. To learn more about these, please refer to „Git on the server – the protocols“.

Sometimes you try to access a server via https, and the server is using a self-signed certificate. Git won´t accept this certificate but if you are sure about the server, for example, because the server is located in your local intranet, one possible solution is the following configuration in Git:

cd /path/to/your/repo
git config http.sslVerify false

As a global setting for your entire Git: git config --global http.sslVerify false.

Once you have cloned the remote to your computer, Git already configured the origin for your project. You can check that by

cd /path/to/parent/<repo>
git remote -v

which will display the fetch and push addresses being bound to the symbolic name origin.

Push data to remote

If you are working on your local project and committed your changes to a branch you want to bring to the remote repository now; the general Git command format is git push <remote> <remote-branch-name>. This will push the contents of the local branch, your current working copy, into the <remote-branch-name> on the remote repository.

Assuming you worked on a branch named lazyload and are currently on that local branch, your command is git push origin lazyload.

As already mentioned, remote repositories do not have working copies or staging areas and no humans who will resolve merge conflicts. Therefore, if you push to a remote and Git can not merge with a simple fast-forward, Git will reject the push. The situation needs to be fixed by pulling down the changes from the remote and then trying to push again.

The local branch names and the remote branch names are not related in Git. Even though they share the same name, Git doesn´t know that they logically represent the same piece of work. Therefore in each push, you have to tell Git to what branch on the remote you want to push. You can configure each local branch of your Git to track its remote counterpart. Once you have done it for a branch, you can push and pull against the remote just by typing git push and git pull without further specifying the remote and remote branch name.

Specify the tracking with git push -u origin lazyload or, generally git push -u <remote> <remote-branch-name>.

-u (alternatively --set-upstream ) is the option which will set up the tracking relationship between your current local branch and the remote branch. You only need to do it once per local branch.

A particular case of pushing is to remove a branch from the remote, as the removing of the local branch doesn´t remove the branch on the remote. The command goes git push origin --delete <remote-branch-name>.

Pull data from remote

If you want to get the latest changes, e.g. from the lazyload branch on the remote repository, into your local branch, call git pull origin lazyload or, in general, git pull <remote> <remote-branch-name>.

Git will automatically merge the remote changes into your local branch. In case of a merge conflict, resolve the conflict marker in your local branch, stage, commit your change and push to the remote.

The big picture

Figure: Git commands and their fields of operation

Command reference

The following list is by far not complete. Though I found myself using these commands and options most often. For a complete list please refer to git-scm.com/docs/.

Create new local repo

cd /path/to/your/prj/
git init

Copy existing repo

cd /path/to/parent/
git clone <repo> [<new-folder-name>]

Where <repo> is one of

/path/to/repo.git
ssh://[user@]host.xz[:port]/path/to/repo.git
git://host.xz[:port]/path/to/repo.git
http[s]://host.xz[:port]/path/to/repo.git

Watch out

git config [--global] http.sslVerify false

as one possible solution in case Git doesn´t allow you to connect to a repo via https.

Status of repo

git status

Stage content

git add <pathspec>

or

git add --all

to un-stage, use

git reset <pathspec>

Removing files which have not been staged

git clean [-ifd]

Commit content

git commit [-a] [-m "your commit message"]

where -a will stage all modified and deleted content and -m indicates the commit message, alternatively

git commit -am "your commit message"

See history of commits

git log [--oneline] [--pretty] [<branch-name>]

Remove file

git rm <pathspec>

Rename file

git mv <current-path> <new-path>

Ignore file

Use one pattern per line in .gitignore file

*.a
build/
doc/*.txt
doc/**/*.txt
!doc/todo.txt

See what branch you are on

git branch [-a|-r]

Create a branch

git branch <branch-name> [<commit>]

or

git checkout -b <branch-name>

to create and checkout the branch.

Checkout a branch

git checkout <branch-name>

Compare changes

git diff [--stat] [<branch-name or commit>]
git diff <older-commit> <newer-commit>

Merge

git checkout <destination-branch-name>
git merge <source-branch-name>

Rename a branch

git -m <branch-name>

Delete a branch

git -d <branch-name>

Activate an older commit

git checkout <commit-hash>

Discard file changes

git checkout <pathspec>

Push to server

git push [-u] <remote> <remote-branch-name>

to push from current branch to remote branch, where -u can be used once to track the remote branch from the current branch. After setting the upstream with -u, it can be pushed and pulled from the current branch without specifying <remote> and <remote-branch-name>.

Pull from server

git pull <remote> <remote-branch-name>

to pull from remote branch into current branch. If remote branch tracking has been activated before via -u in a git push, it can be pulled from the remote branch without specifying <remote> and <remote-branch-name>.

Tell Git who you are

git config [--global] user.name ["your name or user name"]
git config [--global] user.email ["your email address"]

Display your Git config

git config --list

Edit your Git config file

git config [--global] --edit

Configure your editor

Set your editor

git config --global core.editor <editor-name>

Display your current editor setting

git config core.editor

Let Git accept your self-signed certificate

git config [--global] http.sslVerify false

References

[Atlassian Git]
"Getting Git Right, " Altassian Git Tutorial, atlassian.com/git
[Bash Git Completion]
"Install Bash Git Completion, " github.com/bobthecow/git-flow-completion/wiki/Install-Bash-git-completion
[Beams 2014]
C. Beams, "How to Write a Git Commit Message, " 2014, chris.beams.io/posts/git-commit
[Caching Your Git Password]
"Caching your GitHub password in Git, " help.github.com/articles/caching-your-github-password-in-git/
[Chacon, Straub 2014]
S. Chacon, B. Straub, "Pro Git, " 2014, git-scm.com/book
[Cheat Sheet]
"Git Cheat Sheet, " www.git-tower.com/blog/git-cheat-sheet/
[Demaree 2016]
D. Demaree, “Git for Humans”, A Book Apart, 2016, abookapart.com/products/git-for-humans
[GitHub]
"Welcome home, developers, " github.com
[GitHub Desktop]
"Simple collaboration from your desktop, " desktop.github.com
[GitRef]
Git command reference, git-scm.com/docs/
[Git]
Git downloads for different platforms, git-scm.com/downloads
[Learn]
"Learn Git & Version Control, " www.git-tower.com/learn/
[Tower]
Version control with Git – made easy,
In a beautiful, efficient, and powerful app, git-tower.com
[Westby 2014]
E. J. Westby, "Dealing with Emergencies in Git, " 2014 24ways.org/2014/dealing-with-emergencies-in-git/
Comments