Intro

This page is an hands-on introduction to the very basics of Git. Git is a powerful version control system, with many features. In the following examples, we will introduce you to the very basics of Git as a version control system. It is supposed that you have a Git installation ready and running on you PC.

Setting up a Git repository

Identify yourself

First things first: let Git know who you are.

$ git config --global user.name "Your name"
$ git config --global user.email your.email@some.domain.com

In the commands above, the flag --global makes this configuration valid for other repositories you create on the same machine. This needs to be done only once in each machine (because you said it is global).

Create the repository

The init command creates the base structure that will support you repo; in other words, init creates a new repository. Change (cd) to the directory where you want to create your repository and issue the following:

[pf@localhost git_practice]$ git init
Initialized empty Git repository in [current path]/.git/

The new folder, .git, will keep all the data required to manage the repository.

Check the repository

status will probably be the Git command you will use the most. Start now, to see how your repo is:

pf@localhost git_practice]$ git status
On branch master

No commits yet

nothing to commit (create/copy files and use "git add" to track)

Add your first file

Let’s create a small file, that will be used to show how to work with Git. I can be the very basic “Hello, World!” program:

#include <stdio.h>

int main(void)
{
  printf("Hello, world!");
}

and save it as prog.c.

Check again for status:

[pf@localhost git_practice]$ git status
On branch master

No commits yet

Untracked files:
  (use "git add <file>..." to include in what will be committed)

	prog.c

nothing added to commit but untracked files present (use "git add" to track)

Two things are worth noticing: 1) although Git is able to detect files in your folder, it considers them untracked and will ignore them. 2) Git will, most of (if not all) the times, give you a (correct) hint about what to do. In this case: use git add to make the files tracked.

So, add this new file to the repo:

[pf@localhost git_practice]$ git add prog.c

and let us check the status of the repo again:

[pf@localhost git_practice]$ git status
On branch master

No commits yet

Changes to be committed:
  (use "git rm --cached <file>..." to unstage)

	new file:   prog.c

At this point, prog.c is staged, meaning it is ready to be stored in the repository, but it is not yet there. Moving the files to the repository is done by the command commit. This command requires a descriptive message, that can be included in the command line, as follows:

[pf@localhost git_practice]$ git commit -m "First version"
[master (root-commit) 93032fb] First version
 1 file changed, 6 insertions(+)
 create mode 100644 prog.c

The file is now in the repository. We can check this with the status command:

[pf@localhost git_practice]$ git status
On branch master
nothing to commit, working tree clean

There are no pending changes; everything has been stored in the Git repository.

Making Changes

You now realize that the message in the program should terminate with a newline character. Accordingly, you edit the program source code to correct it. After saving the new version, git status now yields:

[pf@localhost git_practice]$ git status
On branch master
Changes not staged for commit:
  (use "git add <file>..." to update what will be committed)
  (use "git checkout -- <file>..." to discard changes in working directory)

	modified:   prog.c

no changes added to commit (use "git add" and/or "git commit -a")

The file is modified but not staged for commit (meaning that, if you issue a commit, prog.c will not be stored in the respository). Notice, again, the suggestions made by Git. You should either add the file or you can use the -a switch, which tells Git to to automatically stage files that have been modified and deleted (no need to add them explicitely).

To add this new version of prog.c:

[pf@localhost git_practice]$ git commit -a -m "Added newline after message."
[master 81c1b51] Added newline after message.
 1 file changed, 1 insertion(+), 1 deletion(-)

See the history of the repository

Often, you will need to know what has happened to the repo. The log command show the commit history of the repo, and it can have several variants:

git log
git log --oneline
git log --graph
git log --all

You can test the result or use the command git help log to check the meaning of these (and many other) switches.

Check the log of your system with the --oneline and --graph switches:

[pf@localhost git_practice]$ git log --graph --oneline
* 81c1b51 (HEAD -> master) Added newline after message.
* 93032fb First version

You can see now the two commits, one per line. Notice that each commit is identified by a 7-digit code, that you can find in the git command output when you performed each of these commit operations. The most recent commit is identified by HEAD.

Going back in history

Suppose that you made some more changes to your source file and committed them. At some point, you are not happy with the current version, and you want to go back to some previous point in time. To to that, you will use Git’s checkout command together with the 7-digit code that identifies the point in the development history where you want to go to. Suppose you want to go back to the very first version of you code (the one with the code 93032fb). In other words, you want to checkout the commit identified by 93032fb.

[pf@localhost git_practice]$ git checkout 93032fb
Note: checking out '93032fb'.

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 93032fb First version

Git issues quite a few messages, warning us that we are in detached HEAD. Let’s issue a git log command to see how we are:

[pf@localhost git_practice]$ git log --oneline
93032fb (HEAD) First version

Now the HEAD is no longer at the most recent version, but pointing at an older version of the code (it is detached). Note also that, as we “went back on time”, all other commits disappeared from the log command output. If that disturbs you, using the --all log switch above can be of help:

