Git is a distributed version control system compatible with most operating system. Faster than most competing solutions and the most used of all.
You make changes to files in your working folder, then add to staging index before finally writting changes to the repository.
Debian and derivatives
$ apt install -y git bash-completion
Configurations can be set to be default (apply to everyone) using the machine, a single user, or apply only to a given project. This depends on where the gitconfig file is stored.
folder: /etc/gitconfig # system wide configuration
folder: ~/.gitconfig # User specific config
folder: project/.git/config # Project specific config
Use the git config
command to write to the .gitconfig file. When followed by --system
configuration data is system wide. Paramater --global
references user specific configurations. Global specifies default git configurations for the logged in user. No parameter is required for Project specific config.
Sample commands for git cofig
$ git config --global user.name "Fred Smith"
$ git config --global user.email "fred@moo.com"
$ git config --global user.mobile "+447593744831"
## text output on command line is in colour
$ git config --global color.ui true
## set default text editor
$ git config --global core.editor "vi"
## Diff / merge tool to use
$ git config --global diff.tool meld
$ git config --global merge.tool meld
$ git config --global --add difftool.prompt false
When git reports conflicts, launch a mergetool to resolve conflicts
$ git mergetool
List contents of gitconfig
$ git config --list
user.name=Fred Smith
user.email=fred@moo.com
color.ui=true
core.editor=vi
$ git config user.name
Fred Smith
# view the contents of the configuration file
$ cat ~/.gitconfig
Git has a 3 step process of working with files
Initialising a project is a way of telling GIT which project to track. Projects are normally contained in separate folders. The way to initialise a GIT project is to CD into the root folder of a project and run the git init
command, e.g.
$ cd ~/dev/www/lcm
$ git init
If you want other users to be able to clone your repository, you should create a bare repository. These should end in extension .git
$ git init --bare project-name.git
Git allows you to create a repository on a server somewhere just so that multiple developers can pull or push to the repo. A bare repository contains just the .git
folder. You could not ssh into the server and try to checkout files or commit.
$ mkdir test.git # // create a folder for a bare repo
$ cd test.git
$ git init --bare # //initialise a bare repository
$ cd ..
$ git clone test.git mylocal-repo # // clone your bare repo
Assume you have already begun some project work on your local machine and later on opened a Github account. Now you want to upload your work including all current history to your newly created remote repository. Use the next command to specify a remote repository for your local git repository:
## Check if you are your repo is already linked with a remote repo
$ git remote -v
## link local repo to project1.git hosted at example.com
$ git remote add origin https://example.com/repos/project1.git
## If you have forked the repo, you want to add the upstream link
$ git remote add upstream git@github.com:user/repo
## push and pull commands have to be explicit
$ git pull upstream master
$ git push upstream master
$ git push origin master
Now remote commands such as push and pull recognise the remote repo.
Consider the staging step as a way of telling Git your intention to place changes into the repository. The Git philosophy is to track set of changes, e.g. if changing System authentication affected 5 files in total, all the 5 files could be staged together so that the commit could be described as, "Changing authentication to use SHA2 encryption in response to vulnerabilities discovered in SHA1 and Blowfish encryption"
The git add
command is used to add files and folders of a change set to the staging index. The changes are described in commit messages.
$ git add . # add current folder and sub folders to staging index.
$ git add file1.txt file2.txt add specific files to staging index
$ git add folder/ # add all files in folder
$ git add folder/* # add all files in folder, same as above
Use Git HEADERS when you want to undo changes
$ git reset HEAD file2.txt # // remove file2.txt from Stage
Commit actually writes changes into the repository that becomes permanent. Git generates a SHA1 checksum value of the change set which is then written in the HEADER. Git headers are central to what goes on in repositories they are useful for checking out and resetting changes.
$ git commit -m "Brief text to describe the change set/commit" # // commit changes to repository
Once commited, changes are permanent. It is possible to use the git ammend command to either edit commit message of the last commit or make adjustments to that commit. Once a commit has become a parent, it can not be amended. The only way to amend a parent commit is to create changes that reverse that parent commit. This way data integrity is maintained in Git.
It is possible to have commit messages that span multiple lines.
Using git from the command line with bash you can do the following:
$ git commit -m "this is
a line
with new lines
maybe"
# // Press Enter for a new line.
# // commit all staged files
$ git commit -m "Description of commit"
# // Stage all files in project and commit at same time
$ git commit -am "Description of commit"
Best practices
Use commit logs to view a snapshop of changes that have been made in the repository.
# -
$ git log # shows author, date and commit description
$ git log --oneline # shows all commits in one line
$ git log --oneline --decorate --all --graph # shows all commits on one line and graph
$ git log -n 3 # show only up to 3 commit logs
$ git log --since=2012-12-30 # show commits since date specified
$ git log --until=2012-12-30 # show commits until date specified
$ git log --author="Fred" # show commits by Fred
$ git log --grep="javascript" # show commits with Javascript in commit messages.
# I'm trying to find a line of code in a specific file,
# and I can't find which branch or commit it was in.
# How I can search for this line of code?
$ git log -S 'function getAllManagers()' --all -- myfile.php
Each change log committed to the repository has a SHA1 checksum value that Git uses to identify the change set.
commit Head points to the position in the repositories where the next Commit will be written, same as a serial tape will record from where the write heads are currently positioned.
There maybe times when you dont want to generate an entirely new change set after making adjustments to files that relate to a change set that you have just committed. If that that change set is the last that has been committed, you can use the git ammend
command to ammend the last commit. The command also works to simply make edits to commit descriptions.
$ git add file2.txt # first stage the files that have been amended.
$ git commit --amend -m "New or same commit description" # amend commit
If you forgot to add a file to a commit, or made a change so small you dont want to create an extra commit message, use the next command
$ git commit -a --amend -C HEAD
Tags are labels you can put at any commit point in your repository. Head is assumed if no commit Id is specified during the creation of a tag. A good use for tags is marking version numbers in your repository.
Tag names are listed next to commit IDs when you use git log.
## Mark current commit (head) as mytag
$ git tag mytag
## List tags
$ git tag --list
## delete tag
$ git tag -d mytag
## Create an annotated tag. Give the tag a commit message "Version 5.0"
$ git tag -a v5.0 -m "Version 5.0"
## Display information about annotated tag
$ git show v5.0
Tags that have release notes attached are called "Git releases"
Deleting a tag or a release does not delete the associated commit.
A git file can either be in Staging area or in Repository. When you bring a file back from staging or go back to a previous version in the repository, it is called checking out a file.
You can restore a file, folder or entire change set using the SHA value. It's important to specify if branch is the current one or another.
$ git checkout filename.xtn # // restore file from staging
$ git checkout -- filename.xtn # // checkout file from staging in current (--) branch.
$ git checkout -- folderName # // checkout a folder
Checkout file from a change set
Use the git log
command to find the SHA-VALUE and copy the whole or part of the value.
$ git checkout SHA-VALUE -- filename.xtn # //checkout from change set in current folder.
Files from change set are put into the Staging Index not in the working folder. You can commit the change if you changed your mind and decided to undo the checkout
$ git reset HEAD filename.xtn # // remove the file from staging index into working folder
$ git checkout -- filename.xtn # // remove file from working folder
This is like using System restore. Reverts everything to what it was before the change set. The command requires the SHA-VALUE or part of the SHA-VALUE.
$ git revert SHA-VALUE
I have had problems reverting a file while it was open in another program. Make sure all files are closed before attempting revert.
Git reset is like rewinding the a tape recorder to previous commit. Its like pressing backspace to a required commit
Soft reset Moves HEAD pointer to the specified commit. Doesn't change the staging index or the working directory. Is the safest of all options. Only files in the repository will change. Those in working folder and staging index will stay at their latest versions.
Mixed reset In between soft and hard and is the default. Changes the staging index and the repository. Does not change working directory.
Hard reset Removes all changes that happened after the specified commit. Hard reset will permanently lose all uncommitted changes in the working directory, and the staging index. Use hard reset with caution because it is highly destructive.
# // rewind changes to the specified commit
$ git reset --heard 103ddc
Git can track changes of files deleted from the repository. Deleted files are first put in staging and must be committed.
$ git rm filename.txt # // stage deletion
$ git commit -m "Deleted filename.txt - use file.txt instead" #// commit changes
It is posible to delete files using your everyday operating system commands but you will still need to run the above commands to actually delete the files and commit the changes. Operating system commands have the advantage of putting things in the recycle bin if you prefer working that way. However, deletions are not added to the staging index like git rm
does.
You can use OS tools to rename, or use GIT commands.
# // rename file and add changes to staging index
$ git mv old-file.txt new-file.txt
# // commit changes
$ git commit -m "renamed old-file.txt to new-file.txt"
If you use OS tools to rename files, GIT perceives the action in terms of a file being deleted and a new 'untracked' file being created. Rename action is detected when you add the new file to the staging index, then remove the old (deleted) file from the repository.
$ mv -v file.txt new-file.txt
# // git reports file.txt as deleted and new-file.txt as untracked file in working directory
$ git add new-file.txt // add new-file.txt to staging index
$ git rm file.txt // delete old file from repository
# // now that both files are in the staging index, git is able to note that the files were actually renamed.
$ git status
# // commit changes
$ git commit -m "file.txt renamed to new-file.txt" </code>
Ignored files are not tracked by git and they will not be committed to repository.
You may wantto set git to ignore
The list of files to ignore is stored is file called .gitignore
stored in the root of the project folder (/project/.gitignore
Files to be ignored are listed one per each line, or you can use regex to specify file patterns. Acceptable regex characters are ? * [abcde] [0-9] !
Generate a .gitignore entries for you project at
https://www.gitignore.io/
Specify that git ignores all txt files except readme.txt
# ignore all .txt files
*.txt
# don't ignore readme.txt
!readme.txt
Use the trailing / to ignore all files in a folder
# ignore all files in the video folder
assets/videos/
After writing instructions into the .gitignore file, you will need to add and commit (optional) .gitignore to your repository.
$ git add .gitignore
$ git commit -m "git ignore"
If you want to ignore certain files accross all the projects on your machine, you can do so by creating a global .gitignore file using the git config command.
$ git config --global core.excludesfile ~/.gitignore
The git ignore file can be located anywhere and can be named anything.
# sample global git ignore
~*
*.swp
*.tmp
Git will not stop ignoring files that it has already started tracking before ignore rules were applied. To stop tracking a file, you need to remove the file from the staging index, also called cache.
$ git rm --cached file.txt
$ git commit -m "Removed file.txt from index"
This action will not delete file from the repository. The file will still be available for download but wont be tracked.
Delete the .git
folder in the root folder of your project to stop git from tracking changes in that project.
Tree-is is a pointer (reference to a commit). One way is to use the SHA-1 key, the who key or part of the key. At least four characters is required when referencing the short key.
Another way is use the HEAD pointer
Also use the branch reference.
Can also use Tag reference.
Reference to ancestry using the caret character or the tilde character
The parent of head can be refrenced using the caret as HEAD^, acf875043^, master^
Use tilde notation to reference the number of generations you want to go up.
HEAD~1, HEAD~
HEAD~1 means head going back one. Same as HEAD^, meaning parent of head.
To reference the parent commit of a parent commit (grandparent) you could use
HEAD^^, acf875043^^, master^^
Using the ^^ character to reference the number of steps up the tree you want to go can be cumbersome. Tilde makes it easy by specifying the number of steps
HEAD~3, HEAD~4
$ git ls-tree HEAD
// list git tree-ish.
You can parse folders that you would like to look into
$ git ls-tree master assets/videos/
$ git ls-tree master^^ assets/videos/
You may notice that are files in one tree added later that did not exist in the other tree.
When files are listed, they as referred to as either a blob or a tree. A tree is a directory and a blob is a file.
See what was changed in a commit
$ git show 4ea4f91
# // navigate the tree. The next two lines do the same thing
$ git show --format=oneline HEAD~2
$ git show --format=oneline HEAD^^
See if any changes were made to files in a repository.
$ git status
See what text changed.
# // difference between working directory and repository
# // does not show you new files. Use 'git status' for that
$ git diff
# // difference between staging directory and working directory
$ git diff --staged
# //See what changed since the last commit.
$ git diff HEAD
# // Use a GUI tool to show differences. Vimdiff is a good ncurses diff tool.
$ git difftool HEAD
# // Configure a custom diff tool to use
$ git config --global diff.tool meld
$ git difftool HEAD
Delete files from repository
# // Do git commit without gid add after removal.
$ git rm filename.xtn
# // Rename files. Remember to commit changes
$ git mv oldfile.xtn newfile.xtn
## // Re-download files from master
$ <code>git pull </code>
To get the most out of Git, you would have to use branches a lot.
They are easy to setup, easy to maintain and do not cause a lot of headaches.
Branches allow you to allow new ideas. Instead of making numerous commit to your main branch trying new ideas, you can create a new branche where you try your ideas. Should they not work out, you can just delete the branch or merge the branch with the the main brach when your ideas work out.
You could also have a branch where developers can collaborate on a feature outside the main branch. Once completed, the new feature could then be merged to the main.
branches are created in the same working directory. Git uses fast context switching where it recognises which files belong to a particular branch.
branches can have sub branches that can merge with parent branches.
List branches on the local machine
# // list all branches
# // The current branch is marked with a * (* master) if you have only one branch.
$ git branch
# // Directory listing of all the branches
$ ls -la .git/refs/HEADS/
# // create a new branch
# // New branches that have not changes point to the same commit as their master.
$ git branch name_of_branch
The working directory must be clean to be able to switch branches. Changes must either be committed, discarded or stashed before switching.
The current branch, or the branch in our working directory is called a checked out branch. you will notice it by the (*) next to its name.
git checkout
command to switch between branches. $ git checkout name_of_branch
If this is the first time you are switching branches, the new branch is an exact copy of the master. However, any changes made will remain in this branch. These changes wont be visible when you switch to the master branch unless you merge the branches first.
New branches can also be created with the git checkout -b
command. Switch -b means create and switch branches at the same time.
We can create a sub branch of any checked out branch like so
$ git checkout -b fancy_login_mockup
To see where HEAD is pointing in all branches
$ git log --graph --oneline --decorate --all
$ git checkout -- <filename>
-- refers to file. Only changes made to the file passed will be discarded in the above example.
Compare one branch with another
$ git diff master..my_branch
Will compare the most recent commits in master to the most recent commits in my_branch and list the difference.
You can compare a tree-ish of a branch
# // compares current HEAD in master to previous HEAD in my_branch.
$ git diff master..my_branch^
See which branches are completely included in the current branch
$ git branch --merged
Any branches that are listed by this command means that those branches can safely be deleted if you choose to because they contain all the commits that are also in the current branch.
# // rename old_branch to new_branch
$ <code> git branch --move old_branch new_branch
$ git branch --delete branch_name
You cannot delete a currently checked out branch. You have to be working on a different branch to delete a branch.
You will get a warning message if you try to delete a branch that has not been merged to its parent branch.
Use a colon before branch name to delete the branch both remotely and locally.
# // branch_name will be deleted from remote
$ git push origin :branch_name
Git completion script must be installed for this to work.
Run this script while in the git folder.
$ __git_ps1
# // result
(branch_name)pc_user@hostname /git/folder $
When you navigate out of a git tracked folder, the prompt changes to normal system prompt.
To merge branches, first you must make sure that the receiving branch is the branch that is currently checked out. E.g to merge branch 'products' with the master branch, the master branch must be the checked out branch.
# // first checkout the branch you want to merge into (target branch)
$ git checkout master
# // Now merge the branch with latest changes
$ git merge products # // thats it.
# // Confirm that the changes were merged.
$ git log
Merges can become really complicated. It is good practice to run merges with a clean working directory.
There are times when you might be working on a Branch A and then have to stop midway to work on another Branch B.
If changes in Branch A are still messy and not ready to be committed, you could stash them.
Stashing takes the dirty state of your working directory – that is, your modified tracked files and staged changes – and saves it on a stack of unfinished changes that you can reapply at any time.
Use the following commands to stash changes
$ git stash
# // alternatively
$ git stash save
# // give a name to your stash
$ git stash save "stash_name"
# // include untracked files in stash
# // files remain untracked when they are applied
$ git stash --include-untracked
# // include all files (even ignored ones)
$ git stash --all
# // pick and choose changes to stashes
$ git stash -p
# // show contents of a stash
$ git stash show stash@{0}
# // create new branch from stash
$ git stash branch < optional stash name >
# // get a single file from stash
$ git checkout -- < filename >
# // apply changes from last stash and delete stash
$ git stash pop
# // delete last stash
$ git stash drop
# // delete specific stash
$ git stash drop stash@{0}
# // delete all stashes
$ git stash clear
It is now safe to switch branches and do work elsewhere.
To see a list of what you have stashed
$ git stash list
# //
stash@{0}: WIP on master: 049d078 added the index file
stash@{1}: WIP on master: c264051 Revert "added file_size"
stash@{2}: WIP on master: 21d80a5 added number to log
In the above example, there are 3 stashes stash@1 to 3.
To get back to the branch that you have just stashed
# // bring back last stashed changes
$ git stash apply
# // bring back a specific stash
$ git stash apply stash@{1}
If git fails to merge because conflicts were detected, you can abort the merge
$ git merge --abort
// abort git merge
After an abort, your git repo is clean for you to merge with other branches that will not cause conflicts.
If you decide you want to resolve the conflicts, list files that are causing conflicts with the git status
command.
$ git status
// show files that are causing conflicts.
Open each of the files in a text editor that have been adentified as causing conflicts. Search for the string '====' to quickly jump to conflict sections and make appropriate corrections. When you are done, the file should read clean without conflict markers, etc.
When conflicts are resolved, add your file to the staging index
$ git add .
Commit your changes ...
$ git commit
Git commit command will open up a commit message in a text editor for you to edit. Edit the commit message to whatever you want. After saving the message, your commit will be complete and you have successfully dealt with a conflict.
Git reuse recorded resolutions are useful where you happen to see the same conflicts again and again. You must enable the feature before you can use it.
$ git config --local rerere.enabled true
// enable recording steps
The first time you perform a merge and resolves a conflict, git will remember how you resolved that conflict. If git sees the same conflict again when you try merging another branch that contains the same conflict, git will offer to resolve the conflict using the previously recorded steps. If you agree, all you will have to do then is to commit your changes.
To rebase a branch called 'A' on Master, you must checkout the branch first and then do a rebase.
# // first - checkout branch A
$ git checkout A
# // run rebase command
$ git rebase master
# // result
# // 1. Git rewinds A to to the point the branch diverged with master
# // 2. All changes that took place in master since A was taken are applied first
# // 3. Any conflicts must be resolved in the normal way
# // 4. Changes to A are applied last. With a rebase, you will be able to see your changes always ahead of master
Git will first rewind all changes on A and then applies them one at a time with changes from master picking up conflicts as it goes. This means ''changes to 'A' will always stay ahead of master''. Conflicts must be resolved as normal; then run rebase continue
to proceed
$ git rebase --continue
// run each time you resolve conflict while doing rebase.
When you merge your branch back into master, git rebase gives you a fast forward commit. Whereas git would have shown where the branch was taken, git will not show that history but will just move HEAD in a linear fashion.
To merge back into master without a fast forward merge
$ git checkout master
$ git merge --no --ff branchA
// git merge no fast forward.
Git will now always show that there was a branch in commit history.
Allows you to rewrite history
$ git rebase -i HEAD~4
Git opens up the last 4 commits in a text editor where you can, delete entire commits and make it look like the commit never happened, mark some commits for rewording, squashing, etc.
When you save and exit this file, git will execute the commands. If you have some commits marked for squashing, git will open a text editor for you to rewrite your commit messages.
Git rebase interactive works well if you have not pushed your changes to origin master. In this situation, git will not make it easy for you to push. You could force a push (not recommended if someone has already pulled and is working on your changes).
$ git push -f
// not recommended
If someone does a force push and you want to re-sync your changes with origin master, then you must reset to origin master.
$ igt reset --hard origin/master
Imagine you have got a lot of commits done. In one of these commits somewhere, a file was deleted but you don't know when.
Git bisect will help.
Start git bisect mode
$ git bisect start # // turn git bisect mode on
# // pick a commit the last commit when you know things were working ok
$ git bisect good 11cd
# // specify a commit when things are broke. We know this is currently broke
$ git bisect bad # // omitting commit ref means you are in HEAD
# // (optional) show a visual representation of your commits. Git opens whatever git gui tool you are using.
$ git bisect visualize
To actually find the file that was deleted, you need a script file, which will return an exit status of TRUE or FALSE when git bisect runs it. The script must be in a folder that is outside your version control.
$ vi test.sh
# // contents of test.sh
#!/bin/bash
[ -e students.html ]
Run git bisect with your script.
$ git bisect run ~/test.sh
Git will test commits within the range specified looking for the file. If found, it will show you the commit hash, commit message, and the file name.
To get out of git bisect mode
$ git bisect reset
# // https://git-scm.com/docs/git-remote
# // origin = alias to remote git url
# // master = the branch you would like to push
# // -u = prompt for login credentials
$ git push -u --tags origin master
Create an alias git s
as shortcut for git status
# // Alias will be placed in global git config file
# // create the alias
$ git config --global alias.s status
# // Inspect global git config to confirm alias was created
$ cat ~/.gitconfig
# // result
# [alias]
# s = status
## use the alias
$ git s
Aliases that are more than a single word must be created as an OS command prompt alias. In Linux, aliases could be added to .bashrc or .profile
alias gco='git config --global'
Windows Registry Editor Version 5.00
[HKEY_CURRENT_USER\Software\Microsoft\Command Processor]
"AutoRun"="%USERPROFILE%\\alias.cmd"
@echo off
:: Temporary system path at cmd startup
set PATH=%PATH%;"C:\Program Files\Sublime Text 2\"
:: Add to path by command
DOSKEY add_python26=set PATH=%PATH%;"C:\Python26\"
DOSKEY add_python33=set PATH=%PATH%;"C:\Python33\"
:: Commands
:: use $* for commands that take an argument
DOSKEY ls=dir /B
DOSKEY sublime=sublime_text $*
:: Common directories
DOSKEY dropbox=cd "%USERPROFILE%\Dropbox\$*"
DOSKEY research=cd %USERPROFILE%\Dropbox\Research\
Sources: https://sethrobertson.github.io/GitBestPractices/