Skip to content

Instantly share code, notes, and snippets.

@phamducminh
Created May 18, 2022 16:14
Show Gist options
  • Save phamducminh/71710d08f34d2cffb9e0d601ba843b0a to your computer and use it in GitHub Desktop.
Save phamducminh/71710d08f34d2cffb9e0d601ba843b0a to your computer and use it in GitHub Desktop.
Git Best Practices

Git Best Practices

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

Contents

Don't panic

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 --

Make clean, single-purpose commits

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

Write meaningful commit messages

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

Commit early, commit often

Instead of waiting to make the commit perfect, it is better to work in small chunks and keep committing your work.

Don’t alter published history

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.

Don’t commit generated files

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.

Don’t merge upstream into your tracking branch

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.

force-with-lease

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

Working with submodule

Git reset use cases

git reset --soft: MOVE HEAD

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

git reset --mixed: UPDATING THE INDEX

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

git reset --hard: UPDATING THE WORKING DIRECTORY

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.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment