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-ish
esAppending ~
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-branch
Notice that:
8
is a merge commit, as it has two parents: 7
and 5
De-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 config
The config
subcommand sets the configuration options
--global
option, configures the tool globallygit config [--global] category.option value
option
of category
to value
As said, --global
can be omitted to override the global settings locally
user.name
and user.email
A 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 nano
How to name the default branch.
Two reasonable choices are main
and master
git config --global init.defaultbranch master
git 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-name
In 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
tty
LF
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\n
LF
character: \n
CR
character: \r
\n
If 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!
.gitattributes
LF
everywhere, but for Windows scripts (bat
, cmd
, ps1
).gitattributes
file in the repository root
* text=auto eol=lf
*.[cC][mM][dD] text eol=crlf
*.[bB][aA][tT] text eol=crlf
*.[pP][sS]1 text eol=crlf
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 bar
foo
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 --oneline
git log --all
git log --graph
git log --oneline --all --graph
git 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 add
git 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)