This lab is part of the foundations training in place at OCTO Technology Australia.
It might take you approximately 1h30/2h to complete, including the game!
Git is a widely used system for version control. This lab focuses on core git operations that will help you to become more productive from the command line.
git help <command>
This Lab will cover the following:
- Configuration Settings
- Creating a Project
- Git Branches
- Merge vs. Rebase - How to Integrate Changes
- Git Remotes
- Pull Requests
- Cherry Picking
- Undoing and Stashing Changes
- Rewriting Git History
- Safe Practises Using Git Force
Configuration for git is not just about .gitignore files which usefully exclude files from being added to git source control.
For git, configuration exists at three different levels:
- System (
/etc/gitconfig
) - Global (
~/.gitconfig
) - Project (
.git/config
)
These are accessed via the git config
command. For a quick description of these (level, location, creation of a config) follow this link.
Alternatively, if you run the command git help config
you will find additional documentation for each option in the help docs.
The order that the files are read is system, global, then local, with the last (local) value taking precedence.
If we want to update global configuration settings, we can do this in a few ways (using the options that you can find in the git help files).
For example:
git config --edit --global
will open the config file in your default editor.
The file you open will look something like the following example which is from git help config
under heading 'EXAMPLES'.
#
# This is the config file, and
# a '#' or ';' character indicates
# a comment
#
; core variables
[core]
; Don't trust file modes
filemode = false
; Our diff algorithm
[diff]
external = /usr/local/bin/diff-wrapper
renames = true
; Proxy settings
[core]
gitproxy=proxy-command for kernel.org
gitproxy=default-proxy ; for all the rest
; HTTP
[http]
sslVerify
[http "https://weak.example.com"]
sslVerify = false
cookieFile = /tmp/cookie.txt
Alternatively you can list your settings on the command line using:
git config --global --list
It's also possible to access and set fields individually. For example, to read an individual field:
git config --global --get user.name
Or to remove an individual field:
git config --global --unset user.name
To set your name:
git config --global --add user.name 'Your Name'
- Configure your name and email address in the global config settings
- Check it is updating global config in ~/.gitconfig
- Update any other settings, for example default editors etc.
- Add basic aliases to your git config, for example:
[user]
name = Erwan Alliaume
email = [email protected]
[alias]
ci = commit
st = status
br = branch
co = checkout
df = diff
cp = cherry-pick
gb = branch
git config --global --add alias.ci commit
Formatted reference:
https://git-scm.com/docs/git-config
Create a new directory and initialise a project.
mkdir toto
cd toto
git init
We will use this project to investigate how changes to files can be added to a repository. Run the following commands to create a basic readme file, add these files to git.
echo hello > README.md
git add .
git status
The git add .
command adds all files to the 'staging' area. Essentially the staging area stores information about what will go into the next commit.
Now the files have been added to git, but have not yet been committed to the repository. The git status
command shows the working tree status which lists the files that will be committed (in this case all have been added), as well as files may not yet be tracked by git.
If we add another change to our file echo world >> README.md
and run git status
again, we will see that some changes are ready to be committed, while others are not yet staged for commit. Add this change to staging.
Another way to interact with staging is to use git add -i
which can be used to interactively add files to the staging area.
Additionally, you can compare the files added to staging with git diff --cached
Go ahead and make a commit using the -m option to add a message. This is good practise to briefly describe the changes being made to the repo.
git commit -m 'update readme'
And to show the log for the last commit:
git log -1
It's also possible to list the log history using git log
or for a more succinct list git log --oneline
- Add another line to the readme file, stage the file, and then use the
git commit --amend
option to amend the previous commit, rather than adding a new one. Use git log to check that you only have one commit.
Branches are typically used with git workflow strategies. This means that commits are not made directly on master, but rather are made on branches before being merged to master.
Because we have been working on master, we can create a branch which is based on master (taking the current history and 'branching out' from there), by using the git checkout command:
git checkout -b my-new-branch
Swap between branches using checkout, in this case the -b
is used to create a new branch.
Additionally, some typical options for git branch
are:
-l
List local branches-r
List remote branches-a
List local and remote branches-d
Delete a branch
- Checkout a new branch, make a commit on the branch, check the log, and then switch back to master.
Bonus: Inspect content of the commit with git show <commit>
Checkout the master branch and run git log
, you can see the commit from the branch does not show up - the history of the two branches has diverged, or forked. If we have completed some work or a series of commits on the branch we can now merge them.
Now that we have a master and feature branch we can merge the feature branch back into master.
git merge my-new-branch
Check the logs again and you will see the commit has now been merged to master.
Both merge and rebase are concerned with integrating changes from one branch into another.
git merge <branch>
Merge is non-destructive (does not modify history), but involves creating extra commits.
- Creates a new commit for every merge which ties together the changes from both branches
- It's non-destructive, meaning that existing branches are not changed
- If you are working on a feature and need to incorporate changes from master into your branch, you will have to merge master into your branch resulting in additional commits as a result of the merges
git pull
git rebase <branch>
Rebase is destructive (modifies project history), but can result in a linear history.
- Moves all commits to the tip of the branch that it is being rebased onto.
- You will not create a new commit just for the purpose of merging
- Results in a linear history
- Golden Rule - never use on public branches!
- A good way to clean up local branches and incorporate changes from a parent branch eg
git pull --rebase
If someone else has a copy of your branch be very careful, you shouldn't consider using rebase! To read more follow the link https://www.atlassian.com/git/tutorials/merging-vs-rebasing.
Take the time to try out a merge and then a rebase using your projects branches and check the logs to inspect the difference.
The remote command allows you manage the repositories (remotes) which contain the branches that you track for a given project.
Git maintains a record of the location of a remote repository. You can check the current remote using git remote -v
. If you have been following along and started a project from scratch using git init
you may not have a remote yet, you could open an existing project and inspect the remote or you can add a remote to your current project now.
You can add the origin for a remote and fetch the existing branches from the remote. For example, you could add the repository https://github.com/wjt866/git-command-line-lab.git (or your own one - just set up a new repo of github), check that it has been added as a remote, and then fetch any changes.
git remote add origin <URL>
git remote -v
git fetch
While working on a branch it will be necessary to regularly synchronise the local branch with the remote branch it tracks, in order to integrate recent changes into your branch.
There are two options for this.
git pull
git pull --rebase
Git pull will perform a merge, whereas git pull --rebase will perform a rebase. See above for a description of the difference between merge and rebase, however a rebase if often a useful way to update local branches from a remote.
###Difference between pull and fetch
git pull
performs both a fetch and merge (or rebase if given as an option). A fetch is a useful way to update local branches which track remotes - it won't actually change your working copy. So for example, if you want to see/inspect the latest work on a remote branch you will need to fetch in order to have the most up to date work. So fetch will retrieve information from the repository, with the option to merge it, while pull will automatically try and merge the changes.
Of course local changes that have been committed can also be pushed back up to the remote branch easily:
git push
When work on a branch is completed it's time to do a pull request. The exact process can be different depending on where the source code is hosted. Typically a PR is made using the GUI, and allows others to review code before it is merged.
- Make a pull request for a project of your choice in gitlab, github, bitbucket etc. It could be for your own project or for this one (For example change this random word: 'RANDOM').
The cherry pick command allows you to take one or more commits from a branch and then to add those same commits to another branch. It is useful when you need to integrate only some recent changes from a branch and need to exclude other older commits.
The simplest way to demonstrate cherry pick is to add a commit to a second branch, and then cherry-pick just the one commit onto another branch.
echo again >> README.md
git add .
git commit -m 'adding another line for a cherry pick'
git checkout master
git cherry-pick my-new-branch
Run git log
to see the commit has been added to master.
It's often common to cherry-pick more than one commit.
In this case, we will need some more information about which commits to pick. Retrieve the commit ID by using git log --oneline
when on the branch you want to cherry pick from.
Then swap back to master and pick the commits you need, for example:
git cherry-pick ae8ee74 2da0c39
When working there will often be times when you wish to change branches but are not ready to commit the current changes yet. In this case you can 'stash' changes using git stash
and retrieve them when needed using git stash pop
Resetting a branch means that commits can be thrown away from history.
If you haven't already set up a remote for the project, do it now and make sure you push your work.
Make a commit.
echo something >> README.md
git add .
git commit -m 'commit for testing a reset'
Check that the commit has been added with git log
. Now reset your branch back to master using git reset
.
To hard reset and get the code from master:
git reset --hard origin/master
To perform a soft reset:
git reset --soft origin/master
Try both a hard and soft reset. What's the difference? Check the log before and the status after resetting to observe the difference.
Reverting changes means that a new commit is made which reverts changes that previous commits have introduced.
Make a commit, then, after checking the hash of the commit via a log:
git revert <hash>
It's easy to amend a previous commit, rather than add an entirely new one (for example, to fix a mistake).
To update the last commit:
echo "first line" > README.md
git add README.md
git commit -m 'a commit message'
echo "second line" >> README.md
git add README.md
git commit --amend
Local history can be re-written to clean up a branch before pushing it to a remote repository.
git rebase --interactive <hashLastPushedCommit>
Make a bunch of commits (but don't push them), then rebase from the start of these commits Then rebase interactively to tidy them, using squash, reword, drop, etc
If you accidentally pushed some local changes before rebasing them, you would need to do a --force
push because you would be changing the history of the remote and this is not normally allowed.
But there is a problem with git push --force
.
It will cause the remote repository to lose commits unconditionally and as is not always 'safe'. For example, if a team member has recently committed some work to a branch without you knowing you could overwrite their work. This could easily happen during a routine rebase from master which then erases a team members work.
Enter git push --force-with-lease
Force with lease helps to ensure that the repository is in the state we expect, allowing us to force push without the ability to overwrite other peoples work.
For a good blog about this see: https://developer.atlassian.com/blog/2015/04/force-with-lease/
Have a look at the config file at the bottom of the page, and investigate some of the aliases (and add them to your own config), you might want to start with the following:
lg
undo
who
last
wip
unwip
pr
- How to customise git configuration
- How to create a project, make commits and understand staging
- How to work with branches
- The difference between merge and rebase
- What remotes are and how to add one
- How to cherry-pick commits
- Handy commands like stash, revert, reset
- How to rewrite local history with interactive rebases
- How to be safer when force pushing by using --force-with-lease
- Add some additional plugins and/or aliases to your git config, you might want to take some inspiration from below:
[user]
name = Erwan Alliaume
email = [email protected]
[color]
diff = auto
status = auto
branch = auto
interactive = auto
ui = auto
[help]
autocorrect = 10
[rerere]
enabled = true
[diff]
renamelimit = 0
[alias]
ci = commit
st = status
br = branch
co = checkout
df = diff
cp = cherry-pick
gb = branch
gba= !git branch -a
who = shortlog -s -n -e --
count = rev-list HEAD --count
ds = diff --stat
staged = diff --cached
unstage = reset HEAD
please = push --force-with-lease
stsh = stash --keep-index
staash = stash --include-untracked
staaash = stash --all
info = !git remote show origin
ignored = "!git ls-files --others --exclude-standard"
lg = log --graph --pretty=format:'%Cred%h%Creset %C(yellow)[%an]%Creset %s %Cgreen(%cr)%Creset' --abbrev-commit --date=relative
ealliaume = log --graph --pretty=format:'%Cred%h%Creset %C(yellow)[%an]%Creset %s %Cgreen(%cr)%Creset' --abbrev-commit --date=relative --committer ealliaume
commiter = log --graph --pretty=format:'%Cred%h%Creset %C(yellow)[%an]%Creset %s %Cgreen(%cr)%Creset' --abbrev-commit --date=relative --committer
log1 = log --pretty=oneline --abbrev-commit --decorate
undo = !git reset --soft HEAD^ # Le flag --soft va conserver les modifications dans le répertoire de travail.
unpush = git push -f origin HEAD^:master
last = log -1 HEAD
first = rev-list --max-parents=0 HEAD
rz = reset --hard HEAD
lost = !"git fsck | awk '/dangling commit/ {print $3}' | git show --format='SHA1: %C(yellow)%h%Creset %f' --stdin | awk '/SHA1/ {sub(\"SHA1: \", \"\"); print}'"
heads = !"git log origin/master.. --format='%Cred%h%Creset;%C(yellow)%an%Creset;%H;%Cblue%f%Creset' | git name-rev --stdin --always --name-only | column -t -s';'"
wip = !"git add -A; git ls-files --deleted -z | xargs -0 -I {} git rm {}; git commit -m \"wip\""
unwip = !"git log -n 1 | grep -q -c wip && git reset HEAD~1"
patch = !"git format-patch origin/master.. -o /git/imaginatio/_patchs"
rb = !"git wip;git rebase -i --autosquash origin/master;git unwip"
pr = !"git fetch;git wip;git rebase --stat origin/master;git unwip;git heads"
prd = !"git fetch;git wip;git rebase --stat origin/develop;git unwip"
[core]
autocrlf = false
excludesfile = /Users/erwan/.gitignore
- Now put your new skills to the test with our git game
There are many many resources to help you continue learning about git. Some useful ones are:
git help <command>
- Atlassian git tutorials
- Git cheatsheet
- Online version of the manual https://git-scm.com/doc
Thanks : )