\documentclass[10pt,a4paper]{article}
\usepackage{float}
\usepackage{graphicx}
\usepackage{hyperref}
\usepackage{enumerate}

\title{Portfolio - Version control system}
\author{Sander van Veen (6167969), Computer Science, University of Amsterdam}

\begin{document}

\maketitle
\tableofcontents

\pagebreak

\section{Why did I choose git?} % {{{
\label{sec:why-did-i-choose-git}

There are a couple of reasons why you should choose Git, or one of the other
decentralized version control systems:

\begin{enumerate}
    \item Git is free libre open source software, which allows me to use the
    software to its full potential, thus without any limitations.

    \item Generally speaking, decentralized version control systems are faster
    than centralized due to the network latency. A decentralized system has all
    the commit history and branches on the local hard disk.

    \item Decentralized means no single point of failure. Every user has what
    is essentially a full backup of the main server, each of which could be
    pushed up to replace the main server in the event of a crash or corruption.
    There is basically no single point of failure with Git unless there is only
    a single point.

    \item Git is fast compared to all version control systems (even compared to
    the decentralized ones).\footnote{Benchmark used for comparison:
    \url{http://whygitisbetterthanx.com/}} Git is designed to be fast,
    especially in merging branches. It is developed to support the kernel
    development and Linus Torvalds was one of Git's core developers.

    \begin{figure}[H]
    \label{fig:benchmarks}
    \centering
    \includegraphics[width=\textwidth]{benchmarks.png}
    \caption{Benchmark of the most popular decentralized version control
    systems. Lower bars (less time to accomplish a task) are better: git is a
    clear winner.}
    \end{figure}

\end{enumerate}

Therefore, I see no reason to use a centralized version control system.
Decentralized allows you to do everything as you would do with centralized
system, but faster. If you have some legacy code depending on a Subversion
repository, you could use \texttt{git-svn}. This script makes it possible to
have all benefits of Git and use the old Subversion repository. In the mean
time, you can migrate the Subversion repository (and all its dependencies) to a
Git repository.

% }}}

\section{Assignment} % {{{
\label{sec:Assignment}

First, I'll create an empty Git repository. The option \texttt{--bare} is used
to create a bare repository: no working tree, just the content of the
\texttt{.git} directory. A bare repository is a so-called ``public'' repository,
allowing others to push their changes to. Non-bare repositories are considered
``private''. You will see an error if you push to a private repository,
because the push can/will mess up the working tree of the repository.

\begin{verbatim}
$ git init --bare /tmp/test.git
Initialized empty Git repository in /tmp/test.git/
\end{verbatim}

\subsection{Clone repository and add some content} % {{{
\label{sub:clone-repository-and-add-some-content}

\begin{verbatim}
$ cd /tmp/uva-git/
$ git clone /tmp/test.git a
Initialized empty Git repository in /tmp/uva-git/a/.git/
warning: You appear to have cloned an empty repository.
\end{verbatim}

\noindent Currently, the repository is empty (notice the warning).
Therefore, I will add some example data to the repository.

\begin{verbatim}
$ echo "foo bar" > baz.txt
$ git add baz.txt
$ git commit -am "Added 'foo bar'."
[master (root-commit) a811fdc] Added 'foo bar'.
1 files changed, 1 insertions(+), 0 deletions(-)
create mode 100644 baz.txt
\end{verbatim}

\noindent And a second commit.

\begin{verbatim}
$ echo "42" >> baz.txt
$ git commit -am "Added '42'."
[master 235d628] Added '42'.
 1 files changed, 1 insertions(+), 0 deletions(-)
\end{verbatim}

% }}}

\subsection{File difference} % {{{
\label{sub:File difference}

View the file difference between two versions of the file \texttt{baz.txt}.

\begin{verbatim}
git diff a811fdcc6b926a462ac2217f26f81a760fbad99d baz.txt
diff --git a/baz.txt b/baz.txt
index d675fa4..20de225 100644
--- a/baz.txt
+++ b/baz.txt
@@ -1 +1,2 @@
 foo bar
 +42
\end{verbatim}

\noindent The second part of the question is ambiguous. Therefore, I'll answer
both interpretations:

\begin{enumerate}
    \item Using \texttt{diff}, a user can compare two files. Thus, if you
    checkout two versions of a file on disk, you can compare those files using
    \texttt{diff a/foo.txt b/foo.txt}.
    \item Perhaps is the question referring to a centralized repository. In that
    case, it is not possible to compare two versions of the file. Since Git
    stores everything locally, comparing is possible without an internet
    connection (which is required for most centralized repositories).
\end{enumerate}

% }}}

\subsection{Show commit history} % {{{
\label{sub:show-commit-history}

If you want an overview of the commit history, you would usually use \texttt{git
shortlog}, since it will print the commit titles only (that's one of the reasons
why proper commit messages are required).

\begin{verbatim}
$ git shortlog
Sander van Veen (2):
      Added 'foo bar'.
      Added '42'.
\end{verbatim}

\noindent The title of a commit message is at most 50 characters. This 50 char
limit is not a hard limit, but a good thing to do. The rest of the message is
stored in the ``body'' of a commit. The title should summarize the commit body
(just like an e-mail). Use \texttt{git log} to show the more detailed history.

\begin{verbatim}
$ git log
commit 235d62817d1ca1977e4e16e440d80ee615b9b8f2
Author: Sander van Veen <sandervv@gmail.com>
Date:   Sun Jan 9 16:49:12 2011 +0100

    Added '42'.

commit a811fdcc6b926a462ac2217f26f81a760fbad99d
Author: Sander van Veen <sandervv@gmail.com>
Date:   Sun Jan 9 16:46:00 2011 +0100

    Added 'foo bar'.
\end{verbatim}

% }}}

\subsection{Clone second repository} % {{{
\label{sub:Clone second repository}

Since I have changed the repository, I'll push the committed content to the
public repository using \texttt{git push}. This command requires a location and
which branch/branches you want to push. In this example, the location is
\texttt{/tmp/test.git}, \texttt{master} is the name of the local branch and the
second \texttt{master} is the name of the remote branch (branch names separated
by a double colon).

\begin{verbatim}
$ git push /tmp/test.git master:master
Counting objects: 6, done.
Delta compression using up to 2 threads.
Compressing objects: 100% (2/2), done.
Writing objects: 100% (6/6), 447 bytes, done.
Total 6 (delta 0), reused 0 (delta 0)
Unpacking objects: 100% (6/6), done.
To /tmp/test.git
 * [new branch]      master -> master
\end{verbatim}

\noindent Notice the creation of the remote branch \texttt{master}.

It was not necessary to push the content before cloning the repository, but it
will reduce the work to create a copy of the repository, if you push the changes
before cloning. Otherwise, your second repository will be empty. After pushing
the commits, clone the public repository.

\begin{verbatim}
git clone /tmp/test.git /tmp/uva-git/b
\end{verbatim}

% }}}

\subsection{Conflicts and conflict solving} % {{{
\label{sub:conflicts-and-conflict-solving}

Creating a conflict is easy. First, add some content to a new file in the first
repository. Then, add some content to a file with the same name as in the first
repository in the second repository.

\begin{verbatim}
$ cd /tmp/uva-git/a
$ echo "abc" > conflict.txt
$ git add conflict.txt
$ git commit -m "Added 'abc' to demonstrate a conflict."
[master 6271a34] Added 'abc' to demonstrate a conflict.
 1 files changed, 1 insertions(+), 0 deletions(-)
  create mode 100644 conflict.txt
$ git push /tmp/test.git master:master
Counting objects: 4, done.
Delta compression using up to 2 threads.
Compressing objects: 100% (2/2), done.
Writing objects: 100% (3/3), 300 bytes, done.
Total 3 (delta 0), reused 0 (delta 0)
Unpacking objects: 100% (3/3), done.
To /tmp/test.git
   235d628..6271a34  master -> master
\end{verbatim}

\noindent Everything is good so far, now create the same file in the second
repository. Notice that until the line containing \texttt{git push}, there are
no errors. Once \texttt{git push} is invoked, errors occur
\texttt{[rejected]}, because Git is unable to push the changes.

\begin{verbatim}
$ cd /tmp/uva-git/b
$ echo "def" > conflict.txt
$ git add conflict.txt
$ git commit -m "Added 'def' to demonstrate a conflict."
[master ebd14e1] Added 'def' to demonstrate a conflict.
 1 files changed, 1 insertions(+), 0 deletions(-)
  create mode 100644 conflict.txt
$ git push /tmp/test.git master:master
To /tmp/test.git
 ! [rejected]        master -> master (non-fast-forward)
error: failed to push some refs to '/tmp/test.git'
To prevent you from losing history, non-fast-forward updates were rejected
Merge the remote changes before pushing again.  See the 'Note about
fast-forwards' section of 'git push --help' for details.
# Thus, pull the changes, merge them and then push.
$ git pull
remote: Counting objects: 4, done.
remote: Compressing objects: 100% (2/2), done.
remote: Total 3 (delta 0), reused 0 (delta 0)
Unpacking objects: 100% (3/3), done.
From /tmp/test
    235d628..6271a34  master     -> origin/master
Auto-merging conflict.txt
CONFLICT (add/add): Merge conflict in conflict.txt
Automatic merge failed; fix conflicts and then commit the result.
\end{verbatim}

\noindent Git is unable to solve this conflict, because it does not know which
content is good and which is garbage. This conflict can be solved by editing
the conflicted file manually:

\begin{verbatim}
$ cat conflict.txt
<<<<<<< HEAD
def
=======
abc
>>>>>>> 6271a34de1458b0b8e7edfc683b476aef84bf631
$ vim conflict.txt
$ cat conflict.txt
def
\end{verbatim}

\noindent Once the merging is done, you can commit the merged content and push.

\begin{verbatim}
$ git add conflict.txt
$ git commit -m "Solved conflict."
[master 9f66c7b] Solved conflict.
$ git push
Counting objects: 7, done.
Delta compression using up to 2 threads.
Compressing objects: 100% (3/3), done.
Writing objects: 100% (4/4), 485 bytes, done.
Total 4 (delta 0), reused 0 (delta 0)
Unpacking objects: 100% (4/4), done.
To /tmp/test.git
   6271a34..9f66c7b  master -> master
\end{verbatim}

% }}}

\subsection{Pull before push} % {{{
\label{sub:pull-before-push}

As mentioned in chapter \ref{sub:conflicts-and-conflict-solving}, it is
necessary to pull before pushing content. Your version of the content should be
up-to-date and in case of a conflict, you should merge conflicts locally.

% }}}

\subsection{Simulate conflicts} % {{{
\label{sub:simulate-conflicts}

See chapter \ref{sub:conflicts-and-conflict-solving} for conflicts and solving
conflicts.

% }}}

\subsection{Adding files and directories} % {{{
\label{sub:adding-files-and-directories}

Adding files to Git is done using the \texttt{git add} command. But there is a
difference between Git and other version control systems: Git tracks content not
files. Git takes a snapshot of the file, when you add the file using \texttt{git
add}. Other version control systems start to track changes when the file is
added. Git requires adding new and modified files and in
both cases it takes a snapshot of the given files and stages that content in the
index, ready for inclusion in the next commit.

According to the Git FAQ\footnote{
\url{https://git.wiki.kernel.org/index.php/GitFaq\#Can_I_add_empty_directories.3F}},
it is not possible to add an empty directory to the git repository:

\begin{quote}
Currently the design of the git index (staging area) only permits files to be
listed, and nobody competent enough to make the change to allow empty
directories has cared enough about this situation to remedy it.

Directories are added automatically when adding files inside them. That is,
directories never have to be added to the repository, and are not tracked on
their own.

You can use "\texttt{git add <dir>}" and it will add files in there.

If you really need a directory to exist in checkouts you should create a file in
it. \texttt{.gitignore} works well for this purpose; you can leave it empty, or
fill in the names of files you expect to show up in the directory.
\end{quote}

% }}}

\subsection{Renaming files} % {{{
\label{sub:renaming-files}

In Git, it is not necessary to mention the renaming of files (since Git tracks
content, not files). However, it is required to add the renamed file to Git's
index, otherwise the file is marked as ``deleted''. Also, Git provides a command to
move (or rename) files and directories: \texttt{git mv foo bar}. This will save
you one command to type, compared to \texttt{mv foo bar; git add bar}.

% }}}

\subsection{Discarding changes} % {{{
\label{sub:discarding-changes}

If you suddenly think your changes are not necessary or not the right solution,
you can discard those changes in Git using \texttt{git checkout -- baz.txt} for
a single file or directory. Or you could reset your entire source tree to the
last commit (thus discarding all changes in all tracked files made since the
last commit) using \texttt{git reset --hard HEAD}.

% }}}

\subsection{Going back to a previous commit} % {{{
\label{sub:going-back-to-a-previous-commit}

Use \texttt{git checkout REV} to go a (previous) commit state.

\begin{verbatim}
$ ls -1
baz.txt
conflict.txt
$ git checkout 235d62817d1ca1977e4e16e440d80ee615b9b8f2
Note: checking out '235d62817d1ca1977e4e16e440d80ee615b9b8f2'.

You are in 'detached HEAD' state. You can look around, make experimental
changes and commit them, and you can discard any commits you make in this
state without impacting any branches by performing another checkout.

If you want to create a new branch to retain commits you create, you may
do so (now or later) by using -b with the checkout command again. Example:

  git checkout -b new_branch_name

HEAD is now at 235d628... Added '42'.
$ ls -1
baz.txt
\end{verbatim}

% }}}

\subsection{Delete a file and revert} % {{{
\label{sub:Delete a file and revert}

When deleting a file outside the repository, you could use \texttt{git checkout
FILE} to restore the file.

\begin{verbatim}
$ ls -1
baz.txt
conflict.txt
$ rm conflict.txt
$ git status
# Not currently on any branch.
# Changed but not updated:
#   (use "git add/rm <file>..." to update what will be committed)
#   (use "git checkout -- <file>..." to discard changes in working directory)
#
#   deleted:    conflict.txt
#
no changes added to commit (use "git add" and/or "git commit -a")
$ ls -1
baz.txt
$ git checkout conflict.txt
$ ls -1
baz.txt
conflict.txt
\end{verbatim}

When a file is deleted and committed to the repository, you could revert this
commit using \texttt{git revert REV}. This will revert the commit and will
commit the result of the reversion to the repository. If you don't want to
commit the result after the reversion, provide the \texttt{-n} flag while using
\texttt{git revert}. This is useful when reverting more than one commits' effect
to your index in a row.

% }}}

\subsection{Branches} % {{{
\label{sub:Branches}

When you want to create an experimental feature, which will break functionality,
it is wise to create a branch. Creating a branch makes it possible to commit
your changes, while other developers can continue their work. If you do not
create a branch, it is possible that the other developers cannot continue,
because your commit broke some functionality they are depending on. When your
feature is stable, you can merge the two branches and your feature is included
in the ``main'' source tree. Branches enable parallel development across the
developers. Git is designed to handle large branches and merging those branches
efficiently. Also notice when pulling from a Git repository, Git will
automatically merge two branches (your repository and the public repository).

Imagine the situation where we need to create a branch. The following commands
will create a branch of the master branch, commit some changes in both branches
and then merge the two branches. In this example after the merge, only the
``master'' branch is available.

\begin{verbatim}
$ git branch
* master
$ git branch foo
$ git checkout foo
Switched to branch 'foo'
$ git branch
* foo
  master
$ ls
baz.txt  conflict.txt
$ cat baz.txt
foo bar
42
$ echo "foo bar" > baz.txt
$ echo "baz" >> baz.txt
$ git commit -am "Addded 'baz' and removed '42'."
[foo dbf6e7d] Addded 'baz' and removed '42'.
 1 files changed, 1 insertions(+), 1 deletions(-)
$ echo "ABC" > abc.txt
$ git add abc.txt
$ git commit -am "Added ABC."
[foo 2ef3c7e] Added ABC.
 1 files changed, 1 insertions(+), 0 deletions(-)
 create mode 100644 abc.txt
$ git checkout master
Switched to branch 'master'
$ cat baz.txt
foo bar
42
$ vim baz.txt
$ git commit -am "Added 'ghi'."
[master 16f0f26] Added 'ghi'.
 1 files changed, 1 insertions(+), 0 deletions(-)
$ git checkout foo
Switched to branch 'foo'
$ cat baz.txt
foo bar
baz
$ git merge master
Auto-merging baz.txt
CONFLICT (content): Merge conflict in baz.txt
Automatic merge failed; fix conflicts and then commit the result.
$ git branch
* foo
  master
$ vim baz.txt
$ cat baz.txt
foo bar
baz
42
$ git commit -am "Fixed merge conflict."
[foo 31469f3] Fixed merge conflict.
$ git branch
* foo
  master
$ git checkout master
Switched to branch 'master'
Your branch is ahead of 'origin/master' by 1 commit.
$ git merge foo
Updating 16f0f26..31469f3
Fast-forward
 abc.txt |    1 +
 baz.txt |    2 +-
 2 files changed, 2 insertions(+), 1 deletions(-)
 create mode 100644 abc.txt
$ cat baz.txt
foo bar
baz
42
\end{verbatim}

In the example above, \texttt{git branch} is used to list the available
branches. The asterisk before a branch name indicates the currently used branch.
It is not necessary to use \texttt{git branch}, but it is used in this example
to show which branch is active. You could delete a branch with \texttt{git
branch -d BRANCH}.

\begin{verbatim}
$ git branch -d foo
Deleted branch foo (was 31469f3).
\end{verbatim}

% }}}

\subsection{Cherry picking} % {{{
\label{sub:cherry-picking}

Cherry picking in Git means applying the changes introduced by an existing
commit. If you want a single commit instead of apply all commits from a branch,
you can use \texttt{git cherry-pick REV}.

\begin{verbatim}
$ git cherry-pick aac61dec6f18fcfb5544ada513a6ad2fd59e23f3
Finished one cherry-pick.
[detached HEAD eb791bf] Added 'ab'.
 1 files changed, 1 insertions(+), 0 deletions(-)
 create mode 100644 def.txt
\end{verbatim}

You can also pick multiple commits, for example, by using \texttt{git
cherry-pick master\~{}4 master\~{}2} (two tildes). This will apply the changes
introduced by the fifth and third last commits pointed to by master and create 2
new commits with these changes. And it is possible to give a range: \texttt{git
rev-list --reverse master -- README | git cherry-pick -n --stdin}. This will
apply the changes introduced by all commits on the master branch that touched
\texttt{README} to the working tree and index. Notice the \texttt{-n} flag, so
the result can be inspected and made into a single new commit if suitable.

% }}}

\subsection{Hooks in Git} % {{{
\label{sub:hooks-in-git}

All hooks are stored in your \texttt{.git}-directory, and saved as
\texttt{.git/hooks/HOOKNAME}, where \texttt{HOOKNAME} is in
$\{$\texttt{applypatch-msg}, \texttt{commit-msg}, \texttt{post-commit},
\texttt{post-receive}, \texttt{post-update}, \texttt{pre-applypatch},
\texttt{pre-commit}, \texttt{prepare-commit-msg}, \texttt{pre-rebase},
\texttt{update}$\}$. Example hooks (\texttt{*.sample}) can be found in the
hooks-directory in your \texttt{.git}-directory.

I'll demonstrate the use of a \texttt{pre-commit} hook. This hook is invoked by
'git-commit', and can be bypassed with the \texttt{--no-verify} option. It takes
no parameter, and is invoked before obtaining the proposed commit log message
and making a commit. Exiting with non-zero status from this script causes the
'git-commit' to abort. Aborting on a non-zero status enables a developer to run,
for example, a set of test cases before committing to the ``main'' Git server
(if you're working with a team of developers). In this example hook for
pre-commit, I'll check if the file \texttt{conflict.txt} contains the
(sub)string \texttt{abc}.

\begin{verbatim}
$ cat <<EOF > /tmp/uva-git/a/.git/hooks/pre-commit
#!/bin/bash
[ ! -f Makefile ] || make
EOF
$ chmod +x /tmp/uva-git/a/.git/hooks/pre-commit
$ cat <<EOF > /tmp/uva-git/a/Makefile
all:
$(printf '\t')grep abc conflict.txt
EOF
$ git add Makefile && git commit -m "Added example Makefile."
[master 78f6e33] Added example Makefile.
 1 files changed, 2 insertions(+), 0 deletions(-)
create mode 100644 Makefile
$ git pull && git push
$ echo "wrong" > conflict.txt
$ git commit -am "Added 'wrong' text to 'conflict.txt'"
grep abc conflict.txt
make: *** [all] Error 1
\end{verbatim}

\noindent The pre-commit hook works as expected (since \texttt{abc} is not found
in conflict.txt due to the commit). Now, I'll demonstrate forcing the commit,
which will bypass the pre-commit hook:

\begin{verbatim}
$ git commit --no-verify -am "Added 'wrong' text to 'conflict' (forced)"
[master 9fc290e] Added 'wrong' text to 'conflict' (forced)
 1 files changed, 1 insertions(+), 1 deletions(-)
\end{verbatim}

\noindent Although the used check (\texttt{grep abc conflict.txt}) in the
example Makefile is pretty useless, the example clearly shows the possibilities
of a pre-commit hook. In this example, I also used here strings (\texttt{<<EOF}
and \texttt{EOF}) to show what data is stored in which file. Here strings allows
a user to paste file content to a file without the need to escape all bash-related
built-in commands (since escaping is normally required to prevent bash from
performing shell expansion).

% }}}

% }}}

\end{document}

% vim: foldmethod=marker:
