Introduction to Git –- Fall 2020
There are many uses for branches:
Until now, we have worked with a repository that only have one branch, with the commits done one at a time:
In the above picture, the master branch points to a commit. The current position is HEAD.
Now we want to look at repositories with several branches:
Branches are used to create another line of development. They are "individual projects" within a git repository.
Usually, a branch is created to work on a new feature. Once the feature is completed, it is merged back with the master branch.
Creating a new branch does not change the repository, it just points out the commit.
Note that the branch is created from the current HEAD.
To create a new branch (called cool-feature in the following):
$ git branch cool-feature
To move to another branch (switch):
$ git checkout cool-feature
If you wish to switch to a new branch that is not yet created, you can do so by adding the flag -b
to git checkout
.
To see which branch you are on:
$ git branch
$ git checkout master
$ git merge cool-feature
$ git branch -d cool-feature
$ mkdir my-project; cd my-project/
$ git init
Initialized empty Git repository in /home/bbrydsoe/my-project/.git/
$ touch file.txt
$ git add file.txt
$ git commit -m "Committing the first file"
[master (root-commit) 1006b51] Committing the first file
1 file changed, 0 insertions(+), 0 deletions(-)
create mode 100644 file.txt
$ git branch cool-feature
$ git checkout cool-feature
Switched to branch 'cool-feature'
$ echo "This is a text" > file.txt
$ git add file.txt
$ git commit -m "Added text to the first file"
[cool-feature 5bad966] Added text to the first file
1 file changed, 1 insertion(+)
$ git checkout master
Switched to branch 'master'
$ echo "Text to the second file" > second-file.txt
$ git add second-file.txt
$ git commit -m "Added a second file"
[master bdec2cf] Added a second file
1 file changed, 1 insertion(+)
create mode 100644 second-file.txt
$ git log --graph --oneline --decorate --all
This is on the master branch
$ git log --graph --oneline --decorate --all
* bdec2cf (HEAD -> master) Added a second file
| * 5bad966 (cool-feature) Added text to the first file
|/
* 1006b51 Committing the first file
We now merge the branches and check again
$ git merge cool-feature
Merge made by the 'recursive' strategy.
file.txt | 1 +
1 file changed, 1 insertion(+)
$ git log --graph --oneline --decorate --all
* cf3e6b7 (HEAD -> master) Merge branch 'cool-feature'
|\
| * 5bad966 (cool-feature) Added text to the first file
* | bdec2cf Added a second file
|/
* 1006b51 Committing the first file
Now we can delete the new branch we had created, since all the content is now in the master branch.
$ git branch -d cool-feature
Deleted branch cool-feature (was 5bad966).
In a somewhat nicer format, it looks like this:
We commit stuff to both branches
digraph {
rankdir=LR
"commitX" -> "commit1"
"commit2" -> "commit1"
"commitY" -> "commitX"
"commit3" -> "commit2"
"master" -> "commit3" [style=dashed]
"cool-feature" -> "commitY"[style=dashed]
master [shape=plaintext]
"cool-feature" [shape=plaintext]
}
Merge 'cool-feature' to 'master'
digraph {
rankdir=LR
"commit2" -> "commit1"
"commitX" -> "commit1"
"commit3" -> "commit2"
"commitY" -> "commitX"
"master" -> "commit4" [style=dashed]
"cool-feature" -> "commitY" [style=dashed]
"commit4" -> "commit3"
"commit4" -> "commitY"
master [shape=plaintext]
"cool-feature" [shape=plaintext]
}
Delete 'cool-feature'
digraph {
rankdir=LR
"commit2" -> "commit1"
"commitX" -> "commit1"
"commit3" -> "commit2"
"commitY" -> "commitX"
"master" -> "commit4" [style=dashed]
"commit4" -> "commit3"
"commit4" -> "commitY"
master [shape=plaintext]
}
As mentioned above, you switch between branches with:
$ git checkout <branch>
What happens if you have uncommitted changes (and/or new files added) when you try to switch?
What if there is a conflict?
Here we create a new branch, switch to it, then add a new file. Then we switch back to the master branch without committing the changes:
$ git checkout -b cool-feature
Switched to a new branch 'cool-feature'
$ touch newfile.txt
$ git add newfile.txt
$ git checkout master
A newfile.txt
Switched to branch 'master'
Git warns that there is a file added in one branch but not the other, but the switch is allowed.
If we make changes to the file in one of the branches but not on the other and do not commit it, then git will again warn:
$ echo "Adding some text" >> newfile.txt
$ git add newfile.txt
$ git checkout master
M newfile.txt
Switched to branch 'master'
Git warns that there is a file that is modified in one branch but not the other, but the switch is allowed.
Assume two branches, "cool-feature" and "morefeatures"
Switch to branch "cool-feature", add some text to a file, stage the file and commit it:
$ git checkout cool-feature
Switched to branch 'cool-feature'
$ echo "add text" >> morefiles.txt
$ git add morefiles.txt
$ git commit -m "Some text"
[cool-feature 469542b] Some text
1 file changed, 1 insertion(+)
create mode 100644 morefiles.txt
Switch to branch "morefeatures". Modify the same file, stage the file and commit it. Then try and switch back to the "cool-features" branch:
$ git checkout morefeatures
Switched to branch 'morefeatures'
$ echo "Adding yet some more text" >> morefiles.txt
$ git add morefiles.txt
$ git checkout cool-feature
error: Your local changes to the following files would be overwritten by checkout:
morefiles.txt
Please commit your changes or stash them before you switch branches.
Aborting
Now Git complains.
So what can we do if there is a conflict?
First do a git status
in the branch where you may have uncommitted changes:
$ git status
On branch morefeatures
Changes to be committed:
(use "git reset HEAD <file>..." to unstage)
modified: file.txt
new file: morefiles.txt
You can see the dirty status.
To fix it, let us use git stash
:
$ git stash
Saved working directory and index state WIP on morefeatures: 4922606 Some text
Checking again with git status
:
$ git status
On branch morefeatures
nothing to commit, working tree clean
You can now switch branches and work on something else.
If you do not want to stash your changes, but just get rid of them, you can use git clean
.
WARNING: This command will remove all non-tracked files in your current directory!
You can safely test which files will be removed by running:
$ git clean --dry-run
--merge
(or -m
):$ git checkout --merge <branch>
error: Entry '<fileName>' would be overwritten by merge.
Cannot merge. (Changes in staging area)
git branch
.Git can automatically try to merge when you give the command:
$ git merge <branch-to-merge-to>
while standing on the branch you want to merge to.
Git has some merge strategies. The most commonly used are:
Here we create a merge conflict:
$ mkdir merge-test
$ cd merge-test/
~/merge-test$ git init
Initialized empty Git repository in /home/bbrydsoe/merge-test/.git/
~/merge-test$ echo "Creating a file with some text to play with." >> myfile.txt
~/merge-test$ git add myfile.txt
~/merge-test$ git commit -m "First commit"
[master (root-commit) 9badcc6] First commit
1 file changed, 1 insertion(+)
create mode 100644 myfile.txt
~/merge-test$ git checkout -b mergebranch
Switched to a new branch 'mergebranch'
~/merge-test$ echo "Adding text to the file in order to merge." > myfile.txt
~/merge-test$ git add myfile.txt
~/merge-test$ git commit -m "Changed the content of myfile.txt"
[mergebranch 41b0e36] Changed the content of myfile.txt
1 file changed, 1 insertion(+), 1 deletion(-)
~/merge-test$ git checkout master
Switched to branch 'master'
~/merge-test$ echo "Put more text to the file" >> myfile.txt
~/merge-test$ git add myfile.txt
bbrydsoe@enterprise-a:~/merge-test$ git commit -m "Added more text"
[master c17e479] Added more text
1 file changed, 1 insertion(+)
~/merge-test$ git merge mergebranch
Auto-merging myfile.txt
CONFLICT (content): Merge conflict in myfile.txt
Automatic merge failed; fix conflicts and then commit the result.
So Git complains
We can get some more information with the git status
command:
~/merge-test$ git status
On branch master
You have unmerged paths.
(fix conflicts and run "git commit")
(use "git merge --abort" to abort the merge)
Unmerged paths:
(use "git add <file>..." to mark resolution)
both modified: myfile.txt
no changes added to commit (use "git add" and/or "git commit -a")
Looking inside the file myfile.txt:
~/merge-test$ cat myfile.txt
<<<<<<< HEAD
Creating a file with some text to play with.
Put more text to the file
=======
Adding text to the file in order to merge.
>>>>>>> mergebranch
Some "conflict dividers" have been added.
$ git merge --continue <branch-to-merge>
Commands to help:
git status
git log --merge
git diff
git reset
If you made a mistake when you resolved a conflict and have completed the merge before realizing, you can roll back to the commit before the merge was done with the command git reset --hard
.
$ git merge <other-branch>
Success!
$ git merge <other-branch>`
$ git merge --continue <other-branch>`
Success!
Branch 'bugfix' was branched from 'master'
digraph {
rankdir=LR
"commit2" -> "commit1"
"commit3" -> "commit2"
"commit4" -> "commit3"
"commitX" -> "commit2"
"master" -> "commit4" [style=dashed]
"bugfix" -> "commitY" [style=dashed]
"commitY" -> "commitX"
master [shape=plaintext]
"bugfix" [shape=plaintext]
}
Rebasing 'bugfix' to the 'master' branch
digraph {
rankdir=LR
splines="line"
"commit2" -> "commit1"
"commit3" -> "commit2"
"master" -> "commitY'" [style=dashed]
"commit4" -> "commit3"
"commitX'" -> "commit4"
"commitY'" -> "commitX'"
master [shape=plaintext]
}
Assume a master branch and the branch "cool-features" and that you want to rebase the branch "cool-features" onto the master branch:
$ git checkout cool-features
$ git rebase master
This works by
Not the same! A rebase moves a branch from one base to another. A fast-forward merge moves a branch head from the current commit to a commit for a descendant.
Example:
Start
digraph {
rankdir=LR
"commit2" -> "commit1"
"commitX" -> "commit2"
"commit3" -> "commit2"
"commit4" -> "commit3"
"commit5" -> "commit4"
"commit6" -> "commit5"
"commitY" -> "commitX"
"commitZ" -> "commitY"
"A" -> "commit4" [style=dashed]
"B" -> "commitZ" [style=dashed]
"C"-> "commit6" [style=dashed]
A [shape=plaintext]
B [shape=plaintext]
C [shape=plaintext]
}
Rebase B onto C
digraph {
rankdir=LR
"commit2" -> "commit1"
"commit3" -> "commit2"
"commit4" -> "commit3"
"commit5" -> "commit4"
"commit6" -> "commit5"
"commitX'" -> "commit6"
"commitY'" -> "commitX'"
"commitZ'" -> "commitY'"
"A" -> "commit4" [style=dashed]
"B" -> "commitZ'" [style=dashed]
"C"-> "commit6" [style=dashed]
A [shape=plaintext]
B [shape=plaintext]
C [shape=plaintext]
}
FF merge C into A:
digraph {
rankdir=LR
"commit2" -> "commit1"
"commitX" -> "commit2"
"commit3" -> "commit2"
"commit4" -> "commit3"
"commit5" -> "commit4"
"commit6" -> "commit5"
"commitY" -> "commitX"
"commitZ" -> "commitY"
"A" -> "commit6" [style=dashed]
"B" -> "commitZ" [style=dashed]
"C"-> "commit6" [style=dashed]
A [shape=plaintext]
B [shape=plaintext]
C [shape=plaintext]
}
Basically, cherry-picking in Git means that you choose a commit from one branch that you apply to another.
Find the hash for the commit you want to apply, using git log
.
Then make sure you are on the right branch that you want to apply the commit to:
$ git checkout <branch>
Now you execute the cherry-picking:
$ git cherry-pick <hash>
Each of the exercises has a README.md file with explanations and descriptions of what to do. You can find all of them in the subdirectory 5.branches. You should do them in the below order: