Adrian Faciu
Principal software engineer. Focused on front-end. Learning something new each day. Building things at WeVideo.
Secrets for Confident Version Control
Nu stiu sa lucrez pe Git.
-- Anca, entering the the dev world
Initial learning curve
Plateau
Mastery
Initial learning curve
Plateau
Mastery
There are days when I feel I'm here
Initial learning curve
Plateau
Mastery
And days when I make stupid mistakes and I feel I'm here
Hi, my name is Anca
Core Software Engineer @ WeVideo
tim.js co-Organizer
...and sometimes speaker
What i learned is that
Bite sized pieces of information that you can apply today
This is not a "git course"
Things that made me a better git user and mentor
I assume you have at least a basic understanding of git
Advanced workflows made approachable
Common mistakes tips on how to fix them
Recovery after making a mistake
Hidden git gems
Just git gud!
Shopping List
Milk
Bread
Apples
Yogurt
Icecream
Sprinkles
Shopping List
Milk
Bread
Apples
Yogurt
Ice cream
Sprinkles
Almond
+
$ git log --oneline
345975a (HEAD -> branch) Get sprinkles
9fdda55 Get icecream
118d058 Get yogurt
6b604b6 Get apples
9108b64 Get bread
afb2210 Get milk
aaa387d Older commit
$ git rebase -i aaa387d
Take the commit hash of the last commit before your list
pick afb2210 Get milk
pick 9108b64 Get bread
pick 6b604b6 Get apples
pick 118d058 Get yogurt
pick 9fdda55 Get icecream
pick 345975a Get sprinkles
# Rebase aaa387d..345975a onto aaa387d (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, unless -C is used, in which case
# keep only this commit's message; -c is same as -C but
# opens the editor
# x, exec <command> = run command (the rest of the line) using shell
# b, break = stop here (continue rebase later with 'git rebase --continue')
# 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>]
# . create a merge commit using the original merge commit's
# . message (or the oneline, if no original merge commit was
# . specified); use -c <commit> to reword the commit message
#
"~/Documents/projects/timjs.ro-v2/.git/rebase-merge/git-rebase-todo" 34L, 1425B
reword afb2210 Get milk
pick 9108b64 Get bread
pick 6b604b6 Get apples
pick 118d058 Get yogurt
pick 9fdda55 Get icecream
pick 345975a Get sprinkles
# Rebase aaa387d..345975a onto aaa387d (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, unless -C is used, in which case
# keep only this commit's message; -c is same as -C but
# opens the editor
# x, exec <command> = run command (the rest of the line) using shell
# b, break = stop here (continue rebase later with 'git rebase --continue')
# 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>]
# . create a merge commit using the original merge commit's
# . message (or the oneline, if no original merge commit was
# . specified); use -c <commit> to reword the commit message
#
"~/Documents/projects/timjs.ro-v2/.git/rebase-merge/git-rebase-todo" 34L, 1425B
reword afb2210 Get milk
drop 9108b64 Get bread
pick 6b604b6 Get apples
pick 118d058 Get yogurt
pick 9fdda55 Get icecream
pick 345975a Get sprinkles
# Rebase aaa387d..345975a onto aaa387d (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, unless -C is used, in which case
# keep only this commit's message; -c is same as -C but
# opens the editor
# x, exec <command> = run command (the rest of the line) using shell
# b, break = stop here (continue rebase later with 'git rebase --continue')
# 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>]
# . create a merge commit using the original merge commit's
# . message (or the oneline, if no original merge commit was
# . specified); use -c <commit> to reword the commit message
#
"~/Documents/projects/timjs.ro-v2/.git/rebase-merge/git-rebase-todo" 34L, 1425B
reword afb2210 Get milk
pick 118d058 Get yogurt
drop 9108b64 Get bread
pick 6b604b6 Get apples
pick 9fdda55 Get icecream
pick 345975a Get sprinkles
# Rebase aaa387d..345975a onto aaa387d (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, unless -C is used, in which case
# keep only this commit's message; -c is same as -C but
# opens the editor
# x, exec <command> = run command (the rest of the line) using shell
# b, break = stop here (continue rebase later with 'git rebase --continue')
# 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>]
# . create a merge commit using the original merge commit's
# . message (or the oneline, if no original merge commit was
# . specified); use -c <commit> to reword the commit message
#
"~/Documents/projects/timjs.ro-v2/.git/rebase-merge/git-rebase-todo" 34L, 1425B
reword afb2210 Get milk
pick 118d058 Get yogurt
drop 9108b64 Get bread
pick 6b604b6 Get apples
pick 9fdda55 Get icecream
squash 345975a Get sprinkles
# Rebase aaa387d..345975a onto aaa387d (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, unless -C is used, in which case
# keep only this commit's message; -c is same as -C but
# opens the editor
# x, exec <command> = run command (the rest of the line) using shell
# b, break = stop here (continue rebase later with 'git rebase --continue')
# 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>]
# . create a merge commit using the original merge commit's
# . message (or the oneline, if no original merge commit was
# . specified); use -c <commit> to reword the commit message
#
"~/Documents/projects/timjs.ro-v2/.git/rebase-merge/git-rebase-todo" 34L, 1425B
Get milk
# Please enter the commit message for your changes. Lines starting
# with '#' will be ignored, and an empty message aborts the commit.
#
# Date: Sun Jul 6 12:51:20 2025 +0300
#
# interactive rebase in progress; onto aaa387d
# Last command done (1 command done):
# reword afb2210 Get milk
# Next commands to do (5 remaining commands):
# pick 118d058 Get yogurt
# drop 9108b64 Get bread
# You are currently editing a commit while rebasing branch 'branch' on 'aaa387d'.
#
# Changes to be committed:
# modified: src/pages/100.astro
#
~
~
~
~
~
~
~
~
~
~
"~/Documents/projects/timjs.ro-v2/.git/COMMIT_EDITMSG" 18L, 556B
Get almond milk
# Please enter the commit message for your changes. Lines starting
# with '#' will be ignored, and an empty message aborts the commit.
#
# Date: Sun Jul 6 12:51:20 2025 +0300
#
# interactive rebase in progress; onto aaa387d
# Last command done (1 command done):
# reword afb2210 Get milk
# Next commands to do (5 remaining commands):
# pick 118d058 Get yogurt
# drop 9108b64 Get bread
# You are currently editing a commit while rebasing branch 'branch' on 'aaa387d'.
#
# Changes to be committed:
# modified: src/pages/100.astro
#
~
~
~
~
~
~
~
~
~
~
"~/Documents/projects/timjs.ro-v2/.git/COMMIT_EDITMSG" 18L, 556B
# This is a combination of 2 commits.
# This is the 1st commit message:
Get icecream
# This is the commit message #2:
Get sprinkles
# Please enter the commit message for your changes. Lines starting
# with '#' will be ignored, and an empty message aborts the commit.
#
# Date: Sun Jul 6 12:52:39 2025 +0300
#
# interactive rebase in progress; onto aaa387d
# Last commands done (6 commands done):
# pick 9fdda55 Get icecream
# squash 345975a Get sprinkles
# No commands remaining.
# You are currently rebasing branch 'branch' on 'aaa387d'.
#
# Changes to be committed:
# modified: src/pages/100.astro
#
~
Get icecream with sprinkles
# Please enter the commit message for your changes. Lines starting
# with '#' will be ignored, and an empty message aborts the commit.
#
# Date: Sun Jul 6 12:52:39 2025 +0300
#
# interactive rebase in progress; onto aaa387d
# Last commands done (6 commands done):
# pick 9fdda55 Get icecream
# squash 345975a Get sprinkles
# No commands remaining.
# You are currently rebasing branch 'branch' on 'aaa387d'.
#
# Changes to be committed:
# modified: src/pages/100.astro
#
~
~
~
~
~
~
~
~
~
~
~
-- INSERT --
$ git log --oneline
be79691 (HEAD -> branch) Get icecream with sprinkles
54c4e8c Get apples
2507c1b Get yogurt
7da26c1 Get almond milk
aaa387d Older commit
$ git log --oneline
345975a (HEAD -> branch) Get sprinkles
9fdda55 Get icecream
118d058 Get yogurt
6b604b6 Get apples
9108b64 Get bread
afb2210 Get milk
aaa387d Older commit
Versus what we started with:
Imagine you are writing in your journal.
Your write each day and you have a bookmark that you keep where you left off.
Now you want to revisit an older entry...
...but you did not move the bookmark.
Your eyes are on the older entry
HEAD
BRANCH POINTER
We are now in a detached head situation
Most times, your eyes (the HEAD) will point to the bookmark (the branch).
If you add new entries to your journal, the HEAD will not change, it will point to the bookmark which is always at the latest entry.
Revisiting your entry is somewhat of a read-only situation, you just move your eyes (the HEAD).
You can read, you can even write some new pages.
When getting back to your regular place, those pages will be loose and fly away.
Commits
Orphans
But you can bind them and add a new bookmark to know where to find them.
$ git log --oneline
9527d6c (HEAD -> journal) Wednesday
44eebf8 Tuesday
dfa591b Monday
$ git checkout 44eebf8
Oh, I remembered I've done something cool on Tuesday!
$ git checkout 44eebf8
Note: switching to '44eebf8'.
You are in 'detached HEAD' state. You can look around, make experimental
changes and commit them, and you can discard any commits you make in this
state without impacting any branches by switching back to a branch.
If you want to create a new branch to retain commits you create, you may
do so (now or later) by using -c with the switch command. Example:
git switch -c <new-branch-name>
Or undo this operation with:
git switch -
Turn off this advice by setting config variable advice.detachedHead to false
HEAD is now at 44eebf8 Tuesday
$ git checkout -b journal-addition
$ git commit "That cool thing i just remembered"
So if we added a commit in the detached state
We can save it by creating a new branch
Our new bookmark
Ways to get in a "detached head state":
Imagine you're cooking and you have just one stove burner.
But you want to make more than just one thing.
Wouldn't it be nice to have more than one burner so all your pots can simmer at once? 🥘🍳
$ git worktree
Allows you to have multiple branch checkouts at the same time!
$ git worktree add ../pot branch-marinara
$ git worktree add ../pan branch-egg
Let's say i have `main` checked out:
$ git worktree list
/Documents/projects/my-repo 23d4330 [main]
/Documents/projects/pan b45a0ec [branch-egg]
/Documents/projects/pot 9527d6c [branch-marinara]
$ cd ../pot
Now while working on main, i can also stir the pot without needing to stash or commit
And if i decide i don't need one anymore
$ git worktree remove ../pan
You enthusiastically work on a new feature
70cf04f (HEAD -> branch-recovery) Add user bio
ed322b7 Get profile picture
1f52356 Implement authentication
7303db4 Create login form
1494c0f Add logo
4ee1fc2 Add navbar
9af0197 Create new page
23d4330 Commit 3
5441a19 Commit 2
3e928a0 Commit 1
You are ready to push your work, but oh no... 😱
The end-to-end tests are failing!
🕵️♀️ Which one of you commits is responsible?
70cf04f (HEAD -> branch-recovery) Add user bio
ed322b7 Get profile picture
1f52356 Implement authentication
7303db4 Create login form
1494c0f Add logo
4ee1fc2 Add navbar
9af0197 Create new page
23d4330 Commit 3
5441a19 Commit 2
3e928a0 Commit 1
We know they were broken here here.
70cf04f (HEAD -> branch-recovery) Add user bio
ed322b7 Get profile picture
1f52356 Implement authentication
7303db4 Create login form
1494c0f Add logo
4ee1fc2 Add navbar
9af0197 Create new page
23d4330 Commit 3
5441a19 Commit 2
3e928a0 Commit 1
70cf04f (HEAD -> branch-recovery) Add user bio
ed322b7 Get profile picture
1f52356 Implement authentication
7303db4 Create login form
1494c0f Add logo
4ee1fc2 Add navbar
9af0197 Create new page
23d4330 Commit 3
5441a19 Commit 2
3e928a0 Commit 1
And OK here
We know they were broken here here.
70cf04f (HEAD -> branch-recovery) Add user bio
ed322b7 Get profile picture
1f52356 Implement authentication
7303db4 Create login form
1494c0f Add logo
4ee1fc2 Add navbar
9af0197 Create new page
23d4330 Commit 3
5441a19 Commit 2
3e928a0 Commit 1
$ git bisect start
$ git bisect bad
We mark the current commit as bad.
❌
70cf04f (HEAD -> branch-recovery) Add user bio
ed322b7 Get profile picture
1f52356 Implement authentication
7303db4 Create login form
1494c0f Add logo
4ee1fc2 Add navbar
9af0197 Create new page
23d4330 Commit 3
5441a19 Commit 2
3e928a0 Commit 1
$ git bisect good 3e928a0
Bisecting: 4 revisions left to test after this (roughly 2 steps)
[4ee1fc238e258c6325a0c36b28f45db1dedb4105] Add navbar
We we tell git which was the last good we know.
✅
❌
70cf04f (HEAD -> branch-recovery) Add user bio
ed322b7 Get profile picture
1f52356 Implement authentication
7303db4 Create login form
1494c0f Add logo
4ee1fc2 Add navbar
9af0197 Create new page
23d4330 Commit 3
5441a19 Commit 2
3e928a0 Commit 1
If this commit is bad.
✅
❌
All of these are bad.
❌
❌
❌
❌
❌
70cf04f (HEAD -> branch-recovery) Add user bio
ed322b7 Get profile picture
1f52356 Implement authentication
7303db4 Create login form
1494c0f Add logo
4ee1fc2 Add navbar
9af0197 Create new page
23d4330 Commit 3
5441a19 Commit 2
3e928a0 Commit 1
✅
❌
❌
❌
❌
❌
❌
$ git bisect bad
Bisecting: 1 revision left to test after this (roughly 1 step)
[23d433059c189c0d05d4a1a1857ed28630532056] Commit 3
70cf04f (HEAD -> branch-recovery) Add user bio
ed322b7 Get profile picture
1f52356 Implement authentication
7303db4 Create login form
1494c0f Add logo
4ee1fc2 Add navbar
9af0197 Create new page
23d4330 Commit 3
5441a19 Commit 2
3e928a0 Commit 1
✅
❌
❌
❌
❌
❌
❌
$ git bisect good
Bisecting: 0 revisions left to test after this (roughly 0 steps)
[9af0197858a7621914bc6f22253e9aa8a5f79a29] Create new page
✅
✅
70cf04f (HEAD -> branch-recovery) Add user bio
ed322b7 Get profile picture
1f52356 Implement authentication
7303db4 Create login form
1494c0f Add logo
4ee1fc2 Add navbar
9af0197 Create new page
23d4330 Commit 3
5441a19 Commit 2
3e928a0 Commit 1
✅
❌
❌
❌
❌
❌
❌
$ git bisect bad
9af0197858a7621914bc6f22253e9aa8a5f79a29 is the first bad commit
commit 9af0197858a7621914bc6f22253e9aa8a5f79a29
Author: Anca Spatariu <anca.spatariu@gmail.com>
Date: Sun Jul 6 17:23:12 2025 +0300
Create new page
src/pages/100.astro | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
✅
✅
❌
👈
FOUND IT!
$ git push --force
$ git push --force-with-lease
$ git stash
$ git stash list
stash@{4}: WIP on dev: f29f5ed Commit message
$ git stash push -m "I am still waiting on the design"
$ git stash list
stash@{0}: On dev: I am still waiting on the images to add
$ git rebase main
On a
public
branch
$ git merge main
On a
public
branch
$ git reset
$ git revert
$ git reset
$ git revert
Use reset it for local cleanup and reorganizing
Using reset instead of revert can cause divergence, team mates will need to force-pull or reset, risking lost work
$ git reset
$ git revert
Ok to use on protected branches, or where you really need the "safety"
Using revert instead of reset can cause cluttered and unnecessary history
$ git reset
$ git revert
$ git mv component.tsx Component.tsx
Some systems, like MacOS, are case-insensitive.
component.tsx ➡️ Component.tsx
So this will not be considered a change!
This will trick git into updating its index.
How this can happen:
Using git push --force, git reset --hard
How you can fix it:
Using git reflog
How you can prevent it:
Disallow --force on public branches
🚮 Orphaned commits are git garbage.
They are not for a 30 days.
$ git reflog
$ git commit -m "Commit 1"
$ git commit -m "Commit 2"
$ git commit -m "Commit 3"
$ git reset HEAD~1
$ git reset --hard
$ git log
commit 5441a1952ed6dd6c5f1dd4001e8c627e7699839e (HEAD -> branch)
Author: Anca Spatariu <anca.spatariu@gmail.com>
Date: Sat Jul 5 19:14:51 2025 +0300
Commit 2
commit 3e928a0fe149cd4ca06063c880678c352f199648
Author: Anca Spatariu <anca.spatariu@gmail.com>
Date: Sat Jul 5 19:14:42 2025 +0300
Commit 1
$ git reflog
5441a19 (HEAD -> branch) HEAD@{0}: reset: moving to HEAD
5441a19 (HEAD -> branch) HEAD@{1}: reset: moving to HEAD~1
1c642a6 HEAD@{2}: commit: Commit 3
5441a19 (HEAD -> branch) HEAD@{3}: commit: Commit 2
3e928a0 HEAD@{4}: commit: Commit 1
😱 Oh no! I needed "Commit 3"
$ git checkout -b branch-recovery
$ git cherry-pick 1c642a6
Create a recovery branch and cherry pick the lost commit:
$ git log
commit 23d433059c189c0d05d4a1a1857ed28630532056 (HEAD -> branch-recovery)
Author: Anca Spatariu <anca.spatariu@gmail.com>
Date: Sat Jul 5 19:15:06 2025 +0300
Commit 3
commit 5441a1952ed6dd6c5f1dd4001e8c627e7699839e (branch)
Author: Anca Spatariu <anca.spatariu@gmail.com>
Date: Sat Jul 5 19:14:51 2025 +0300
Commit 2
commit 3e928a0fe149cd4ca06063c880678c352f199648
Author: Anca Spatariu <anca.spatariu@gmail.com>
Date: Sat Jul 5 19:14:42 2025 +0300
Commit 1
🙌
$ git checkout -
$ git rebase branch-recovery
Now let's put the commit on the original branch!
$ git notes
What it does: Adds notes to a commit without changing the commit.
$ git notes add -m "Text"
Adding notes
Viewing notes
$ git log
$ git notes show <commit-hash>
Editing notes:
$ git notes edit <commit-hash>
$ git notes add -m "The bug was already reproducing here"
$ git log
commit 1e1d59dcd639cc6bdaca137e1f7c24da3648b73a (HEAD -> branch, main)
Author: Anca Spatariu <anca.spatariu@gmail.com>
Date: Thu Jul 3 22:02:53 2025 +0300
Fix date and location mobile
Notes:
The bug was already reproducing here
$ git shortlog
What it does: Creates a clean commit summary grouped by author
Adrian Fâciu (1):
Add package lock file
Anca Spatariu (1):
Update astro.config.mjs
Andrei Pfeiffer (1):
Add about page content (#17)
Lucian Pacurar (1):
Add position relative to the center section and picture
$ git cherry
What it does: Compares two branches and shows a list of commits that are not upstream.
➜ git:(branch) git cherry branch2
- 6693eba70e075c018e11e26c57ccf97798ef65d4
+ e717b429d68da4b2eb105e9f701bafef5be144a4
Output:
"+" - unique commits that are not branch 2
"-" - equivalent commits that are on branch 2, but with different hashes (ex. cherry-picked)
$ git blame -L <start>,<end> <file>
What it does: Blames line by line, showing who modified lines from <start> to <end>/
f95e0bef src/pages/100.astro (Lucian Pacurar 2025-07-03 19:35:11 +0300 61) </div>
970126fd src/pages/100.astro (Lucian Pacurar 2025-06-30 18:22:07 +0300 62) <Container>
ac0f7b6a src/pages/100.astro (Anca Spatariu 2025-07-03 21:36:20 +0300 63) <section>
ac0f7b6a src/pages/100.astro (Anca Spatariu 2025-07-03 21:36:20 +0300 64) <p class="...">
Alternatively: Hover line by line like savages... 🙄
git config --global rerere.enabled true
frontendmasters.com
learngitbranching.js.org
By Adrian Faciu
Principal software engineer. Focused on front-end. Learning something new each day. Building things at WeVideo.