GIT - Commands & HowTo's

What's the difference between a merge request (MR) and a pull request (PR) ?

Stuff that is common to both types of requests :

  • In both cases, you have a local repository on your workstation. You commit and pull there and it is up-to-date. The difference is mostly in the context :
    • how / why / when you interact with a repository which isn't yours
    • and "how" your commits reach such repository
  • Since there's the word request, you can guess you're actually asking someone to let your commits in.
  • Both involve actions in a web UI such as GitLab or GitHub.

merge request (aka MR) :

At work, there's a shared Git repository where you can push any branch (mostly feature branches, actually) except the master branch. This is because the development team leaders / code quality specialists want to review every commit before accepting it on master. Thus, they make sure every commit meets the internal coding standards (and succeeds at the CI tests, of course).

So, when the development of your feature is done and you feel it's ready to join the master branch, you ask —via the web UI— your colleagues to merge your branch.

  • if your commits are accepted, an actual merge is performed (and your feature branch may optionally be deleted once merged)
  • otherwise, they'll ask you to fix things and to make a new merge request

pull request (aka PR) :

  1. There's this very interesting project on GitHub you wish to give a try / have a look at, so you fork it.
    There is no git fork command. forking is a "special clone" you can do on GitHub, which actually clones the remote repository on the remote server (not locally like a regular clone).
  2. This forked repository is yours, you can work normally with it (usually starting by making a local clone on your workstation).
  3. You can, of course, push / pull / merge as you like since this is yours.
  4. You can also receive ("pull") the commits made on the original repository after you forked it. (commands ?)
  5. But what if you want to share your work / contribute to this original repository ? This is what the pull request is for : you ask the owner of the original repository to pull commits from your repository to his own.
  6. At this time, it's very likely there will be some discussion / comments between you and them before they actually pull your commits (coding standards, code quality, ...)

Git repeatedly prompts for credentials

Situation :

I have a script (an Ansible Galaxy Makefile, actually) that gets stuff from a list of Git repositories (pull or clone ? Whatever...), via HTTPS. Running this prompts for my username and password for every repository it has to get stuff from, which is rather long / annoying / inefficient / error-prone / I-want-to-stop-that!!!

Solution :

Ask Git to cache your credentials :
git config --global credential.helper cache
If the default 15 minutes aren't enough :
git config --global credential.helper "cache --timeout=3600"
Thus, you'll only be prompted once.

Alternate solution :

Append to ~/.gitconfig :
[credential]
	helper = cache --timeout=3600

New --color-moved option in Git v2.15

When text is moved from one place to another, Git reports it as This new --color-moved option (available in Git v2.15) highlights such text in a different color. It works with any command that generates a diff :

Use it :

One shot :
git diff --color-moved=default
Permanently :
  • git config --global diff.colorMoved default
  • or, in ~/.gitconfig :
    [diff]
    	...
    	colorMoved = default
Then git diff as usual.

Try it :

lineOfCode='this is a nice line of code'; alternateLineOfCode='this is a GREAT line of code'; currentDir="$PWD"; tmpDir=$(mktemp -d); tmpFile='myFile'; cd "$tmpDir"; git init; echo -e "$lineOfCode 1\n$lineOfCode 2\n$lineOfCode 3" > "$tmpFile"; echo -e '\nBEFORE :'; cat "$tmpFile"; git add "$tmpFile"; git commit -m 'initial version'; echo -e "$alternateLineOfCode 1\n$lineOfCode 3\n$lineOfCode 2" > "$tmpFile"; echo -e '\nAFTER :'; cat "$tmpFile"; echo -e '\ngit diff --color-moved=no :'; git diff --color-moved=no; echo -e '\ngit diff --color-moved=default :'; git diff --color-moved=default; cd "$currentDir"; [ -d "$tmpDir" ] && rm -rf "$tmpDir"
BEFORE :
this is a nice line of code 1
this is a nice line of code 2
this is a nice line of code 3
[master (root-commit) d25ae79] initial version
 1 file changed, 3 insertions(+)
 create mode 100644 myFile

AFTER :
this is a GREAT line of code 1
this is a nice line of code 3
this is a nice line of code 2

