GIT - The 'b*' Git commands

mail

git bisect

Usage :

Use binary search to find the commit that introduced a bug

Imagine you wrote a program : it's made of several files, plenty of commits, and one of them introduced a bug (or anything unwanted). git bisect can help finding which commit caused that To do so, you'll have to tell Git whether commits are bad (the bug is present in this commit) or good (bug not present in this commit).

Manual execution

  1. get commit IDs :
    git log --oneline
  2. enter bisect mode :
    git bisect start
    you can also narrow the search area to some files / directories with :
    git bisect start -- someFile someDir
  3. specify a bad commit :
    • git bisect bad badCommitId
    • git bisect bad will generally suffice since the latest commit in history actually has the bug
  4. specify a good commit :
    git bisect good goodCommitId
  5. git bisect picks a commit between those two endpoints and expects you to specify whether the selected commit is good or bad (does it "git checkout commitId" ? Anyway, you'll end in "detached head" during the process ;-)). Validate this commit by running the program, running unit tests, ..., and let Git know : :
    • git bisect good
    • git bisect bad
    • git bisect skip (undetermined)
  6. git bisect continues narrowing down the range until it finds the exact commit that introduced the change and displays results
  7. at any point, leave bisect mode :
    git bisect reset

Automated execution

mail

git blame

Usage :

Show what revision and author last modified each line of a file

About git annotate :

You may also read about the git annotate command and its differences with git blame.

git annotate is now obsolete, use git blame instead.

mail

git branch

Usage :

List, create, or delete branches
For Git, a branch is just a label moving with a series of commits. Deleting a branch is then just removing this label, and does not actually delete commits. Without this label :
  • merged commits are now referenced by the branch they've been merged into, so everything is going extremely well
  • unmerged commits become "anonymous" (i.e. they belong to 0 branch). They'll be lost (nothing points to them) and "garbage collected" (i.e. deleted) shortly.

Flags :

Flag Usage
(none) list local branches. An asterisk * highlights the current branch.
-a list all branches : local + remote
git branch -a = git branch + git branch -r
-D branch Delete branch branch irrespective of its merged status
-d branch delete the fully merged branch branch
To delete a remote branch : git push -d remote branch
-f --force
git branch -f branch startPoint
forces Git to reset branch to startPoint, even if branch exists already. Otherwise, Without -f, git branch refuses to change an existing branch.
-m oldBranch newBranch Rename (move) oldBranch into newBranch :
git branch -m oldBranch newBranch
-r --remotes list the remote-tracking branches
-u remoteBranch
--set-upstream-to=remoteBranch
Set up localBranch's tracking information so remoteBranch is considered localBranch's upstream branch. If no localBranch is specified, then it defaults to the current branch.
git branch -u origin/branch
--unset-upstream branch Remove the upstream information for branch.
Defaults to the current branch if no branch is specified.
-v -vv --verbose when in list mode (toggled by -a and others), list not only the branch name (the default), but also :
  • the commit ID
  • the commit subject line (1st line of commit message)
  • relationship with upstream branch (if any)
  • -vv only : name of the upstream branch

Example :

Create a new empty branch :

basic command :

git branch myNewBranch

This new branch is not selected by default. To do so : git checkout myNewBranch

Create + checkout :

git checkout -b myNewBranch

Create a new branch from a known point :

Create a new branch called branch. If not provided, startPoint defaults to HEAD :
git branch branch [startPoint]

Let's give this a try

  1. Let's create a new repository and some commits :
    tmpDir=$(mktemp -d --tmpdir=/dev/shm tmpDir.XXXXXXXX); cd "$tmpDir"; git init; echo 'hello world' > myFile; git add myFile; git co -m 'initial version'; for i in {A..D}; do echo $i >> myFile; git add myFile; git co -m "Added '$i'"; done; git log --graph --oneline --decorate
    * 1d791e1 (HEAD ->  master) Added 'D'
    * 7d07f0e Added 'C'
    * b5e0a67 Added 'B'	this will become the "common ancestor" to both branches
    * 7177ae6 Added 'A'
    * b728cee initial version
  2. Now we create a new branch then "jump" on it :
    git branch myNewBranch b5e0a67; git checkout myNewBranch
    Switched to branch 'myNewBranch'
  3. Creating some commits in the new branch :
    for i in {E..H}; do echo $i >> myFile; git add myFile; git co -m "Added '$i'"; done; git log --graph --oneline --decorate
    * bad4a24 (HEAD ->  myNewBranch) Added 'H'
    * 680aaa3 Added 'G'
    * 52ded27 Added 'F'
    * 5cf765a Added 'E'
    * b5e0a67 Added 'B'	actually the ancestor of the 2nd round of commits (as expected)
    * 7177ae6 Added 'A'
    * b728cee initial version
  4. Back to our first branch, what can we see ?
    git checkout master; git log --graph --oneline --decorate
    Switched to branch 'master'
    * 1d791e1 (HEAD ->  master) Added 'D'
    * 7d07f0e Added 'C'
    * b5e0a67 Added 'B'	still there, with the same children
    * 7177ae6 Added 'A'
    * b728cee initial version
  5. Done playing, let's clean our mess before leaving :
    cd ..; [ -d "$tmpDir" ] && rm -rf "$tmpDir"

Get the current Git branch in a shell script :

currentGitBranch=$(git branch 2>/dev/null | awk '/^\*/ {print $2}')
echo "$currentGitBranch"

random notes

PLAYING WITH LOCAL / REMOTE BRANCHES :

Setup playground :
	beforeDir="$PWD"; tmpDir='/run/shm'; workDir=$(mktemp -d --tmpdir="$tmpDir"); cd "$workDir"; mkdir local remote; cd local; git init


Create some commits :
	testFile='myFile'; for i in {A..C}; do echo $i >> "$testFile"; git add "$testFile"; git commit "$testFile" -m "$i"; done; git log --graph --oneline --decorate --all

* f8b2e6c (HEAD -> master) C
* e6f9346 B
* 33d5557 A


Clone into a remote repo and get 'master' branch :
	cd ../remote; git clone ../local .; git log --graph --oneline --decorate --all

* f8b2e6c (HEAD -> master, origin/master, origin/HEAD) C
* e6f9346 B
* 33d5557 A


And about our "remote" thing :
	git remote -v
	origin	/run/shm/tmp.fLG8SwT9fr/remote/../local (fetch)
	origin	/run/shm/tmp.fLG8SwT9fr/remote/../local (push)

	==> the 'local' directory is indeed known as 'origin'


Let's go back to the 'local' dir and create a new branch :
	branchName='my2ndBranch'; cd ../local; git branch "$branchName"; git checkout "$branchName"; git branch

	  master
	* my2ndBranch


And some more commits, on the branch this time :
	for i in {D..E}; do echo $i >> "$testFile"; git add "$testFile"; git commit "$testFile" -m "$i"; done; git log --graph --oneline --decorate --all

* 4aa931d (HEAD -> my2ndBranch) E
* 9ecdc9a D
* f8b2e6c (master) C
* e6f9346 B
* 33d5557 A


One more branch from 'master' :
	branchName='my3rdBranch'; git checkout master; git branch "$branchName"; git checkout "$branchName"; git branch

  master
  my2ndBranch
* my3rdBranch


And some commits :
	for i in {G..I}; do echo $i >> "$testFile"; git add "$testFile"; git commit "$testFile" -m "$i"; done; git log --graph --oneline --decorate --all

* 6e8b6e0 (HEAD -> my3rdBranch) I
* ebe9167 H
* 16be4e4 G
| * 4aa931d (my2ndBranch) E
| * 9ecdc9a D
|/
* f8b2e6c (master) C
* e6f9346 B
* 33d5557 A

==========================================

Now, what if I want to get ALL OF THIS on my remote repo ?

cd ../remote; git branch
* master

	==> only aware of 'master' so far.


Trying to simply pull the branch :
	git pull origin my2ndBranch; git log --graph --oneline --decorate --all

	* 4aa931d (HEAD -> master, origin/my2ndBranch) E
	* 9ecdc9a D
	* f8b2e6c (origin/master, origin/HEAD) C
	* e6f9346 B
	* 33d5557 A

	==> all my commits are there

But :
	git branch
	* master

	==> ooops!


Clean and retry :
	cd ..; rm -rf remote; mkdir remote; cd remote; git clone ../local .; git log --graph --oneline --decorate --all

* 6e8b6e0 (HEAD -> my3rdBranch, origin/my3rdBranch, origin/HEAD) I
* ebe9167 H
* 16be4e4 G
| * 4aa931d (origin/my2ndBranch) E
| * 9ecdc9a D
|/
* f8b2e6c (origin/master) C
* e6f9346 B
* 33d5557 A

But at the same time :
	git branch
		* master
		  my3rdBranch


	==> got it all because of 'clone'. Not what I wanted to do right now
	==> missing the 'my2ndBranch' branch, but commits seen to be there (WTF?!?)



Clean before leaving :
	cd "$beforeDir"; [ -d "$workDir" ] && rm -rf "$workDir"