Lab 07
In this lab session you will practice advanced command line features of git, notably strategies to combine work of
multiple features on the main
branch.
You do not need any specific code to follow this lab session.
Preparation
You'll be working with a playground repository for the remainder of the lab. Before you proceed to the actual exercises, here are some preliminary preparing steps.
Create a playground repo
- Create a new empty folder on your Desktop. (Or wherever else you prefer)
- Open a terminal, navigate inside the newly created folder
- Initialize a new repository with
git init
Create a markdown file
- You will not be working with actual code, the remainder of the lab session requires only a short poem in markdown format.
- Tracking changes of the sample poem allows you to simulate version control, e.g using branches.
- Inside your playground repository, create a new text file: "keksli.md"
-
Paste the following the poem file, and save the file:
# Keksli, the Little Piece of Cake 1) Oh Keksli, sweet as frosting’s kiss, 2) A tiny fluff of gentle bliss. 3) With eyes that sparkle, soft and bright, 4) You bring the world pure joy and light. 5) A little piece of cake, they say, 6) But cuter than the sweetest tray. 7) With every purr and playful leap, 8) You melt our hearts and softly keep— 9) That sprinkle of sweetness, warm and true, 10) A Keksli-sized delight in all you do.
-
Add the file to your repository and create an initial commit:
-
Backup the playground folder in its current state, e.g. by making a copy, or by zipping it.
- I'll regularly ask you to reset to the initial state. In that case, remove the original folder and restore from your backup on disk.
Creating sample commits
Throughout the remainder of the lab I'll frequently ask you to modify the poem and add the changed version to a new commit.
- If I do not further specify which line to edit, simply add a new line at the end with "...". Otherwise change the
specified line (
1)
,2)
, ...). - Finally, make sure your poem edit manifests to a new commit, with:
- You can use a custom commit message if you wish, for better traceability.
Dog logging
- The standard
git log
command neither prints the entire graph, nor does it neatly visualize how commits connect. - In class, you've seen the ADOG acronym, as little memory-help for the longer command:
git log --all --decorate --oneline --graph
- Create an alias for the ADOG acronym, with:
git config --global alias.adog "log --all --decorate --oneline --graph"
- Test the new alias command. Go into your sample repo and type:
git adog
New branches
In this first exercise you'll be training the creation of new branches, and how to navigate branches
Creating a new branch
- Branches are usually created per feature. We'll now assume that you want to create an extended version of the poem with some extra lines.
Your turn
- Create a new branch
extended-poem
, usinggit checkout -b
- Note: the checkout command automatically places your HEAD on the new branch. No need to manually switch.
- Modify the poem and create a new commit
- Inspect the full commit graph with
git adog
- Carefully read the output. Why is the graph visualized graph still a line?
Switching branches
- The universal command for navigating the graph is
git checkout
git checkout
expects either a commit hash, or a branch name.- commit hashes detach the HEAD and place it in the requested commit.
- branch names attach the HEAD to the last commit of the specified branch
Is there a difference between checkout of the last commit on a branch, and checkout by branch name ?
Yes, there is a difference! In both cases, the HEAD ends up on the last commit of a line. But checkout by commit hash cannot reliably result in an attached HEAD, because commits can be shared by multiple branches.
Illustration:
* 059ba92 (HEAD, main, feature) fixed merge conflict
|\
| * e0a2244 feature XYZ implemented
* | ecf8311 little bugfix
|/
* 1a31ca2 initial commit
059ba92
is shared by main
and feature
branch. A git checkout 059ba92
cannot be resolved to a unique branch name. HEAD will be detached.(In fact, checkout by hash always detaches HEAD, just to avoid the above ambiguity !)
git switch
is a bit securer thangit checkout
, because it is a variant ofcheckout
that only accepts branch names.- Using
git switch
you can never end up in detached HEAD state. - Navigating between branches with
git switch
is preferable for novice users.
- Using
Your turn
- Navigate back to the
main
branch - Modify the poem and create a new commit
- Inspect the graph again with
git adog
. What has changed ?
Detached HEAD commits
- Usually you want your branches to diverge from an attached HEAD, i.e. you want branches to spawn from the end of a branch.
- Yet, it is possible to create implicit branches, by committing while in detached HEAD.
Illustration:- In a series of two commit, checkout of the initial commit results in detached HEAD.
- Creating a new commit in detached HEAD would be in conflict with
second commit
- Git therefore implicitly creates a new unnamed branch. (
HEAD
does not point to a branch name !)
- Commits from detached HEAD (as the above
310e6b7
) are dangerous, because they are not associated with any branch, and hard to find again later. Work committed from detached HEAD is easily lost. - Luckily it is possible to belatedly associate the commits to a branch name!
Your turn
- Reset the playground repo to it's original state.
- Add a new commit.
- Inspect the repo graph with
git adog
. You should only see two consecutive commits, with HEAD attached.
- Inspect the repo graph with
- Detach the HEAD, by checkout of the initial commit by ID.
- Inspect again with
git adog
, the output should match the first of the above listings.
- Inspect again with
- With your HEAD detached, create a new commit.
- Inspect your graph again with
git adog
. The output should now match the second of the above listings. - Write down the ID of the commit created in detached HEAD mode.
- Inspect your graph again with
- Attempt to abandon your last commit, by checkout of the
main
branch.- Git will reattach your HEAD.
- Git will also issue a warning that you are leaving a commit behind. Git will also tell you how to belatedly associate the commit left behind to a new branch.
- Switch back to the commit left behind, by
checkout
(of the ID you noted down) - Use the command previously proposed by git, to associate the commit left behind with a new branch name, e.g.
implicit-branch
- Inspect the repo graph once more with
git adog
- Inspect the repo graph once more with
Is your HEAD now attached?
No. git adog
shows that HEAD is on the last commit of implicit-branch
. But there is no ->
symbol. The HEAD remains detached until you attach it with git checkout implicit-branch
(or git switch implicit-branch
).
Before attaching HEAD:
* 310e6b7 (HEAD, implicit-branch) commit from detached head
| * adce8cb (main) second commit
|/
* 3af628f initial commit
git switch implicit-branch
)
Merging
- So far you've learned how to create new branches
- Branches are like diverging paths. With every branch created, you create a new bifurcation.
- Next you'll train how to unite them again. This process is called merging.
Merging without conflict
- In the easiest case the changes made on two branches are not in conflict, e.g. because the changes made affect entirely disjoint files.
- The standard procedure for merging is:
1) Place your HEAD on the branch where you want to receive / integrate changes:
git checkout receiving-branch
2) Merge form the branch containing the changes you want to integrate:git merge branch-with-commits-to-integrate
Your turn
- Restore the playground project to its original state.
- Add a new branch
feature
- Add a new commit to the
feature
branch. - Place your HEAD back on
main
- Create a new file
toto.md
with content# Hello
and store it in a new commit onmain
. - Merge
feature
intomain
-
Verify the merge was successful:
-
You should still have the
toto.md
file. - You should also see the extended poem version.
git adog
should visualize the merge.
Merging with auto-conflict resolving
Merging with auto-conflict resolving happens when two branches modified the same file, but in a non-conflicting way, i.e. not the same line or code element.
Your turn
- Restore the playground project to its original state.
- Add a new branch
feature
- Add a new commit to the
feature
branch, where the first line of the poem is changed to "1) Oh Keksli, sweet as a nutella kiss," - Place your HEAD back on
main
- Add a new commit to the
main
branch, where the last line of the poem is changed to "10) Oh Keksli-sized delight, we all love you. <3" -
Merge
feature
intomain
-
At this point you might see a weird editor opening up.
- That's VIM, a powerful, but unintuitive text editor.
-
To just quit the editor, type this sequence:
ESC : w q ENTER
(for write and quit the editor) -
Verify the merge was successful:
-
Inspect the
git
output for a success message. - Inspect the
keksli.md
content and verify first and last line of the poem have been modified. git adog
should visualize the merge.
Merging with conflict
- Restore the playground project to its original state.
- Add a new branch
feature
- Add a new commit to the
feature
branch, where the first line of the poem is changed to "1) Oh Keksli, sweet as a nutella kiss," - Place your HEAD back on
main
- Add a new commit to the
main
branch, where the first line of the poem is changed to "1) Oh Keksli, sweet as a maple syrup kiss," - Merge
feature
intomain
- Verify you get a
CONFLICT
message: - Inspect the
keksli.md
file. You should see version markers: - Resolve the merge conflict by removing the version markers and deciding for one version to remain. Then safe the file and create a final "resolve" commit.
When one of the branches is main
, always first merge into the other. Then merge back.
- The interest of branches is usually to develop a feature without damaging
main
while work is in progress. - When you're ready to merge, always first merge
main
into the feature branch, merging can fail and leave your repo in a problematic state. A safer procedure is:- Merge
main
intofeature
. - If needed, resolve conflicts and create a new "solved" commit.
- Merge
feature
back intomain
.--- title: "Merging first main into feature, then merging back" --- gitGraph: commit id: "1" commit id: "2" branch "feature" checkout "feature" commit id: "3" checkout "main" commit id: "4" checkout "feature" merge "main" type: REVERSE commit id: "manual resolve" checkout "main" merge "feature" type: HIGHLIGHT
- Merge
Deleting branches
- If you only create branches and never delete them, you git graph will over time end up pretty cluttered.
- Ideally branches are removed from the graph once they are no longer needed.
- To definitely remove a branch, you have to remove it locally and remotely.
Local
- Removing a branch locally translates to removing a branch from the local graph.
- Revise the course content and look up how to delete a local branch.
Your turn
- Add another commit to your
feature
branch - Remove the last feature branch from your repo.
- First attempt the command
git branch --delete feature
- Why is the command rejected. Identify two strategies to delete the branch anyway.
- First attempt the command
- Use
git adog
to verify it has disappeared. - Use
git branch -a
to verify it is no longer listed.
Remote
- Removing a branch remotely translates to removing a branch from the remote (server) graph.
- Removing a remote branch only makes sense if there is a remote repository associated to your local repository.
Your turn
- Go to UQAM's Gitlab webpage and link you local repo to a remote server:
- Login into gitlab.
- Click the
+
symbol on the top left, thennew project / repository
- Select:
Create blank project
- Select a name for your project, e.g.
lab08
- Uncheck:
Initialize repository with a README
- Click:
Create project
- Select:
- Enter the command to link you local repo to the remote server: (replace
max
by your username)
git remote add origin git@gitlab.info.uqam.ca:max/lab08.git
- Use
git branch -a
, inspect the list of local and remote branches. - Create a new local branch
feature
. - Add at least one commit to the
feature
branch. - Push the branch to the remote repository, with:
git push --set-upstream origin main
- Figure out how to push the
feature
branch to the remote repository. - Verify with
git branch -a
that both branches exist on the remote repository.- Additionally log in to the UQAM gitlab website and inspect the project page. Can you spot both branches ?
- Delete the remote
feature
branch using:
git push -d origin feature
- Use
git branch -a
again to verify the `feature branch has disappeared.
Tagging
Tags are additional human-readable identifiers for commits.
Adding tags
Tags are always first created locally, and then pushed to the server.
Your turn
- Reset your playgound project.
- Create tag for the initial commit on the main branch.
Illustration:
--- title: "Tagged initial commit" --- gitGraph: commit id: "1" type: HIGHLIGHT tag: "v1.0"
- Push the tag to the server
- Find the tags
- Inspect
git adog
. Can you find the tag ? - Log into UQÀM's GitLab server, and navigate to your project. Can you find the tag ?
- Inspect
Cherry Picking
Cherry-picking allows you to merge with a specific commit rather than merge with an entire branch.
Cherry pick a single poem line
- In this exercise you'll be making several modifications to the
keksli
poem on a feature branch. - Afterward, you'll merge exactly one of these changes into
main
branch. - Illustration:
--- title: "Cherry picking a single commit from a feature branch" --- gitGraph: commit id: "1" branch "feature" checkout "feature" commit id: "2" commit id: "3" commit id: "4" checkout "main" commit id: "5" checkout "main" cherry-pick id: "3"
Your turn
- Reset the playground project to its original state
- Create a
feature
branch - Make three consecutive commits on the
feature
branch:- Change the first poem line to
1) Oh Keksli, sweet as a cinnamon kiss,
. Make a commit onfeature
. - Change the second poem line to
2) A tiny fluff of chocolate bliss.
. Make a commit onfeature
.
Write down the commit id of the second line change. - Change the third poem line to
3) With eyes that glitter, soft and bright,
. Make a commit onfeature
.
- Change the first poem line to
- Place the HEAD back on
main
. Make a new commit. - Cherry-pick the second poem line change to
main
, using the commit ID of the second line-change. - You'll receive a merge conflict. Inspect and solve the conflict. Issue a final commit for the solved conflict.
- Inspect the graph with
git adog
Is there anything peculiar with the git adog
output ?
Yes! The graph representation does not show the chery-picking! Indeed git log
never shows cherry-picks, only full branch merges.
Rebase
Rebasing allows you to straighten the git commit graph by hindsight.
Rebasing a feature branch
- Usually rebase is applied before merging a feature branch back to
main
- By first rebasing
main
intofeature
, there is no risk of a brokenmain
branch (just like when mergingmain
intofeature
) - Rebasing creates a replay of every commit on
feature
, based on the most recentmain
commit. - This transforms the graph, so commits of
main
andfeature
no longer occur concurrent, but sequential. - The final merge is always guaranteed to work, for any conflicts have already been cleared out during rebase.
Your turn
- Reset your playground project
- Create a
feature
branch - Make three consecutive commits on the
feature
branch - Attach your HEAD to
main
- Change the poem title line to
#Keksli, the Rebased piece of Cake
- Attach your HEAD to
feature
again. Your commit graph should now look like this:
--- title: "Feature branch lacks recent main commit" --- gitGraph: commit id: "1" branch "feature" checkout "feature" commit id: "2" commit id: "3" commit id: "4" type: HIGHLIGHT checkout "main" commit id: "5"
- Rebase the feature branch, to spawn from the most recent
main
commit, usinggit rebase main
- Merge
feature
intomain
- Inspect the graph structure with
git adog
. Verify it corresponds to:
--- title: "Feature branch rebased and merged to main" --- gitGraph: commit id: "1" commit id: "5" branch "feature" checkout "feature" commit id: "2R" commit id: "3R" commit id: "4R" checkout "main" merge "feature" type: HIGHLIGHT
Rebase instead of merge
Take the habit of rebasing rather than merging. A graph that does not showcase concurrent progress is easier to maintain and navigate. Rebasing effectively transforms the graph to ressemble a history where every feature was developed instantly, without concurrency to the main branch.