logo telecom ipp

GIT in practice

Context:

  • GIT Level 1: INF103 discovery
  • GIT Level 2: PACT heavy usage
  • GIT Level 3: SLR201 advanced techniques

GIT Level 1: INF103

Overview:

  • concept of working directory, stage and local repository (.git)
  • git init: create a local repository
  • git status: get info on the local repository
  • git log: get info on the commits
  • git add: start tracking a new file, or stage new changes for next commit
  • git commit: store a new set of changes to tracked files
  • git remote add: connect a remote repository to the local repository
  • git push: push the state of the local repository to a remote repository
  • git pull: pull the state of a remote repository to the local repository
  • git clone: clone a remote repository to a local folder
  • git checkout: change the version of the local copy
  • .gitignore: list of files to ignore

GIT Level 2: PACT

Overview:

  • git branch: manage multiple branches
    • local branches (add remove)
    • remote branches (add remove)
  • git revert: cancel the changes from a commit (by creating an opposite commit)
  • git stash: save local changes from the workingg directory into a temp container
  • complex git project organisation
  • git remove: untrack and delete files
  • git commit --amend: add to the previous (unpushed) commit
  • git checkout -- file: restore file to its previous state, cancelling local modifications
  • git fetch: get state from remote repository without merging
  • git tag: associate a symbolic name to the current repository state
  • git reset: used to unstage modifications (opposite of git add)
  • git stash pop, git stash clear

GIT Level 3: advanced SLR201

  • anything from L1 and L2 that is still unclear for you
  • git mv
  • git diff
  • git merge vs rebase
  • cherrypick
  • squash
  • github/gitlab: what is git and what is more/other
  • git config
  • pull request
  • how to fix tracked files that should not be tracked (*.class)
  • unrelated histories: wtf ? how to fix ?
  • git grep
  • git blame
  • git rerere
  • git hooks

GIT Level 1: all clear ?

  • concept of working directory, stage and local repository (.git)
  • git init: create a local repository
  • git status: get info on the local repository
  • git log: get info on the commits
  • git add: start tracking a new file, or stage new changes for next commit
  • git commit: store a new set of changes to tracked files
  • git remote add: connect a remote repository to the local repository
  • git push: push the state of the local repository to a remote repository
  • git pull: pull the state of a remote repository to the local repository
  • git clone: clone a remote repository to a local folder
  • git checkout: change the version of the local copy
  • .gitignore: list of files to ignore

GIT Level 2: all clear ?

  • git branch: manage multiple branches
    • local branches (add remove)
    • remote branches (add remove)
  • git revert: cancel the changes from a commit (by creating an opposite commit)
  • git stash: save local changes from the workingg directory into a temp container
  • complex git project organisation
  • git remove: untrack and delete files
  • git commit --amend: add to the previous (unpushed) commit
  • git checkout -- file: restore file to its previous state, cancelling local modifications
  • git fetch: get state from remote repository without merging
  • git tag: associate a symbolic name to the current repository state
  • git reset: used to unstage modifications (opposite of git add)
  • git stash pop, git stash clear

Git mv

git mv is usually not required. If you just move files around within the working directory, git SHOULD be able to detect the moves and keep the history of the files.

SHOULD! If you do not want to take the risk, or want to do moves that you know will result in ambiguities, then use git mv

Rename detection can be disabled in the configuration.

Git diff

git diff = what are the changes between the working directory and the local repository

git diff branch2 = what are the changes from the current branch to branch2

Imagine somebody committed something new, and you want to check what this person did without checking out.

git diff distant/current if your checked out branch is "current"

diff --git a/src/Main.java b/src/Main.java
index a0428da..f515cbb 100644
--- a/src/Main.java
+++ b/src/Main.java
@@ -1,6 +1,6 @@
 public class Main {
 
     public static void main(String[] args) {
-        System.out.println("Hello World!");
+        System.out.println("Bonjour!");
     }
 }

Git Merge vs Rebase

Merge and rebase are two ways to merge changes from two branches (or two states).

The principle, graphically:

rebase

What happens with the two commands is the exact opposite:

Merge: you are on master, the destination of the merge, and you do git merge feature which means "merge commits from branch feature into the current branch"

Rebase: you are on feature, the branch you want to merge, and you do git rebase master which means "rebase commits from this branch onto the master branch"

There has to be a common commit for a merge to be allowed.

Pros and cons:

  • Merge: PRO:history is not rewritten CON:complex history,
  • Rebase: PRO:new history is linear and simple, CON: history was rewritten

Rewriting History

Rewriting history is moving commits around the tree in the repo.

If the part of history you are rewriting has not been pushed, i.e. not shared with anyone, then everything is fine.

If it was pushed, but no one is using it, then fine.

If it was pushed, and someone based one or more commits onto what you are changing, then THEY will have a problem: when they pull, a mess appears and they do not have access to the info about what happened.

Git Squash

You have many small commits, one for each of your attempts to fix one elusive bug. example to squash

Either you go into a GIT GUI and select the 3 commits and then squash, or you do:

git rebase -i master

then replace 'pick' by 'squash' for all commits but the first to get something like this:

