git
.git
commands.gitlab
/ github
.Install https://gitforwindows.org
Accept all proposed options except:
no version tags in file names any more
never loose old files
undo changes efficiently
distribute code in a controlled way
analyse history / changes which introduced a bug
program code
text files used for documentation (latex files, markdown, html, ..)
textual data files (like .csv, .txt, ..)
git
is not suited to track huge data files
git lsf
or git annex
instead.git
is not suited to track binary files (.xlsx, .zip, .gz, .doc, ...)
git
has many features
You have to exercise to learn it!
In case you accidentelly added private data such as passwords, or large files
to your repository, commands such as git rm
or rewriting history with git rebase
is not enough.
See these docs on how to fix such situations
git
can be used on a single computer (without github et al)
A local git
repository is a folder including sub folders, holding a
hidden .git
subfolder.
git
manages local git repositories
git
allows synchronization of git repositories (eg with github)
In a team developers have their local git repositories
github / gitlab
offer just another local git repository on a remote computer.
git
assists in synchronizing repositories with such a remote.
Setting:
Workflow:
print_squares.py
:
for i in range(10): # display i and its square print("i is", i) print("i^2 is", i ** 2)
print_squares.py
after modification:
for i in range(10): # this is the magic computation print("i^2 is", i ** 2) print("i is", i)
and this is the according patch:
--- a/git/sandbox/print_squares.py+++ b/git/sandbox/print_squares.py@@ -1,4 +1,4 @@for i in range(10):- # display i and its square- print("i is", i)+ # this is the magic computation print("i^2 is", i ** 2)+ print("i is", i)
git
allows management, contribution and manipulation of patches.
git chains patches over time
if you know the initial state of an folder and this chain you can reconstruct the state of the file for any time.
that's it
A commit consists of a single or multiple related patches.
The git history is a list of commits:
$ git log
commit 8ab13c9a930d4b0fd1bc1937f382196bc3a1bbdc (HEAD -> main)Author: Uwe <uwe.schmitt@id.ethz.ch>Date: Fri Feb 9 12:50:00 2018 +0100 changed order of output in print_squares.pycommit 0daab54f48ac33b5cfa71f90634ad82dd655e33cAuthor: Uwe <uwe.schmitt@id.ethz.ch>Date: Fri Feb 9 12:49:16 2018 +0100 initial version of print_squares.py
8ab13c9a930d4b0fd1bc1937f382196bc3a1bbdc
$ git log -p
(Inverted order, older commit first)
This patch adds four new lines to an empty file (marked as /dev/null
):
commit 90b3a581cae221342ca82fe2862fd5daa219a4beAuthor: Uwe <uwe.schmitt@id.ethz.ch>Date: Fri Feb 9 16:47:49 2018 +0100 initial version of print_squared.pydiff --git a/print_squares.py b/print_squares.pynew file mode 100644index 0000000..d45e7ab--- /dev/null+++ b/print_squares.py@@ -0,0 +1,4 @@+for i in range(10):+ # print i and i squared+ print("i is", i)+ print("i^2 is", i ** 2)
commit b3ed251fa0701ee5f986adc7a63eafd87d7d0bd2 (HEAD -> main)Author: Uwe <uwe.schmitt@id.ethz.ch>Date: Fri Feb 9 16:48:20 2018 +0100 changed order of output in print_squares.pydiff --git a/print_squares.py b/print_squares.pyindex d45e7ab..893f18d 100644--- a/print_squares.py+++ b/print_squares.py@@ -1,4 +1,4 @@ for i in range(10): - # print i and i squared - print("i is", i) + # print i squared an i print("i^2 is", i ** 2) + print("i is", i)
Related patches (maybe over multiple files) are grouped as commits
First use git add
to group changes in staging area
git commit
adds this group of patches as a single entry (commit) to the history chain
git
command line interface uses your current working directory to detect
the repository you want to work with. Even if you are in a subfolder.
git init
initializes given folder and its subfolders as a local git repository
git status
shows state of current git repository
git diff
shows state of current git repository
git add
creates patches to create current versions of file(s) and places them into the staging area
git commit
creates a commit from the current staging area
git log
shows the history of commits
git show
shows details single commits
git
version$ git --versiongit version 2.32.0
Should be at least 2.32
to follow the tutorial.
In case you use an older version of git
you will have to replace
commands
git switch -c ...
by git checkout -b ...
git checkout ...
by git switch ...
later.
git
Before you use git
the first time we configure some settings:
$ git config --global user.name "Uwe Schmitt"$ git config --global user.email "uwe.schmitt@id.ethz.ch"
git
Note: Not needed in Online Course on jupyterhub
Then we set your default text editor.
On Mac / Linux: (replace nano
by vim
if you like):
$ git config --global core.editor /usr/bin/nano$ git config --global core.autocrlf input
On Windows:
$ git config --global core.editor "'C:/Program Files/Notepad++/notepad++.exe' \-multiInst -notabbar -nosession -noPlugin"$ git config --global core.autocrlf true
Now check if the configured editor works, but don't modify the shown file!
$ git config --global -e
Every folder on your file system can be turned into a local git repository:
$ cd$ mkdir git_demo$ cd git_demo$ git init .hint: Using 'main' as the name for the initial branch. This default branch name..Initialized empty Git repository in ~/git_demo/.git/$ ls -a. .. .git
This means: the folder and all subfolders are under control now!
The folder is not necessarily empty, you can put existing folders under version control.
Use seperate repositories for seperate software projects.
git
Now create a new file greet.py
in the folder git_demo
:
def greet(name): return "hi %s" % name
If we ask git
about the status of the repository, we see that have one file which is not
under controly yet:
$ git statusOn branch mainNo commits yetUntracked files: (use "git add <file>..." to include in what will be committed) greet.pynothing added to commit but untracked files present (use "git add" to track)
git add
creates a patch to build the current version of greet.py
and
places this patch into the index:
$ git add greet.py$ git statusOn branch mainNo commits yetChanges to be committed: (use "git rm --cached <file>..." to unstage) new file: greet.py
git add
also works with multiple files and / or foldersgit add
is required to put files under version control.$ git commit -m "first version of greet.py"[main (root-commit) 8175092] first version of greet.py 1 file changed, 2 insertions(+) create mode 100644 greet.py
8175092
are the first characters of the commit id.
If you ommit the "-m ...." git
will open the configured text editor
where you can enter the message.
Think about your commit messages. You might want to understand them in one year or later.
Show the history:
$ git logcommit 8175092830d7722c30d339106f3738a7ff5f53fc (HEAD -> main)Author: Uwe Schmitt <uwe.schmitt@id.ethz.ch>Date: Wed Sep 30 21:58:30 2015 +0200 first version of greet.py
Show which change was introduced by the latest commit:
$ git showcommit 8175092830d7722c30d339106f3738a7ff5f53fc (HEAD -> main)Author: Uwe Schmitt <uwe.schmitt@id.ethz.ch>Date: Thu Oct 1 15:47:11 2015 +0200 first version of greet.pydiff --git a/greet.py b/greet.pynew file mode 100644index 0000000..7a875b9--- /dev/null+++ b/greet.py@@ -0,0 +1,2 @@+def greet(name):+ return "hi %s" % name
First we add a new feature to greet.py
:
def greet(name): return "hi %s" % namedef say_hello(name): print(greet(name))
And we create a new file run.py
:
import greetgreet.say_hello("monty")
$ git diffdiff --git a/greet.py b/greet.pyindex 7a875b9..53bb92b 100644--- a/greet.py+++ b/greet.py@@ -1,2 +1,5 @@ def greet(name): return "hi %s" % name++def say_hello(name):+ print(greet(name))
$ git statusOn branch mainChanges 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: greet.pyUntracked files: (use "git add <file>..." to include in what will be committed) run.pyno changes added to commit (use "git add" and/or "git commit -a")
git add
to setup stagin areaWe place now multiple patches to the staging area:
$ git add run.py$ git add greet.py
Now we have two changes in our staging area:
$ git status$ git statusOn branch mainChanges to be committed: (use "git restore --staged <file>..." to unstage) modified: greet.py new file: run.py
We use git diff --cached
to see the patches in the staging area:
$ git diff --cacheddiff --git a/greet.py b/greet.pyindex 7a875b9..53bb92b 100644--- a/greet.py+++ b/greet.py@@ -1,2 +1,5 @@ def greet(name): return "hi %s" % name++def say_hello(name):+ print(greet(name))diff --git a/run.py b/run.pynew file mode 100644index 0000000..1b804f3--- /dev/null+++ b/run.py@@ -0,0 +1,3 @@+import greet++greet.say_helo("monty")
We create a commit now:
$ git commit -m "improved greet.py and added test script"[main c3cdcfa] improved greet.py and added test script 2 files changed, 6 insertions(+) create mode 100644 run.py
And inspect the history:
$ git logcommit c3cdcfa04b7de45403a8291c567fba6ff4ae0b25 (HEAD -> main)Author: Uwe Schmitt <uwe.schmitt@id.ethz.ch>Date: Wed Sep 30 22:14:10 2015 +0200 improved greet.py and added test scriptcommit 8175092830d7722c30d339106f3738a7ff5f53fcAuthor: Uwe Schmitt <uwe.schmitt@id.ethz.ch>Date: Wed Sep 30 21:58:30 2015 +0200 first version of greet.py
$ git showcommit c3cdcfa04b7de45403a8291c567fba6ff4ae0b25 (HEAD -> main)Author: Uwe Schmitt <uwe.schmitt@id.ethz.ch>Date: Thu Oct 1 16:03:30 2015 +0200 improved greet.py and added test scriptdiff --git a/greet.py b/greet.pyindex 7a875b9..2cd4ad9 100644--- a/greet.py+++ b/greet.py@@ -1,2 +1,5 @@ def greet(name): return "hi %s" % name++def say_hello(name):+ print(greet(name))diff --git a/run.py b/run.pynew file mode 100644index 0000000..3fb5e9b--- /dev/null+++ b/run.py@@ -0,0 +1,5 @@+import greet++greet.say_hello("monty")
Edit greet.py
:
def greet(name): return "hi %s" % namedef go_to_hell(): print("to hell with python")def say_hello(name): print(greet(name))
We check the changes again:
$ git diffdiff --git a/greet.py b/greet.pyindex 2cd4ad9..e6b949a 100644--- a/greet.py+++ b/greet.py@@ -1,5 +1,8 @@ def greet(name): return "hi %s" % name+def go_to_hell():+ print("to hell with python")+ def say_hello(name): print(greet(name))
$ git add greet.py$ git commit -m "added go_to_hell"
Here we use git log --oneline
to reduce output:
$ git log --oneline523eb90 (HEAD -> main) added go_to_hellc3cdcfa improved greet.py and added test script8175092 first version of greet.py
you worked on a new feature
git diff
and git status
to inspect changes
git add
to add files to index (staging area)
git diff --cached
to check stating area
git commit
to add index to history
First lookup the commit id of your last commit. Replace the COMMIT_ID_HERE
fields by this id.
$ git revert COMMIT_ID_HERE --no-edit
git created an "inverse" patch of commit COMMIT_ID_HERE and committed it:
$ git log --onelined3cf980 (HEAD -> main) Revert "added go_to_hell"523eb90 added go_to_hellc3cdcfa improved greet.py and added test script8175092 first version of greet.py
And we see that the feature is removed again:
$ cat greet.pydef greet(name): return "hi %s" % namedef say_hello(name): print(greet(name))
git revert
didThe last commit holds the mentioned "inverse patch":
$ git showcommit f4ba88676875a087ac7fac3bee2b0dbb676202d2 (HEAD -> main)Author: Uwe Schmitt <uwe.schmitt@id.ethz.ch>Date: Thu Oct 1 16:20:40 2015 +0200 Revert "added go_to_hell" This reverts commit COMMIT_ID_HEREdiff --git a/greet.py b/greet.pyindex e6b949a..2cd4ad9 100644--- a/greet.py+++ b/greet.py@@ -1,8 +1,5 @@ def greet(name): return "hi %s" % name-def go_to_hell():- print("to hell with python")- def say_hello(name): print(greet(name))
git revert
git revert
may be used not only to revert the most recent commit.
even reverted commits can be reverted again!
if the "inverse" patch can not be applied, git indicates a so called "merge conflict"
git status
shows current status of repository
git add PATH, PATH, ...
adds files to staging area
git diff
shows current changes for files already tracked by git
git commit -m "...."
commits changes with given commit message
git log
shows history
git log --oneline
shows only commit uniqued ids plus messages from history
git show
shows changes introduced in most recent commit
git show COMMIT_ID
shows changes introduced in given commit
git revert COMMIT_ID
reverts changes introduced in given commit.
git rm
to remove a file under version control.
git mv
to move / rename a file under version control.
git commit -a ....
adds all changes of tracked files + creates commit.
git reset
clears staging area in case of unintended added files.
git log -N
only shows the last N
patches in the history
git restore FILE_NAME
overwrites the current changes in the given file by its last recoreded
contents. (undo current edits)
git restore .
resets the current folder and its subfolder to the state of the last commit.
.gitignore
is a file where you can specify file names, folders and patterns
ignored by git
(e.g. binary files or backup files from your editor)
In future slides you see the branch named main
which is the default branch we
were already working on. Depending on the version of git
or the git repositories
you are working on this name might be master
.
Up to now the evolution of our repository was linear, our repository has
only one branch, the main
branch:
A --> B --> C --> D main (*)
(the * marks the active branch)
To create a new branch ans work with it we enter:
$ git switch -c new_featureSwitched to a new branch 'new_feature'
And if we check the status we get:
$ git statusOn branch new_feature...
Our last commit C
now is the last commit for both branches, new_feature
is
active.
A --> B --> C --> D main, new_feature(*)
$ git log --onelineaf68936 (HEAD -> new_feature, main) Revert "added go_to_hell"389d0ea added go_to_hella8ba071 improved greet.py and added test script2c9fcec first version of greet.py
To list the existing branches:
$ git branch main* new_feature
Now we add a new function
def greet(name): return "hi %s" % namedef say_hello(name): print(greet(name))def greet_two(name_1, name_2): return "hi %s and %s" % (name_1, name_2)
and commit it to our new branch
$ git add greet.py$ git commit -m "added function greet_two"
This is now the history of the active branch new_feature
:
$ git log --oneline3732022 (HEAD -> new_feature) added function new_featureaf68936 (main) Revert "added go_to_hell"389d0ea added go_to_hella8ba071 improved greet.py and added test script2c9fcec first version of greet.py
A --> B --> C --> D main \ E new_feature (*)
We switch to the main branch we use git switch BRANCH_NAME
:
$ git switch mainSwitched to branch 'main'
this diagram shows the current state of our repository:
A --> B --> C --> D main (*) \ E new_feature
$ git log --onelineaf68936 (HEAD -> main) Revert "added go_to_hell"389d0ea added go_to_hella8ba071 improved greet.py and added test script2c9fcec first version of greet.py
and the files in our repository reflect the main
branch:
$ cat greet.pydef greet(name): return "hi %s" % namedef say_hello(name): print(greet(name))
And back ...
$ git switch new_feature$ cat greet.pydef greet(name): return "hi %s" % namedef say_hello(name): print(greet(name))def greet_two(name_1, name_2): return "hi %s and %s" % (name_1, name_2)
We implement a "bug fix" in our main branch:
$ git switch main
And change "hi %s"
to "hello %s"
:
def greet(name): return "hello %s" % namedef say_hello(name): print(greet(name))
$ git add greet.py$ git commit -m "fixed spelling"
We decide that the new feature is ready and want to introduce it to the main branch. This is the current state of the commits and branches:
A --> B --> C --> D --> F main (*) \ E new_feature
To merge our commit(s) from the new_feature
branch to the current active
branch (here main
) we enter
$ git merge new_feature
This created a "merge commit" G:
A --> B --> C --> D --> F --> G main (*) \ / E ------- new_feature
$ git log --oneline --graph* 9abfcea (HEAD -> main) Merge branch 'new_feature'|\| * 32c3d76 (new_feature) added function greet_two* | 27e558a fixed spelling|/* 03f9fbb Revert "added go_to_hell"* bbf296e added go_to_hell* 4b79572 improved greet.py and added test script* 8c637a8 fist version of greet.py
Lets assume this is the state of your repository at the time of publicaton:
A --> B --> C --> D main (*)
$ git switch -c publication$ git switch main
publication
and main
point at the same HEAD
:
A --> B --> C --> D main (*) ^ publication
Now you continue to work on main, and commit more changes:
A --> B --> C --> D --> E --> F main(*) ^ new_feature
To reset your project into the state of publication:
$ git switch publication
Which results in:
A --> B --> C --> D --> E --> F main ^ new_feature (*)
Note: git
also supports so called "tags".
If a patch can not be applied we get a "merge conflict".
Let us try to trigger this by committing conflicting changes in
main
and new_feature
.
First in main
:
$ git switch main$ echo name > name.txt$ echo uwe >> name.txt$ git add name.txt$ git commit -m "added name.txt"
Then in new_feature
:
$ git switch new_feature$ echo name > name.txt$ echo schmitt >> name.txt$ git add name.txt$ git commit -m "added name.txt"
Let's try to merge:
$ git switch main$ git merge new_featureCONFLICT (add/add): Merge conflict in name.txtAuto-merging name.txtAutomatic merge failed; fix conflicts and then commit the result.
git status
also indicates this:
$ git statusOn branch mainYou 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 added: name.txtno changes added to commit (use "git add" and/or "git commit -a")
YOU MUST RESOLVE MERGE CONFLICTS OR ABORT THE MERGE
YOU MUST RESOLVE MERGE CONFLICTS OR ABORT THE MERGE
YOU MUST RESOLVE MERGE CONFLICTS OR ABORT THE MERGE
YOU MUST RESOLVE MERGE CONFLICTS OR ABORT THE MERGE
To abort the merge use
$ git merge --abort
Continue working with an unresolved merge conflict will mess up your repository!
The computer was not able to merge conflicting patches and we have to
decide how the final result in name.txt
should look like after the merge.
git
left markers in the conflicing files which we have to fix, there can be
multiple markers in a file:
$ cat name.txtname<<<<<<< HEADuwe=======schmitt>>>>>>> new_feature
We must edit the file to remove the markers and bring the file to the intended state. Open a text editor and modify the file to
nameuwe schmitt
The last step to finish the merge is
$ git add name.txt$ git commit
commits should be small
commit messages should be understandable and descriptive
use git status
, git diff --cached
before you commit.
only track plain source and text files (e.g. put *.pyc
into .gitignore
)
Working with remote repositories is about synchronization of repositories:
push commits and branches to another repository
fetch commits and branches from another repository and merge them
You can share your code
You can use it as a backup
You can use it as an "staging area" if you work on different computers
I updated my local repository by fetching and merging all new commits from a remote repository
with git pull
I work on a new feature / fix a bug / ... (git add
, git commit
, ...)
I push the new commits and branches to the remote repository with git push
Continue with 1.
Before you want to work with a remote reposiory, we first have to clone
it.
First look at https://gitlab.com/uweschmitt/git-handson.git before you continue
with the examples below.
$ cd$ mkdir gitlab_demo$ cd gitlab_demo$ git clone https://gitlab.com/uweschmitt/git-handson.gitCloning into 'git-handson'.......Unpacking objects: 100% (20/20), done.$ cd git-handson$ ls -altotal 16drwxr-xr-x 5 uweschmitt staff 160 Feb 12 17:32 .drwxr-xr-x 47 uweschmitt staff 1504 Feb 12 17:32 ..drwxr-xr-x 13 uweschmitt staff 416 Feb 12 17:33 .git-rw-r--r-- 1 uweschmitt staff 252 Feb 12 17:32 greet.py-rw-r--r-- 1 uweschmitt staff 123 Feb 12 17:32 run.py
When we ran git clone
we automatically registered the remote repository as origin
:
$ git remote -vorigin https://gitlab.com/uweschmitt/git-handson.git (fetch)origin https://gitlab.com/uweschmitt/git-handson.git (push)
Our default branch is main
:
$ git log --oneline --graph* 2f16683 (HEAD -> main, origin/main, origin/HEAD) Merge branch 'new_feature'|\| * 2ab650b (origin/new_feature, new_feature) added function greet_two* | 381ebd0 fixed spelling|/* ba63be2 Revert "added go_to_hell"* c1f84d2 added go_to_hell* 69e5167 improved greet.py and added test script* 16cf3f2 first version of greet.py
We can now switch to an existing branch:
$ git switch new_feature$ git log --oneline --graph* 2ab650b (HEAD -> new_feature, origin/new_feature) added function greet_two* ba63be2 Revert "added go_to_hell"* c1f84d2 added go_to_hell* 69e5167 improved greet.py and added test script* 16cf3f2 first version of greet.py
And also list existing branches:
$ git branch main* new_feature
$ git switch main
Make sure that your active branch is main
.
Please create a text file with a unique name (eg choose your name) and some random content.
Commit this file.
Now push these changes using arguments REMOTE_NAME
and BRANCH_NAME
$ git push
When all your colleagues are done or the push
fails, because the remote
repository changed, you have to update your local repository:
$ git pull
$ ls -al.....
http://swcarpentry.github.io/git-novice/
http://githowto.com/resolving_conflicts
https://eev.ee/blog/2015/04/24/just-enough-git-to-be-less-dangerous/
https://www.learnenough.com/git-tutorial
https://about.gitlab.com/images/press/git-cheat-sheet.pdf
This slide show was created with http://remarkjs.com/
Keyboard shortcuts
↑, ←, Pg Up, k | Go to previous slide |
↓, →, Pg Dn, Space, j | Go to next slide |
Home | Go to first slide |
End | Go to last slide |
b / m / f | Toggle blackout / mirrored / fullscreen mode |
c | Clone slideshow |
p | Toggle presenter mode |
t | Restart the presentation timer |
?, h | Toggle this help |
Esc | Back to slideshow |