This tutorial will address the source code management (SCM) tool named Git. By following these steps you should learn about the basic usage of Git, which is required for the whole practical course. Furthermore, Git is a great SCM tool, and it's good to know how to use it. During this tutorial, we will follow Alan Turing's thoughts towards developing the Turing Machine.
More in-depth Git documentation can be found on the official home page, which mentions books, videos, and links to other tutorials and references. Furthermore, the shell command
git help lists the most commonly used Git commands, and
git help <command> gives very detailed documentation for the specified Git command.
Most steps of this tutorial are done by typing shell commands. The grey boxes contain the commands you should enter, preceded by a
$ symbol, and followed by their output. While you may copy & paste these commands, some of them may require modifications to adapt them to your own projects. The output will be slightly different for many commands when you enter them, since it also depends on parameters such as the user name and time of execution.
- Read the Git for Computer Scientists introduction (skip this if you are already familiar with Git).
- For Linux, Git is available in its own package. Windows users can install msysGit. For Mac OSX, Git is available as part of Xcode; if you cannot install that, use Git for OSX.
Configure your name and email address (will be included in all commits you create):
Create a local repository for the "Turing Project":
.gitsubdirectory contains all history and metadata of the repository. You should not modify it. The
turingdirectory contains the working copy, that is the currently checked-out snapshot. You work by modifying your working copy and committing the modifications to the repository (contained in
Add and commit some content: copy
The file is now stored in the local history of your repository.
- Replace "fixed" with "infinite" in line 1.
- Replace "... (TODO)" with "a finite state machine" in line 4.
View the status of your current working copy:
Mark the modified file to include it in the next commit, then view the status again and compare with the previous output:
Commit the modified content to your local repository and view the status:
After the preceding steps you have two commits in your local repository, each with one file in the index. You have different commands for viewing these commits:
Note that each commit is identified by a looong hash value, but it is possible to use only a prefix when referencing them (if the prefix is not ambiguous): the example above uses
52e2d49 to identify the second commit. The commit hashes in your repository will be different from those seen in this tutorial, because the name of the author and the exact time of committing is also considered in the hash calculation. Also try the command
gitk to get an overview of your commits (a better alternative available for Mac OSX is GitX).
Branching and Merging
In the previous section you have created two commits on the default branch, which is named
master. Now you will create a new branch and commit there, thus adding complexity to the commit graph. In general, you may create as many local branches as you like, since they are simple to use and can be a great tool to structure your work.
Create a branch with name sketches:
View the list of branches:
The star reveals that you are still on the old
Switch to the new branch:
It is also possible to create a branch and switch immediately to it using the option
Download and add the new file
Inspecting the commit graph with
gitk(or another graphical viewer) you see that the
sketchesbranch now has three commits, while
masteris still at the second commit.
mastermeans that all changes that have been made in
sketchesare also applied to
master. In order to perform this merge, we have to check out the
This was a fast-forward merge: since the
masterbranch was completely contained in the
sketchesbranch, the merge could be done by simply changing the head pointer of
masterto be the same as the head of
Now add the line "
see some examples in 'examples.txt'" to the file
notes.txtand commit this change in the current branch:
Switch back to the
sketchesbranch and modify it as shown below. Note that the
checkoutcommand modifies your working copy, hence you have to update your text editor's content if you opened one of the files.
Add the line "
Move one step left:" followed by an accordingly updated version of the tape with tape head at the end of the file
examples.txt, then commit.
Now your two branches have diverged, which means that they cannot be fast-forwarded anymore.
gitkyou can see that a new commit was created that has two parent commits. Such a commit is called merge commit and is done automatically when a non-fast-forward merge is applied. See how both the change to
notes.txtdone in the
masterbranch and the change to
examples.txtdone in the
sketchesbranch are now contained in the repository state that results from the merge.
- Add a commit in each of the two branches using the commands you have already learned.
- Check out
Insert the following line after line 4 of
* The finite state machine has an initial state and one or more final states
- Commit the change of
- Check out
sketches(make sure to refresh your text editor so that
notes.txtis reset to its previous state, without the change made above).
Insert the following line after line 4 of
* Each state transition can trigger head movement and data read/write
- Commit the change of
- Check out
masterbranch into the current branch (
As expected, the branches could not be merged automatically, since both branches modified the same line in the same file.
statuscommand to see the list of affected files:
notes.txtshould now contain the following text:
<<<<<<< HEAD * Each state transition can trigger head movement and data read/write ======= * The finite state machine has an initial state and one or more final states >>>>>>> master
The upper line is the one committed to
sketches, while the lower line was committed to
master. You have to resolve the conflict by editing the file. In this case the conflict is resolved by keeping both lines in arbitrary order, that means you should just remove the conflict markers (lines 5, 7, and 9 in
addcommand to mark
notes.txtas resolved. Entering
git commitwithout a message will open a text editor with an automatically created commit message. Just close the editor, and the merge commit is completed:
gitk tool should now display this graph:
In the previous sections you have worked only with a local repository. The next step is to share this content with a remote repository, which we manage with Stash. You will first have to configure your Stash account:
- Login to our Stash server with your Rtsys account information. If you haven't received your password yet, either wait until you have that password or register yourself in Stash (but don't use your IfI login name – that one will be used later when we create your account).
- Through the button in the top right corner, access your profile.
- Switch to the SSH keys tab.
- Click Add Key and upload a public SSH key that you want to use to access the repository.
- If you don't have an SSH key: use the shell command
ssh-keygen, confirm the default destination file
~/.ssh/id_rsa, and choose whether to give a passphrase. If you have a passphrase, you need to enter it whenever you use your SSH key for the first time in a session. You can omit the passphrase, but that makes the key less secure. As result, the tool generates a private key
~/.ssh/id_rsa, which has to be kept secret, and a public key
- If you don't have an SSH key: use the shell command
Usually it is sufficient to have only one local copy of a Git repository. However, in this tutorial you will create a second copy in order to "simulate" what can happen if two users access the same remote repository: imagine the directories
turing2 are each managed by a different user. You will simulate the resulting interference by switching your working directory between these two.
- Go to Stash → Create Project and call it "personal-<login>", replacing <login> with your own login name. Use your uppercase login name as project key, e.g. "MSP".
- Go to the Permissions tab of the project page and add the user "msp" as observer.
- On the project page, select Create Repository and name it "turing".
- Copy the SSH URL shown in the top right and email it to firstname.lastname@example.org. This will serve as proof for your work on this tutorial.
masterbranch to the new server-side repository. Replace the URL in the following command by the one copied from Stash:
The first command adds a remote named "stash" to your local repository, which is just a bookmark for the long URL. The second command transfers the
masterbranch to the server, which is called pushing. After that is done, reload the Stash page in your browser, and you see all changes that are transferred to the server-side repository.
Create a local clone of your remote repository (replace the URL accordingly):
clonecommand automatically creates a remote named
originin the new local repository, which is set to the given URL. You will use this second clone to simulate another user with access to the repository.
- Edit the file
examples.txtin the new clone (
"a"in line 6 by
"c"and correct the tape representations in lines 9, 14, and 19 accordingly. Commit the change.
Push the new commit to the server:
In this case the push command can be used without arguments, which means that it pushes all branches as configured in
Here the branch
masteris linked with the remote
git pushdoes the same as
git push origin master.
Go back to the original local repository and check out the
Now your local
masterbranch and the one on the server-side repository have diverged
Fetch the server-side changes:
Now the change to
examples.txtthat was previously committed in the
turing2repository is stored in a remote tracking branch named
You can analyze the remote tracking branch using the
showcommands. However, you should never directly modify a remote tracking branch.
You can merge the remote changes into your local
masterbranch with the following command:
Since this combination of
mergeis used very often, Git offers a shortcut for it, namely the
pullcommand. In this case the according command would have been
git pull stash master.
Push the merged branch to the server, and then push the
sketchesbranch, which is not on the server yet:
As next step change your working directory to the second local repository
turing2, add the following line to the end of
turing2directory, and commit the change:
TODO: formal definition
Trying to push this commit to the server results in the following error message:
This is because you have modified the branch while working in the original
turingrepository, and these changes have to be merged with the new commit you have just made for
The solution is to apply the
pullcommand followed by the
pushtransfers the new merged branch to the server. Note that during the merge operation conflicts can occur. In that case you have to resolve them and commit the changes before you can push. When used without parameters like shown above,
.git/configto determine which branches to pull from which remotes.
In order to check out the
sketchesbranch locally, which was previously pushed to the server, simply type the following command:
This branch can be pushed and pulled with the server in the same way as you did for the
masterbranch. Never check out
origin/sketches, since that is a remote tracking branch!
master branch should look like this:
Other Useful Commands
This section contains optional steps that you don't need to push online, but can be useful for you to learn.
While working on his Machine, Alan Turing has produced a temporary file
experiments.tmp, which he does not want to commit in the repository:
Since the extra mention of that file can make Git's status reports unnecessarily cluttered, Alan wants to ignore it permanently. Help him by adding a
.gitignore file to the repository:
experiments.tmp file is not considered when viewing the status. You can add arbitrary file name patterns to the
.gitignore file; for example it is a good idea to ignore
*.class, which are binary files generated for Java projects.
While working on his Machine, Alan Turing has made some changes to notes.txt that he later found out to be nonsense:
Help Alan by restoring the last committed state of that file:
Instead of HEAD, which is the last commit on the current branch, you can also name any other branch or commit hash. In that case you would have to commit the change to make it permanent. While resolving conflicts it is possible to use
--ours instead of HEAD, which replaces the whole content of the respective file by their version (the one on the remote branch) or our version (the one on the current branch).
A more brute-force option is using the
This resets all changes to the working copy to the head of the current branch, so use it with caution! However,
reset does not remove unstaged files. In order to do that in one command, use
Consider the following situation:
If you want to merge the changes made on the
master branch into the
sketches branch, the normal way is to use the
merge command and create a merge commit. However, the
rebase command gives an interesting alternative to that: it reapplies all commits done in the current branch starting from a given reference.
Afterwards the commit graph looks like this:
The two commits made in
sketches are reapplied starting from the head of the
master branch. The resulting structure of commits is much cleaner than before.
rebase even allows to squeeze multiple commits into one. Note that in this example a merge conflict had to be resolved in the same way as it was done in Section "Branching and Merging"; instead of committing the resolved file, the rebase command is resumed with
git rebase --continue.
Never rebase a branch that is already pushed online! Due to the structural change the rebased branch is no longer compatible with the previous one, and pushing it will fail, since fast-forward merge is not possible.
Finally Alan Turing has made a great success in the development of his Machine, and he would like to fix that stage as "Milestone 1". Help him by tagging the current state of the project:
Then the head of the current branch is stored under the name
milestone1, so it can be found very easily at later stages of the project:
Tags can also be loaded to the server using the