A practical introduction to git

Anna Kennedy

Overview

  • Version control
  • Git concepts
  • Using git locally
  • Using git with a (Gitlab) remote
  • Undoing mistakes
  • Branches
  • Caring for your commit history

Version control

  • Version control means that we keep a history of all the changes made in a set of documents.
  • Git is one example of a version control system

  • Pre-version control:
    • Copy file1 to file1.bak
  • CVS / SVN:
    • Centralised storage with revision history
  • Git:
    • Distributed system

SVN

Centralised storage with revision history

  • client-server model
  • central server houses master repo
  • developer laptop is the client
  • check-out -> modify -> check-in

  • Advantages / disadvantages
  • + simple system
  • - difficult to work offline
  • - difficult to work collaboratively
  • - advanced functionality quite hard to get right

Git

Distributed version control

  • every copy of the project is a full repository

  • Advantages / disadvantages
  • - steep learning curve
  • + save changes locally when working offline
  • + branching
  • + merging
  • + every copy is a full backup
  • + very fast

Using git locally

Projects in git

  • Any directory can be brought under git's control
    • both new and existing projects

  • git init initialises a directory with a .git subdir
    • This directory is now a local git repository
    • The .git folder contains metadata about the project
    • To remove a project from git, simply delete the .git folder

Exercise: Initialise a new project in git

  • Make a new directory and move into it
  • Initialise the directory as a git repository

$ mkdir myproject
$ cd myproject

$ git init  
  Initialized empty Git repository in /home/myname/projects/myproject

$ ls -la
  drwxr-xr-x 4096 Apr  5 09:54 .
  drwxr-xr-x 4096 Apr  4 12:40 ..
  drwxr-xr-x 4096 Apr  5 08:13 .git
           

Configure global git variables

  • Git requires that version history is associated with users
  • Thus we need to set name and email as global variables
  • Trying to use git without this results in the following annoying message:

  Your name and email address were configured automatically based
  on your username and hostname. Please check that they are accurate.
  You can suppress this message by setting them explicitly:

  $  git config --global user.name "Your Name"
  $  git config --global user.email you@example.com
          

Exercise: Check / set your name and email settings

  • Use git config -l to see if you've already set your name and email address
  • If you need to set them, use

$ git config --global user.name "Your Name"
$ git config --global user.email you@example.com
          

Checking the current status

  • Running git status at any point shows the current state of the directory
  • It also often gives hints about how to proceed or undo steps

$ git init  
  Initialized empty Git repository in /home/myname/projects/myproject

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

Getting help

  • Access the man pages on the command-line like:

$ git help
  The most commonly used git commands are:
   add        Add file contents to the index
   branch     List, create, or delete branches
   checkout   Checkout a branch or paths to the working tree

$ git help add
  NAME
       git-add - Add file contents to the index

  SYNOPSIS
       git add [-n] [-v] [-f] [-i] [-p] [--] [...]
          

Adding files to the project

  • git add filename to add files to staging
  • git add . to add everything

Exercise: Add files to the project

  • Make a file inside the directory "helloworld.txt"
  • Add this file to git

$ vim helloworld.txt

$ git status
  On branch master
    Untracked files:
    (use "git add file..." to include in what will be committed)
      helloworld.txt

$ git add helloworld.txt

$ git status
  On branch master
  Changes to be committed:
    (use "git reset HEAD file..." to unstage)
       new file: helloworld.txt
              

Committing the changes to the local repo

  • After a file is added to staging, we then have to commit the changes
  • git commit and then enter commit message on the next screen
  • git commit -m "commit message" to do both steps at once

Exercise: Commit the changes to your local repo

  • Commit this file to your local repository
  • 
    $ git commit -m "First commit"
    
    $ git status
    On branch master
    nothing to commit, working directory clean
                    

Basic git rhythm

  • Make changes
  • Stage the changes with git add
  • Commit the changes with git commit

View commit history

  • git log shows a history of commits
  • git log -p shows what has changed with each commit
  • git log -2 only shows the last two commits