git diff --color-moved=no :
diff --git i/myFile w/myFile
index 574049d..7d015c9 100644
--- i/myFile
+++ w/myFile
@@ -1,3 +1,3 @@
-this is a nice line of code 1
-this is a nice line of code 2
+this is a GREAT line of code 1
 this is a nice line of code 3
+this is a nice line of code 2

git diff --color-moved=default :
diff --git i/myFile w/myFile
index 574049d..7d015c9 100644
--- i/myFile
+++ w/myFile
@@ -1,3 +1,3 @@
-this is a nice line of code 1
-this is a nice line of code 2
+this is a GREAT line of code 1
 this is a nice line of code 3
+this is a nice line of code 2

How to create SSH keys for GitHub ?

  1. Read this tutorial
  2. Test SSH connection to GitHub :
  3. Append to ~/.ssh/config :
    Host github.com
    	User		git				not an example, this MUST be "git"
    	IdentityFile	/home/stuart/.ssh/github
  4. Then, you can create a new repository and store it on GitHub

Untracking files and reclaiming disk space

Let's try this :

Script :
#!/usr/bin/env bash

workDir=$(mktemp --tmpdir -d tempDir_XXXXXXXX)
zerosFile="$workDir/zeros"
otherFile="$workDir/other"

getDirSize() {
	directory=$1
	message=$2
	echo -en "\t==>\tDirectory size [$message] : "
	du -sh $1
	}

countOccurrencesInGitLog() {
	fullPathToFile=$1
	trackedFile=$(basename "$1")
	echo -en "\t==>\tNUMBER OF OCCURRENCES OF '$trackedFile' IN 'git log' : "
	git log --name-status | grep -c "$trackedFile"
	}

# init
cd "$workDir"
git init
getDirSize "$workDir" 'Empty Git repository'

# create and commit "$otherFile"
echo 'this is an other file' > "$otherFile"
git a "$otherFile"
git co -m 'this is an other file'
getDirSize "$workDir" "After creating + committing the 'other' file"

# create and commit "$zerosFile"
dd if=/dev/zero bs=1K count=100 of="$zerosFile"

getDirSize "$workDir" "'zeros' file created but not committed yet"
git a "$zerosFile"
git co -m 'Lots of zeros !!! (initial commit)'
getDirSize "$workDir" "'zeros' file committed"

# append data to "$zerosFile" + commit
for i in {1..5}; do
	echo "hello world ($i)" >> "$zerosFile"
	git a "$zerosFile"
	git co -m "hello world ($i)"
	getDirSize "$workDir" "After hello world ($i) in 'zeros' file"
done

# cleaning the git repo
git gc
getDirSize "$workDir" "After 'git gc' (1)"

# untracking "$zerosFile"
git rm --cached "$zerosFile"
git co -m "rm --cached 'zeros'"
getDirSize "$workDir" "After untracking the 'zeros' file + commit"

# what about the git log (1) ?
countOccurrencesInGitLog "$zerosFile"

# cleaning the git repo
git gc
getDirSize "$workDir" "After 'git gc' (2)"

# removing the 'zeros' file
rm "$zerosFile"
getDirSize "$workDir" "After 'rm' of 'zeros' file"

# cleaning the git repo
git gc
getDirSize "$workDir" "After 'git gc' (3)"

# what about the git log (2) ?
countOccurrencesInGitLog "$zerosFile"

# cleaning before leaving
cd ..
[ -d "$workDir" ] && rm -rf "$workDir"
Output :
Initialized empty Git repository in /tmp/tempDir_FWkkVbkH/.git/
	==>	Directory size [Empty Git repository] : 35K	/tmp/tempDir_FWkkVbkH
[master (root-commit) 599b54f] this is an other file
 1 file changed, 1 insertion(+)
 create mode 100644 other
	==>	Directory size [After creating + committing the 'other' file] : 50K	/tmp/tempDir_FWkkVbkH
100+0 records in
100+0 records out
102400 bytes (102 kB, 100 KiB) copied, 0.000523495 s, 196 MB/s
	==>	Directory size ['zeros' file created but not committed yet] : 150K	/tmp/tempDir_FWkkVbkH
