Lab5: Traveling between parallel universes
Objectives¶
The goal of this lab is to understand the following commands and concepts:
Merging divergent branches¶
Merging
Merging is very common in Git.
When you “synchronize” your Local Repo and your Remote Repo (push and pull), you are actually performing automatic merges between different versions of your files.
Most of these merges are automatic: when you are ahead or behind by a certain number of commits on the same branch, Git understands that it is enough to apply the missing commits to merge the different versions of the files.
For example, I am a student who has finished their lab. I push my Local Repo, from a computer at the IUT, to my Remote Repo on the IUT’s GitLab servers: my Local Repo is ahead of the Remote Repo, and Git applies the new changes to the Remote Repo, which is behind.
To simplify, commit hashes are replaced here by uppercase letters.
main is ahead of origin/main by 2 commits. Git therefore understands that it is enough to apply the last 2 commits to origin/main to merge the two versions.
When a merge cannot be done automatically, we end up with divergent branches, with manual merges to perform and potential conflicts to resolve.
Divergent branches
We observed that a merge of two histories can be done automatically when the commit graph of one branch fully contains the other graph as a subgraph: it is enough to add the missing vertices and edges, in other words, to apply the missing commits to the branch that is behind.
When neither graph fully contains the other, we get divergent branches. Let’s take the following example:
main and origin/main have 3 commits in common: A, B, and C. Unfortunately, in main, two commits D and E were applied after C, while in origin/main, commit F was applied after C: neither history fully contains the other, and no automatic merge is possible.
A manual merge must therefore be performed (git merge), and we then decide how it should be done.
These “new” changes (G), which follow both E and F, are then added to the commit graph of main with an add followed by a commit.
This new graph now fully contains the history of origin/main and can therefore be merged automatically with git push.
Merge without conflicts¶
First, let’s try to reproduce divergent branches.
Create a
Lab5/directory with a filemerge-without-conflict.txtcontaining three arbitrary lines of text on lines 1, 3, and 5.Synchronize your Local Repo with the Remote Repo.
Locally, change the third line of text of
merge-without-conflict.txt.Commit this change without pushing.
In the Web IDE, change the fifth line of text of
merge-without-conflict.txtin order to simulate a change made on another workstation or by a collaborator.Commit this change.
You should now have two versions of merge-without-conflict.txt.
For example, in the Local Repo, you have:
First line of text
Third line of text
Fifth lineIn the Remote Repo, you have:
First line of text
Third line
Fifth line of textRun
git pushand observe the error.
Git detects that changes were made on the Remote Repo that are not present in our Local Repo, and suggests running git pull first to update our Local Repo.
Run
git pulland observe the error. Have you understood why we created divergent branches? If not, call your instructor.Run
git graphand observe.
We must now perform a manual merge.
Run the following command.
git merge origin/maingit merge
git mergeTo merge two branches (here, origin/main into main), we place ourselves on the target branch (here, main) and run the command git merge <source branch> (here, origin/main).
This merge is considered an (automatic) merge without conflicts because the modifications coming from the two branches are on different blocks of lines.
Git performed an add automatically and asks you to write a commit message for the merge operation, with an auto-generated message already filled in. You may modify this message if you wish.
Once the commit is done, check that the contents of
merge-without-conflict.txtcombine bothorigin/mainandmain.
For example:
First line of text
Third line of text
Fifth line of textRun
git graphand observe.Run
git pushto synchronize your Local Repo with the Remote Repo.
Merge with conflicts¶
In the previous example, we saw a merge where the changes made to the two versions of the same file did not overlap, which allowed Git to automatically merge the changes.
This time, we will again create and merge divergent branches, but with changes that are in conflict.
Create a file
merge-with-conflict.txtinLab5/with a single line of arbitrary text.Synchronize your Local Repo with the Remote Repo.
Locally, add a second line of text to the file
merge-with-conflict.txt.Commit this change without pushing.
In the Web IDE, add another second line of text to
merge-with-conflict.txt.Commit this change.
You should now have two versions of merge-with-conflict.txt.
For example, in the Local Repo, you have:
First line of text
Second line of textIn the Remote Repo, you have:
First line of text
Another second line of textWhen running
git pushorgit pull, you will receive the same errors as before regarding divergent branches.Run
git graphand observe.
We must now perform a manual merge.
Run
git merge origin/main.
This time, the automatic merge fails because the two versions of merge-with-conflict.txt have different modifications on the same line.
Git tells you that you must resolve the conflict(s) in merge-with-conflict.txt, then commit that resolution.
Run
git statusand observe.Open
merge-with-conflict.txtin a text editor. You should see the following lines:
First line of text
<<<<<<< HEAD
Second line of text
=======
Another second line of text
>>>>>>> origin/mainConflict syntax
Between <<<<<<< HEAD and ======= is the current version, where HEAD is located in the commit graph (here, at the latest commit of the target branch main), and between ======= and >>>>>>> <source branch name> is the version coming from the source branch in our merge (here, origin/main).
Replace this block of text (between
<<<<<<< HEADand>>>>>>> origin/main) with the final version of your choice (which may differ from the two proposed versions). For example:
First line of text
Merged line of textThis time, our (manual) merge with conflicts does not come with an automatic add and commit.
Add, commit, and push this conflict resolution.
Run
git graphand observe.
Branches¶
Divergent branches are branches created by accident, but we can also create branches intentionally to better manage our project (for example, to follow collaborative workflow principles in your SAÉ project).
Creating and traveling between branches¶
Create a branch
experiment/branchby running the following command.
git branch experiment/branchCreating a branch
The command git branch <branch name> creates a branch.
Switch branches by running the following command.
git checkout experiment/branchSwitching branches
The command git checkout <branch name> moves to the latest commit of <branch name> in the history graph.
Create a file
experiment-branch.txtinLab5/with one line of arbitrary text.Add and commit this change.
Synchronize this new branch with your Remote Repo by running the following command.
git push --set-upstream origin experiment/branchReminder: git push --set-upstream
git push --set-upstreamThe first time you push a new branch, Git asks you to specify which branch you want to “push” your current Local Repo branch to. Here, we simply say that we want to push our local branch experiment/branch to the branch with the same name on the Remote Repo. We only need to do this once per branch, and we can use git push and git pull in the future without specifying the branch.
If you have already configured automatic remote branch creation when running git push with the following command:
git config --global push.autoSetupRemote truethen you do not need git push --set-upstream, only git push.
Go back to the
mainbranch by running the following command.
git checkout mainNote that experiment-branch.txt does not appear on the main branch because the commit that created this file is on the experiment/branch branch.
Create a file
main-branch.txtinLab5/with one line of arbitrary text.Add and commit this change.
Run
git graphand observe.
Merging two branches¶
Use what you have learned to merge the
experiment/branchbranch into themainbranch.Run
git graphand observe.
Creating a branch from a past commit¶
Sometimes it is useful to create a branch from a commit in the past
Go back to an older commit using
git checkout.Run
git statusand observe that you are in the detached HEAD state.
We can now create a branch from this commit and make changes that will be saved in our history.
Create a branch
experiment/old-commitusinggit branch.Switch branches using
git checkout.Make changes, then add and commit them on the new branch.
Run
git graphand observe.Synchronize this new branch with your Remote Repo.
Listing branches¶
List the branches in your Local Repo with the following command.
git branchYou should see main, experiment/branch, and experiment/old-commit.
List the branches in your Local Repo and your Remote Repo with the following command.
git branch -aYou should see six branches, including the remote versions of the local branches.
List the branches in your Local Repo that are already merged into the
mainbranch with the following command.
git branch --merged mainYou can also combine the -a and --merged options with the following command.
git branch -a --merged mainList the branches in your Local Repo that are not yet merged into the
mainbranch with the following command.
git branch --no-merged mainSimilarly, you can also combine the -a and --no-merged options.
git branch -a --no-merged mainStashing across multiple branches¶
We have seen how to stash work in progress on a branch before checking out commits in the past or another branch. Naturally, we also want to be able to stash work in progress on several different branches without mixing them up.
On the
mainbranch, make a few changes without adding or committing.Stash your work in progress with the following command.
git stash push -a -m "main: work in progress"git stash push -m "<message>"
git stash push -m "<message>"We have seen that git stash -a puts all uncommitted changes into a stack.
git stash push -a -m "<message>" not only adds these changes to the stack, but also associates them with the message "<message>".
Do the same on the
experiment/branchandexperiment/old-commitbranches, with the messages"experiment/branch: work in progress"and"experiment/old-commit: work in progress"respectively.Check the state of the stash stack with the following command.
git stash listYou can see a number for each stash along with its associated message.
Let’s go back to the
mainbranch and re-apply the changes frommainwith the following command.
git stash apply stash@{<the correct number>}git stash apply stash@{<number>}
git stash apply stash@{<number>}git stash apply stash@{<number>} re-applies the work in progress from the stash with the corresponding number.
Check the state of the stash stack again. Note that the stash from the
mainbranch is still present.
git stash pop vs. git stash apply
git stash pop vs. git stash applygit stash pop applies the work from the first stash stash@{0} and removes it from the stash stack.
When we have stashes coming from multiple branches, it is often safer to use git stash apply rather than git stash pop, and then delete the corresponding stash when we no longer need it.
Delete the stash containing work from the
mainbranch with the following command.
git stash drop stash@{<the correct number>}Check the state of the stash stack again and note that the stash numbers have changed.
Repeat questions 5 to 7 for the other two branches to get familiar with the commands around
git stash.
Deleting branches¶
We cannot delete a branch while we are on that branch, so first go back to the main branch.
To delete a local branch that is already merged into the
mainbranch (for exampleexperiment/branch), use the following command.
git branch -d <branch name>To delete a local branch that is not yet merged into the
mainbranch (for exampleexperiment/old-commit), use the following command.
git branch -D <branch name>To delete the same branches on the Remote Repo, use the following command.
git push origin --delete <branch name>For example:
git push origin --delete experiment/branchGo back to the objectives and check off the points you have mastered. Practice the commands and concepts you do not fully understand yet. Ask your instructor for help if needed.