$ git log -p
commit 320a8f00a4a0de7c9b3f39851d3bc164d0769697
Author: myname myname@ak-rl.example.com
Date:   Mon Apr 4 12:52:23 2016 +0200
    First commit

diff --git a/file1.txt b/file1.txt
new file mode 100644
index 0000000..907b308
--- /dev/null
+++ b/file1.txt
@@ -0,0 +1 @@
+Hello everyone!
                

A little more about commits

  • Commits are basically snapshots
  • Each commit is identified by a unique hash
  • The most recent commit is called HEAD
  • The two before that are called HEAD~ and HEAD~2
  • We can use both the reference number and the HEAD notation to refer to previous commits


$ git log 
commit 320a8f00a4a0de7c9b3f39851d3bc164d0769697
Author: myname myname@ak-rl.example.com
Date:   Mon Apr 4 12:52:23 2016 +0200
    First commit
                

Amend the commit message

  • If you accidentally did a commit with a mistake in the commit message
  • git commit --amend lets you change it

Exercise: Edit your most recent commit

  • Edit your most recent commit message
  • 
    $ git commit --amend
                    
    
    Allow user login
    
    # Please enter the commit message for your changes. Lines starting
    # with '#' will be ignored, and an empty message aborts the commit.
                    

Exercise: Make and view a more interesting commit history

  • So far we literally only have one commit - let's make a bit more to work with
  • Make two more files, and commit them
  • Edit one file, and commit that
  • Make two files and only commit one of them
  • Now commit the other one
  • Use git log and git status to follow the history as you go

Comparing files

  • git diff compares current files with the latest committed version
  • After you've made a change to a file, but before it's committed
  • Use 'git diff' to see what the difference between the files is


$ echo "Pleased to meet you all." >> helloworld.txt

$ git diff
diff --git a/file1.txt b/file1.txt
index 404c772..2d48565 100644
--- a/file1.txt
+++ b/file1.txt
@@ -1,2 +1,3 @@
Hello everyone!
+Pleased to meet you all.
                

Exercise: Compare files

  • Make a change to a file
  • Use 'git diff' to see what the difference between the file now and the last committed version


$ git diff
diff --git a/file1.txt b/file1.txt
index 404c772..2d48565 100644
--- a/file1.txt
+++ b/file1.txt
@@ -1,2 +1,3 @@
Hello everyone!
+Pleased to meet you all.
                

Stashing your work

  • We like to try and keep our commits meaningful (more on this later)
  • but if you're in the middle of working on something and you need to pause or switch branches
  • you can save your work-in-progress state with git stash
  • Can stash multiple events
  • git stash list to view them
  • git stash apply to pick up your most recent stash

Exercise: Stashing your work

  • Make some changes to a file
  • Stash your changes
  • View the stash list
  • Verify the directory is at the state of the latest commit
  • Apply the stash to regain your work in progress


$ git stash
  Saved working directory and index state WIP on master: e99a3ac Allow user login
  HEAD is now at e99a3ac Latest hegnar configs

$ git stash list
  stash@{0}: WIP on master: e99a3ac Allow user login
            

Using git with
a remote

Using a remote repository

  • So far we only used our local repository
  • If we're interested in collaboration or backups, we need to look at communicating with a remote repository

  • For this course, our remote is housed in Gitlab
  • Otherwise the remote could be
    • plain git
    • gitolite
    • Gitorious
    • Github

Gitlab

  • Gitlab provides a web-gui frontend
  • Lots of useful collaborative and exploratory features

gitlab

Creating a remote in Gitlab

  • Pretty straightforwards to create a new remote in Gitlab
gitlab

Exercise: Create a new remote repository

  • Within the Gitlab UI, make a new project
  • Name your project, leave it as private
  • Notice that it tells you how to proceed!
  • Within your local repository, tell git where the remote is:

$ git remote add origin git@gitlab.example.com:myname/myprojectname.git
  # Set a new remote
                

Pushing changes from the local to the remote repo

  • So far all our work is only in our local repository
  • Use git push to send our commits to the remote repository
  • The first time we do this we need to set the upstream branch
  • origin is git's default name for the remote repo

