Prerequisites

Git ready!

Hey! In this course, we'll dive into Git, a system for tracking changes in files and coordinating work in teams.

$ git init

//Output Below

Initialized empty Git repository in /Users/user/project/.git/

While Git is very popular for software projects, we can use it to keep track of changes in any file. But what exactly did we just do?

Repositories

We've just created a repository, which is a special directory that imports our work to Git. Every project we want to track needs such a repository.

$ mkdir project
$ cd project
$ git init

//Output Below

Initialized empty Git repository in /Users/user/project/.git/

Sweet! With git init, we can turn an ordinary directory into a repository and start tracking changes.

Psst: repositories are also referred to as repos.

Checking the status

Up until now, we haven't made any changes to our repository, have we? In order to confirm that, let's check its status!

$ git status

//Output Below

On branch master
Initial commit
nothing to commit (create/copy files and use "git add" to track)

Everything in Git starts with the git command. Don't worry about branches for now. All we need to know is that we're working on the master branch!

Psst: when there aren't any files with changes in the working directory, we say that the directory is clean.

Making changes

>

It's time to make some changes. Let's create a text file and check the status of the repository again!

$ touch cookie.txt
$ git status 

//Output Below

On branch master
Initial commit
Untracked files:
 cookie.txt
nothing added to commit but untracked files present (use "git add" to track)

Right there! The git status command reveals that cookie.txt is currently untracked.

Tracking files

Now, in order to keep track of cookie.txt, we need to add it to the list of tracked files

$ git add cookie.txt
$ git status

//Output Below

On branch master
Initial commit
Changes to be committed:
 new file: cookie.txt

Perfect! When we add a file to the list of tracked files using git add, we're said to stage it.

Unstaged changes

Let's add something to cookie.txt so we can see what happens to its status!

$ echo Hello world! > cookie.txt
$ git status

//Output Below

On branch master
Initial commit
Changes to be committed:
 new file: cookie.txt
Changes not staged for commit:
 modified: cookie.txt
Hello world!

Sweet! cookie.txt is still tracked but now it's not staged anymore because its content has changed. We need to use git add to stage it again.

Tracking multiple files

Sometimes we want to stage multiple files. While it would take a while to do this file by file, there's a great shortcut.

$ touch cookie.txt
$ touch cake.txt
$ git add . 
$ git status

//Output Below

On branch master
Changes to be committed:
 new file: cookie.txt
 new file:   cake.txt

Nice! Using git add with ., we can add all the unstaged files in the current directory (and deeper), making this a great way to add multiple files.

Time to commit!

When we run git add, Git captures a snapshot of the changes that we can make permanent by committing them to the repository.

$ git commit -m "Add cookie and cake" 

//Output Below

[master (root-commit) cf010b1] Add cookie and cake
2 files changed, 1 insertion(+)
 create mode 100644 cookie.txt
 create mode 100644 cake.txt

That's it! After the -m, we can provide a commit message, which is useful to keep note of our commits.

The Git workflow

As we've seen, there's a workflow that we almost always follow when we work with Git.

  1. Make changes in the working directory
  2. Use git add to stage them
  3. Use git commit to save the changes to repository

Sweet! We make changes in the working directory, stage the files we want to commit in the staging area, and make them permanent by committing them.

Installing Git (macOS)

If you're on macOS, you can get Git along with the Xcode Command Line Tools.

You can run xcode-select --install or try to run git from the Terminal; if you don’t have it, you'll be prompted to install it.

$ xcode-select --install 

//Output Below

xcode-select: note: install requested for command line developer tools

Great! After installing the Xcode Command Line Tools, we're all set to run git from the Terminal.

Installing Git (Windows)

If you're using Windows, it's a great idea to download Git for Windows.

After you've installed it, you can use the Command Prompt to check if it's in place.

C:\>git --version 

//Output Below

git version 2.8.4

There! Git for Windows is ready.

Psst: please note that some commands in this course only work on macOS and other Unix-based systems.

Logging

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.

Logging again

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.

HEAD

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.

Turning back time

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.

Lost in time

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.

Back to the future

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.

Tagging

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.

Checking out 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.

What's a branch?

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.

Creating a branch

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.

Switching branches

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.

Committing again

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.

Logging with flags

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.

Logging with branches

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.

Information overflow

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.

Merging branches

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.

Conflicts

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.

Help me, 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.

Locating conflicts

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.

Resolving conflicts

Now that we've located the merge conflict, how can we resolve it?

  1. Decide which version we want to keep
  2. Open a text editor and delete the other version along with the special markings
  3. Commit the file

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.

Merge tools

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.

Remote repositories

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.

Cloning

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.

Viewing remotes

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.

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.

Fetching changes

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.

Merging fetched changes

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.

Pulling changes

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.

Pushing

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.