Git is a version control system that allows users to synchronize branches and rewrite history. It stores project data in objects including blobs for file contents, trees for directories, commits for snapshots, and tags for references. Commits, branches, and tags are referenced by SHA-1 hashes or names. Rebasing replays commits onto another branch to keep a linear history, while merging uses commit objects to join branches. The reflog tracks reference updates and allows recovering from errors. Interactive rebasing edits the commit history. Bisecting helps identify the commit introducing a bug by binary searching the commit history.
5. Git directory's main items
• HEAD: a pointer to the commit we have currently checked
out.
• Config: text file that stores repo’s configuration.
• Objects: directory containing git objects’ database.
• Refs: directory that contains all repo’s references
• Heads: local branches
• Remotes: remote branches organized by remote servers (origin, etc).
• Tags: Immutable references to commits.
• Some other files and folders used for different purposes.
.git
├── HEAD
├── config
├── description
├── hooks
├── index
├── info
├── logs
├── objects
└── refs
├── heads
├── remotes
│ └── origin
└── tags
6. Git objects store
.git
├── objects
│ ├── 2a
│ │ └── 6c4628266fd8913b711741cb84f4f77ffcb6c2
│ ├── 4d
│ │ └── 5fcadc293a348e88f777dc0920f11e7d71441c
│ ├── e6
│ │ └──
9de29bb2d1d6434b8b29ae775ad8c2e48c5391
│ ├── info
│ └── pack
└── refs
• All git objects are stored under
.git/objects directory.
• It is basically a key-value map.
• Keys (filenames) are SHA-1 hashes
generated from the value.
• Values depends on the type of object.
7. Inspecting objects
• Shows the information about an object in the repository:
• -t: show type
• -p: shows object content
• -s: object size
> git cat-file (-t|-p|-s) <object-sha1>
9. Blob
• Stores the contents of a file in a compressed format.
• It is just a bunch of bytes.
• Meaningless by its own, does not contain any reference to the file
which it refers to.
10. Tree
• Equivalent to a ‘directory’ in the file system.
• Contains a list of Blobs (along with the files they refer to) and other
Tree objects.
• Links blobs and trees to the files and folders they represent.
11. Commit
• Snapshot image of the repository in a particular point in time.
• Contains references to the root tree and to its parent commit.
• Additional metadata:
Timestamp
Author
Committer
Log message
12. Tag
• Only annotated tags generates an object in the store.
• A container that holds a reference to an object, usually points to a
commit but can point to blobs and trees also.
• Metadata: tag name, tagger and message.
14. References
• Pointers to commits.
• Usually text files stored under
.git/refs
• Holds the SHA-1 hash of a commit.
• Allows us to refer to commits in a
human-friendly way.
16. What is a branch?
• It is just a reference to a commit!
• Stored in text files under .git/refs/heads (.git/refs/remotes)
• Tags are also immutable references stored in .git/refs/tags
17. The HEAD reference
• HEAD is a special reference that points to the
commit you have currently checked out.
• Stored in .git/HEAD file.
• It usually points to another reference instead
of a commit!
• When it points to a commit, we said it is in a
detached state.
18. Tip
• You can just do as follows:
• After creating a local branch, we need to provide its upstream before
pushing.
> git checkout -b my-branch-with-a-long-name
> git push
fatal: The current branch my-branch-with-a-long-name has no upstream branch.
To push the current branch and set the remote as upstream, use
git push --set-upstream origin my-branch-with-a-long-name
> git push origin HEAD
19. Referencing commits
• SHA-1, short SHA-1
• Any valid reference: branch, tag, HEAD
• Reflog short names: HEAD@{5}, @5… (we will see later)
• Ancestry references: HEAD^, HEAD~
• Commit ranges
Double dot: <ref-A>..<ref-B>
Exclude commits reachable from ref: ~<ref>, --not <ref>
Multiple points: <ref-A> <ref-B> ~<ref-C>
Triple dots: <ref-A>…<ref-B> (Shows the commits reachable by either of two
references but not by both of them.
20. Ancestry references
• A = = A^0 = A~0
• B = A^ = A^1 = A~ = A~1
• C = = A^2
• D = A^^ = A^1^1 = B~ = A~2
• E = B^2 = A^^2
• F = B^3 = A^^3 = A^2^ = C~
<rev>^<n> = The <n>th parent of rev.
<rev>~<n> = The <n>th generation ancestor of <rev>, following only the first parents.
22. Git rev-parse
• It is a plumbing command used by many other commands to do
several things.
• Convert a commit reference to a real SHA-1:
> git rev-parse <ref>
• Obtain current branch name:
> git rev-parse --abbrev-ref HEAD
• Obtain relative path to go back to repo’s root:
> git rev-parse --show-cdup
23. Tip: Going back to repository’s root
• Useful alias to go back to the repo’s root directory, it does not matter
how deep you are in the tree:
$> alias groot = “cd `git rev-parse --show-cdup || pwd`”
26. Merge
• $> git merge master feature
• The most common and easy way of synchronization.
• Can produce ugly merge commits.
• Keeps the context of the synchronization.
• Branch history can be difficult to follow.
32. Rebase
• Replays all the commits of a branch on top of another branch.
• Allows to have a perfectly linear project history.
• Drawbacks:
• It rewrites the history! Generate new different commits, with different SHA-1.
• Loses the context of synchronization provided by a merge commit (unless you
use --no-ff option for merging).
• Golden rule: never use it on public branches!!
• Merging vs Rebasing
34. Lab: Rebase
• Clone repo: https://github.com/beni0888/gitlikeapro-2019
• Execute a rebase:
• Checkout rebase-no-conflicts
• Rebase it onto rebase-no-conflicts -base
• Tip: git log rebase-no-conflicts-base…rebase-no-conflicts (show divergent
commits)
• Execute a rebase with conflicts:
• Checkout rebase-conflicts
• Rebase it onto rebase-conflicts-base
• Resolve conflicts and finish rebase
36. Interactive rebase
> git rebase (--interactive | -i) <commit-ref>
Allows to edit the history from a past commit to the tip of the branch.
It opens an editor showing the list of commits that are going to be replayed, and
allows to perform certain actions on them:
Join commits: squash, fixup
Edit commit message: reword
Edit commit content: edit
Remove commit: drop
Reorder commits: just change commit’s order in the editor
37. Tip: Git merge-base
• Returns the SHA-1 of the base commit of feature branch.
• Useful to interactively rebase the whole branch since its fork from master.
> git merge-base master feature
> git rebase -i `git merge-base master HEAD`
39. Rebase --autostash
• Git does not allow to execute git pull on a dirty working directory.
• This way git will stash your changes, update your branch and unstash
your changes again.
• Nice but, wouldn’t be possible to do all this in just one step??
> git fetch
> git rebase --autostash
40. Tip: autostash
• Everytime you run git pull, it will perform a rebase under the hood.
• And everytime git rebase is executed, it will run with autostash
option.
• Update your branch in just one step, even if you are in a dirty
working directory!
> git config --global rebase.autostash true
> git config --global pull.rebase true
41. Rebase - Squash & Fixup
• Squash:
• Join multiple commits into one.
• Every commit is meaningful by its own.
• By default git will concatenate original commits’ messages.
• Fixup:
• Melt commit into the previous one (the same as squash).
• Semantically, the commit is a fix to the previous one.
• By default git will only keep the first commit’s message.
43. Tip: Squash & Fixup at commit time
• Mark for squash/fixup at commit time, any existent commit.
• Use --autosquash option with interactive rebase.
• Let the magic happen
git commit --squash <commit-ref>
git commit --fixup <commit-ref>
git rebase -i --autosquash <base-commit-ref>
45. Rerere
• Stands for Reuse Recorded Resolution.
• It is kind of a rudimentary machine learning mechanism.
• Learns from your previous decisions on conflicts resolution.
• Apply them automatically reducing the number of manual
interventions.
• Warning! If you do it wrong, it will learn also…
git config --global rerere.enabled 1
48. Git reset to the rescue!
• Versatile and powerful command to undoing changes.
• Similar to checkout in some aspects.
• Potentially dangerous: destructive operation.
• It allows to:
• Undo changes.
• Unstage files.
> git reset
50. Reset main modes
• Soft:
• Keep undone changes on the index.
• Mixed:
• Default mode, keep undone changes in the working tree, but without staging
them (out of the index).
• Hard:
• Totally removes the undone changes, they are not keep neither in the index,
nor in the working tree.
• Use it with care!!
• Great explanation in stackoverflow
51. Reset vs Revert
• Revert is a safe operation, it takes a commit and generates a new
commit which inverses the specified commit.
• Revert add a new commit to the history, but does not modify the
existent ones.
• Reset rewrites the history to make the branch match the state of an
specific commit.
• Reset vs Revert vs Checkout
git revert <commit-ref>
54. What really happens when rebasing?
• New commits are generated at the tip of the target branch.
• But the original old commits are still available in the repo.
• Although they are unreachable (there is no reference pointing to
them).
56. Reflog
• Reference logs, or "reflogs", record when branches and other
references were updated in the local repository.
• Remember: a reference is a pointer to a commit.
• Tracks refs updates up to 90 days by default.
• It allows us to recover from errors by accessing to “unreachable
commits”.
• Subcommands: show, expire and delete.
57. Reflog
subcommands
• Show:
Show the log for a reference, or all
reference (--all option).
• Expire:
Prunes older reflog entries, rarely
used by end users.
• Delete:
Removes single entries from the log,
typically not used by end users
neither.
58. Reflog show
• Shows the reflog of the HEAD ref.
git reflog
git reflog show HEAD
f90e666 (HEAD -> master) HEAD@{0}: checkout: moving from branch to master
d92a3b7 (branch) HEAD@{1}: rebase -i (finish): returning to refs/heads/branch
d92a3b7 (branch) HEAD@{2}: rebase -i (start): checkout 19e164a71d87ffa0902c71e92da9f0b61bcc858f
d92a3b7 (branch) HEAD@{3}: rebase -i (finish): returning to refs/heads/branch
d92a3b7 (branch) HEAD@{4}: rebase -i (pick): BRANCH - Add e
da961c4 HEAD@{5}: rebase -i (pick): BRANCH - Add d
5eeef42 HEAD@{6}: rebase -i (fixup): BRANCH - Add c
84d25d0 HEAD@{7}: rebase -i (start): checkout 19e164a71d87ffa0902c71e92da9f0b61bcc858f
59. Reflog show
• Shows the reflogs for all references
git reflog show --all
git reflog stash
• Also tracks the stash references
60. Undoing a rebase: reflog + reset
1. Look in the reflog for the commit at the tip of the branch at the
moment prior to the rebase.
2. Git reset --hard to that commit
> git reflog
1544114 (HEAD -> feature) HEAD@{0}: rebase finished: returning to refs/heads/feature
1544114 (HEAD -> feature) HEAD@{1}: rebase: Add bar
3b021d4 HEAD@{2}: rebase: Add foo
d92a3b7 (master) HEAD@{3}: rebase: checkout master
bf12c54 HEAD@{4}: commit: Add bar
e99a22b HEAD@{5}: commit: Add foo
…
> git reset --hard HEAD@{4}
HEAD before rebase
61. Undoing changes: ORIG_HEAD
• ORIG_HEAD is a special reference that stores the previous state of
HEAD. It is set by commands that have possibly dangerous behaviour
(rebase, merge, reset, etc), to be able to easily revert them.
• Undoing a rebase, reset, merge:
git reset --hard ORIG_HEAD
63. Bisect
• Sometimes you discover that a bug has been introduced in your code
base, but you do not know when it happened.
• The bisect command does a binary search through your commit
history to help you identify as quickly as possible which commit
introduced an issue.
• It allows to identify the commit that introduced the issue, through a
large range of commits, in just a few steps.
64. Bisect
• First, you have to indicate git to start bisecting your history.
• Then you indicate that the current commit is “bad”, or provide a reference
to a previous commit you know is bad.
• And you provide the commit with the last good state known.
• Or just in one command:
> git bisect start
> git bisect bad [<commit-ref>]
> git bisect good v1.2.6-rc
> git bisect start HEAD v1.2.6-rc
65. Bisect
• After that, git will check out the commit in the middle of the
provided range, so you can test it.
• Git will figure out the number of revisions left to test before finding
the target commit.
Bisecting: 2 revisions left to test after this (roughly 1 step)
[da961c4e9813bbc92c308e4487ef527aa25f3bc7] Foo commit message
66. Bisect
• After testing the commit, you should inform git whether it is ”good” or “bad” with
git bisect (good|bad) commands.
• Git will narrow down the range of remaining revisions, and will repeat the same
process until the commit which introduced the bug is found.
• You can skip testing a commit with bisect skip subcommand.
d92a3b789d3e5e7fcee880b106bb6d0d9694f58b is the first bad commit
commit d92a3b789d3e5e7fcee880b106bb6d0d9694f58b
Author: Jesús Miguel Benito Calzada beni0888@hotmail.com
Date: Thu Oct 4 18:49:17 2018 +0200
Added a really cool feature!
67. Bisect
• You should finish the process with bisect reset command, to cleanup
the bisection state and return to the original HEAD.
> git bisect reset
Previous HEAD position was d92a3b7... Foo commit message
Switched to branch 'feature'
69. Bisect run
• This is a really manual error-prone process, huh?
• Bisect provides a run subcommand that allows us to run a script to
determine whether the commit is good or bad.
• The script must exit with 0 if the commit is good, or whatever code
between 1 and 127 (except 125, used to skip) for a bad commit.
• You can for example run your test suite to check your commits.
> git bisect start HEAD v2.1.0-rc
> git bisect run vendor/bin/phpunit
> git bisect reset
70. Lab: Bisect run
We have two lab options to illustrate this
feature, you can choose whatever you
prefer:
• Shell script
• PHP
71.
72. Oh-my-zsh git plugin
• Oh-my-zsh comes with a really handy git plugin that saves us to write a lot:
https://github.com/robbyrussell/oh-my-zsh/wiki/Plugin:git
g=git
ga='git add’
gaa='git add --all’
gap='git apply’
gapa='git add --patch’
gau='git add --update’
gb='git branch’
gba='git branch -a’
gbd='git branch -d’
gbda='git branch --no-color --merged | command grep -vE
"^(*|s*(master|develop|dev)s*$)" | command xargs -n 1 git branch -d’
gbl='git blame -b -w’
…
Dejar claro que a partir de un blob por sí mismo no podemos recuperar el fichero que lo generó, necesitamos el objeto tree para eso.
Un commit puede tener varios padres
Double dot: all commits reachable from <ref-B> that are not rechable from <ref-A> (eg. Commits in a branch but not in master)
Multiple points: you want to specify more than two branches to indicate your revision, such as seeing what commits are in any of several branches that aren’t in the branch you’re currently on.
Cuando todos los commits de nuestra rama (master) están contenidos en la rama que estamos mergeando, simplemente se actualiza el puntero de la rama.
You can ask the students to:
Squash all “add method” commits
Remove “intentionally add a failing commit”
Reorder commits: move “add instructions to readme” to the tip of the branch
Squash and fixup while on interactive rebase
The main difference between reset and checkout is that checkout only operates on the HEAD ref, it doesn’t change the branch’s tip, while reset operates on both HEAD and branch references.