$ git push -u origin master

  Counting objects: 5, done.
  Delta compression using up to 4 threads.
  Compressing objects: 100% (5/5), done.
  Writing objects: 100% (5/5), 721 bytes | 0 bytes/s, done.
  Total 5 (delta 2), reused 0 (delta 0)
  To git@gitlab.example.com:myname/myproject.git
     e99a3ac..f91ed8c  master -> master
                

Exercise: Push changes from the local to the remote repo

  • Use git push to send your commits to the remote repository
  • The first time we do this we need to set the upstream branch with -u
  • You should see your files in Gitlab now

$ git push -u origin master

  Counting objects: 5, done.
  Delta compression using up to 4 threads.
  Compressing objects: 100% (5/5), done.
  Writing objects: 100% (5/5), 721 bytes | 0 bytes/s, done.
  Total 5 (delta 2), reused 0 (delta 0)
  To git@gitlab.example.com:myname/myproject.git
     e99a3ac..f91ed8c  master -> master

                

Retrieving changes from the remote

  • If changes have been made on the remote, we need to update our local repository to get those changes
  • Use git pull to retrieve any changes

Exercise: pulling remote changes

  • Edit a file in Gitlab
  • Pulled the changes your local repository
  • What do git status and git log say?

$ git pull

  remote: Counting objects: 3, done.
  remote: Compressing objects: 100% (3/3), done.
  remote: Total 3 (delta 2), reused 0 (delta 0)
  Unpacking objects: 100% (3/3), done.
  From gitlab.example.com:myname/myproject
   f91ed8c..3f522c5  master     -> origin/master
  Updating f91ed8c..3f522c5
  Fast-forward
    file1.txt | 3 ++-
    1 file changed, 2 insertions(+), 1 deletion(-)
          

Pull behind the scenes

  • So far we've done git pull to get the changes from the remote repository
  • git pull really means git fetch then git merge
  • We'll talk more about merging soon
  • Be aware that sometimes doing a fetch and then a merge instead of pull can give you more insight into what git is doing

Viewing remotes on the command line

  • git remote shows and can amend the remote repository
  • This information is also available in .git/config
  • origin is just git's default name for the remote
  • 
    $ git remote -v
    origin  git@gitlab.example.com:anna/testrepository.git (fetch)
    origin  git@gitlab.example.com:anna/testrepository.git (push)
              

Basic git rhythm with remote

  • Get updates with git pull
  • Make changes
  • Stage the changes with git add
  • Commit the changes with git commit
  • Push the changes to the remote with git push

Basic git rhythm with remote

gitlab

Working collaboratively

  • Git is an amazing tool for working collaboratively
  • However then we need to start worrying about changes we didn't make
  • If you're the sole committer to a repository, things are usually straightforwards
  • However in a team, chances are that people will make conflicting changes at times
  • Let's take a pragmatic look at two common issues

Troubleshooting 1: the push is rejected

  • If you try to git push your changes and see something like:

$ git push
To git@gitlab.example.com:myname/myproject.git
 ! [rejected]        master -> master (fetch first)
error: failed to push some refs to 'git@gitlab.example.com:myname/testrepository.git'
hint: Updates were rejected because the remote contains work that you do not have locally. 
            
  • Then you're trying to push before you've updated your local repo with remote changes.
  • Fix this by doing a git pull first

$ git pull
$ git push
          

Troubleshooting 2: we got a merge conflict

  • If we try to git pull (git fetch + git merge) and see something like:

Auto-merging file1.txt
CONFLICT (content): Merge conflict in file1.txt
Automatic merge failed; fix conflicts and then commit the result.
          
  • Then we have a merge conflict we need to resolve

Let's see what the problem is:


$ git status
Unmerged paths:
  (use "git add ..." to mark resolution)
        both modified:   file1.txt
          

$ git diff
  diff --cc file1.txt
  index 528038e,f324c51..0000000
  --- a/file1.txt
  +++ b/file1.txt
  @@@ -1,4 -1,4 +1,8 @@@
  ++<<<<<<< HEAD
   +hello
  ++=======
  + goodbye
  ++>>>>>>> 3911012567a3a5a0432bea090b82b58d74d5aa9d
          

  • Resolution: decide what the file should contain
  • Git writes markers in the file around conflicting sections
  • Edit the file (including deleting the markers)
  • Add, commit, push.

