Did you ever need to roll back some project or assignment to a previous version?
How did you track the history of the project?
Inefficient!
Did you ever need to develop some project or assignment as a team?
How did you organize the work to maximize the productivity?
Tools meant to support the development of projects by:
Distributed: Every copy of the repository contains (i.e., every developer locally have) the entire history.
Centralized: A reference copy of the repository contains the whole history; developers work on a subset of such history
Git is now the dominant DVCS (although Mercurial is still in use, e.g., for Python, Java, Facebook).
At a first glance, the history of a project looks like a line.
Anything that can go wrong will go wrong
$1^{st}$ Murphy’s law
If anything simply cannot go wrong, it will anyway $5^{th}$ Murphy’s law
Go back in time to a previous state where things work
Then fix the mistake
If you consider rollbacks, history is a tree!
Alice and Bob work together for some time, then they go home and work separately, in parallel
They have a diverging history!
If you have the possibility to reconcile diverging developments, the history becomes a graph!
Reconciling diverging developments is usually referred to as merge
Project meta-data. Includes the whole project history
Usually, stored in a hidden folder in the root folder of the project
(or worktree, or working directory)
the collection of files (usually, inside a root folder) that constitute the project, excluding the meta-data.
A saved status of the project.
A named sequence of commits
If no branch has been created at the first commit, a default name is used.
To be able to go back in time or change branch, we need to refer to commits *
tree-ishesAppending ~ and a number i to a valid tree-ish means “i-th parent of this tree-ish”
The operation of moving to another commit
Moves the HEAD to the specified target tree-ish
Let us try to see what happens when ve develop some project, step by step.

