Introductory tutorial to Git on Linux

Git is without doubt the most used version control system in the world. The software is open source, released under the GPLv2 license, and was created by Linus Torvalds, which is also the father of Linux. In this tutorial we learn
the basic concepts behind its usage, we see how to create or clone a git repository and how to perform the basic actions involved in the git workflow.

In this tutorial you will learn:

  • The basic git concepts
  • How to create a git repository
  • How to clone a git repository
  • How to add file contents to the repository index
  • How to create a commit
  • How to push changes to a remote repository
  • How to pull changes from a remote repository
main-image

Introductory tutorial to Git on Linux

Software requirements and conventions used

Software Requirements and Linux Command Line Conventions
Category Requirements, Conventions or Software Version Used
System Distribution independent
Software git
Other None
Conventions # – requires given linux-commands to be executed with root privileges either directly as a root user or by use of sudo command
$ – requires given linux-commands to be executed as a regular non-privileged user

Basic git concepts

Before we start learning the basic git commands we will use in our workflow, we should clarify some key concepts that will recur in this tutorial. In the table below you can see some of the git terminology keywords and their meaning:

Term Definition
index The “staging” area of a repository. Changes we include with the add command are “stored” here. When we create a commit, it is the index content that is included
branch An isolated line of development which spawns from a certain point of its “parent”
commit An operation which consists into integrating the changes stored in the index of a repository into the repository history
HEAD A reference to the last commit of a branch
working tree The directory associated with our repository; usually, but not necessarily, the one containing the .git subdirectory
checkout The act of switching between different states of a repository, represented by branches or commits

Creating a git repository

Let’s start from the beginning. Suppose we want to create a new, local, git repository. How can we do it? The git command which let us accomplish this task is init: with it, we create an empty repository or re-initialize an
existing one. Supposing we want to create a repository in a directory called “linuxconfig”, we would run:

$ git init linuxconfig

In the example above we provided the path of the project directory as argument to the command. When we do so, the directory is created if it doesn’t already exist. Passing the directory path as argument to the command is, optional: if it is omitted the repository will be initialized in the current working directory.

If the command above is successful, a .git subdirectory is created in the specified path: this is where all the files needed by git are kept:

$ ls -a linuxconfig/.git
.  ..  branches  config  description  HEAD  hooks  info  objects  refs


Typically, the directory which contains the .git subdirectory, represents our working tree: it is here that we will work on our code and our project files are (or will be) placed. Here we say “typically” because when initializing a git repository it is possible to create detached working trees. We will not expand on this topic here: the important thing, at this time, is getting the basic concepts.

Creating a “bare” repository

In the previous section we saw how to create a standard git repository, which, as we saw, includes a working tree. Another type of git repository exists, however: it is what is called a “bare” repository. What does differentiate a
“bare” repository from a “standard” one? Git “bare” repositories are used as “remotes” counterparts of the local repositories. In the git workflow, they are used to share the code, not to work directly on it, therefore they don’t
include a working tree. To create a git “bare” repository, all we have to do is to add the --bare option to the command we saw in the previous example:

$ git init --bare linuxconfig

A “bare” repository does not contain a .git subdirectory, but the files and directories normally contained inside of it:

$ ls linuxconfig
branches  config  description  HEAD  hooks  info  objects  refs

A typical example of “bare” repositories, are those we create when using services like github or gitlab.

Cloning a git repository

In case the source code of a project is already managed using git and we want to contribute on it, we need to create a local copy of it on our system. To do so we have to use the clone git command. Supposing the repository URL is
https://github.com/egdoc/linuxconfig, we would run:

$ git clone https://github.com/egdoc/linuxconfig

The command above will clone the repository in a directory called linuxconfig; if a directory with the same name already exists and is not empty, the command will fail. It is possible, however, to explicitly provide the name of the directory that should be used for the cloned repository. For example, to clone the repository as linuxconfig_repo, we would run:

$ git clone https://gitlab.com/egdoc/linuxconfig linuxconfig_repo

When we clone a git repository a full “copy” of the remote one, with all its branches, is created locally and the currently active branch of the cloned repository (typically the “master” branch) is checked out.

Cloning an existing local repository to a bare one

In previous examples we saw what is the difference between a “bare” and “standard” repository. We also saw how to clone a repository, created on platforms like github or gitlab. What if we started by creating a local, standard, repository and we now want to share it on a private server so it can be cloned by other users? The fastest method to use in this case is to clone the local repository into a “bare” one; we can do this by using the --bare option. For example:

$ git clone --bare linuxconfig linuxconfig.git
Cloning into bare repository 'linuxconfig.git'...
done.

In the above example you can see we cloned the repository contained in the linuxconfig directory into the linuxconfig.git directory. Using the .git suffix is a convention for naming directories containing “bare” repositories. At this point, all we have to do is to transfer the “bare” repository to the server, so that it can be reached and cloned by other users.

Git basic workflow

The basic git workflow consists into performing the changes we need to our source code, adding the changed files content to the repository index and finally creating a commit which will include them and integrate them in the
repository index. When ready we also may want to push the changes to the remote repository. Let’s see some examples.

Adding and removing file contents to the repository index

Suppose we want to add a new file to our repository, or that we have modified the content of an already existing one. How can we add the changes to the index of the repository? This is what the add git command is for. Let’s see an
example. First we create a new file in the repository (it contains only the “hello world” string):

$ echo "hello world" > newfile.txt

To add the content of the file to the index of our project we run the following command:

$ git add newfile.txt

