In the previous lesson we used git rebase for one thing only: changing a branch's base. But the mechanism underneath — breaking a branch down into a list of commits and applying them again one by one — allows a great deal more if somebody lets you edit that list before it runs.
That is git rebase -i. Git opens a text file containing the commits it is about to reapply, one per line, and waits for you to decide what to do with each: leave it as it is, change its message, join it with the one before, split it in two, move it somewhere else or delete it. When you save and close, Git carries out your instructions to the letter.
It is the tool that turns a real branch — with its second thoughts, its backtracking and its wips — into a sequence of commits somebody else can read and understand. Bruno needs it right now: his feature/csv-export branch has six commits, three of them called fix, wip 2 and wip 3, and he wants to publish it for review.
And with it comes back, stronger than ever, the golden rule: interactive rebase rewrites commits, so it applies only to work you have not yet published (or to a published branch that only you use and about which you can warn people).
Contents
- What the to-do list is
- The available commands
- The starting point: Bruno's branch
- A full session: from six commits to two
squashversusfixup- Reordering and removing commits
- Splitting a commit in two with
edit - Changing just a message with
reword --autosquash:commit --fixupandcommit --squash--exec: validating every commitbreak,label,resetandmerge- What to do if you get lost halfway
- What the to-do list is
When you launch an interactive rebase, Git prepares the list of commits it is going to reapply and opens it in your editor:
pick b1c4f80 Start the export pick 3d6e9a1 Add export button pick c8f2b47 more csv stuff pick 2a7f4c1 fix pick 9e3b8d6 wip 2 pick 5c1a9f2 wip 3 # Rebase e91d4a8..5c1a9f2 onto e91d4a8 (6 commands) # # Commands: # p, pick <commit> = use commit # r, reword <commit> = use commit, but edit the commit message # e, edit <commit> = use commit, but stop for amending # s, squash <commit> = use commit, but meld into previous commit # f, fixup [-C | -c] <commit> = like "squash" but keep only the previous # commit's log message # x, exec <command> = run command (the rest of the line) using shell # b, break = stop here (continue rebase later) # d, drop <commit> = remove commit # l, label <label> = label current HEAD with a name # t, reset <label> = reset HEAD to a label # m, merge [-C <commit> | -c <commit>] <label> [# <oneline>] # # These lines can be re-ordered; they are executed from top to bottom. # # If you remove a line here THAT COMMIT WILL BE LOST.
Three details to take in before touching anything:
- The order is chronological ascending: the oldest commit at the top. That is the opposite of
git log, which shows the most recent first. This inversion is beginners' number-one source of mistakes. - The lines run from top to bottom. Reordering lines reorders commits.
- Deleting a line is the same as
drop: that commit disappears. Git warns you in capitals for a reason.
And a point about the argument: git rebase -i main means "reapply onto main everything that is on my branch and not on main", just as in the previous lesson. If you only want to touch the last N commits of the branch you are on, without changing the base, you use:
Careful with that second one: git rebase -i <commit> does not include <commit> in the list, because <commit> is the base. To touch the last four commits, the base is the fifth counting back from the end: HEAD~4.
If your default editor is not the one you want, you change it with
git config --global core.editor "code --wait"(ornano,vim, whatever you use). It was explained in lesson 01-05, and here you will be grateful for it.
- The available commands
| Command | Short form | What it does |
|---|---|---|
pick |
p |
Applies the commit as it is. It is the default |
reword |
r |
Applies the commit and opens the editor to change its message |
edit |
e |
Applies the commit and stops so that you can modify it (or split it) |
squash |
s |
Joins the commit with the previous one and opens the editor to combine the two messages |
fixup |
f |
Joins the commit with the previous one and discards its message |
fixup -C |
— | Joins with the previous one but keeps this message instead of the earlier one |
drop |
d |
Removes the commit. Equivalent to deleting the line |
exec |
x |
Runs a shell command at that point in the sequence |
break |
b |
Stops there without doing anything, so that you can look |
label |
l |
Gives a temporary name to the current HEAD |
reset |
t |
Goes back to a previous label |
merge |
m |
Creates a merge commit with a label |
The first four cover 95% of real use. drop and exec turn up from time to time. break is a convenience. label, reset and merge only appear when you use --rebase-merges to rebuild a history containing merges, and we shall not need them day to day: it is enough to know what they mean if you come across them.
A note on squash and fixup: they join with the previous commit in the list, that is, with the line above. That is why they can never be the first line (there is nothing to join with) and why the order matters so much.
- The starting point: Bruno's branch
Bruno has spent a week on exporting tasks to CSV. The branch works, but the history is a shambles:
5c1a9f2 wip 3 9e3b8d6 wip 2 2a7f4c1 fix c8f2b47 more csv stuff 3d6e9a1 Add export button b1c4f80 Start the export
With --stat you can see what is really in each one:
5c1a9f2 wip 3 app.js | 4 ++-- 9e3b8d6 wip 2 app.js | 7 +++++-- 2a7f4c1 fix app.js | 2 +- c8f2b47 more csv stuff app.js | 18 ++++++++++++++++++ 3d6e9a1 Add export button index.html | 3 +++ styles.css | 8 ++++++++ b1c4f80 Start the export app.js | 6 ++++++
Read calmly, the branch does two independent things:
- A function that converts the task list to CSV and downloads it (
app.js): commits 1, 3, 4, 5 and 6. - A button in the interface that calls it (
index.html+styles.css): commit 2.
Bruno's aim is to end up with exactly those two commits, in that order, with messages that explain what they do. And the important part: the final content of the branch is not going to change by a single line. A well-executed interactive rebase reorganises how the story is told, not what ends up there.
That is the hash of the tree at the tip of the branch. When the rebase finishes it must be the same. It is the best quality control there is for this operation.
- A full session: from six commits to two
Bruno launches the interactive rebase:
And edits the list that appears. The first job is to reorder so that what belongs together sits together, and then to decide the command for each line. This is how the file looks before saving:
pick b1c4f80 Start the export squash c8f2b47 more csv stuff fixup 2a7f4c1 fix fixup 9e3b8d6 wip 2 fixup 5c1a9f2 wip 3 pick 3d6e9a1 Add export button
Read it from top to bottom like a recipe:
pick b1c4f80— apply the first commit as it is. It will be the basis of the final export commit.squash c8f2b47— join it with the previous one, and let me combine the messages.fixup 2a7f4c1— join it with the previous one and throw its message away (fixcontributes nothing).fixup 9e3b8d6— the same withwip 2.fixup 5c1a9f2— the same withwip 3.pick 3d6e9a1— the button commit, moved to the end, stays as it is for now.
On saving and closing, Git starts running. At the squash step it stops and opens the editor with the two messages:
# This is a combination of 2 commits. # This is the 1st commit message: Start the export # This is the commit message #2: more csv stuff # Please enter the commit message for your changes.
Bruno deletes all of that and writes a decent message:
Add the export of tasks to CSV Generates a CSV file with the title, the status and the creation date of each task and downloads it from the browser with no calls to the server. Quotation marks and semicolons in the title are escaped as per RFC 4180.
He saves, closes, and the rebase carries on. The three fixups ask nothing. At the end:
The second one's message still needs polishing, but we shall see that in section 8. First, the check we promised:
Identical to the one from before the rebase. The branch's content has not changed; only its history has. That is the guarantee that makes this operation reasonable rather than reckless.
gitGraph commit id: "e91d4a8" branch before commit id: "b1c4f80" commit id: "3d6e9a1" commit id: "c8f2b47" commit id: "2a7f4c1" commit id: "9e3b8d6" commit id: "5c1a9f2" checkout main branch after commit id: "d72e6b8" commit id: "a3f9c14"
squash versus fixup
squash versus fixupBoth join a commit with the previous one. The only difference is in the message, but it determines which to use in each case:
squash |
fixup |
|
|---|---|---|
| Resulting content | That of the two commits combined | That of the two commits combined |
| Resulting message | The two messages together, editable | Only that of the previous commit |
| Does it open the editor? | Yes | No |
| When to use it | Both commits contribute information to the message | The second one was a fix, a typo, a wip |
| Variant | — | fixup -C keeps the second one's message |
In practice: if the message of the commit you are absorbing is worth anything, squash; if it is rubbish (fix, wip, now it works, semicolon), fixup. And since a squash of five commits forces you to clean up five messages by hand in the editor, as soon as you have three or more it usually pays to use fixup on all of them and write the message once with reword.
- Reordering and removing commits
You have already seen reordering: in Bruno's session, moving 3d6e9a1 from second position to last was enough to group the work by topic.
Removing is just as straightforward: put drop in front of the line, or delete the line entirely. The two forms are equivalent; drop is preferable because it leaves a visible record of the intention and avoids deleting a line by accident:
pick b1c4f80 Start the export drop 4c8f1a5 Add console.log for debugging pick 3d6e9a1 Add export button
Two warnings that apply to both operations:
- Reordering and removing can cause conflicts. If commit
c8f2b47modified a line thatb1c4f80had just created and you separate them, the second one no longer finds the context it expected. It is resolved like any rebase conflict (lesson 05-01): resolve,git add,git rebase --continue. Remember that theours/theirssides are still inverted. - Removing a commit from the middle leaves the branch with fewer changes, not necessarily with less code that is needed. If you
dropthe commit that created a function and keep the one that calls it, the branch no longer builds.--exec(section 10) exists precisely to detect that.
- Splitting a commit in two with
edit
editThis is interactive rebase's star turn and the one that impresses most the first time. Suppose Bruno looks at d72e6b8 and decides it mixes two things: the function that generates the CSV and the one that triggers the download. He wants to split it.
Step 1: mark the commit with edit.
Git applies the commit and stops:
Stopped at d72e6b8... Add the export of tasks to CSV You can amend the commit now, with git commit --amend Once you are satisfied with your changes, run git rebase --continue
Step 2: undo the commit but keep its changes.
This is what does the magic: git reset with no options (equivalent to --mixed) moves HEAD one commit back and leaves all the changes in the working tree, unstaged. The commit has gone; its content is still in the file. It is the only appearance of reset in this module; its three modes and its use as an undo tool are studied thoroughly in lesson 09-02.
Step 3: stage and commit in parts. This is where git add -p comes in, which you learned in lesson 02-04: staging only some hunks of the file.
Bruno accepts with y the hunks belonging to the tasksToCSV function and rejects with n those belonging to downloadFile. Then:
And the rest:
Step 4: carry on.
f04b7e2 Add export button b8c2a70 Download the generated CSV from the browser 6e1d9f3 Add the conversion of tasks to CSV format
One commit has become two, without losing a line. Note that the commit after it (a3f9c14 → f04b7e2) has changed hash too: by inserting commits before it, its parent is a different one, and you know by now what that implies.
Trick: if when you run
git reset HEAD^you realise the split is not clean (the changes are interleaved within the same function), you can edit the files by hand before eachcommit. You are not obliged to make each half an exact subset of the original; only to make the end result the same. Verify it with therev-parse HEAD^{tree}trick.
- Changing just a message with
reword
rewordThe button commit's message is terse and says nothing about where the button goes. To change only the message, without touching anything else:
pick 6e1d9f3 Add the conversion of tasks to CSV format pick b8c2a70 Download the generated CSV from the browser reword f04b7e2 Add export button
Git applies the first two, and at the third it opens the editor with the current message. Bruno changes it to:
He saves, closes, and that is that:
9c7e5a1 Add the export button to the action bar b8c2a70 Download the generated CSV from the browser 6e1d9f3 Add the conversion of tasks to CSV format
Three commits, three messages that explain what each one does, and a branch ready to publish and to be reviewed.
A note for the most frequent case: if the commit you want to rewrite is the last one, you do not need an interactive rebase. git commit --amend (lesson 02-04) does the same with less ceremony. reword is for the ones further back.
What makes a good commit message — the imperative, the subject line, the body, the trailers — is the subject of lesson 08-01. Here we are concerned only with the mechanism of changing it.
--autosquash: commit --fixup and commit --squash
--autosquash: commit --fixup and commit --squashThere is a far more comfortable flow than marking fixup by hand, and it consists of deciding it at the moment you make the commit, while you still remember which commit it is correcting.
Imagine Bruno is working and spots a typo in commit 6e1d9f3, which he made two days ago. Instead of creating a commit called fix:
Git has created an ordinary commit, but with a special message: fixup! followed by the subject of the commit it corrects. That prefix is a marker Git knows how to interpret.
2b8f4d1 fixup! Add the conversion of tasks to CSV format 9c7e5a1 Add the export button to the action bar b8c2a70 Download the generated CSV from the browser 6e1d9f3 Add the conversion of tasks to CSV format
And now for the pretty part:
The list that appears comes already ordered and with the commands filled in:
pick 6e1d9f3 Add the conversion of tasks to CSV format fixup 2b8f4d1 fixup! Add the conversion of tasks to CSV format pick b8c2a70 Download the generated CSV from the browser pick 9c7e5a1 Add the export button to the action bar
Bruno only has to save and close without touching anything. Git has moved the correcting commit right behind its target and marked it as fixup.
| Command | Message prefix | In the rebase it becomes | Effect on the message |
|---|---|---|---|
git commit --fixup <sha> |
fixup! |
fixup |
The fix's message is discarded |
git commit --squash <sha> |
squash! |
squash |
The editor opens so you can combine them |
git commit --fixup=reword:<sha> |
amend! |
fixup -C |
Only changes the original's message |
So that you do not have to remember --autosquash every time:
And a warning: --autosquash pairs commits by the text of the subject, not by the hash. If two commits on the branch have exactly the same subject, the pairing can be ambiguous. In practice it almost never happens, and you can always review the list before saving (which is exactly what the editor is asking you to do).
This flow — work, correct with --fixup, and flatten everything with a rebase -i --autosquash just before publishing — is probably the most productive way to use Git day to day. It frees you from thinking about the history while you are programming, and lets you tidy it up in one go at the end.
--exec: validating every commit
--exec: validating every commitA clean history that does not build is worth nothing. When you reorder, join or split commits, it is easy for an intermediate one to end up broken: the one that calls a function that does not exist yet, the one that imports a module which arrives two commits later.
--exec runs a command after every reapplied commit and stops the rebase if the command returns a non-zero exit code:
Git automatically inserts an exec line after each pick:
pick 6e1d9f3 Add the conversion of tasks to CSV format exec npm test pick b8c2a70 Download the generated CSV from the browser exec npm test pick 9c7e5a1 Add the export button to the action bar exec npm test
If the second npm test fails:
Executing: npm test ... warning: execution failed: npm test You can fix the problem, and then run git rebase --continue
The rebase stops at that point, with the repository in exactly the state of that commit. You can fix whatever it is, run git commit --amend, and carry on.
You can also write the exec lines by hand wherever you like, instead of after every commit. And it does not have to be a test suite; any quick check will do:
pick 6e1d9f3 Add the conversion of tasks to CSV format exec node --check app.js pick b8c2a70 Download the generated CSV from the browser exec node --check app.js exec grep -rn "console.log" app.js && exit 1 || exit 0
That last line is a small guard: it fails if there is a forgotten console.log left. Automating this sort of check permanently, so that it runs by itself on every commit, is the subject of hooks (lesson 06-01).
break, label, reset and merge
break, label, reset and mergeThree less frequent commands, but it is worth recognising them.
break stops the rebase at that point without doing anything else. It is a deliberate pause:
pick 6e1d9f3 Add the conversion of tasks to CSV format break pick b8c2a70 Download the generated CSV from the browser
Useful for looking at the state of the project in the middle of the sequence, running something by hand or simply drawing breath. You carry on with git rebase --continue.
label, reset and merge only appear when you use git rebase -i --rebase-merges, which rebuilds a history preserving its merge commits instead of flattening it. Git then generates a list looking like this:
label onto reset onto pick 6e1d9f3 Add the conversion of tasks to CSV format label export reset onto pick 9c7e5a1 Add the export button to the action bar label button reset onto merge -C 4a7d2f8 export # Merge the export merge -C 8e3b6c1 button # Merge the button
It reads like this: label X saves a marker at the current point, reset X goes back to that marker, and merge -C <sha> X creates a merge commit with the branch marked as X, reusing the message of commit <sha>. It is a small language for describing a graph. You will hardly ever need it; it is enough not to be alarmed if it turns up.
- What to do if you get lost halfway
A long interactive rebase can turn into a maze: four conflicts, an edit half done and the feeling of not knowing where you are. Ways out, from the least to the most drastic:
1. Ask where you are. git status during an interactive rebase is extraordinarily informative:
interactive rebase in progress; onto e91d4a8 Last commands done (3 commands done): pick 6e1d9f3 Add the conversion of tasks to CSV format fixup 2b8f4d1 fixup! Add the conversion of tasks to CSV format Next commands to do (2 remaining commands): pick b8c2a70 Download the generated CSV from the browser pick 9c7e5a1 Add the export button to the action bar You are currently rebasing branch 'feature/csv-export' on 'e91d4a8'.
It tells you what has been done and what is left. The live list is also in .git/rebase-merge/git-rebase-todo, and git rebase --edit-todo opens it so you can change what remains to be done without aborting. It is the elegant way out when you realise halfway through that you planned badly:
2. See what is failing right now.
It shows the commit being applied, with its full diff. Very useful when the conflict makes no sense at first glance.
3. Abort.
It undoes the whole rebase and returns the branch to its original state, with the original hashes. It is safe, it is instantaneous and it leaves no trace. When in doubt, abort: replanning the list with a clear head takes two minutes; untangling a badly handled rebase, much longer.
4. If you have already finished and the result is a disaster. This is where the safety net comes in: the original commits are still in the object database and the reflog records where your branch was before you started. The full procedure for going back is the subject of lesson 09-04. And the same cheap reflex as always, which saves you having to use it:
git branch backup-before-rebase
git rebase -i main
# Gone wrong? -> git reset --hard backup-before-rebaseCommon Mistakes and Tips
Mistake 1: reading the list as though it were git log. In the editor, the oldest commit is at the top. Marking squash on the wrong line because of this inversion is mistake number one.
Mistake 2: putting squash or fixup on the first line. There is no previous commit to join with and the rebase fails before it starts. The first line is always pick, reword, edit or drop.
Mistake 3: deleting a line by accident. That commit disappears. Use an explicit drop when you mean to remove one, and if you delete a line by mistake, close the editor without saving: Git aborts the rebase.
Mistake 4: interactively rebasing a branch that is already published and shared. The golden rule has no convenient exceptions. If other people work on that branch, do not rewrite it.
Mistake 5: running git commit instead of git rebase --continue after resolving a conflict. Except in the edit flow (where you do commit yourself), the one that creates the commit is the rebase.
Mistake 6: forgetting that a rebase runs the hooks. If you have a slow pre-commit, a rebase of twenty commits runs it twenty times. --no-verify disables it, with the responsibility that implies.
Mistake 7: flattening everything into one gigantic commit "so that it looks clean". A 900-line commit saying "Add the export" is as useless as six commits called wip. The aim is for each commit to be one complete, comprehensible change, not for there to be only one.
Tip 1: check the tree before and after. git rev-parse HEAD^{tree} must match if your intention was only to reorganise. If it does not match, you have lost or duplicated something.
Tip 2: adopt --fixup from today. As soon as you have the reflex of correcting with git commit --fixup <sha> instead of with a commit called fix, the final tidy-up becomes trivial. Enable rebase.autoSquash true.
Tip 3: do small, frequent interactive rebases. Cleaning up five commits is comfortable; cleaning up forty is not. A rebase -i at the end of each day's work on a branch is an excellent habit.
Tip 4: use --exec when you reorder or split. It is the only cheap way of knowing that no intermediate commit has been left broken.
Tip 5: review the list before saving, always. That open editor is not a formality: it is your last chance to see the whole plan before it runs.
When it is worth tidying up and how much — whether the team insists on a linear history, whether each branch is squashed into one commit when it is integrated, whether rewriting is forbidden — is not a technical decision but a matter of team policy, and it is dealt with in lesson 08-02: Keeping a Clean History.
Exercises
Exercise 1: the full clean-up
Create a repository with a main branch (one commit) and a feature/report branch with these five commits, in this order:
Start the report(createsreport.jswith a function)Add report styles(createsreport.css)more stuff(extendsreport.js)wip(touchesreport.js)fix(touchesreport.js)
With a single git rebase -i, leave the branch with two commits: one with everything belonging to report.js and a decent message, and another with the styles. Demonstrate with git rev-parse HEAD^{tree} that the final content has not changed.
Exercise 2: splitting a commit
Starting from the result of exercise 1, split the report.js commit in two: one with the data generation and another with the rendering. Use edit, git reset HEAD^ and git add -p.
Exercise 3: --fixup and --exec
On a new branch with three commits:
- Deliberately introduce a syntax error in the second commit.
- Carry on working and make a third, ordinary commit.
- Correct the error with
git commit --fixup <sha of the second>. - Apply it with
git rebase -i --autosquash. - Launch another rebase with
--exec "node --check file.js"and check that all three commits now pass validation.
Solutions
Solution 1:
mkdir /tmp/practice-rebase-i && cd /tmp/practice-rebase-i
git init -b main
echo "# Project" > README.md && git add . && git commit -m "Base"
git switch -c feature/report
echo "function generateReport() {}" > report.js && git add . && git commit -m "Start the report"
echo ".report { margin: 1rem; }" > report.css && git add . && git commit -m "Add report styles"
echo "function reportData() {}" >> report.js && git commit -am "more stuff"
echo "// pending" >> report.js && git commit -am "wip"
echo "function renderReport() {}" >> report.js && git commit -am "fix"
git rev-parse HEAD^{tree}The edited list (the report.js commits together at the top, the styles at the end):
pick 4a1c8e3 Start the report fixup 9d2f7b5 more stuff fixup 1e6a4c8 wip fixup 7b3d9f2 fix pick 5c8e1a4 Add report styles
Then a second pass for the message (or mark reword on the first line from the outset):
New message: Add the generation and rendering of the report.
The tree matches the one noted down: only the history has changed.
Solution 2:
(The file was new in that commit, so it appears as untracked. If it already existed, it would show as M.)
# Stage only the data generation part
git add -p report.js # accept the generateReport/reportData hunks
git commit -m "Add the generation of the report data"
git add report.js
git commit -m "Add the rendering of the report"
git rebase --continue
git log --oneline main..HEAD9e7c3b1 Add report styles 2f8a5d6 Add the rendering of the report b41e9c7 Add the generation of the report data
If
git add -pdoes not offer separable hunks because the file is new, usegit add -N report.jsfirst: it registers the file in the index as empty and allows its content to be split into hunks.
Solution 3:
mkdir /tmp/practice-autosquash && cd /tmp/practice-autosquash
git init -b main
echo "const a = 1;" > app.js && git add . && git commit -m "Base"
git switch -c tests
echo "const b = 2;" >> app.js && git commit -am "Add b"
echo "const c = ;" >> app.js && git commit -am "Add c" # syntax error
echo "const d = 4;" >> app.js && git commit -am "Add d"
git log --oneline main..HEAD# 3. Fix the error, pointing at the guilty commit
sed -i 's/const c = ;/const c = 3;/' app.js
git add app.js
git commit --fixup 3f9b2d6The list comes ready-made; you only have to save:
Executing: node --check app.js Executing: node --check app.js Executing: node --check app.js Successfully rebased and updated refs/heads/tests.
Had you run it before the --autosquash, the second exec would have failed and the rebase would have stopped at the broken commit.
Conclusion
Interactive rebase is the history repair shop. What we have learned:
git rebase -i <base>opens a to-do list with the commits that are going to be reapplied, from oldest to most recent, and carries out whatever you write in it from top to bottom.- The four commands you will always use are
pick(leave it),reword(change the message),edit(stop to modify or split) andsquash/fixup(join with the previous one, keeping or discarding the message).dropremoves andexecvalidates. - Reordering lines reorders commits and lets you group by topic what was done out of order; deleting a line removes the commit.
- Splitting a commit is
edit+git reset HEAD^+ severalgit add -pandgit commit+git rebase --continue. It is the most powerful operation of the lot. git commit --fixup <sha>withrebase -i --autosquashturns the clean-up into a formality: you mark the corrections as you go and Git places them on its own. Enable it withrebase.autoSquash true.--execruns a check after every commit and stops the rebase if it fails: the only cheap way of guaranteeing that no intermediate commit is left broken.- If you get lost:
git statussays where you are,git rebase --edit-todoreplans what is left,--show-current-patchshows what is failing and--abortundoes it all without a trace. git rev-parse HEAD^{tree}before and after is the best verification that you have reorganised the history without altering the content.- And above all: the golden rule. This is done before publishing.
What comes next
Rebase and interactive rebase always work with the set of commits on a branch. But sometimes what you need is far more surgical: to take one specific commit from one place to another, leaving everything else where it is.
Ana is going to need it tomorrow. She has fixed a bug on main that causes the focus to be lost when a task is deleted, and it turns out that version 1.x, still deployed at the client, has the same bug and lives on a separate maintenance branch. She does not want to merge the whole of main into it: she wants that commit and nothing else.
That is what git cherry-pick is for, and we shall see it in lesson 05-03: Cherry-Picking Commits.
Mastering Git: From Beginner to Advanced
Module 1: Introduction to Git
- What Is Git?
- Installing Git
- Basic Git Terminology
- The Git Data Model
- Configuring Git
- Initial Configuration
Module 2: Basic Git Operations
- Creating a Repository
- Cloning a Repository
- The Basic Git Workflow
- Staging and Committing Changes
- Inspecting Changes with git diff
- Viewing Commit History
Module 3: Branching and Merging
- Understanding Branches
- Creating and Switching Branches
- Merging Branches
- Merge Strategies
- Resolving Merge Conflicts
- Branch Management
Module 4: Working with Remote Repositories
- Understanding Remote Repositories
- Adding a Remote Repository
- Authenticating with Remote Repositories
- Fetching and Pulling Changes
- Pushing Changes
- Tracking Branches
Module 5: Advanced Git Operations
Module 6: Git Tools and Techniques
- Using Git Hooks
- Git Bisect
- Git Blame
- Git Log and Aliases
- Git Submodules
- Multiple Working Copies with git worktree
Module 7: Collaboration and Workflow Strategies
- Forks and Pull Requests
- Code Reviews with Git
- The Git Flow Workflow
- GitHub Flow
- Trunk Based Development
- Continuous Integration with Git
Module 8: Git Best Practices and Tips
- Writing Good Commit Messages
- Keeping a Clean History
- Ignoring Files with .gitignore
- File Attributes with .gitattributes
- Security Best Practices
- Performance Tips
Module 9: Troubleshooting and Debugging
- Common Git Problems
- Undoing Changes
- Resolving Divergence with the Remote
- Recovering Lost Commits
- Dealing with Corrupted Repositories
- Advanced Debugging Techniques