Oh, no, there was a mistake! We need to roll back!
6 whenever we want to.5, I’d like to have it into new-branchNotice that:
8 is a merge commit, as it has two parents: 7 and 5De-facto reference distributed version control system
¹ Less difference now, Facebook vastly improved Mercurial
Git is a command line tool
Although graphical interfaces exsist, it makes no sense to learn a GUI:
I am assuming minimal knowledge of the shell, please let me know NOW if you’ve never seen it
Configuration in Git happens at two level
Set up the global options reasonably, then override them at the repository level, if needed.
git configThe config subcommand sets the configuration options
--global option, configures the tool globallygit config [--global] category.option value
option of category to valueAs said, --global can be omitted to override the global settings locally
user.name and user.emailA name and a contact are always saved as metadata, so they need to be set up
git config --global user.name "Your Real Name"git config --global user.email "your.email.address@your.provider"Some operations pop up a text editor.
It is convenient to set it to a tool that you know how to use
(to prevent, e.g., being “locked” inside vi or vim).
Any editor that you can invoke from the terminal works.
git config --global core.editor nanoHow to name the default branch.
Two reasonable choices are main and master
git config --global init.defaultbranch mastergit init.git folder.git folder marks the root of the repository
cd to locate yourself inside the folder that contains (or will containe the project)
mkdir)git init.git folder.Git has the concept of stage (or index).
git add <files> moves the current state of the files into the stage as changesgit reset <files> removes currently staged changes of the files from stagegit commit creates a new changeset with the contents of the stageIt is extremely important to understand clearly what the current state of affairs is
git status prints the current state of the repository, example output:
❯ git status
On branch master
Your branch is up to date with 'origin/master'.
Changes to be committed:
(use "git restore --staged <file>..." to unstage)
modified: content/_index.md
new file: content/dvcs-basics/_index.md
new file: content/dvcs-basics/staging.png
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: layouts/shortcodes/gravizo.html
modified: layouts/shortcodes/today.html
git config --global user.name 'Your Real Name'git config --global user.email 'your@email.com'git config user.name 'Your Real Name'git config user.email 'your@email.com'-m, otherwise Git will pop up the default editor
git commit -m 'my very clear and explanatory message'At the first commit, there is no branch and no HEAD.
Depending on the version of Git, the following behavior may happen upon the first commit:
master
master, but warns that it is a deprecated behavior
main as seen as more inclusivegit config --global init.defaultbranch default-branch-nameIn general, we do not want to track all the files in the repository folder:
Of course, we could just not add them, but the error is around the corner!
It would be much better to just tell Git to ignore some files.
This is achieved through a special .gitignore file.
.gitignore, names like foo.gitignore or gitignore.txt won’t work
echo whatWeWantToIgnore >> .gitignore (multiplatform command)git add is called with the --force option).gitignore example# ignore the bin folder and all its contents
bin/
# ignore every pdf file
*.pdf
# rule exception (beginning with a !): pdf files named 'myImportantFile.pdf' should be tracked
!myImportantFile.pdf
Going to a new line is a two-phased operation:
In electromechanic teletypewriters (and in typewriters, too), they were two distinct operations:
![]()
Terminals were designed to behave like virtual teletypewriters
ttyLF was sufficient in virtual TTYs to go to a new line
\n means “newline”we would get
lines
like these
CR character followed by an LF character: \r\nLF character: \nCR character: \r
\nIf your team uses multiple OSs, it is likely that, by default, the text editors use either LF (on Unix) or CRLF
It is also very likely that, upon saving, the whole file gets rewritten with the “locally correct” line endings
Git tries to tackle this issue by converting the line endings so that they match the initial line endings of the file,
resulting in repositories with illogically mixed line endings
(depending on who created a file first)
and loads of warnings about LF/CRLF conversions.
Line endings should instead be configured per file type!
.gitattributesLF everywhere, but for Windows scripts (bat, cmd, ps1).gitattributes file in the repository root
# Automatically normalize line endings to LF for all text files (not for binaries)
* text=auto eol=lf
# For files with .cmd (case-insensitive) extension, enforce CRLF (Windows style) line endings.
*.[cC][mM][dD] text eol=crlf
# For .bat files (Windows batch scripts), use CRLF endings, consistent with Windows shell requirements.
*.[bB][aA][tT] text eol=crlf
# For PowerShell script files (.ps1), enforce CRLF endings.
*.[pP][sS]1 text eol=crlf
This setup ensures that platform-specific scripts retain the line endings expected by their respective interpreters, while all other text files use consistent Unix-style LF endings. Binary files are tracked with no modification.
git add adds a change to the stagegit add someDeletedFile is a correct command, that will stage the fact that someDeletedFile does not exist anymore, and its deletion must be registered at the next commit.
foo into bar:
git add foo barfoo has been deleted and bar has been createdOf course, it is useful to visualize the history of commits. Git provides a dedicated sub-command:
git log
HEAD commit (the current commit) backwards
git log --onelinegit log --allgit log --graphgit log --oneline --all --graphgit log --oneline --all --graph* d114802 (HEAD -> master, origin/master, origin/HEAD) moar contribution
| * edb658b (origin/renovate/gohugoio-hugo-0.94.x) ci(deps): update gohugoio/hugo action to v0.94.2
|/
* 4ce3431 ci(deps): update gohugoio/hugo action to v0.94.1
* 9efa88a ci(deps): update gohugoio/hugo action to v0.93.3
* bf32a8b begin with build slides
* b803a65 lesson 1 looks ready
* 6a85f8f ci(deps): update gohugoio/hugo action to v0.93.2
* b474d2a write more on the introductory lesson
* 8a7105e ci(deps): update gohugoio/hugo action to v0.93.1
* 6e40642 begin writing the first lesson
<tree-ish>esIn git, a reference to a commit is called <tree-ish>. Valid <tree-ish>es are:
b82f7567961ba13b1794566dde97dda1e501cf88.b82f7567.HEAD, a special name referring to the current commit (the head, indeed).It is possible to build relative references, e.g., “get me the commit before this <tree-ish>”,
by following the commit <tree-ish> with a tilde (~) and with the number of parents to get to:
<tree-ish>~STEPS where STEPS is an integer number produces a reference to the STEPS-th parent of the provided <tree-ish>:
b82f7567~1 references the parent of commit b82f7567.some_branch~2 refers to the parent of the parent of the last commit of branch some_branch.HEAD~3 refers to the parent of the parent of the parent of the current commit.In case of merge commits (with multiple parents), ~ selects the first one
Selection of parents can be performed with caret in case of multiple parents (^)
git rev-parse reference on specifying revision is publicly availableWe want to see which differences a commit introduced, or what we modified in some files of the work tree
Git provides support to visualize the changes in terms of modified lines through git diff:
git diff shows the difference between the stage and the working tree
git addgit diff --staged shows the difference between HEAD and the working treegit diff <tree-ish> shows the difference between <tree-ish> and the working tree (stage excluded)git diff --staged <tree-ish> shows the difference between <tree-ish> and the working tree, including staged changesgit diff <from> <to>, where <from> and <to> are <tree-ish>es, shows the differences between <from> and <to>git diff Example output:diff --git a/.github/workflows/build-and-deploy.yml b/.github/workflows/build-and-deploy.yml
index b492a8c..28302ff 100644
--- a/.github/workflows/build-and-deploy.yml
+++ b/.github/workflows/build-and-deploy.yml
@@ -28,7 +28,7 @@ jobs:
# Idea: the regex matcher of Renovate keeps this string up to date automatically
# The version is extracted and used to access the correct version of the scripts
USES=$(cat <<TRICK_RENOVATE
- - uses: gohugoio/hugo@v0.94.1
+ - uses: gohugoio/hugo@v0.93.3
TRICK_RENOVATE
)
echo "Scripts update line: \"$USES\""
The output is compatible with the Unix commands diff and patch
Still, binary files are an issue! Tracking the right files is paramount.
Navigation of the history concretely means to move the head (in Git, HEAD) to arbitrary points of the history
In Git, this is performed with the checkout commit:
git checkout <tree-ish>
HEAD to the provided <tree-ish><tree-ish>The command can be used to selectively checkout a file from another revision:
git checkout <tree-ish> -- foo bar baz
foo, bar, and baz from commit <tree-ish>, and adds them to the stage (unless there are uncommitted changes that could be lost)-- is surrounded by whitespaces, it is not a --foo option, it is just used as a separator between the <tree-ish> and the list of files
<tree-ish> and we need disambiguationGit does not allow multiple heads per branch
(other DVCS do, in particular Mercurial):
for a commit to be valid, HEAD must be at the “end” of a branch (on its last commit), as follows:
When an old commit is checked out this condition doesn’t hold!
If we run git checkout HEAD~4:
The system enters a special workmode called detached head.
When in detached head, Git allows to make commits, but they are lost!
(Not really, but to retrieve them we need git reflog and git cherry-pick, that we won’t discuss)