To verify the content of the file was added to the repository index we can use the git status command. In our case it produces the following output:

$ git status
On branch master

No commits yet

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

To accomplish the opposite action, and so to remove a file from the repository index, we use the git rm subcommand. By default this command does remove the content from the index and the file from the working tree. If we want only the former action to be performed, we should invoke the command with the --cached option:

# This command will remove the content from the index and the file from the
# working tree
$ git rm newfile.txt

# If we use the --cached option, the file content will be removed from the index
# but the file will not be removed from the working tree (it will become
# 'untracked')
$ git rm --cached newfile.txt


If we run the git status command after removing the content from the index, we can see that newfile.txt is now untracked:

$ git status
On branch master

No commits yet

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

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

The next step in the workflow is to create a commit which will include the staged changes.

Creating a commit

In the previous section we saw how to add a content to our index. We can now create a commit which will record the staged changes to the history of our repository. The git command we have to use to perform this task is, as you
can expect, commit:

$ git commit

As soon as we launch the command, the default text editor will be opened, so to let us write our commit message. It is very important for it to be clear and descriptive of the changes we done in the repository:

git-commit-editor

Writing the commit message The commit is registered as soon as we save and close the editor. Immediately

after, a message describing the changes included in the commit, will appear in the terminal:

master (root-commit) c92ba37] Added newfile.txt
 1 file changed, 1 insertion(+)
 create mode 100644 newfile.txt

In this case the commit message was “Added newfile.txt”. If we don’t want our editor to be opened but we want to provide the message directly from the command line, we can use the -m (--message) option when launching the
commit command, and provide the message as argument:

$ git commit -m "Added newfile.txt"

When creating commits is very important to be as atomic as possible, and include small changes, in order to keep the history of our repository as clean as possible.

Obtaining a list of created commits

To obtain a list of all the commits in our repository, we can use the git log command. For the sake of this example we changed the content of the newfile.txt (we just added an exclamation mark at the end of the line), and created another commit. When we run the command we obtain the following result:

$ git log
commit a90ed0a680659777e5f589904720b8055fb6ab4b (HEAD -> master)
Author: egdoc <egdoc.dev@gmail.com>
Date:   Fri Jun 25 07:31:01 2021 +0200

    Added exclamation mark

commit c92ba378b81031c74c572e043a370a27a087bbea
Author: egdoc <egdoc.dev@gmail.com>
Date:   Fri Jun 25 07:06:22 2021 +0200

    Added newfile.txt

As you can see, recent commits are displayed first; for each one of them we can see the SHA-1 checksum, the Author, the Date and the message. As you can see, the actual content of the commit is not displayed by default.
If we want to include it in the output we should use the -p option to the command. In this case the output becomes:

commit a90ed0a680659777e5f589904720b8055fb6ab4b (HEAD -> master)
Author: egdoc <egdoc.dev@gmail.com>
Date:   Fri Jun 25 07:31:01 2021 +0200

    Added exclamation mark

diff --git a/newfile.txt b/newfile.txt
index 3b18e51..a042389 100644
--- a/newfile.txt
+++ b/newfile.txt
@@ -1 +1 @@
-hello world
+hello world!

commit c92ba378b81031c74c572e043a370a27a087bbea
Author: egdoc <egdoc.dev@gmail.com>
Date:   Fri Jun 25 07:06:22 2021 +0200

    Added newfile.txt

diff --git a/newfile.txt b/newfile.txt
new file mode 100644
index 0000000..3b18e51
--- /dev/null
+++ b/newfile.txt
@@ -0,0 +1 @@

Pushing changes to the remote repository

We created two commits in our local repository “master” branch, how can we include them in the remote repository used to share the code? To perform this action we must use the push command:

$ git push

When we run this command without any argument, just as we did above, the “destination” of the push will be the remote counterpart of the branch we are working in. If we want to specify the remote branch explicitly, instead, we
should use the following syntax:

git push <remote> <branch>


This can be useful, for example, if the branch we are working in locally doesn’t already exist in the remote. With the command above it will be automatically created for us. Since in our case we are working in the “master”
branch, and the remote repository is called “origin”, we would run:

$ git push --set-upstream origin master

In the example you can notice that we used the --set-upstream option to the command: this sets the remote repository branch as the upstream counterpart of the local one, so each time we will run git push without any other arguments, git will know in what remote branch it should push the changes.

Pulling changes

The pull git subcommand basically performs the opposite action of push: it makes so that changes that exist in the remote repository are integrated with our local working copy. Suppose a new commit exists in the remote repository
(perhaps it was created by a colleague); to integrate it in our local copy, we should run:

$ git pull

Or to be more explicit, in this case:

$ git pull origin master

In our example, a README.md file was added to the project, so the result of the command above, in this case, is the following:

From https://github.com/egdoc/linuxconfig
 * branch            master     -> FETCH_HEAD
Updating 1bfd5fd..6f5ca0d
Fast-forward
 README.md | 1 +
 1 file changed, 1 insertion(+)
 create mode 100644 README.md

Conclusions

In this tutorial we learned the basic concepts and terminology behind the git usage. We learned the difference between a standard and a bare repository, how to create them, how to clone an existing repository locally and the typical actions involved in the git workflow: we saw how to add changes to the repository index, how to create a commit, and how to push it to a remote repo. We also saw how to perform the opposite action, and pull changes existing in the remote repo to our local, working version. Hopefully this will be enough to get you started, but is just a brief introduction: the best way to learn and get better at something is to try it!



Comments and Discussions
Linux Forum