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
//Output Below
commit ff698ab Author: Elliot <elliot@allsafe.com> Date: Tue Nov 15 14:28 2016 Add cookie
Nice! 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
//Output Below
commit 7c1b0c4 Author: Elliot <elliot@allsafe.com> Date: Tue Nov 15 14:49 2016 Hello friend commit ff698ab Author: Elliot <elliot@allsafe.com> Date: Tue Nov 15 14:28 2016 Add cookie
Nice! Now 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.
The so-called HEAD
is a pointer that points to the current commit.
$ git show HEAD
//Output Below
commit 7c1b0c4 Author: Elliot <elliot@allsafe.com> Date: Tue Nov 15 14:49 2016 Add cat
Sweet! Using git show
, we can see that the HEAD
is pointing to the commit with the hash code 7c1b0c4
.
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
//Output Below
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
//Output Below
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
//Output Below
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
//Output Below
v1.2
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 checkout
command.
$ git tag v1.2 $ git checkout v1.2
//Output Below
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
//Output Below
* master
Ah yes! The master
branch is the default branch of every repository. Every time we add a commit, we're moving along the master
branch.
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 develop
.
$ git branch develop $ git branch
//Output Below
* master develop
Great! Using git branch
with a branch name, we can create a new branch, an environment for commits that won't change the master
branch.
Psst: the *
indicates that we're still on the master
branch.
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
//Output Below
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"
//Output Below
[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
//Output Below
* commit 86b289d | Author: Elliot <elliot@allsafe.com> | Date: Tue Nov 15 11:41 2016 | | Add cake | * commit 52ebf24 Author: Elliot <elliot@allsafe.com> 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
//Output Below
* commit 86b289d | Author: Elliot <elliot@allsafe.com> | Date: Tue Nov 15 11:41 2016 | | Add cake | | * commit 168291b |/ Author: Elliot <elliot@allsafe.com> | Date: Tue Nov 15 11:38 2016 | | Change cookie | * commit 52ebf24 Author: Elliot <elliot@allsafe.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
//Output Below
* 86b289d Add cake | * 168291b Change cookie |/ * 52ebf24 Add cookie
Nice! What a beautiful piece of condensed information we have there!
Psst: --oneline
is a short version of --pretty=oneline --abbrev-commit
.
We can merge branches to bring their changes together.
$ git checkout alternate_reality $ git merge master $ git log --graph --all --oneline
//Output Below
* 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
//Output Below
Auto-merging cookie.txt CONFLICT: Merge conflict in cookie.txt Automatic merge failed; fix conflicts and then commit the result.
Snap! Merging alternate_reality
into master
causes a merge conflict and Git doesn't know which version we want to keep.
Trying to merge alternate_reality
into master
resulted in a merge conflict. But what exactly does such a conflict look like?
$ cat cookie.txt
//Output Below
<<<<<<< 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 master
and "Frosted Snowman Cookie"
in alternate_reality
.
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?
git remote
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
//Output Below
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
//Output Below
origin
Nice! Using git remote
, we can see that the git clone
command from earlier added WhiteRose
as origin
.
But where does the origin
remote actually point to?
$ git remote show origin
//Output Below
* 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 ...
There! Using git remote show
with the short name of the remote, we can see that it points to URLs and that its HEAD
points to master
branch.
Psst: 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
//Output Below
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
Great! Using git fetch
with origin
and master
brings these changes to a branch in our local repository called origin/master
.
Now that we've fetched the changes, we need to merge them from the origin/master
branch into master
.
$ git merge origin/master master
//Output Below
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
//Output Below
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
//Output Below
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
Nice! 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.