Advanced git [2d]

Beyond commit, push and pull, Git lets you rewrite history, split and squash commits, and recover from mistakes. This lesson covers the tools we use daily to keep feature branches clean.

Important

Work on this lesson in advisor mode.

Learning goals

  • You can explain what working tree, index and commits each contain, and use that to read diffs and reset changes precisely (e.g. diff vs. diff --staged, reset vs. reset --hard).
  • You can stage and discard changes hunk by hunk.
  • You can rewrite the history of a feature branch — reorder, squash, split and amend commits — and explain what that means for an already pushed branch and for your colleagues.
  • You can rebase a branch onto a rewritten base and resolve the resulting conflicts.
  • You can undo mistakes: revert a commit, recover lost commits with the reflog, restore a file from another branch.
  • You can explain the difference between merging and rebasing when pulling, and our cherry-pick workflow for production branches.
  • You follow our recommended workflow for feature branches.

Resources

Read what's new to you, skim what's familiar, skip what you already master. Stop when you can meet the learning goals.

Your agent can also generate an overview, a tutorial or an explanation for anything here, tailored to what you already know. Just ask.

Pick one introduction to rewriting history

Our workflow

Reference

Exercises

While you are working on the movie DB as a single developer, real life applications are usually developed by entire teams. That means that local branches will frequently need to be rebased as they diverge from remote code. These exercises emulate such situations.

You are free to apply better naming conventions to the requested commit messages, they are only kept short here to fit inside the visualization.

Exercise 1: Split up the most recent commit

Head over to https://code.makandra.de/makandra/advanced-git-example-project and fork Show archive.org snapshot the repository to your private space. Ask your mentor to give you access to the repository.

Have a look at these branches and their commits:

%%{init: { 'gitGraph': { 'mainBranchName': 'master'}} }%%
gitGraph:
commit id: "new-git-repo"
branch feature-documentation
commit id: "document-everything" type: HIGHLIGHT

Check out feature-documentation of your fork and rewrite its history into multiple commits using git rebase:

%%{init: { 'gitGraph': { 'mainBranchName': 'master'}} }%%
gitGraph:
commit id: "new-git-repo"
branch feature-documentation
commit id: "README" type: HIGHLIGHT
commit id: "Setup" type: HIGHLIGHT
commit id: "Docs" type: HIGHLIGHT

The hash of the commit new-git-repo should not change (785951a). Verify this by looking at git log --oneline.

Exercise 2: Split up an earlier commit

Now imagine you are the author of the branch feature-binaries.

%%{init: { 'gitGraph': { 'mainBranchName': 'master'}} }%%
gitGraph:
commit id: "new-git-repo"
branch feature-binaries
commit id: "docs"
commit id: "add binaries" type: HIGHLIGHT
commit id: "init rails"

You are now asked to split up the add binaries commit to one commit per binary.

%%{init: { 'gitGraph': { 'mainBranchName': 'master'}} }%%
gitGraph:
commit id: "new-git-repo"
branch feature-binaries
commit id: "docs"
commit id: "echo binary" type: HIGHLIGHT
commit id: "curl binary" type: HIGHLIGHT
commit id: "init rails"

Exercise 3: Rebase onto a diverged feature branch with merge conflicts

%%{init: { 'gitGraph': { 'mainBranchName': 'master'}} }%%
gitGraph:
commit id: "new-git-repo"

branch feature-node
commit id: "add NPM" type: HIGHLIGHT
branch feature-echo
checkout feature-node
commit id: "setup NVM" type: HIGHLIGHT

checkout feature-echo
commit id: "add echo script"

feature-echo was branched of feature-node while it only contained commit "add NPM". It added a new feature on it (add-echo-script).

In the meantime, feature-node was code reviewed, squashed and force pushed. It now looks like this:

%%{init: { 'gitGraph': { 'mainBranchName': 'master'}} }%%
gitGraph:
commit id: "new-git-repo"

branch feature-node
commit id: "setup node" type: HIGHLIGHT

Rebase feature-echo onto feature-node so it has this history:

%%{init: { 'gitGraph': { 'mainBranchName': 'feature-echo'}} }%%
gitGraph:
commit id: "new-git-repo"
commit id: " " tag: "setup node" type: HIGHLIGHT
commit id: "add echo script"

The hash of the commit "setup node" should be identical within the two branches. Solve the resulting merge conflicts at the README so that the paragraph about the echo script comes after the introduction to Node.

Henning Koch