Part2 - Branching and Merging
Video: https://youtu.be/FyAAIHHClqI
#Git #GitHub #Version_Control #Best_Practices #IT
Table of Contents:
A) Intro to Branching
In git, we can use branches to work in parallel in our project. But first lets answer the question:
A.1) What is a branch for?
Branches result in a separation of versions of the same files.
We can have branches for different purposes, we could have:
- A production branch
- A development branch
- A branch for bug fixes
As we saw previously, we can represent commits using the Git Commit Graph
But, we can also represent commits and branches with a Git Branch Diagram.
For example, here is a diagram for a Git Flow (a type of branching strategy)
Note: It is similar to what we can see when using a Git GUI Tool like SmartGit.
A.2) How are branches implemented?
Let's start with an example,
Here is our new repo with 2 commits.
Note: Every commit has a 40 hexadecimal SHA-1 hash. The first 7 characters of those hashes are shown in the example diagram.
A branch is just a pointer to a SHA-1 hash.
In cryptography, SHA-1 (Secure Hash Algorithm 1) is a hash function which takes an input and produces a 160 bit (20byte) hash value known as a "message digest" typically rendered as 40 hexadecimal digits
A.2.1) The master branch and the HEAD pointer
Let's analyse the master branch,
As long as we are on the master branch, every time we make a commit, the branch moves up and follows along, staying at the tip of our line of work.
The way Git knows which branch we are on is a special pointer called HEAD.
HEAD is a pointer that normally points to a branch.
So far, we only have the first branch (the master branch). In Git terminology, the HEAD pointer tells us what we have cheeked out, so right now we know we have the master branch checked out
Since HEAD usually points to a branch and not directly to a commit it is sometimes called a "symbolic pointer"
A.2.2) How to create new branches
We make new branches with the git branch
command.
The branches will be "instantiated" where the HEAD pointer is.
We will start a branch named SDN and another called auth.
A.2.3) How to checkout and work on other branches
In this commit graph we have 3 branches and the master branch is checked out.
Now we will checkout the SDN branch with the git checkout SDN
command.
Now let's try something else...
While we are on the SDN branch, we will:
- edit our S1 File
- stage and commit that change.
Since we have the SDN branch checked out, only the SDN branch will move up to the new commit.
The master and auth branches will stay where they are at the previous commit.
Now, we will work on the auth branch.
First we will run git checkout auth
, that will move the HEAD pointer from SDN to auth.
While on the auth branch, we will:
- make a different change to S1
- stage and commit that change
This will create a new commit, and only the with auth branch will move to it.
We will end up with our 3 branches (master, SDN and auth) pointing to different commits.
The content of "S1" will be different depending on which branch we check out.
B) Intro to Merging
Now let's say our work is done on the SDN and auth branches.
We want to integrate these changes back into the master branch.
In other words, we want to "merge" our new changes into the master.
We will talk about 2 types of merges:
B.1) Fast-Forward merge
From our previous example,
Let's say we decided we want to get the S1 file from the SDN branch into our master branch.
The commit where we added the S1 file is here...
This commit parent is here...
Since there is a direct path from master to SDN, git can perform what's called a fast-forward merge
Note: To merge SDN into master, we use the git merge <branch name>
command.
The fast forward merge means git will just move the master branch to where SDN is. The master just has to catch up with SDN.
Note: Even if there were multiple commits between the 2 branches, there is still a fast-forward merge. We just need a direct path.
If we drew a Git Branch Diagram, the Fast-Forward Merge would look like this,
And with our example, it would look like this,
Now we are done working on the SDN branch. The work has been merged into master.
Delete Branches
We don't need the 2 branches pointing to the same commit. So we can delete the SDN branch now. We integrated our SDN work into master, so we can delete the SDN branch.
A fast-forward merge does not delete branches. It only updates the pointer of the receiving branch (e.g., master
) to point to the latest commit of the branch being merged (e.g., feature
). The branch being merged (e.g., SDN
) remains intact after a fast-forward merge. If you want to clean up your branch structure after the merge, you would need to manually delete the branch using a command like git branch -d <branch_name>
.
Before deleting the SDN branch, it is a good practice to check which commits are already merged with the git branch--merged
command.
We want to be careful when we delete branches because we could eventually lose work we did.
B.2) The 3-Way Merge
Now that we've merged and deleted the SDN branch, let's do the same with the auth branch.
Looking at the commit graph,
13:19
we see there is not a direct path from the master branch to the auth branch. Git can not do a fast-forward merge this time. For this case, a 3-way merge will happen. 3-way merge To merge master and auth, we can't just move the master pointer here. If we did that, we would lose our SDN changes we made here.
We need to merge these branches together into a new commit called a merge commit. To make this merge commit, Git looks at three commits. First, the base commit the two branches started from. Then the last commit of each branch. Let's merge auth into master now. "git status" shows we are on master.
"git merge auth" starts the merge. Since we are making a merge commit, we need a commit message. We can accept the default message here, save and exit. The merge is done. The output does not say fast-forward merge like last time. Now it says "Merge made by the 'recursive' strategy.
" With "graph" we see the merge commit joining the two branches. Now we can safely delete the auth branch. "git branch --merged" confirms this. We delete the auth branch with "git branch -d auth". This 3-way merge worked out without any conflicts. However, sometimes there are conflicts when merging branches together.
Next, we will see how to resolve a basic merge conflict. Merge Conflicts A merge conflict occurs when we try to merge branches that have changed the same lines in the same files. Let's see an example of this by creating some conflicts in two branches. We will work on a new branch named dev. We can use a one line shortcut to both create and checkout a new branch.
"git checkout -b dev". This saves us a tiny bit of overhead from typing "git branch dev" and then "git checkout dev" as separate commands. Now, let's make some edits to S1. We will change the red VLAN to green. Also, we can change port 1 to VLAN 10 and port 2 to VLAN 20. I'll save and exit.
"git diff" shows our new modifications. Let's get this staged and committed with one command. "git commit -a -m 'update S1 VLANs'" Now let's checkout master and make changes to S1 on the master branch. Here we will change the mgmt IP. We will change the red VLAN to pink.
We will move port 1's VLAN to VLAN 10. Finally, we will delete port 2. Now we can save and exit. Let's stage and commit this "git commit -a -m 'update S1'" "graph" shows our 2 branches have diverged. So we know there can't be a fast-forward merge. This is going to be a 3-way merge, and there are going to be conflicts.
Here is the version of S1 on the base commit. Here is the version of S1 on the master branch, and here it is on the dev branch. On the master branch, we changed the mgmt IP. On the dev branch, we did not change the mgmt IP. Therefore on the merge, Git will assume we do want to change the mgmt IP. With a change vs. no-change, the change will win.
Next, on the dev branch, we changed VLAN red to green. On master, we changed that same plan to pink. We've changed the same line in both branches, so we have a conflict. Git can't guess for us which one we want in our merge commit. Next, we changed port one from VLAN 20 to VLAN 10. However, we did the exact same change on both branches - so there will be no conflict there.
Finally port 2. On Dev, we changed it from VLAN 10 to VLAN 20. However, on Master, we just deleted it. This will be another conflict. OK, let's try our merge now. "git status" shows we are on master. "git merge dev" is used to start our merge. This time we get a notice. "Merge conflict for S1, fix and commit the result.
" "git status" looks different than what we usually see. Git indicates we are in the middle of a merge by saying "You have unmerged paths." First, we can see Git gives us a back out plan. If we don't want to deal with resolving the conflicts here, we can run "git merge --abort".
Assuming we started with a clean working tree and staging area, we can do this. Let's try that now. "git merge --abort". Running "git status" shows we are out of the merge process. "graph" shows us we are right back where we were before. Let's actually proceed with our merge though.
We can run "git merge dev" again. Now, "git status" again. Git has modified S1 to mark where we have conflicts. Let's edit S1 and see this. We see the two commit conflicts. The VLAN color here, and the deletion of port 2 vs. a VLAN change on port 2 here. Let's start with the VLAN conflict.
This set of equals signs separates out the state of the file in the two branches. On HEAD, which currently points to master, VLAN red changed to pink. Below the equals signs shows that on the commit the dev branch points to, VLAN red changed to green. We need to decide how we want our merged file to look.
When we decide, we delete the Git markers: the equals, and the brackets as well as the text we want to drop. Let's go with VLAN green and delete pink. Also, we delete the Git markers. Now to the second conflict. On master, we removed port 2 entirely, on dev we moved the VLAN from 10 to 20. Let's say we want to stick with VLAN 20.
We keep that and delete everything else. Now we've finished our conflict resolution, so we save and exit. Our conflict resolution changes are in the working tree. We stage this as we do for a standard commit. "git add S1". Now "git status" shows "All conflicts are fixed but you are still merging.
" When we commit this, Git creates our merge commit. "git commit." The default commit message is acceptable, so we save and exit. graph shows our merge commit. We can safely delete our dev branch with "git branch -d dev". That's it for our look at merging. Now let's look at a state in Git that can look pretty ominous when you are new to Git.
However, as we will see, usually it's quite easy to get out of. The detached HEAD state Usually HEAD points to a branch which in turn points to a commit. When HEAD is instead pointing directly to a commit, we have a detached HEAD state. Let's try checking out a commit instead of a branch. "git log" shows our commit history.
Maybe we just want to look around at how our files were at this snapshot here. "git checkout" and then a commit hash. With this command, we are checking out a commit directly by its sha-1 hash. Now, Git gives us some warnings. "graph" shows us the situation. We have the HEAD pointer directly referencing a commit and not a branch.
This is a detached HEAD. One way out of this state is to just checkout a branch again. "git checkout master". Now we no longer have a detached head. Let's go back to that commit to see another way to handle that detached HEAD state. "git checkout" and then the commit hash. We can put a new branch label here.
"git branch stage" creates a stage branch here. "graph" shows we have our new branch, but HEAD isn't attached to it. "git checkout stage" , checks out our new branch. "graph" shows the HEAD pointer is no longer detached. It is attached to our new stage branch. From here we can proceed normally.
Now let's cover our final topic. Up until now, every time we checked out a branch or performed a merge, we had a clean working tree and staging area. We had no modified files and no staged changes that were not committed. Sometimes when we don't have a clean slate, we will see an error in Git. We will be blocked from changing branches, or we can complicate merging of branches.
An excellent way to quickly get to a clean state is the git stash command. Git Stash Let's checkout the master branch. We can edit S1. I'll add a yellow van with id 30. "git status" shows our working tree is changed for S1. Now we try to checkout the stage branch we made earlier. Git actually blocks us from doing this.
To checkout the stage branch, Git would update our working tree and our staging area. If Git did this, we would lose our recent changes to S1. Git is stoping us from wiping those changes out. Git tells us what we can do here, we can commit our changes or we can use Git stash. Let's do the latter. "git stash".
With "git stash," Git has saved our new changes to S1 so we can apply them back later. "git status" shows we are back to a clean state. Now we can freely checkout branches or perform a merge. Let's do another edit and stash that as well. We can remove the auth server. We will run "git stash" again to save these changes.
With "git stash list" we see our two stashes. With the dash p option we can observe the edits that occurred with each stash point. We can re-apply these stashes at any time. We do that with "git stash apply." On its own "git stash apply" will re-apply the most recent stash. With "git diff" we see that removal of the auth_server has been restored.
Let's commit this change. With "git stash list" we see that we still have two stash points. We didn't pop or remove the most recent stash. So we can reuse stashes as needed. If we really wanted to apply the stash point and remove it from the list, we could have used "git stash pop" instead of "git stash apply.
" If we want to utilize a different stash point other than the most recent one, we can call it out by one of these labels. So for the first stash we made we can apply it with "git stash apply" and the label. "git diff" shows that the yellow VLAN is back from the stash point. In the output of "git stash list" , we may want a better reminder of what each stash contains.
We can provide a message with a stash with "git stash save" and then our message. For example git stash save "add yellow VLAN". Now "git stash list" shows a more helpful message with the stash point. OK, that was our last topic for this video. In a moment we will wrap up with a quick review of everything we've discussed.
Before the final review, a quick plug. Thanks so much for watching this video. If the video benefited you in any way, please support this channel by commenting and subscribing. It really helps to hear back from people watching the videos. If you have a question feel free to post it. Even if I don't know the answer, often another viewer will.
If you want to get in touch with me, feel free to message me on LinkedIn. Final Review: This video focused on branching and merging in Git. We started by creating a new repo and made two commits. We witnessed how Git automatically creates our first branch in our repo. By default Git names this first branch the master branch.
A branch is a small label that points to a commit. We also saw the HEAD pointer. The HEAD pointer tells us what we have checked out in Git. Usually, HEAD points to a branch and is a symbolic pointer. If we checkout a commit by its SHA-1 hash, we get the detached HEAD state. We can get out of a detached HEAD state by checking out a new branch at the commit we are on.
Also, we can just checkout an existing branch. In our repo, we created two new branches with the "git branch" command. The branches were created based on where our HEAD pointer was located. We saw how checking out a branch means moving the HEAD pointer to point to a branch. We worked on the two branches, SDN and auth.
We saw that when we make a commit, the checked out branch and the HEAD pointer move forward to the new commit. We merged our two branches into master. First, we merged the SDN branch. We checked out master and ran "git merge SDN" This was a fast-forward merge since there was a direct path from master to SDN.
In our case, we only had one hop, but many hops still would have been a fast-forward merge. We saw how to delete branches with "git branch -d." This command will only delete merged branches as seen with "git branch --merged." If we wanted to delete branches that are not merged, we could use upper case D.
We performed a 3-way merge to get the auth branch into master. This was a 3-way merge since there was no direct straight line path from auth to master. A 3-way merge looks at the common base commit, and the tip of each branch to figure out what the final merge commit should look like. Next, we looked at some basic merge conflicts and how to resolve them.
We saw how git puts markers in files to indicate the lines in conflict. We edited the file with conflicts, removed the Git markups and committed the change. This resulted in a merge commit, completing our 3-way merge. Finally, we wrapped up with a look at "git stash." git stash is useful when we have unfinished work that is not committed.
We can stash that work for later use. Once we run git stash our working tree and staging area are clean. That's it on this video. The next video will cover working with remote repos. Also it will cover some commands that alter your commit history - rebasing, amending and cherry-picking. Thanks for watching!
D) 💬 Summary (copy code 🖥️)
E) Coming Next...
Next, we will learn how to use remote repos. And other stuff like amending, rebasing, and cherry-picking.
Z) 🗃️ Glossary
File | Definition |
---|