git
Author(s): Darren Wee
git
is a popular source code management tool and commonly used in many open-source projects, especially those on GitHub.
git
can be an incredibly useful tool for collaboration or it can be a terrible headache. Best practices exist in order to create a common understanding between users so that the latter does not happen.
Best practices are guidelines that are mostly sensible, but are still guidelines. You can always choose to ignore them although best if you have a compelling reason to do so.
Good commit messages can help reviewers or other contributors to understand:
They also assist you in the development process if you forget what has been done, or if you need to cherry-pick commits for elsewhere.
The easiest way to attain commit message discipline is to stop putting in one-liner descriptions using git commit -m "Add some things to that."
. Instead, write a proper commit message in an editor:
# opens your editor to write a commit message properly
git add files-to-stage
git commit
# like above, but shows the diff of the currently staged files
git add files-to-stage
git commit --verbose
# amend the most recent commit message
git commit --amend HEAD^
Every commit must have a well written commit message subject line.
Try to limit the subject line to 50 characters (hard limit: 72 chars)
Capitalize the subject line e.g. Move index.html file to root
Use the imperative mood in the subject line
Add README.md
rather than Added README.md
or Adding README.md
or Adds README.md
.Use {scope}: {change}
format when applicable
Person class: remove static imports
, or Unit tests: remove blank lines
Commit messages for non-trivial commits should have a body giving details of the commit.
Give an explanation for the change(s) that is detailed enough so that the reader can judge if it is a good thing to do, without reading the actual diff to determine how well the code does what the explanation promises to do. If your description starts to get too long, that’s a sign that you probably need to split up your commit to finer grained pieces.
Commit messages need to be wrapped to 72 characters or less so that the entire message can be shown without overflow on a standard, 80-column terminal while leaving room for indents/nested reply indicators if you pass .patch
or .diff
files via traditional mailing list (source).
Read more: Formats and Conventions: Commit Messages
As a litmus test, you can try to read your commit message summary in the following manner:
If applied, this commit will
your commit message summary here
For example:
If applied, this commit will
implement getHash() functionality in HashHelper
.
Adapted from se-edu/addressbook-level4 (patch). This commit message follows the guidelines above and also includes the context of the change (how it worked before this patch) as it is necessary to understand why it needed to change.
UniquePersonList#remove(Person): update return type
UniquePersonList#remove(Person) returns true if the person passed into
this method can be found in the internal list, and false otherwise. It
also throws PersonNotFoundException if a person is not found.
Returning a boolean is not required as the exception is thrown before
the value is returned.
Let's update the return type for UniquePersonList#remove(Person) to
void.
Adapted from torvalds/linux (patch).
drm/amd/display: Fix memleaks when atomic check fails
While checking plane states for updates during atomic check, we create
dc_plane_states in preparation. These dc states should be freed if
something errors.
Although the input transfer function is also freed by
dc_plane_state_release(), we should free it (on error) under the same
scope as where it is created.
More examples can be found here: Formats and Conventions: Commit Messages
git
-related functionality, e.g. vim
, do one of either in your terminal:git config --global core.editor "vim" # or you can do the following
export GIT_EDITOR=vim # add to your .bashrc or equivalent
vim
, you can do this by adding this to your .vimrc
:autocmd Filetype gitcommit spell textwidth=72
Merges to the following must always leave the project in a working state, i.e. it can be built and run on:
master
branch, or equivalent,staging
branch, development
branch or equivalent, if any.Changes to your own branches that no one else is using can have non-functioning commits. However, you may wish to hide the sausage making to squash non-functioning commits into a single, functioning commit before you make a pull request.
Changes to your own branches that is used by others should obey always-functioning-commits rule to minimize surprise. This is especially important if you expect your branch to be cherry-picked by another collaborator because they require a specific bit of code that you wrote.
If you need to switch between branches while in the middle of developing a commit, you can use the git stash
command. Stashing saves the uncommitted changes made in your current working directly. This allows you to save your progress without having to commit non-functioning code.
# stash your work not committed to HEAD yet by pushing it onto the stash stack
git stash
git stash push # equivalent to git stash
# restore your most recently stashed work to your current working copy
git stash pop
# acts like git stash pop, but keeps a copy of the stash in the current stash stack
git stash apply
# list all stashes in the stack
git stash list
Stashes are a purely local construct and cannot be pushed to a remote repository.
Read more:
Commits are the building blocks of a codebase; each building block should contribute exactly one useful thing, like:
Each logical change in code should translate to exactly one commit, nothing more or less. Doing so allows you to:
It may also become necessary to scope down what you may deem as a single logical change if it results in a very large commit, as that can also introduce other problems. For example, implementing a single, new feature can be thought of as one logical change to the codebase, but making a pull request for a single, large commit also makes the above benefits disappear.
If you are concerned about appearances, you can always opt to hide the sausage making to clean up your commit history.
If you have made several overlapping changes on your working directory (e.g. forgot to commit, etc), you can always perform a patch-wise stage using git add -p
.
Read more:
Sausage making refers to the process by which code is incrementally worked on, where a series of commits (like links in a sausage) make up a branch. It is often desirable to hide the sausage making where the commit history is cleaned up so that it looks neater and is easier to follow.
When working on a feature
/fix
branch, you may:
This may clutter your history with low-level details or make it convoluted to follow for a maintainer or reviewer. Like sausage, you may enjoy eating it but not the process of making it.
Hiding the sausage is typically achieved by either/both:
git rebase -i
git reset -p
and git add -p
Ensure that you do this before:
Read more:
Always avoid rewriting the published history unless you are very sure of what you are doing, like:
A failed git push
usually means that your local branch is behind its remote counterpart, indicating that the local and remote branches have diverged.
$ git push origin my-branch
To git@github.com:foo/foo.git
! [rejected] my-branch -> my-branch (non-fast-forward)
error: failed to push some refs to 'git@github.com:foo/foo.git'
hint: Updates were rejected because the tip of your current branch is behind
hint: its remote counterpart. Integrate the remote changes (e.g.
hint: 'git pull ...') before pushing again.
hint: See the 'Note about fast-forwards' in 'git push --help' for details.
Alternatively, you may also see this when a branch diversion has occurred when you run git status
.
$ git status
Your branch and 'origin/my-branch' have diverged,
and have 3 and 5 different commit(s) each, respectively.
You can override this by making a force push, i.e. git push --force
but that would result in rewriting the published history or overwrite changes in the divergent remote commits. Observe the guidelines and ensure that the force push can be made in good faith with respect to your collaborators.
Read more:
Remotes refer to versions of the project you are working on that are hosted elsewhere, usually on the Internet. Remotes are very handy for managing collaboration, e.g. if you have to keep your code in sync with the upstream
branch of the project, or if you need to pull code from someone else which may not be merged yet.
You can have as many remotes as you want, each possibly being read-only or with read/write privileges.
Remotes are managed using the git remote
command.
# view all remotes
git remote -v
# add a remote called "upstream" that points to https://github.com/TEAMMATES/teammates
git remote add upstream https://github.com/TEAMMATES/teammates
# branch off from the master branch of the upstream repository
git fetch upstream # get data from upstream repo
git checkout -b your-fancy-branch upstream/master # makes a new branch off the head of upstream/master
# change the URL for the upstream remote from HTTPS to SSH
git remote set-url upstream git@github.com:TEAMMATES/teammates.git
# remove a remote named "upstream"
git remote remove upstream
Read more:
You should... | When ... |
---|---|
merge | you created a branch to develop a feature, and now you want these changes to be inside master |
rebase | you created a branch from master to develop a feature, and someone else pushed a change to master before you finished |
It is generally considered good practice to rebase your feature branch onto whatever branch you're trying to patch before you make the pull request, resolving any conflicts that arise. This:
git bisect
to find regressions on your branch easily without involving unrelated changes from master
Read more:
git
, Linus TorvaldsThese are the resources used in the writing of this chapter, as well as any additional, interesting readings.
Rebasing and merging: some git best practices involves wisdom from Linus Torvalds
Commit only part of a file in Git this is useful to use as a cheatsheet during interactive staging
When do you use git rebase instead of git merge - StackOverflow
git
(merkle tree)