[master daa0106] Lots of zeros !!! (initial commit)
 1 file changed, 0 insertions(+), 0 deletions(-)
 create mode 100644 zeros
	==>	Directory size ['zeros' file committed] : 155K	/tmp/tempDir_FWkkVbkH
[master bc46b8c] hello world (1)
 1 file changed, 0 insertions(+), 0 deletions(-)
	==>	Directory size [After hello world (1) in 'zeros' file] : 162K	/tmp/tempDir_FWkkVbkH
[master 8ceab4f] hello world (2)
 1 file changed, 0 insertions(+), 0 deletions(-)
	==>	Directory size [After hello world (2) in 'zeros' file] : 168K	/tmp/tempDir_FWkkVbkH
[master d35a527] hello world (3)
 1 file changed, 0 insertions(+), 0 deletions(-)
	==>	Directory size [After hello world (3) in 'zeros' file] : 174K	/tmp/tempDir_FWkkVbkH
[master 7db7977] hello world (4)
 1 file changed, 0 insertions(+), 0 deletions(-)
	==>	Directory size [After hello world (4) in 'zeros' file] : 182K	/tmp/tempDir_FWkkVbkH
[master 6cd929a] hello world (5)
 1 file changed, 0 insertions(+), 0 deletions(-)
	==>	Directory size [After hello world (5) in 'zeros' file] : 188K	/tmp/tempDir_FWkkVbkH
Counting objects: 21, done.
Delta compression using up to 2 threads.
Compressing objects: 100% (19/19), done.
Writing objects: 100% (21/21), done.
Total 21 (delta 6), reused 0 (delta 0)
	==>	Directory size [After 'git gc' (1)] : 153K	/tmp/tempDir_FWkkVbkH
rm 'zeros'
[master d68e9bd] rm --cached 'zeros'
 1 file changed, 0 insertions(+), 0 deletions(-)
 delete mode 100644 zeros
	==>	Directory size [After untracking the 'zeros' file + commit] : 156K	/tmp/tempDir_FWkkVbkH
	==>	NUMBER OF OCCURRENCES OF 'zeros' IN 'git log' : 9
Counting objects: 22, done.
Delta compression using up to 2 threads.
Compressing objects: 100% (14/14), done.
Writing objects: 100% (22/22), done.
Total 22 (delta 6), reused 21 (delta 6)
	==>	Directory size [After 'git gc' (2)] : 154K	/tmp/tempDir_FWkkVbkH
	==>	Directory size [After 'rm' of 'zeros' file] : 53K	/tmp/tempDir_FWkkVbkH
Counting objects: 22, done.
Delta compression using up to 2 threads.
Compressing objects: 100% (14/14), done.
Writing objects: 100% (22/22), done.
Total 22 (delta 6), reused 22 (delta 6)
	==>	Directory size [After 'git gc' (3)] : 53K	/tmp/tempDir_FWkkVbkH
	==>	NUMBER OF OCCURRENCES OF 'zeros' IN 'git log' : 9

What we can remember from this experience :

  1. an empty Git repository is about 100KB
  2. after a few commits, git gc helps shrinking the repository
  3. untracking a "big" file having some history but without deleting it (--cached mode) actually adds some data to the repository
  4. after git rm --cached fileIDontWantToTrackAnymore, the full history of fileIDontWantToTrackAnymore is still visible
  5. after git rm --cached, git gc does not make miracles
  6. the most efficient way to save space is to (mv|rm) fileIDontWantToTrackAnymore
  7. right after this again, git gc is helpless
  8. it is now impossible to checkout fileIDontWantToTrackAnymore since it's unknown to Git
  9. fileIDontWantToTrackAnymore still appears in git log, even though it's been untracked and deleted
    How to make fileIDontWantToTrackAnymore disappear from history ? Is it relevant ?

Use a template for better commit messages