$ cat file1.txt
  <<<<<<< HEAD
  hello
  =======
  goodbye
  >>>>>>> 3911012567a3a5a0432bea090b82b58d74d5aa9d

$ nano file1.txt
$ git add file1.txt
$ git commit -m "Fixing conflict"
$ git push
          

Working collaboratively

  • With that in mind, let's move on to using a common repository
  • For the purposes of this course, we've set up an example repo in Gitlab for us all to clone
  • Then we can experiment with making and resolving conflicts

Cloning an existing repository

  • If we want to work on an existing repository, we can either edit the files in Gitlab, or clone the repo locally.
  • Editing in the UI tends to only be practical for very small, isolated changes.
  • To clone a repo, find the correct URL in Gitlab, displayed ssh/http on the project page.
  • SSH needs a key to have been set up, HTTPS needs a username and password.

$ git clone git@gitlab.example.com:myname/myproject.git
or
$ git clone https://gitlab.example.com/myname/myproject.git
          

Exercise: Clone a remote

  • Clone the example repo to your local directory
  • Make a new file, add and commit the changes locally
  • If you're working in a group, remember to do git pull to get any changes made by other people
  • Push the changes back to the remote
  • Did you get any conflicts?
  • Verify the new file is visible in Gitlab
  • 
    $ git clone git@gitlab.example.com:myname/myproject.git
    $ git add ...
    $ git commit ...
    $ git pull
    $ git push
                

Exercise: resolving issues

  • Make a file in the Github UI and commit it
  • Without doing a pull, edit the same file locally
  • Add and commit the file
  • What happens when you try to push it back to the remote?
  • Look at git diff and git status
  • Do a git pull to get the remote changes
  • This should cause a merge conflict
  • Edit the file to resolve the conflict
  • Now try adding, committing, and pushing again

Undoing mistakes

Undoing mistakes

  • We're human, and as such we make mistakes
  • If you use git regularly, you will screw it all up from time to time
  • Let's look at how to un-screw it up so it's not so terrifying next time

Git reset to undo local changes

  • git reset file1.txt un-stages a file (undoes git add)
  • git reset --soft HEAD~ undoes a git commit back to staging
  • git reset --hard HEAD~ undoes a git commit entirely
  • use the commit number to revert to a specific commit
  • use HEAD~2 to undo the last two commits


$ git log
  commit ecb76ad2f5b753c10f5f24094ea27f29f8c2d004
    Second commit
  commit 320a8f00a4a0de7c9b3f39851d3bc164d0769697
    First commit

$ git reset --hard HEAD~
or
$ git reset --hard 320a8f00a4a0de7c9b3f39851d3bc164d0769697

$ git log
  commit 320a8f00a4a0de7c9b3f39851d3bc164d0769697
    First commit
                

Exercise: Experiment with git reset

  • Create some new files
  • Add the changes and then use git reset to un-add them
  • Add and commit some changes then use git reset to undo
  • How do the --soft and --hard flags change the behaviour?
  • Can you reset the whole project back to a specific commit?


$ git reset file2.txt

$ git reset --soft HEAD~

$ git reset --hard HEAD~2

$ git reset --hard 320a8f00a4a0de7c9b3f39851d3bc164d0769697
                

Git revert

Undo a commit but retain history


$ git log
  commit 28972f7572e9465cc6a0994ce5fcc31ba2d1afbb
  "Adding deploy button"

$ git revert HEAD~
  Revert "Adding deploy button"
  This reverts commit 28972f7572e9465cc6a0994ce5fcc31ba2d1afbb.

$ git log
  commit 78413bb6fec87d10f24ec5c62592795befdb66f7
  Reverts "Adding deploy button"

  commit 28972f7572e9465cc6a0994ce5fcc31ba2d1afbb
  "Adding deploy button"
          

Exercise: using git revert

  • Make a change in a file
  • Add, commit, and push it
  • Now use git revert to roll back that change
  • Keep an eye on git log
  • What does the history look like when you've finished?
  • How is this different to git reset?

