Crafting Commits
How should look the commits?
- Atomic: self-contained, coherent (one commit, one logical change), relatively small
- Consistent:
- Incremental:
- Documented:
Staging Commits
Add more changes to the same file
Following the example:
// Have 2 files modified
$ git status
README
Calculation.c
// Intention: create 2 commits
$ git add README
$ git status
stage: README
WD: Calculation.c
// See what is going in the next commit, that is in staged
$ git diff --staged
// See changes in Working Directory
$ git diff
// Do changes in README
$ vi README
// It is not included in stage area
$ git diff --staged
// It is in fact in WD
$ git diff
// What happen? Git creates a cached copy of the modified file
$ git diff --cached
// It is needed to include it again to stage area
$ git add README
In order to remove the file from index (stage):
$ git reset HEAD
// Same as the following, update HEAD ref and stage to match the commit specified by the reference
$ git reset --mixed
Doing an alias:
git config --global alias.unstage "reset HEAD"
Adding two changes from one file in two commits
Following the example:
// Use -p option for patch, to select hunks (parts to be included in one changed file)
$ git add -p calculation.c
// options:
// y: yes
// n: no
// j: jump to next unstaged hunk
// k: jump to previous unstaged hunk
// q: quit
// a:
// d: skip the rest of file
// /:
// s: split hunk into smaller ones
// e: edit, open the file in an editor to edit it manually
// ?:
// See status, see the same file in staging and WD
$ git status
Interactive staging
Use it:
// git add -i
It displays all files and its stage and unstage area. Also the operations: update -> for stage all the file, patch -> for stage a hunk.
Verifying Commits
First think to check is Spaces.
// Using this command give us all the differences including the spaces
$ git diff
// If we want only spaces
$ git diff --check
How to check Git where to find for spaces
Using the global configuration:
// Using global config
$ git config --global core-whitespace "blank-at-eol, blank-at-eof, tab-in-indent"
// Using tab-in-indent here tells Git to find for tabs instead of spaces
What are the options for check spaces in indentation
// Option: check with spaces that are used for indentation instead of tabs
indent-with-no-tab
// Option: check with tabs instead of spaces
tab-in-indent
How to remove whitespaces in only local commits
If there is a local commit, we can used:
git rebase --whitespace
// Search for diff between current and two commits before
$ git diff HEAD~2..HEAD
// To remove the spaces
$ git rebase HEAD~2 --whitespace=fix
// check that the spaces are removed, starting from the last 2 commits
$ git diff HEAD~2..HEAD
The power of stash
What is stash? Useful to test the stage area before commiting. It is a storage area where we can temporarily put unfinished work that we wish to take out of the working directory.
Stash area is conformed of modified files included in WD and Stage.
Example:
// Exist calculation.c which contains two functions: add and substract. I want to take out the substration
// function. This file is already in staged
$ git diff --staged
// Create the temporary area: stash which constains all files I dont want in my current commit
// --keep-index to keep the stage area as it is currently
// --include-untracked is to include all not yet tracked files in WD
$ git stash save --keep-index --include-untracked "WIP"
// See how are my changes, without the files stashed
$ git status
To see what is in stash:
$ git stash list
This is the time to run all our scripts to compile and test, to verify that the patch is going to leave the code in a consistent state.
// Example of compilation
$ make
// Proceed to commit
$ git commit -m "Supports addition"
Now it is time to restore the files in stash
// To restore the files without removing stash
$ git stash apply
// To restore the files and delete the stash
$ git stash pop
Documenting Commits
Documenting and validating messages:
- Run a reminder, checking the well-formed commit message (automatically)
- Manually doing Code Reviews to check the description
Wrap the description at 72 characters to make it readable on a standard 80-character console.
Well-formed commit message
There is a Git convention for commit messages.
- Create a little reminder every time someone is about to make a commit. This reminder could take the form of a shell script that runs every time a commit is created in the local repository. The script will check the commit message and notify user.
- In order to Git runs the script automatically: commit client-side hook
- Download the scripts in .git/hooks/
wget http://git.io/validate-commit-msg --show-progress --quiet -O .git/hooks/commit-msg
The file looks like:
#!/bin/sh
#
# A hook script that checks the length of the commit message.
#
# Called by "git commit" with one argument, the name of the file
# that has the commit message. The hook should exit with non-zero
# status after issuing an appropriate message if it wants to stop the
# commit. The hook is allowed to edit the commit message file.
DEFAULT="\033[0m"
YELLOW="\033[1;33m"
function printWarning {
message=$1
printf >&2 "${YELLOW}$message${DEFAULT}\n"
}
function printNewline {
printf "\n"
}
function captureUserInput {
# Assigns stdin to the keyboard
exec < /dev/tty
}
function confirm {
question=$1
read -p "$question [y/n]"$'\n' -n 1 -r
}
messageFilePath=$1
message=$(cat $messageFilePath)
firstLine=$(printf "$message" | sed -n 1p)
firstLineLength=$(printf ${#firstLine})
test $firstLineLength -lt 51 || {
printWarning "Tip: the first line of the commit message shouldn't be longer than 50 characters and yours was $firstLineLength."
captureUserInput
confirm "Do you want to modify the message in your editor or just commit it?"
if [[ $REPLY =~ ^[Yy]$ ]]; then
$EDITOR $messageFilePath
fi
printNewline
exit 0
}
Leaving a Trail
It is important to have a clear trail of commits.
Interactive rebase
Useful to clearing up our local history before publishing. To rearrange the commit messages to be more clearly to the developer.
// Starting from the fourth commit ago
$ git rebase -i head~4
// Git opens a list of commits
Options allowed: p,r,e,s,f,x.
Example: allow reorder commits, pick, reword, edit, squash, etc.
To add new changes to the current stage:
$ vi calculation.c
$ git add .
$ git commit --amend -C head
Continue with the interactive rebase:
$ git rebase --continue
See the comments history:
$ git lg master
Public vs Private History
Private = local repository
Public = remote repository
People can and propably should rebase their own work. That is cleanup. But never other people's code. That is a destroy history.