Good commit messages are important because we tend to use them to describe what has changed (what git diff is for), instead of explaining why we made those changes (which is not written in the code itself). To keep this in mind at the time of writing a commit messages, let's setup a template !
  1. Start by creating the template file : touch ~/.gitCommitTemplate
  2. Here is my current template :
    # Commit type + target:
    #	ADD | CHANGE | MOVE | DELETE | FIX | COSMETICS
    #	function | class | doc | article | link
    ‌
    # Reference: ticket number, URL
    ‌
    # I made this because...
    # When applied, this commit will...
    ‌
    ########################################## #####################################
    # A good commit message gives CONTEXT about the change:
    #	- WHY was this change necessary ?
    #	- the WHAT and HOW are already visible in the diff: DRY!
    ########################################## #####################################
  3. Then register it :
    • git config --global commit.template ~/.gitCommitTemplate
    • OR: append to ~/.gitconfig :
      [commit]
      	template = /home/bob/.gitCommitTemplate
  4. Enjoy

I've seen the term fast forward in Git output. What is this ?

A fast forward is a special-case operation performed by git merge (remember: git pull = git fetch + git merge FETCH_HEAD) where a branch can be advanced along a linear sequence. This happens whenever you pull changes that build directly on top of the same commit you have as your most recent commit. In other words, there was never any divergence or simultaneous commits created in parallel in multiple repositories. If there had been parallel commits, then git merge would actually introduce a new merge commit to tie the two commits together.

When a non-fast-forward merge occurs, there is always the possibility that a conflict occurs. In this case, git merge will leave conflict markers in the files and instruct you to resolve the conflicts. When you are finished, you would issue a git commit -a to create the merge commit.

For more details, read this article Fast-Forward Git Merge, or my local copy.

git diff and file permissions

What does Git know / track about file permissions ? Let's experiment (or jump to the conclusion) :

Increasing permissions :

workDir='/tmp/testGit'; mkdir "$workDir"; cd "$workDir"; git init; myFile="$workDir/test.txt"; echo 'hello world' > $myFile; chmod 000 $myFile; ls -l $myFile
Just creating our testing environment.
---------- 1 kevin developers 12 Dec 23 11:37 /tmp/testGit/test.txt
git add $myFile; git commit $myFile -m 'hello'
Our 1st commit :
[master (root-commit) e13e5bb] hello
 1 files changed, 1 insertions(+), 0 deletions(-)
 create mode 100644 test.txt
Committed with 644 permission whereas the file actually has 000.
chmod u+r $myFile; ls -l $myFile; git diff
-r-------- 1 kevin developers 12 Dec 23 11:37 /tmp/testGit/test.txt
(no diff seen by Git)
chmod u+w $myFile; ls -l $myFile; git diff
-rw------- 1 kevin developers 12 Dec 23 11:37 /tmp/testGit/test.txt
(no diff seen by Git)
chmod u+x $myFile; ls -l $myFile; git diff
-rwx------ 1 kevin developers 12 Dec 23 11:37 /tmp/testGit/test.txt
diff --git a/test.txt b/test.txt
old mode 100644
new mode 100755
With the executable bit, the file is now considered having 755 permission whereas it actually has 700.
Let's commit it to go further : git add $myFile; git commit $myFile -m "u+x"
[master 93560cf] u+x
 0 files changed, 0 insertions(+), 0 deletions(-)
 mode change 100644 => 100755 test.txt/
chmod g+r $myFile; ls -l $myFile; git diff
-rwxr----- 1 kevin developers 12 Dec 23 11:37 /tmp/testGit/test.txt
(no diff seen by Git)
chmod g+w $myFile; ls -l $myFile; git diff
-rwxrw---- 1 kevin developers 12 Dec 23 11:37 /tmp/testGit/test.txt
(no diff seen by Git)
chmod g+x $myFile; ls -l $myFile; git diff
-rwxrwx--- 1 kevin developers 12 Dec 23 11:37 /tmp/testGit/test.txt
(no diff seen by Git)
chmod o+r $myFile; ls -l $myFile; git diff
-rwxrwxr-- 1 kevin developers 12 Dec 23 11:37 /tmp/testGit/test.txt
(no diff seen by Git)
chmod o+w $myFile; ls -l $myFile; git diff
-rwxrwxrw- 1 kevin developers 12 Dec 23 11:37 /tmp/testGit/test.txt
(no diff seen by Git)
chmod o+x $myFile; ls -l $myFile; git diff
-rwxrwxrwx 1 kevin developers 12 Dec 23 11:37 /tmp/testGit/test.txt
(no diff seen by Git)

