As we already know, Git keeps track of every commit that we make. Because of that, we can easily review them in the so-called log.
$ git log
commit ff698ab Author: Elliot <email@example.com> Date: Tue Nov 15 14:28 2016 Add cookie
git log brings up the commit history, where every commit is displayed with a unique hash code.
Let's commit again to see multiple commits in the Git log. This time, let's also add a message.
$ touch cat.gif $ git add cat.gif $ git commit -m "Hello friend" $ git log
commit 7c1b0c4 Author: Elliot <firstname.lastname@example.org> Date: Tue Nov 15 14:49 2016 Hello friend commit ff698ab Author: Elliot <email@example.com> Date: Tue Nov 15 14:28 2016 Add cookie
git log reveals both of the commits we've made so far. From the commit messages we get an idea of what we've changed.
HEAD is a pointer that points to the current commit.
$ git show HEAD
commit 7c1b0c4 Author: Elliot <firstname.lastname@example.org> Date: Tue Nov 15 14:49 2016 Add cat
git show, we can see that the
HEAD is pointing to the commit with the hash code
We can easily go back to a previous commit and put the working directory back to the state it was in at the time.
$ git checkout f871379
Note: checking out 'f871379'. You are in 'detached HEAD' state. HEAD is now at f871379... Add cookie
Perfect! Now (almost) everything is like it was when we made that commit.
Psst: we can either use the first seven characters or the whole hash code.
When we go back to a previous commit, we can see each file as it was when we made the commit but adding or changing files in there isn't a good idea.
$ git checkout f871379 Note: checking out 'f871379'. You are in 'detached HEAD' state. HEAD is now at f871379... Add cookie.txt $ touch cake.txt $ git status
HEAD detached at f871379 Untracked files: cake.txt
See that? Now the current commit isn't the latest commit anymore, which means we're in detached
HEAD state, in which committing can lead to data losses.
Be gone, detached
HEAD, we're going back to the future!
$ git checkout master
Previous HEAD position was f871379 Add cookie.txt Switched to branch 'master'
Great Scott, that's it! Using
checkout master, we can go back to the latest commit, where everything is okay again.
Psst: now the
HEAD points to the latest commit again.
Instead of using and having to remember hash codes, we can also put tags on commits.
$ git checkout f871379 HEAD is now at f871379 Add cookie and cake $ git tag v1.2 $ git tag
See that? We use
git tag with a parameter to add a tag and the command without any parameters to display the previously added tags.
We can also use tags in combination with the
$ git tag v1.2 $ git checkout v1.2
Note: checking out 'v1.2'. You are in 'detached HEAD' state. HEAD is now at f871379 Add cookie and cake
Again, we are back at commit
f871379. Using tags can help us memorize important commits.
Branches allow us and others to work on different things, like features, at the same time. Let's find out what branch we're on!
$ git branch
Ah yes! The
master branch is the default branch of every repository. Every time we add a commit, we're moving along the
Psst: as soon as we have additional branches,
git branch will display those branches as well.
We can create branches to work in isolated environments. A new branch is just an offshoot of an existing branch.
Let's create another branch called
$ git branch develop $ git branch
* master develop
git branch with a branch name, we can create a new branch, an environment for commits that won't change the
* indicates that we're still on the
With multiple branches, we can have different versions of
fortune_cookie.txt that we can access by moving between branches.
Let's create an
alternate_reality branch and switch to it!
$ git branch alternate_reality $ git checkout alternate_reality
Switched to branch 'alternate_reality'
Sweet! Our reliable
checkout command can be used with the branch name to switch to another branch.
Whenever we commit something, it'll be committed to the currently checked-out branch.
Let's switch back to
master and commit to it.
$ cat cookie.txt Chocolate Chip Cookie $ git branch alternate_reality $ git checkout master $ echo Frosted Snowman Cookie > cookie.txt $ git add . $ git commit -m "Modify cookie"
[master a8ed121] Modify cookie 1 file changed, 1 insertion(+), 1 deletion(-)
Wohoo! We switched to
master, so the commit happened in that branch. If we would switch to
alternate_reality, so would the commit.
There's a way to display logs that makes reading them a lot easier.
$ git log --graph
* commit 86b289d | Author: Elliot <email@example.com> | Date: Tue Nov 15 11:41 2016 | | Add cake | * commit 52ebf24 Author: Elliot <firstname.lastname@example.org> Date: Tue Nov 15 11:36 2016 Add cookie
Way better! With flags like
--graph, we can specify what we want Git to do in a more precise way.
There's also a flag that allows us to view branches and the divergence of commits.
$ git log --graph --all
* commit 86b289d | Author: Elliot <email@example.com> | Date: Tue Nov 15 11:41 2016 | | Add cake | | * commit 168291b |/ Author: Elliot <firstname.lastname@example.org> | Date: Tue Nov 15 11:38 2016 | | Change cookie | * commit 52ebf24 Author: Elliot <email@example.com> Date: Tue Nov 15 11:36 2016 Add cookie
Sweet! When we use the
--all flag along with
log --graph, we can see all commits from all branches.
When there are tons of commits, however, we might not want to see all of the available information at once.
$ git log --graph --all --oneline
* 86b289d Add cake | * 168291b Change cookie |/ * 52ebf24 Add cookie
Nice! What a beautiful piece of condensed information we have there!
--oneline is a short version of
We can merge branches to bring their changes together.
$ git checkout alternate_reality $ git merge master $ git log --graph --all --oneline
* 4b45192 Merge commit |\ | * 1b18076 Add cake * | c07222e Add cookie |/ * f8eff11 Initial commit
Sweet! We switch to the branch we want to merge into and use
git merge followed by the branch name we want to take the changes from.
Psst: as long as the files in the branches don't conflict, the merge will go ahead smoothly.
If there are versions of a file with different changes in branches we want to merge, we'll face a conflict that we need to resolve.
$ git log --graph --all --oneline * 1b18076 Modify cookie | * c07222e Modify cookie |/ * f8eff11 Add cookie
See that? Both branches have modified
cookie.txt. If we try to merge the branches, we'll see a conflict.
Now, if we switch back to
master and try merge
alternate_reality into it, we'll be presented with an error message.
$ git checkout master $ git merge alternate_reality
Auto-merging cookie.txt CONFLICT: Merge conflict in cookie.txt Automatic merge failed; fix conflicts and then commit the result.
master causes a merge conflict and Git doesn't know which version we want to keep.
Trying to merge
master resulted in a merge conflict. But what exactly does such a conflict look like?
$ cat cookie.txt
<<<<<<< HEAD Chocolate Chip Cookie ======= Frosted Snowman Cookie >>>>>>> alternate_reality
There! The special markings in
cookie.txt reveal that we've added
"Chocolate Chip Cookie" in
"Frosted Snowman Cookie" in
Now that we've located the merge conflict, how can we resolve it?
Great! Because Git doesn't know which version of the file it should keep, we have to decide, delete everything we don't need, and commit the file.
As projects grow, more and more merge conflicts appear. Wouldn't it be great to have a tool to review and resolve them? Well, that's what merge tools are for.
There are a number of tools for macOS and Windows but SemanticMerge is a good choice if you're not sure which tool to go for.
What makes Git so great is that it allows multiple people to work on the same project at the same time.
But how can we coordinate work when our repository is stored on our local computer?
That's it! We can create remote repositories on websites like Github and share them publicly or privately with others.
As soon as we have a link to a remote repository, we can download, or clone, it to our local computer.
$ git clone https://github.com/Elliot/WhiteRose.git
Cloning into 'WhiteRose'... remote: Counting objects: 51, done. remote: Total 51, reused 0 Unpacking objects: 100% (51/51), done. Checking connectivity... done.
Fantastic! When we use
git clone with the link to the repository, Git downloads the repository to the directory we're currently in.
Now that we've cloned
WhiteRose, let's take a closer look at the remote repository!
$ cd WhiteRose $ git remote
git remote, we can see that the
git clone command from earlier added
But where does the
origin remote actually point to?
$ git remote show origin
* remote origin Fetch URL: https://github.com/Elliot/WhiteRose.git Push URL: https://github.com/Elliot/WhiteRose.git HEAD branch: master Remote branch: master tracked ...
git remote show with the short name of the remote, we can see that it points to URLs and that its
HEAD points to
origin is just a convention, a name that's automatically given to a remote for which we don't specify a name.
If the remote has changes in its, say,
master branch that we want to bring to our local repository, we need to fetch them.
$ git fetch origin master
remote: Counting objects: 3, done. remote: Total 3, reused 0 Unpacking objects: 100% (3/3), done. From https://github.com/Elliot/WhiteRose.git * branch master -> FETCH_HEAD c07222e..1b18076 master -> origin/master
git fetch with
master brings these changes to a branch in our local repository called
Now that we've fetched the changes, we need to merge them from the
origin/master branch into
$ git merge origin/master master
Updating 4b45192..c239b52 Fast-forward cookie.txt 1 insertion(+), 5 deletions(-)
Sweet! We've used
git merge to bring the changes we fetched into
origin/master to the
master branch of our local repository.
Now, pulling is what we do to bring a branch in our local repository up-to-date with its remote version.
$ git pull origin master
remote: Counting objects: 3, done. remote: Total 3, reused 0 Unpacking objects: 100% (3/3), done. From https://github.com/Elliot/WhiteRose.git * branch master -> FETCH_HEAD c07222e..1b18076 master -> origin/master Updating c07222e..1b18076 Fast-forward cookie.txt 1 file changed, 2 insertions
Great! Notice how similar this is to fetching and merging at the same time? Well, that's exactly what
git pull does.
Finally, let's find out how we can get our local changes to the remote repository!
$ git add README.md $ git commit -m "Add title to README" $ git push origin master
Counting objects: 3, done. Writing objects: 100% (3/3), 292 bytes Total 3, reused 0 To https://github.com/Elliot/WhiteRose.git 1b18076..c07222e master -> master
git push, well, pushes commits from our local repository to the remote.
Psst: we need to commit changes before we can push them to the remote.