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
Software requirements and conventions used
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:
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!