Setting permission bits individually :

  1. Let's start by creating our test file :
    myOtherFile="$workDir/test2.txt"; echo 'blah blah blah' > $myOtherFile; chmod 000 $myOtherFile; git add $myOtherFile; git commit $myOtherFile -m 'Blah'
    [master 891cf3b] Blah
     1 files changed, 1 insertions(+), 0 deletions(-)
     create mode 100644 test2.txt
  2. Then, let's toggle permission bits one by one and see what Git detects :
    for person in u g o; do
    	for permission in r w x; do
    		echo $person+$permission
    		chmod $person+$permission $myOtherFile
    		ls -l $myOtherFile
    		git diff $myOtherFile
    		chmod $person-$permission $myOtherFile
    		echo
    	done
    done
    u+r
    -r-------- 1 kevin developers 15 Dec 23 12:55 /tmp/testGit/test2.txt
    
    u+w
    --w------- 1 kevin developers 15 Dec 23 12:55 /tmp/testGit/test2.txt
    
    u+x
    ---x------ 1 kevin developers 15 Dec 23 12:55 /tmp/testGit/test2.txt
    diff --git a/test2.txt b/test2.txt
    old mode 100644
    new mode 100755
    
    g+r
    ----r----- 1 kevin developers 15 Dec 23 12:55 /tmp/testGit/test2.txt
    
    g+w
    -----w---- 1 kevin developers 15 Dec 23 12:55 /tmp/testGit/test2.txt
    
    g+x
    ------x--- 1 kevin developers 15 Dec 23 12:55 /tmp/testGit/test2.txt
    
    o+r
    -------r-- 1 kevin developers 15 Dec 23 12:55 /tmp/testGit/test2.txt
    
    o+w
    --------w- 1 kevin developers 15 Dec 23 12:55 /tmp/testGit/test2.txt
    
    o+x
    ---------x 1 kevin developers 15 Dec 23 12:55 /tmp/testGit/test2.txt

Conclusion

Git can only store two types of modes: 755 (executable) and 644 (not executable). If your file was 444 Git would store it has 644. (source)
Git is a content tracker, where content is de facto defined as "whatever is relevant to the state of a typical sourcecode tree". Basically, this is just files' data and "executable" attribute. (source)

Git hooks

Git hooks :

Available hooks (source) :

hook name is run at trigger
post-receive a local repository when the local repository is the destination of a git push

Hook execution fails on fatal: not a git repository: '.' (source) :

So far this is wizardry to me, but the solution is to unset the GIT_DIR variable. Suggested way of proceeding :
OLD_GIT_DIR=$GIT_DIR
unset GIT_DIR

(part of the script where taking actions on a repo occur)

GIT_DIR=$OLD_GIT_DIR

How are generated Git commit IDs ?

In order to find out, let's build a repo and commit some stuff :
testDir='/tmp/test'; testFile='test.txt'; mkdir -p "$testDir"; cd "$testDir"; git init; echo 'hello world' > "$testFile"; git add "$testFile"; git commit -m 'Hello to the world.'; echo 'hello everybody' >> "$testFile"; git add "$testFile"; git commit -m 'Hello to people.'; git show
Here's our 2nd commit, git show returns :
commit 93ce9bef143d57b6c0133d659db0c3030c24f75f
Author: Thomas ANDERSON <thomas.anderson@metacortex.com>
Date:	Mon Dec 15 17:54:22 2014 +0100

	Hello to people.
So, how is generated this commit ID : 93ce9bef143d57b6c0133d659db0c3030c24f75f ?
Try this :
(printf "commit %s\0" $(git cat-file commit HEAD | wc -c); git cat-file commit HEAD) | sha1sum
This should output the exact commit ID we're talking about : 93ce9bef143d57b6c0133d659db0c3030c24f75f

More details :

The commit ID is the sha1sum of :
  • the string commit [length of commit metadata]NULL
  • then commit metadata itself, being :
    • the tree ID
    • the parent ID
    • the string author firstname lastname email
    • the string committer firstname lastname email
    • (a blank line)
    • the string commit message

Fine, but why is this important ?

This shows that the commit message is used to compute the commit ID. So if a commit is amended, and the commit message changes, so will the commit ID. But, if the previous commit ID has already been pushed to a remote, this breaks one of Git's rules :

Only rewrite that part of history which you alone possess (source) : don't amend your last commit if you've already pushed it (source) !

My Git beginner's guide

I'm afraid this article will only be a collection of links (but a good link is worth 1000 words )

Git bare repositories

Git has "regular" and bare repositories :

"regular" repositories :

  • contain a working copy (i.e. all the files handled by Git)
  • have a .git sub-directory for the metadata
  • can NOT be pushed to
    You may, actually, push to a non-bare repository, but this requires extra precautions and is not recommended.

bare repositories :

  • have no working copy, just the metadata
  • have no .git sub-directory. Actually, they don't need it since bare repositories only contain metadata : what is stored in .git in non-bare repositories is found one level higher in bare repositories
  • are directories named after (by convention) myRepoName.git
  • CAN be pushed to

How to create a bare repository.

How to convert a "regular" repository into a bare repository ?

The idea is to (source) :
  1. Rename the repository directory to append it a .git : myRepoName.git
  2. Delete the working copy
  3. Move one level up the contents of the .git subdirectory (and delete .git once empty)
  4. Make Git aware of the change : git config --bool core.bare true
  5. You may have some remotes to update

It is also possible to proceed with git clone --bare.

Can not clone behind a proxy

Situation :

Commands such as : git clone git@github.com:/everzet/capifony.git end up with :
Cloning into 'capifony'...
ssh: connect to host github.com port 22: Connection timed out
fatal: The remote end hung up unexpectedly
Same for : git clone git://github.com/everzet/capifony.git
Cloning into 'capifony'...
fatal: unable to connect to github.com:
github.com[0: 204.232.175.90]: errno=Connexion terminée par expiration du délai d'attente

Solution :

  1. define the http_proxy environment variable :
    http_proxy=http://user:password@host:port
  2. teach it to Git :
    • git config --global http.proxy $http_proxy
    • OR add into ~/.gitconfig :
      [http]
      	proxy = http://user:password@host:port
  3. clone via HTTP : git clone http://github.com/everzet/capifony.git

commit errors

Wrong commit message (fixes the latest commit only) :

git commit --amend will open an editor in which you can edit the commit message.

Committed as the wrong user/email (fixes the latest commit only) :

  • git commit --amend --author='firstName lastName <email@provider.com>'
  • Alternate solution (source) :
    1. login as the user who is the legitimate owner of the commit
    2. git commit --amend --reset-author
    3. Edit the commit message if required, save, exit, enjoy !

General rule for Git : Only rewrite that part of history which you alone possess (source) : don't amend any commit you have already pushed (source) !

To avoid committing as root, you can add into /root/.bash_profile :

alias git='echo "Please do NOT use git as root"'

Forgot to commit a file (source) :

git commit theFileIForgotToCommit --amend
will open the editor so that the commit message can be updated
git commit theFileIForgotToCommit -m "commit message" --amend
will commit theFileIForgotToCommit into the previous commit
git commit theFileIForgotToCommit --amend -C HEAD
-C commitId states that the commit message of commit having ID commitId will be reused.

.gitconfig

.gitconfig is for personal Git settings, which can be defined via aliases.
[diff]
	# Use better, descriptive initials (c, i, w) instead of a/b (source) :
	#	c : 'Commit' (most often: HEAD)
	#	i : 'Index' (aka: staging area)
	#	w : 'Working directory'
	mnemonicPrefix = true
	colorMoved = default


[alias]
	a	= add
	ap	= add --patch
	b	= branch
	co	= commit
	cane	= commit --amend --no-edit

#	d	= diff			# highlight differences (at line level) with green/red and -/+
#	d	= diff --word-diff	# highlight differences (at word level) with green/red, brackets and -/+
	d	= diff --color-words	# highlight differences (at word level) with green/red only

					# NB : a 'word' is anything but whitespaces. So '--word-diff' and '--color-words'
					# may show nothing (they ignore whitespaces) while 'git status' list some changed files.

	ds	= diff --staged

	l	= ls-files
	lf	= log --name-status
	lg	= log --graph --abbrev-commit --decorate --format=format:'%C(bold blue)%h%C(reset) - %C(bold green)(%ar)%C(reset) %C(white)%s%C(reset) %C(dim white)- %an%C(reset)%C(bold yellow)%d%C(reset)' --all
	# (source)