[pf@localhost git_practice]$ git log --oneline --all --graph
* 19c859a (master) Comments added.
* 81c1b51 Added newline after message.
* 93032fb (HEAD) First version

HEAD is down on the last line, but all the others have reappeared (no, they were not gone from the repo…)

Making changes on an previous snapshot

Let us now make some changes on the code that was recovered from “First version” and try to commit them. We want the message to be now “Hello, Brave New World!”.

[pf@localhost git_practice]$ git commit -a -m "New message: Brave New World"
[detached HEAD 74db8e7] New message: Brave New World

After this commit, the log command with all the above switches gives us the following:

[pf@localhost git_practice]$ git log --oneline --all --graph
* 74db8e7 (HEAD) New message: Brave New World
| * 19c859a (master) Comments added.
| * 81c1b51 Added newline after message.
|/  
* 93032fb First version

The code has now two branches:

  • one made by the snapshots with code 93032fb, 81c1b51 and 19c859a, which was the first we created; and
  • a new one, composed by the nodes 93032fb and 74db8e7, that runs in parallel with the first one and which is the one we are currently in (HEAD is in this branch).

Giving the new branch a name

The first branch created in a Git repository is, by default, called master1 (note the name in parentheses in the most recent node in the branch). The second branch that was created, is an independent line of development and it has still no name. The command branch is used to create a new branch:

pf@localhost git_practice]$ git branch new-branch

To check the result, issue the log command again:

[pf@localhost git_practice]$ git log --oneline --all --graph
* 74db8e7 (HEAD, new-branch) New message: Brave New World
| * 19c859a (master) Comments added.
| * 81c1b51 Added newline after message.
|/  
* 93032fb First version

The HEAD has now a branch name. Notice that branch command assigns the given name to the branch of the current snapshot.

Changing to another branch

The checkout command can be used to move between branches. To go back to the master branch, one must issue:

[pf@localhost git_practice]$ git checkout master
Previous HEAD position was 74db8e7 New message: Brave New World
Switched to branch 'master'

The log command allows to verify where we are in the development tree:

[pf@localhost git_practice]$ git log --oneline --all --graph
* 74db8e7 (new-branch) New message: Brave New World
| * 19c859a (HEAD -> master) Comments added.
| * 81c1b51 Added newline after message.
|/  
* 93032fb First version

The HEAD is now associated to the master branch.

Merging branches

At this point, we have contributions in two different branches. In many situations, we reach a point where we want to join the two development lines (or branches) into a single one. That is the job of the merge command. In this case, we have two different versions of prog.c in the master and new-branch branches.

The following command causes the new-branch branch merge into the master branch:

[pf@localhost git_practice1]$ git merge new-branch
Auto-merging prog.c
CONFLICT (content): Merge conflict in prog.c
Automatic merge failed; fix conflicts and then commit the result.

This signals that Git was not capable of solving the differences and creating a single, merged file on its own and user intervention is required. Git signals the differences among versions in the source file:

/*
 * Git demo program
 */
#include <stdio.h>

int main(void)
{
<<<<<<< HEAD
  printf("Hello, world!\n");
=======
  printf("Hello, Brave New World!");
>>>>>>> new-branch
}

You can see the sections of code that correspond to the HEAD snapshot and to the new-branch that are conflicting. The section corresponding to the HEAD version is between <<<<<<< and =======, and the one in new-branch is between ====== and >>>>>>>>. You should edit the file selecting the version you want to keep and removing all the conflict markers. Suppose that you want to keep the “Hello, Brave New World” version, the resulting file would now be:

/*
 * Git demo program
 */
#include <stdio.h>

int main(void)
{
  printf("Hello, Brave New World!");
}

git status shows the current state:

[pf@localhost git_practice1]$ git status
On branch master
You have unmerged paths.
  (fix conflicts and run "git commit")
  (use "git merge --abort" to abort the merge)

Unmerged paths:
  (use "git add <file>..." to mark resolution)

	both modified:   prog.c

no changes added to commit (use "git add" and/or "git commit -a")

You should now commit this new version, having solved the conflicts signaled by Git:

[pf@localhost git_practice1]$ git commit -a -m "Conflits solved. Merge with new-branch"
[master fc8b580] Conflits solved. Merge with new-branch

The graph in the log shows that the two branches have merged into a single one:

[pf@localhost git_practice1]$ git log --oneline --all --graph
*   fc8b580 (HEAD -> master) Conflits solved. Merge with new-branch
|\  
| * 74db8e7 (new-branch) New message: Brave New World
* | 19c859a Comments added.
* | 81c1b51 Added newline after message.
|/  
* 93032fb First version

More to go

The examples above cover just a fraction of what is possible to do with Git. They merely explore the basics of committing and checking out from a local repository. Most usage of Git involves remote repositories, a topic we did not address.

There are many sources of information that you can use to evolve in using Git. The Git Book would be a good choice. Many other pages are also available; feel free to explore them.

  1. In recent editions of Git, and following contemporary cultural tendencies, the default name master has been changed to main