Skip to content

Instantly share code, notes, and snippets.

@phamducminh
Created May 18, 2022 16:14
Show Gist options
  • Select an option

  • Save phamducminh/71710d08f34d2cffb9e0d601ba843b0a to your computer and use it in GitHub Desktop.

Select an option

Save phamducminh/71710d08f34d2cffb9e0d601ba843b0a to your computer and use it in GitHub Desktop.

Revisions

  1. phamducminh created this gist May 18, 2022.
    228 changes: 228 additions & 0 deletions git-best-practices.md
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,228 @@
    # 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](#dont-panic)
    * [Make clean, single-purpose commits](#make-clean-single-purpose-commits)
    * [Write meaningful commit messages](#write-meaningful-commit-messages)
    * [Commit early, commit often](#commit-early-commit-often)
    * [Don’t alter published history](#Dont-alter-published-history)
    * [Don’t commit generated files](#dont-commit-generated-files)
    * [Don’t merge upstream into your tracking branch](#dont-merge-upstream-into-your-tracking-branch)
    * [force-with-lease](#force-with-lease)
    * [Working with submodule](#working-with-submodule)
    * [Git reset use cases](#git-reset-use-cases)

    ## 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>`<br/>`git reset --hard`<br/>Non-git commands |
    | Files unknown to Git | `git clean`<br/>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:

    ```bash
    $ git reflog --all
    ```

    To restore all those deleted files in a folder enter the following command

    ```bash
    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**:

    ```bash
    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:

    ```bash
    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.

    ```bash
    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.