example after squash

Git Cherrypick

Cherrypick is an option of merge where you can pick which commit you want to integrate during a merge and which commit is ignored. For example, your v2 branch includes one commit that fixes a bug also in v1. You can create a v1plus with v1 + only one commit from v2.

Either you go into a GIT GUI and select cherrypick, or you do:

git rebase -i master

then replace 'pick' by 'skip' for the commits you want to ignore.

This method also allows you to reorder commits. I never had to do it.

GitHub/GitLab: pure Git and additions

Github/gitlab evolved from a git server and added a web interface to:

  • create and manage and explore repositories
  • read the content of a distant repository
  • change files and commit the changes
  • track bugs though issues
  • include documentation with readmes and wikis
  • manage forks (a copy of a repo remembering where it was copied from)
  • allow merge requests
  • allow integration of tools, such as upon a commit,
    • recompile the source code and check that there are no compilation error, and refuse the commit if there are errors
    • run a series of (unit-)tests and refuse the commit if some tests fail
    • update the documentation from special types of comments in the source
    • send a notification to any interested party

Git Config

In the ~/.ssh folder, the config file contains:

Host gitlab.enst.fr
  IdentityFile ~/.ssh/gitkraken_rsa
  IdentitiesOnly yes

This file indicates, per git site, which SSH key to use to connect.

Git has other "central" configuration with a loooong list of fields:

  • user.name determines what goes in the author field of the log
  • user.email => committer field
git config --global user.name "JC Dufourd" 

Configuration can be machine-wide (file somewhere in the system), account-wide (file ~/.gitconfig) or just for the current folder (file .git/config).

Use git config --system or git config --global or git config --local respectively.

how to fix tracked files that should not be tracked

Case 1: *.class (files that can be recreated easily)

You have added everything in the folder to the repository including the *.class.

Edit the file .gitignore to add *.class

git rm *.class

Then commit

Case 2: files that could be important

Files used by your IDE (Eclipse, IntelliJ, Atom...) may still be better untracked, but not deleted like the *.class.

Edit the file .gitignore to add these files

git rm --cached .idea/*

Then commit

Mirroring a repository

Open Terminal.

Create a bare clone of the repository.

$ git clone --bare https://github.com/exampleuser/old-repository.git

Mirror-push to the new repository.

$ cd old-repository.git
$ git push --mirror https://github.com/exampleuser/new-repository.git

Remove the temporary local repository you created in step 1.

$ cd ..
$ rm -rf old-repository

Unrelated Histories: Wtf ?

You have two branches you want to merge. You get a message about unrelated histories. What happened ?

One possible scenario is one person A did git init, git commit, git remote add, git push on a certain distant repo. A person B did git init, git commit, git remote add on the same distant repo. Then B did git push, got an error saying that you cannot push to master, and then B changed the name of the local master to master2, then tried pushing again.

Unrelated Histories: How to fix ?

Here the repo history looks like this:

UH

There is no common commit but two separate commit lines, instead of one tree. By default, git thinks a merge should not happen: all its tools rely on the existence of a common commit.

If you are sure the two branches are mergeable meaningfully, then you can force a merge.

Try the merge command again, this time with the option --allow-unrelated-histories

GIT Grep

There was an exception in function XYZ, you fixed the bug, but you had to change the behaviour of the function, which is used in many places.

You want to know where the keyword XYZ appears in the project, regardless of folder or branch.

git grep XYZ

GIT Blame

Sometimes, you are looking at code, there is no comment, you have no idea what it does or why. And you want to know who wrote these lines and when.

git blame file types the file with each line annotated with who, when, which commit

I have never used it on the command line, but it is pretty handy in an IDE.

GIT ReReRe

Sometimes, you are doing a long merge, you solved 200 conflicts and on the last file, you realize something is wrong. The guy responsible for the changes missed something, you have to abort the merge and tell him to fix the problem.

He does, and you start merging, solving again the same 200 conflicts.

GIT has a tool to help with that: ReReRe for "reuse recorder resolutions"

First, configure it:

git config rerere.enabled true

Then cleanup before doing the first merge: git rerere clear

Then first merge, abort, rework, you start the second merge: every conflict that you already solved is solved automatically, and the first action you have to make is on the last file which was not yet merged.

GIT Hooks

Git hooks are scripts that Git executes before or after events such as: commit, push, and receive. Git hooks are a built-in feature - no need to download anything. Git hooks are run locally.

These hook scripts are only limited by your imagination. Some example hook scripts include:

  • pre-commit:
    • Check the commit message for spelling errors.
    • Make the project and refuse the commit if make fails
  • post-commit: Email/SMS team members of a new commit.

The hooks are all stored in the hooks subdirectory of the Git directory. In most projects, that’s .git/hooks

Look at .sample files in this directory.

Note: a pre-commit hook can be bypassed by git commit --no-verify so it is not a perfect weapon against lazy team mates.

Resources and credits

Other courses in this series are:

This course was designed by Jean-Claude Dufourd, and shared as Creative Commons 3, BY-NC-SA (attribution, non commercial, share alike)