This hands-on tutorial will help you get familiar with Git
-
Install Git on your computer
Linux
$ sudo apt install git
MacOS
brew install git
Windows
Download the latest Git installer at Git for Windows.
-
Configure your account. This will set the name and email that will appear in your commit.
Use the email that you will use to create GitHub account
$ git config --global user.name "Your Name" $ git config --global user.email [email protected]
-
Create your first git repository
$ mkdir bbmri-it-git-tutorial $ cd bbmri-it-git-tutorial $ git init Initialized empty Git repository in /home/user/bbmri-it-git-tutorial
-
Create a file
$ echo . > README.md
-
Check the status
$ git status On branch main No commits yet Untracked files: (use "git add <file>..." to include in what will be committed) README.md nothing added to commit but untracked files present (use "git add" to track
git returns some useful information:
- The name of the current branch:
main
- The fact that there are no commits yet
- A list of untracked files (i.e., files that are in the current directory but not part of the repository yet)
- Some hints about actions that you can do
- The name of the current branch:
-
Let's follow the hint and add the README.md file to the list of tracked files and check the status
$ git add README.md $ git status On branch main No commits yet Changes to be committed: (use "git rm --cached <file>..." to unstage) new file: README.md
Something has changed: now git lists the changes that can be committed. The changes are: a new file has been added that was not part of the repository
-
Let's create our first commit: we commit the addition of the README.md
$ git commit -m "Add README.md" [main (root-commit) 660ae42] Add README.md 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 README.md
Git returns some information about the newly created commit, in particular the id of the commit (
660ae42
) followed by the commit message. Then some information about the files involved in the commit
Tip
In case you didn't give the commit message with -m "My first git commit"
git would have opened your default editor to add one.
The default editor can be configured using the command:
$ git config --global core.editor "vim"
NB: The editor must be a valid command on your computer
-
Let's check again the status of the files
$ git status On branch main nothing to commit, working tree clean
git tells that there are no changes to commit
-
Now we can see the history of our repository
$ git log commit 660ae424b9c05703a03d2f1b947c93e9ecc39497 (HEAD -> main) Author: Vittorio Meloni <[email protected]> Date: Thu Jun 12 14:16:55 2025 +0200 Add README.md
git returns the list of commits that (surprise) contains only our commit. As before git returns some useful information:
- the commit id, which is the long version of the id. The first part is, of course, the same as the one returned after the commit
- The position of the HEAD of our repository, which is our first commit
- The information of the author of the commit, corresponding to the ones provided configured at the beginning
- The date of the commit
-
Let's make some changes to the file. Open README.md with your favourite editor and add the line:
This is the repository of <your name> for the BBMRI.it Data Team School
Close the editor and check the status of the changes
$ git status On branch main Changes not staged for commit: (use "git add <file>..." to update what will be committed) (use "git restore <file>..." to discard changes in working directory) modified: README.md no changes added to commit (use "git add" and/or "git commit -a")
Since
README.md
is part of the repository, the list of changes shows there is amodified
file now.Notice also the new hints about the actions that you can perform:
git add <file>
to stage the file for commit orgit restore <file>
to restore the file to the HEAD version -
Before staging the file for commit we can see the diff of the file against the HEAD
$ git diff diff --git a/README.md b/README.md index e69de29..31016e2 100644 --- a/README.md +++ b/README.md @@ -0,0 +1 @@ +This is the first repository of Vittorio for the BBMRI.it Data Team School \ No newline at end of file
For each changed file git shows the changes of the files with respect to the HEAD
Here we can see that the line was added (+)
-
Let's stage the file for commit, commit the changes and see the status
$ git add README.md $ git status On branch main Changes to be committed: (use "git restore --staged <file>..." to unstage) modified: README.md
We still have the same list of files but the message changed: git tells that the changes are staged and ready to be committed.
Also look at the new hint: git suggests how to unstage the file.
-
Let's commit the changes and check the log
$ git commit -m "doc: add description of the repository" [main 172dff9] doc: add description of the repository 1 file changed, 1 insertion(+) $ git log commit 172dff9cbf78df528ee88170fb6cd71a56cbc0b0 (HEAD -> main) Author: Vittorio Meloni <[email protected]> Date: Thu Jun 12 15:17:30 2025 +0200 doc: add description of the repository commit e91c0112bade625b4425d3d15d18b910da3df4d3 Author: Vittorio Meloni <[email protected]> Date: Thu Jun 12 14:16:55 2025 +0200 Add README.md
git returns the list of commits. We see the first commit and the last one and we can see that the HEAD is changed
Tip
In case, after a commit, we notice some error in the commit message we can use the --amend
option to change the last commit message.
(e.g., git commit --amend -m "correct message"
)
NB: only the last commit can be amended
In this section we will start to use branches so we can diverge from the main line without messing with it and restarting
-
First of all we can check the list of branches in our repository
$ git branch * main
Currently, we only have the
main
branch. -
Let's create a new branch named
develop
and check again the list of branches$ git branch develop $ git branch develop * main
Now the list contains the new
develop
branch, but we are still in themain
branch as indicated by the*
before the name.
Tip
When your repository contains several branches, it may be really (really!) helpful to always have a glimpse of the current branch
In bash, you can set the PS1 variable in your .bashrc file to include the current git branch in your command prompt
An example is:
PS1='$(__git_ps1 " (%s)")\ $ '
that results in
(main) $
-
We can now move to
develop
branch:(main) $ git switch develop Switched to branch 'develop'
(develop) $ git branch * develop main
-
Let's start to add some new content to our project by creating a new python script and commiting it
(develop) $ mkdir src/ (develop) $ echo . > src/script.py (develop) $ git add src/script.py (develop) $ git commit -m "feat: add the hello world script" [develop f3de86b] feat: add the hello world script 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 src/script.py
Tip
Git doesn't allow the addition of a new empty directory (i.e., we couldn't directly add the src directory, it would have resulted in no effect). There may be situations when we want to have an empty directory. In this cases we can add a .keepme
file into the directory to keep it
-
Let's check the updated history
(develop) $ git log commit f3de86bc2769edefa18f728649b43b6fff3ecb83 (HEAD -> develop) Author: Vittorio Meloni <[email protected]> Date: Thu Jun 12 17:20:22 2025 +0200 feat: add the hello world script commit 172dff9cbf78df528ee88170fb6cd71a56cbc0b0 (main) Author: Vittorio Meloni <[email protected]> Date: Thu Jun 12 15:17:30 2025 +0200 doc: add description of the repository commit e91c0112bade625b4425d3d15d18b910da3df4d3 Author: Vittorio Meloni <[email protected]> Date: Thu Jun 12 14:16:55 2025 +0200 Add README.me
Now the log shows that the HEAD is in the develop branch where we have one commit while the
main
is one commit behind -
We can start editing our script
Add the following function to your python script
print("Hello world!")
And now perform the commit as you know at this point! :)
(develop) $ git add src/script.py (develop) $ git commit -m "feat: say hello to world"
NB You may have noticed that we have started using conventional commits. This is really encouraged!
-
Let's change our files to say hello to lot of people
def say_hello(recipient): print(f"Hello {recipient}") say_hello("biobankers!") say_hello("BBMRI IT!") say_hello("")
And stage the file for changes
(develop) $ git add src/script.py On branch develop Changes to be committed: (use "git restore --staged <file>..." to unstage) modified: src/script.py
-
Wait! In the last line we said hello to no one. We don't want to create a commit with this error. Let's unstage the file
(develop) $ git restore --staged src/script.py (develop) $ git status On branch develop Changes not staged for commit: (use "git add <file>..." to update what will be committed) (use "git restore <file>..." to discard changes in working directory) modified: src/script.py no changes added to commit (use "git add" and/or "git commit -a")
We unstaged the file so we don't have anything to commit.
NB: Notice that unstaging the file doesn't remove the changes to it. To discard the changes we should
git restore
the file after unstaging it -
After removing the line, commit the changes
(develop) $ git add src/script.py (develop) $ git commit -m "feat: add say_hello function to say hello to lot of people"
-
We have made quiet some changes now. Let's check the log and see the history them
commit 5ce1ad6f1809bf1069a2754a555eca86f8540a65 (HEAD -> develop) Author: Vittorio Meloni <[email protected]> Date: Fri Jun 13 10:20:44 2025 +0200 feat: add say_hello function to say hello to lot of people commit b86eb31793155a5539646d7475c193610308e95b Author: Vittorio Meloni <[email protected]> Date: Fri Jun 13 10:01:40 2025 +0200 feat: say hello to world commit 757baec96c0dc6b802a2a785e3f2521c774df82d Author: Vittorio Meloni <[email protected]> Date: Fri Jun 13 09:52:06 2025 +0200 feat: add the hello world script commit 504e6942e04d808c2004d46ad6034c6b6873f0b3 (main) Author: Vittorio Meloni <[email protected]> Date: Fri Jun 13 09:50:56 2025 +0200 doc: add description of the repository commit c8dc3aa36b62dc47c961bc5b7a866656144e8694 Author: Vittorio Meloni <[email protected]> Date: Fri Jun 13 09:48:51 2025 +0200 Add README.md
We notice that there are 5 changes now in develop now, but main is still at commit
504e694
Let's visualize the repository to clarify the current situation. (we are using gitg here)
-
We decided that we have enough changes and we want to merge them into the
main
branch. First let's go back to main(develop) $ git switch main (main) $ git log commit 504e6942e04d808c2004d46ad6034c6b6873f0b3 (HEAD -> main) Author: Vittorio Meloni <[email protected]> Date: Fri Jun 13 09:50:56 2025 +0200 doc: add description of the repository commit c8dc3aa36b62dc47c961bc5b7a866656144e8694 Author: Vittorio Meloni <[email protected]> Date: Fri Jun 13 09:48:51 2025 +0200 Add README.md
Now the
HEAD
is inmain
and the list of commits only contains the previous commits -
And now we can merge the changes
(main) $ git merge develop Updating 504e694..5ce1ad6 Fast-forward src/script.py | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 src/script.py
Git recap the changes performed in
develop
. -
As usual let's see the log
(main) $ git log commit 5ce1ad6f1809bf1069a2754a555eca86f8540a65 (HEAD -> main, develop) Author: Vittorio Meloni <[email protected]> Date: Fri Jun 13 10:20:44 2025 +0200 feat: add say_hello function to say hello to lot of people commit b86eb31793155a5539646d7475c193610308e95b Author: Vittorio Meloni <[email protected]> Date: Fri Jun 13 10:01:40 2025 +0200 feat: say hello to world commit 757baec96c0dc6b802a2a785e3f2521c774df82d Author: Vittorio Meloni <[email protected]> Date: Fri Jun 13 09:52:06 2025 +0200 feat: add the hello world script commit 504e6942e04d808c2004d46ad6034c6b6873f0b3 Author: Vittorio Meloni <[email protected]> Date: Fri Jun 13 09:50:56 2025 +0200 doc: add description of the repository commit c8dc3aa36b62dc47c961bc5b7a866656144e8694 Author: Vittorio Meloni <[email protected]> Date: Fri Jun 13 09:48:51 2025 +0200 Add README.md
Git shows here that now
develop
andmain
converged on the same commit -
It's time to raise the bar and create some conflicts!
First, we create two more branches from develop that we call featA and featB
(main) $ git switch develop (develop) $ git branch featA (develop) $ git switch -c featB
Notice that we used
git switch -c featB
that is a shorcut forgit branch featB; git switch featB
We are in featB branch now
Let's edit the script adding a sentence to tell people what we're doing.
def say_hello(recipient): print(f"Hello {recipient}. I'm learning Git!") say_hello("biobankers!") say_hello("BBMRI IT!")
And as usual we commit our changes.
(featB) $ git add src/script (featB) $ git commit -m "feat: tell people what I'm doing" (featB) $ git log commit 9891aadff70fbed845e5a85c17271d8d7badf3fa (HEAD -> featB) Author: Vittorio Meloni <[email protected]> Date: Fri Jun 13 11:34:58 2025 +0200 feat: tell people what I'm doing commit 5ce1ad6f1809bf1069a2754a555eca86f8540a65 (main, featA, develop) Author: Vittorio Meloni <[email protected]> Date: Fri Jun 13 10:20:44 2025 +0200 feat: add say_hello function to say hello to lot of people ...
We added a new commit in
featB
. Nothing different for now -
Let's leave
featB
and move tofeatA
(featB) $ git switch featA
Edit again the script.
def say_hello(recipient): print(f"Hello {recipient}. I'm learning GIT!") print(f"It's awesome") say_hello("biobankers!") say_hello("BBMRI IT!")
And commit the changes
(featA) $ git add src/script.py (featA) $ git commit -m "feat: tell people what I am doing and how awesome it is" (featA) $ git log commit a586eb0fc9e3e72b5e9e963e212346711a2ac2e3 (HEAD -> featA) Author: Vittorio Meloni <[email protected]> Date: Fri Jun 13 11:51:28 2025 +0200 feat: tell people what I am doing and how awesome it is commit 5ce1ad6f1809bf1069a2754a555eca86f8540a65 (main, develop) Author: Vittorio Meloni <[email protected]> Date: Fri Jun 13 10:20:44 2025 +0200 feat: add say_hello function to say hello to lot of people ...
-
We decided that both feature branches have done enough work and we want to merge them in develop.
First we switch to
develop
branch(featA) $ git switch develop
Let's visualize the situation now
We see that both branches diverged from the same point and both have made one change.
-
Let's start merging from
featB
(develop) $ git merge featB Updating 5ce1ad6..9891aad Fast-forward src/script.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-)
Great! It works.
Time to merge
featA
(develop) $ git merge --no-ff featA Auto-merging src/script.py CONFLICT (content): Merge conflict in src/script.py Automatic merge failed; fix conflicts and then commit the result. (develop|MERGING) $
Doh! Git tell us there is a conflict. We need to fix it before continuing.
NB: the changes to PS1 gives us also some information about the status. In this case it tells us that we have a MERGE in progress
-
Let's open the file and check what's going on
def say_hello(recipient): <<<<<<< HEAD print(f"Hello {recipient}. I'm learning Git!") ======= print(f"Hello {recipient}. I'm learning GIT!") print(f"It's awesome") >>>>>>> featA say_hello("biobankers!") say_hello("BBMRI IT!")
Git shows us the conflict!
The code between
<<<<<<< HEAD
and=======
is the current develop, where we already mergedfeatB
The code between
=======
and>>>>>>> featA
is the one we want to merge.The problem here is that we modified the same line differently. In
featB
we wrote "Git" infeatA
we wrote "GIT", all uppercase.Git couldn't decide which line was correct so it gives us the responsibility to resolve the conflict
We decide to keep "Git!" from
featB
and the rest fromfeatA
def say_hello(recipient): print(f"Hello {recipient}. I'm learning Git!") print(f"It's awesome") say_hello("biobankers!") say_hello("BBMRI IT!")
-
Now we need to tell Git that the conflicts are fixed. As usual
git status
comes in handy(develop|MERGING) $ git status On branch develop 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: src/script.py no changes added to commit (use "git add" and/or "git commit -a")
Git suggests what to do to tell it that we solved the conflicts. Let's do this.
(develop|MERGING) $ git add src/script.py (develop|MERGING) $ git status On branch develop All conflicts fixed but you are still merging. (use "git commit" to conclude merge) Changes to be committed: modified: src/script.py
Hang in there! We're almost done
We just need to commit the fixing as
git status
suggests(develop|MERGING) $ git commit -m "feat: merge featA" [develop cfb3f54] feat: merge featA (develop) $
NB: Git doesn't take responsiblity of the content of the conflicting files. It's up to you. In other words, if you don't change the conflicting files and perfom
git add
without removing the conflict, git accept it. No worries, though you can always perform another commit to solve the situation -
Let's check the history now
(develop) $ git log commit cfb3f5409d653a62a650a3ffa2311c8f693368c1 (HEAD -> develop) Merge: 9891aad a586eb0 Author: Vittorio Meloni <[email protected]> Date: Fri Jun 13 12:26:24 2025 +0200 feat: merge featA commit a586eb0fc9e3e72b5e9e963e212346711a2ac2e3 (featA) Author: Vittorio Meloni <[email protected]> Date: Fri Jun 13 11:51:28 2025 +0200 feat: tell people what I am doing and how awesome it is commit 9891aadff70fbed845e5a85c17271d8d7badf3fa (featB) Author: Vittorio Meloni <[email protected]> Date: Fri Jun 13 11:34:58 2025 +0200 feat: tell people what I'm doing commit 5ce1ad6f1809bf1069a2754a555eca86f8540a65 (main) Author: Vittorio Meloni <[email protected]> Date: Fri Jun 13 10:20:44 2025 +0200 feat: add say_hello function to say hello to lot of people
Now we have in
develop
the commits from the two feature branches plus a "merge" commit.Visually, we can see that all the two branches have converged to
develop
-
Since the works on
featA
andfeatB
are done and everything is merged indevelop
we can remove the branches.(develop) $ git branch -D featA featB Deleted branch featA (was a586eb0). Deleted branch featB (was 9891aad)
It's time to work with remote branches to keep our files on a server and to collaborate on projects
We will use repository on GitHub so, if you don't have one, create an account
and a repository (steps 1 and 2).
You can name your repository as you want. For the tutorial we will use the name bbmri-it-data-training
-
Clone your newly created repository locally and enter the directory
$ git clone https://github.com/<your_username>/bbmri-it-training Cloning into 'bbmri-it-training'... $ cd bbmri-it-training (main) $
We have created a complete copy of the remote repository in our local machine
-
By defult, git refers to the remote repository as the
origin
. We can check the name and the url of the remote repository with the commandgit remote -v
(main) $ git remote -v origin [email protected]:<your_username>/bbmri-it-training.git (fetch) origin [email protected]:<your_username>/bbmri-it-training.git (push)
-
Let's create a README.md, write a simple documentation and commit it.
(main) $ git add README.md (main) $ git commit -m "doc: initialize documentation"
-
We have created a new commit in our local repository. The remote
origin
repository is not updated though. To send our commits to the remote repository we use thepush
command(main) $ git push origin main Enumerating objects: 3, done. Counting objects: 100% (3/3), done. Writing objects: 100% (3/3), 268 bytes | 268.00 KiB/s, done. Total 3 (delta 0), reused 0 (delta 0), pack-reused 0 (from 0) To github.com:<your_username>/bbmri-it-training.git * [new branch] main -> main
-
Let's see the current log
(main) $ git log commit ba78648d9b538267cad8335c67f7ef028281f077 (HEAD -> main, origin/main) Author: Vittorio Meloni <[email protected]> Date: Fri Jun 13 14:57:00 2025 +0200 doc: initialize documentation
Git log shows that
main
and theorigin/main
(the version of main in the remote) are aligned -
Let's create the script as previously, commit and push
(main) $ git add src/script.py (main) $ git commit -m "feat: add script" (main) $ git push origin main
-
The next step is to fetch remotely created branches.
Go to your repository page on Github and create the develop branch following the official tutorial
Now launch the command to fetch branches
(main) $ git fetch From github.com:<your_username>/bbmri-it-training * [new branch] develop -> origin/develop
It created a copy of the remote branch called
origin/develop
that will get the changes from the remote repository each time we will fetch them.To start working on
develop
we need to move to the local branch(main) $ git switch develop branch 'develop' set up to track 'origin/develop'. Switched to a new branch 'develop'
Notice the info
branch 'develop' set up to track 'origin/develop'.
Git is telling us that the branch
develop
is linked to the branchorigin/develop
-
Let's start adding some changes. We can edit the
src/script.py
, commit the changes and check the statusdef say_hello(recipient): print(f"Hello remote {recipient}. I'm learning Git!") print(f"It's awesome") say_hello("biobankers!") say_hello("BBMRI IT!")
(develop) $ git add src/script.py (develop) $ git commit -m "feat: add remote to greetings" (develop) $ git status On branch develop Your branch is ahead of 'origin/develop' by 1 commit. (use "git push" to publish your local commits) nothing to commit, working tree clean
Notice that the
status
gives us hints about the status of the local copy ofdevelop
with respect to theorigin
Now you can push to
origin/develop
(develop) $ git push origin develop
-
Next step is getting changes from remote branch to our local version repository.
Go to your repository page on GitHub and go to
develop
Navigate to the
src/script.py
file.Click on the edit icon
write some changes and commit them
Tip
On GitHub you can open an online Visual Studio Code editor of your repository by pressing .
(dot) on your keyboard.
-
Our local copy is now behind the remote. We need to fetch the changes that will be applied to
origin/develop
(develop) $ git fetch origin develop remote: Enumerating objects: 7, done. remote: Counting objects: 100% (7/7), done. remote: Compressing objects: 100% (3/3), done. remote: Total 4 (delta 0), reused 0 (delta 0), pack-reused 0 (from 0) Unpacking objects: 100% (4/4), 1.09 KiB | 1.09 MiB/s, done. From github.com:<your_username>/bbmri-it-training * branch develop -> FETCH_HEAD f461f60..7adb0e6 develop -> origin/develop
And finally, apply them with merge
(develop) $ git merge origin/develop Updating f461f60..7adb0e6 Fast-forward src/script.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-)
Tip
git provides a shortcut for fetch
+ merge
which is git pull
We could obtain the same result with git pull origin develop