This is the aggregation of best practices while using Git. I will continue adding them as I find other useful ones.
By Minh Pham, 2020
- Don't panic
- Make clean, single-purpose commits
- Write meaningful commit messages
- Commit early, commit often
- Don’t alter published history
- Don’t commit generated files
- Don’t merge upstream into your tracking branch
- force-with-lease
- Working with submodule
- Git reset use cases
Changes | How they can get lost |
---|---|
Changes committed to git | Not at all, unless you insist |
Uncommitted changes to git-controlled files | git checkout <file-or-directory> git reset --hard Non-git commands |
Files unknown to Git | git clean Non-git commands |
Git will try hard to preserve your changes:
- Any changes you committed will be part of the reflog for at least two weeks, even if you change or abandon them.
- Uncommitted changes to git-controlled-files will only get overwritten if you run one
of the commands
git checkout <file-or-directory>
git reset --hard
- And of course any non-git commands that change files
- Files unknown to Git will only get lost with5
git clean
- Again, any non-git commands that change files
To see almost every change that was ever8 known to git:
$ git reflog --all
To restore all those deleted files in a folder enter the following command
git ls-files -d | xargs git checkout --
It is better to keep commits as small and focused as possible for many reasons, including:
- Making code reviews more efficient.
- Easier to roll back
- Track these changes with your ticketing system
Descriptive commit messages that concisely describe what changes are being made as part of a commit make life easier for others as well as your future self
A very good heuristic for writing good commit messages is this:
feat: add beta sequence
^--^ ^---------------^
| |
| +-> Summary in present tense.
|
+-------> Type: chore, docs, feat, fix, refactor, style, or test.
For example
chore: add Oyster build script
docs: explain hat wobble
feat: add beta sequence
fix: remove broken confirmation message
refactor: share logic between 4d3d3d3 and flarhgunnstow
style: convert tabs to spaces
test: ensure Tayne retains clothing
DON'T DO THIS: they are all bad commit messages
- fix bug
- fix issue
- merge code
- fix build
Instead of waiting to make the commit perfect, it is better to work in small chunks and keep committing your work.
Once a commit has been merged to an upstream default branch (and is visible to others), it is strongly advised not to alter history. While git-rebase
is a useful feature, it should only be used on branches that only you are working with.
It is useful to add a .gitignore
file in your repository’s root to automatically tell Git which files or paths you don’t want to track.
Suppose you just started developing code on master. Your branches look like this (A and B are commits, the ‘o’ is just a visual connector):
--A---B----- origin/master (remote branch)
\
o--- master (local tracking branch)
Now you commit some changes X, Y to your local tracking branch:
--A---B---------- origin/master
\
X---Y---- master
and want to push them to the server. If the server is still at commit B, this will result in
--A---B---X---Y----- origin/master
\
o--- master
and you are done.
However, if somebody has committed changes to the server before you push, you will get an error message:
To [...]
! [rejected] master -> master (fetch first)
error: failed to push some refs to [...]
hint: Updates were rejected because the remote contains work that you do
hint: not have locally. This is usually caused by another repository pushing
hint: to the same ref. You may want to first integrate the remote changes
hint: (e.g., ’git pull ...’) before pushing again.
hint: See the ’Note about fast-forwards’ in ’git push --help’ for details.
Before you can fix the problem, you need to ‘git fetch’ to update the remote branch:
--A---B---C---D---E--- origin/master
\
X---Y--------- master
To bring the two lines of development together, usin rebase:
git rebase origin/master
if necessary deal with conflicts (that will temporarily throw your repository into a headless state) and end up with
--A---B---C---D---E----------- origin/master
\
X’---Y’--- master
You have changed your commits by turning them into descendants of E (and possibly by including solutions for conflicts) and you can now push to get
--A---B---C---D---E---X’---Y’---- origin/master
\
o--- master
While it is completely feasible to first fetch, then rebase, you can have both in one command:
git pull --rebase
This is equivalent to git fetch; git rebase origin/master
, so it is exactly what we need.
When someone updates his branch and pushes it up to the remote repository, the ref pointing head of the branch will be updated. Now, unless you do a pull from the remote, your local reference to the remote will be out of date. When you go to push using --force-with-lease, git will check the local ref against the new remote and refuse to force the push. --force-with-lease effectively only allows you to force-push if no-one else has pushed changes up to the remote in the interim.
git push --force origin master # DON'T DO THIS
git push --force-with-lease origin master # DO THIS
It essentially undid the last git commit
command. When you run git commit
, Git creates a new commit and moves the branch that HEAD points to up to it. When you reset back to HEAD~ (the parent of HEAD), you are moving the branch back to where it was, without changing the Index or Working Directory. You could now update the Index and run git commit
again to accomplish what git commit --amend
would have done.
Use Case:
- Combine/squash a series of local commits
- You are satisfied with what you end up with (in term of working tree and index)
- You are not satisfied with all the commits that took you to get there
This is also the default, so if you specify no option at all (just git reset HEAD~ in this case), this is where the command will stop. It still undid your last commit
, but also unstaged everything. You rolled back to before you ran all your git add
and git commit
commands.
Use Case:
- Remove a couple of files in a previous commit
- Split as many commits with any changes as you want
Undid your last commit, the git add
and git commit
commands, and all the work you did in your working directory.
It’s important to note that this flag (--hard) is the only way to make the reset command dangerous, and one of the very few cases where Git will actually destroy data. Any other invocation of reset
can be pretty easily undone, but the --hard option cannot, since it forcibly overwrites files in the Working Directory.