#	s	= status
	s	= status --untracked-files=no

	# count commits (yesterday / today / total) (inspired by)
	cc = "!f() { yesterday=$(date --date 'now -1 days' +'%a %b %-d ..:..:.. %Y'); today=$(date +'%a %b %-d ..:..:.. %Y'); nbCommitsYesterday=$(git log | grep -Ec \"$yesterday\"); nbCommitsToday=$(git log | grep -Ec \"$today\"); nbCommitsTotal=$(git log --oneline | wc -l); echo \"yesterday/today/total\n$nbCommitsYesterday/$nbCommitsToday/$nbCommitsTotal\" | column -s '/' -t; }; f"

	# squash commits, quicker than 'git rebase -i HEAD~n' (defaults to 'HEAD~2' if number not specified)
	squash = "!f() { git rebase -i HEAD~${1:-2}; }; f"
	sq	= squash


[pull]
	rebase = true	# turn "git pull" into "git pull --rebase" automatically
			# (i.e. "git pull" = "git fetch" + "git merge origin/master")
			# This produces a linear history (my commit came after all commits that were pushed before it,
			# instead of being developed in parallel). It makes history visualization much simpler and git bisect easier to see and understand.


[color]
	branch	= auto
	diff	= auto
	status	= auto


[user]
	email	= thomas.anderson@metacortex.com
	name	= Thomas ANDERSON


[commit]
	template = ~/.gitCommitTemplate


[credential]
	helper = cache --timeout=3600
(source) In Git >= 1.7.9 :
[branch]
	autosetuprebase = always
has been replaced by :
[pull]
	rebase = true

Git glossary

bare
Git bare repositories
FETCH_HEAD
several definitions until I find one that summarizes them all
  • the SHAs of branch/remote heads that were updated during the last git fetch (source)
  • a short-lived reference (i.e. a pointer) to keep track of what has just been fetched from the remote repository by git fetch (source)
  • FETCH_HEAD records the branch which you fetched from a remote repository with your last git fetch invocation (source)
HEAD
  • HEAD is the commit on top of which git commit would make a new one. (details)
  • Refers to a named branch, which in turn refers to a commit (a branch is updated after each commit to point to the latest commit).
  • HEAD is often considered as the latest commit of the current branch, which is partially true since HEAD can actually point to any commit. In such case (pointing to a specific commit instead of pointing to a named branch), we would be in detached HEAD mode (details 1, 2).
head
  • a reference to a commit object (i.e. a commit ID)
  • Each head has a name (branch name, tag name, ...). By default, there is a head in every repository called master.
  • A repository can contain any number of heads.
  • At any given time, one head is selected as "the current head". This one is aliased to HEAD (always in capitals).
  • References, heads or branches can be considered like post-it notes stuck onto commits in the commit history. Usually they point to the tip of series of commits, but they can be moved around with Git commands (checkout, revert, ...)
index (aka "cache" or "staging area")
This is where you place files before committing them into the Git repository. You can imagine it works like this :
  • the working area is the plant floor were your product is manufactured
  • the index is the "packaging / shipping" floor : this is where you bring the product itself, some accessories and shipment documents. You pack everything in a box and ship the whole package.
  • well, now, "shipping" is actually what is made with a commit
Adding file(s) to the index is made with git add.
The index is a binary file, usually found at .git/index.
remote
Any Git repository you "synchronize" yours with, via push / pull commands. This is typically a GitLab or GitHub repository.
upstream
Checking out a local branch from a remote-tracking branch automatically creates what is called a "tracking branch" (and the branch it tracks is called an upstream branch). There are several ways to associate a local branch with a remote branch :
What's the point of defining an upstream branch ?
Adding a remote tracking branch means that Git then knows what you want to do the next time you'll git fetch, git pull or git push. It assumes that you want to keep the local branch and the remote branch it is tracking in sync and does the appropriate thing to achieve this. (source)
working tree (aka "working copy")
These are the files you're working on and that are tracked by Git.