$ git revert HEAD~
  Revert "Adding deploy button"
  This reverts commit 28972f7572e9465cc6a0994ce5fcc31ba2d1afbb.
          

Branches

Branches

  • One of git's most powerful features
  • Allow simultaneous work in different environments
  • Develop features
  • Only merge in new code when you're ready

Git workflow: master branch only

git_master_branch

master is git's default main branch

Git workflow: master branch and a hotfix branch

git_master_branch

Git workflow: development and release branches

git_master_branch

Branch commands

  • git branch displays the checked-out branches
  • git branch branchname creates a new branch
  • git checkout branchname switches to the new branch
  • git checkout -b combines the above commands to create a new branch and switch to it

$ git branch
*  master

$ git branch featurebranch

$ git branch
*  master
   featurebranch

$ git checkout featurebranch

$ git branch
* featurebranch
  master
          

Exercise: working with branches

Let's make a new feature branch of the existing repository that you cloned.


$ git checkout -b featurebranch
Switched to a new branch 'featurebranch'

$ git branch
* featurebranch
  master
          
  • Create some more files and edit existing files
  • Add and commit your changes
  • The first time you push, you'll need to set up the remote for this branch like:
  • 
     $ git push --set-upstream origin featurebranch
              

Merging branches

  • You've developed a whole new feature in the feature branch, and now it's time to merge it back into the master branch
  • We can merge branches on the command line
  • or use merge request in Gitlab (this is called 'pull request' in Github)
  • Merge requests are more appropriate for merging to production branches as they encourage code review and thoughtful deploys

Exercise: merging branches in Gitlab

  • In Gitlab, create a new merge request to merge your feature branch into the master branch
  • Explore the request and check the changes you made
  • Accept the merge request
  • See the changes now appear in the main branch in Gitlab
  • Locally switch back to the master branch with git checkout and do a git pull to get up-to-date

$ git checkout master
  Switched to branch 'master'
  Your branch is behind 'origin/master' by 2 commits, and can be fast-forwarded.
    (use "git pull" to update your local branch)

$ git pull
  Updating 7a59581..4d4adb3
  Fast-forward
   file2.txt | 1 +
   1 file changed, 1 insertion(+)
    create mode 100644 file2.txt
          

Exercise: merging branches on the command line

  • On the command line, ensure you're on the feature branch
  • Make some changes in the feature branch, add, commit, and push them
  • Switch back to the master branch, merge in the feature branch, and push back to the remote

$ git checkout master
$ git merge master featurebranch
$ git push
            
  • Note that we merge into the branch we're standing in

Investigative tools

  • Sometimes we need to work out exactly what's going on in a repository
  • git log to see a history of commits
  • git reflog to see a history of both commits and amends
  • git blame filename to see who authored every line in a file
  • git log --graph to see the relationship of branches

$ git log --graph
* commit f91ed8cabfd053cde63f168087885bfdd0358419
|     Updated readme
|    
*   commit b8775a17e6fa9c9b6a9dd9f7b323a280a4db8faf
|\  Merge: 9544040 e99a3ac
| |     Merge branch 'master' of gitlab.example.com:myname/myproject
| |   
| * commit e99a3ac35a86c9bce4c8993ed8b6f1b7c8bf2a39
| |     Allow user login
| |   
* | commit 9544040efc0537d2f5a10975c0b779c3ccad0142
|/      Add remote users
|
          

Exercise: Investigative tools

  • Try out the following tools:
  • git log to see a history of commits
  • git reflog to see a history of both commits and amends
  • git log --graph to see the relationship of branches
  • git blame filename to see who authored every line in a file

Working with multiple stable branches

  • Alongside the main master branch, there can also be other stable branches
  • A common workflow is to develop features in a feature branch and then merge up through the environments

Working with multiple stable branches

git_stable_branch

Feature vs stable branches

  • Feature branches
    • can be considered private branches
    • a place to experiment
    • therefore often messy commit history
  • Stable branches
    • can be considered public branches
    • need to think a bit more carefully about commit history

  • Further reading: sandofsky.com/blog/git-workflow.html

Caring for your
commit history

Why do we need to worry about our commit history?

  • A well-maintained commit history will allow you to:
    • track when features were introduced
    • discover when bugs were introduced
    • provides some amount of documentation
  • A good commit history tells a story

Commit message guidelines

  • Use one line to describe what the commit does
  • Examples of good commit messages:
    • Allow users to sign in remotely
    • Fixed bug #123
    • Added postcode field to address table
  • Examples of bad commit messages:
    • Commit4
    • Typo
    • dfkasdjefskjh

Commit history choices: Rebasing

  • You've made a feature branch, and done some work on it
  • but while you've been working on it, your colleagues have made some changes to master
  • if you do a git merge all the master commits will go on top of your feature commits
  • So instead we do a git rebase to apply the commits underneath your feature commits

  • Let's demonstrate the difference with a diagram

merge_rebase

Commit history choices:
Merging with fast-forwarding

  • If no changes have occured on the master branch while feature development was underway
  • git will try to merge branches with fast-forward
  • All commits from the feature branch are visible in the master branch
  • This can be useful, or can be clutter in the version history
  • We can use --no-ff to submit a single commit for the merge

  • Let's visualise the difference

fastforward

Editing the commit history

  • In order to tell a good story with your commit history, you will probably need to edit your commit history at times
  • git commit --amend lets you amend your most recent commit
  • git rebase --interactive for more complex history rewrites
  • git push --force branchname to push your new historys
  • NB NEVER rewrite your commit history after you've 'gone public' with the branch (ie, pushed to a shared repository).

Interactive rebase

  • git rebase --interactive will drop you into a text editor where we can specify all the things we want to change
  • Commands:
    • pick = use commit
    • reword = use commit, but edit the commit message
    • edit = use commit, but stop for amending
    • squash = use commit, but meld into previous commit
    • fixup = like "squash", but discard this commit's log message
    • exec = run command (the rest of the line) using shell

Interactive rebase


$ git rebase -i HEAD~3

pick 6c169da Fixed bug #234
pick 78413bb Typo in user prefs 
pick 2bc4f9b Allow users to update their preferences

# Rebase 28972f7..2bc4f9b onto :w28972f7
#
# Commands:
#  p, pick = use commit
#  r, reword = use commit, but edit the commit message
#  e, edit = use commit, but stop for amending
#  s, squash = use commit, but meld into previous commit
#  f, fixup = like "squash", but discard this commit's log message
#  x, exec = run command (the rest of the line) using shell
#
# These lines can be re-ordered; they are executed from top to bottom.
# If you remove a line here THAT COMMIT WILL BE LOST.
# However, if you remove everything, the rebase will be aborted.
# Note that empty commits are commented out
          

Rewrite a commit message


$ git rebase -i HEAD~3

reword 6c169da Fixed bug #234
pick 78413bb Typo in user prefs 
pick 2bc4f9b Allow users to update their preferences
            

Fixed bug #234: do not allow remote access to db

# Please enter the commit message for your changes. Lines starting
# with '#' will be ignored, and an empty message aborts the commit.
            

$ git log
commit 6c169da

  Fixed bug #234: do not allow remote access to db
          

Squashing commits together


$ git rebase -i HEAD~3

pick 6c169da Fixed bug #234: do not allow remote access to db
squash 78413bb Typo in user prefs 
pick 2bc4f9b Allow users to update their preferences
            

Rebasing
# This is a combination of 2 commits.
# The first commit's message is:
Fixed bug #234: do not allow remote access to db
# This is the 2nd commit message:
Typo in user prefs
            

$ git log

commit 9107a08
    Fixed bug #234: do not allow remote access to db
    Typo in user prefs

commit 2bc4f9b
    Allow users to update their preferences
          

Final Exercise: Rewrite history

  • Try out git rebase --interactive
  • Rewrite a historic commit message
  • Squash some commits together
  • Edit a previous commit
  • Don't panic!

Summary

  • Version control
  • Git concepts
  • Using git locally
  • Using git with a remote
  • Undoing mistakes
  • Branches
  • Caring for your commit history