ruk·si

📁 Git
Basics

Updated at 2019-11-22 00:56

Git is a distributed version control and source code management system.

Installing Git.

# Prefer installing from the source (http://git-scm.com)

# Linux alternative
yum install git-core
apt-get install git # alternative

# Mac alternative
# Install Git with homebrew (http://brew.sh/)
brew install git

# Windows alternative
# Install Git for Windows. (http://msysgit.github.io/)

Help is the most important command.

git help
git help <COMMAND>

Start by configuring your Git. Settings can either be global or repository specific.

# Global configurations.
git config --global user.name "Example"
git config --global user.email "name@example.com" # same as in GitHub
git config --global color.ui true

# Repository specific configurations.
git config user.name "Example"
git config user.email "name@example.com"

Git saves repository changes to a directory named .git. This directory is in the project root, which is the directory where you initialized Git e.g. with git init. All previous versions of the source code and Git configurations are in that folder. Local changes can be synced with remote repositories like GitHub by using git push.

Initialize a new empty repository with git init.

# Project to an empty directory.
mkdir <PROJECT_ROOT_DIRECTORY>
cd <PROJECT_ROOT_DIRECTORY>
git init

# Project from existing files.
cd <PROJECT_ROOT_DIRECTORY>
git init
git add .
git commit -m "Initial commit"

Initialize a local repository from an existing repository with git clone. Clone automatically creates default remote repository reference with the name 'origin', more commonly called default upstream repository.

# Creating local copy from existing repository.
git clone <URL_PATH>
git clone git@github.com:tommi-laine/project-name.git

# Creating local copy from existing repository to specific directory.
git clone <URL_PATH> <DIRECTORY_NAME>

# Create local copy with only partial commit history.
git clone <URL_PATH> --depth <NUMBER_OF_PREVIOUS_COMMITS>

# List all remote repositories.
git remote -v

In Git, a file can be in various different states and most of them are not exclusive. Simply put:

  • In Working Directory: All files inside the project directory.
  • Untracked: File has not ever been in a git commit.
  • Tracked: File has been included in at least one commit .
  • Ignored: Git has been specified to exclude the file from being staged.
  • Unstaged: File has been changed since the previous commit, but is not planned to be included in the next commit.
  • Staged: File has been changed since the previous commit and is planned to be included in the next commit.
  • Committed: File is tracked and is the same in your working directory as in currently selected repository and branch.

Check what has changed since your last commit and what you have currently staged with git status. Status also tells you on what branch are you currently on. Commits will only save staged changes.

git status

# On branch master
# Changes to be committed:
#   (use "git reset HEAD <file>..." to unstage)
#
#       modified:   nomicon/ruksinomicon.sublime-workspace
#
# Changes not staged for commit:
#   (use "git add <file>..." to update what will be committed)
#   (use "git checkout -- <file>..." to discard changes in working directory)
#
#       modified:   nomicon/software_development/git.html
#

Before you can commit changes, you must stage them. Stage is simply a list of changes what your next commit will contain. Stage changes to be committed with git add.

git add <PATH_TO_FILE>
git add .       # Stage modified and new files, without staging deleted files.
git add -u      # Stage modified and deleted files, without staging new files.
git add -A      # Stage all (git add .; git add -u).
git add *.txt   # Stage all text files in current directory.
git add '*.txt' # Stage all text files inside current directory recursively.

Commit is a snapshot of a set of changes. You can commit changes with git commit. Each commit is identified with automatically generated SHA hash e.g. 02b365d4af3ef6f74b0b1f18c41507c82b3ee571.

# Use capitalized present imperative for commit messages.
# Single quotes if quotes are needed.
# No sentence periods.
# This is what git generates by default e.g. in merges
git commit -m "Fix login bug B-2134"
git commit -m "Update copyright year"
git commit -m "Ignore log directory"
git commit -m "Add formatting library initial version"

# The default git merge commit message looks like this:
# Merge branch 'myfeature'

# Stage and commit.
# But -a does not stage any new files, only already tracked ones.
git commit -a -m "Modify readme"

Your commit message header should be max 50 character. If you need to include more text, use git commit without the -m. This will open a text file where you can write your commit message. First write a 50 character subject line, then after a blank line, write as much as you please but wrap at 70 characters. Focus on "why/what" and not "how".

Fix login bug 123

If this bugfix causes a problem in the future, it can also be fixed by
using a workaround inside the Collector-library.
# Please enter the commit message for your changes. Lines starting
# with '#' will be ignored, and empty message aborts the commit.
# On branch master
# Changes to be committed:
# ...

Check past changes with git log.

# Show all commits.
git log

# Last 5 commits.
git log -5

# Commits in time spans.
#git log --until=1.minute.ago
git log --since=1.day.ago
#git log --since=1.month.ago --until=2.weeks.ago
#git log --since=2000-01-01 --until=2012-12-21

# Search commits by their message.
git log --grep="<REGEX>" -i

# Search commit contents.
git log -S<SEARCH_TERM>
git log -Saws_secret_key    # Did this repo ever have an aws_secret_key?
git log -Saws_secret_key -p # To see diffs.

# Show changes for a specific file.
git log -p <FILE>

You can manage remotes with git remote. It's a good practice to name them by their usage e.g. production and staging, not the service your are using for them like heroku.

git remote add <REMOTE_NAME> <URL_TO_REPOSITORY>
git remote add origin git@github.com:my-username/my-project.git

git remote rm <REMOTE_NAME>
git remote rm origin

# List all remote repositories.
git remote -v

Send local data to remote repository with git push.

git push <REMOTE_NAME> <LOCAL_BRANCH_NAME>
git push origin master

git push <REMOTE_NAME> <LOCAL_BRANCH_NAME>:<REMOTE_BRANCH_NAME>
git push production fix-price-format:master

# With no parameters, will use default upstream.
git push

Get and merge remote data with your local repository with git pull. Does git fetch followed by git merge to the current branch.

git pull <REMOTE_NAME> <REMOTE_BRANCH_NAME>
git pull origin master

Git manages pointers named HEAD, index and working directory that relate to how you create commits.

  • HEAD: The parent commit in your current position e.g. branch or commit. Current HEAD will be the parent of the next commit you do, while the next commit becomes the HEAD. Changes in HEAD are said to be committed.
  • Index: Planned files for the next commit. Changes in index are said to be staged.
  • Working Directory: Changes in working directory but nowhere else are said to be unstaged. Possibly even untracked or ignored.
git add      # Move change from unstaged to staged (Working -> Index).
git commit   # Move change from staged to committed (Index -> HEAD).
git reset    # Moves where current HEAD points.
git checkout # Changes what is the current HEAD.

You can change HEAD, index and working directory with git reset.

# Only moves the HEAD without changing stage or working files.
# New HEAD will be the parent of current HEAD.
# Situation before committing (git commit).
git reset --soft HEAD~

# Moves the HEAD and changes stage without changing working files.
# New HEAD will be the parent of current HEAD.
# Situation before staging (git add).
git reset --mixed HEAD~

# Moves the HEAD, changes stage and changes working files.
# New HEAD will be the parent of current HEAD.
# Situation before modifying files.
git reset --hard HEAD~

You can use HEAD to move relatively to the current position. HEAD^ is the first parent of the HEAD. HEAD^^ is the same as HEAD~2, the second generation ancestor of the HEAD. HEAD^^^ is the same as HEAD~3, the third generation ancestor of the HEAD.

# To understand relation between ^ and ~
G   H   I   J
 \ /     \ /
  D   E   F
   \  |  / \
    \ | /   |
     \|/    |
      B     C
       \   /
        \ /
         A
A =      = A^0
B = A^             = A~
C = A^2  = A^2
D = A^^            = A~2
E = B^2  = A^^2
F = B^3  = A^^3
G = A^^^           = A~3
H = D^2  = B^^2    = A^^^2  = A~2^2
I = F^   = B^3^    = A^^3^
J = F^2  = B^3^2   = A^^3^2

HEAD is said to be detached if it isn't attached to top of a branch.

# Attached
HEAD -> master -> $commit$hash$

# Detached
HEAD -> $commit$hash$

Upstream reference is used by argument-less command like git pull. It's like a default remote repository.

# Adding upstream reference.
git push -u origin master

# Shortcuts do not work if upstream branch settings are not set.
git checkout -b <NEW_BRANCH_NAME>
git push origin <NEW_BRANCH_NAME>
git pull
# Error.

# Using 'git push -u' you can set the 'branch.<name>.merge' setting
# which is used as default value for pull/fetch/rebase/push for
# this branch.
git checkout -b <NEW_BRANCH_NAME>
git push -u origin <NEW_BRANCH_NAME>
git pull

Check differences between versions with git diff.

# Show unstaged differences since last commit.
git diff

# Show staged differences since last commit.
git diff --staged

# Notifies you about any basic style errors in your code.
git diff --check <WHAT_COMMIT_TO_COMPARE_TO>

# Comparing to current working version.
git diff HEAD

# Compare to previous commit.
git diff HEAD^

# Differences between two commits.
# Difference between previous commit and the commit before that.
git diff HEAD^ HEAD
git diff HEAD^..HEAD

# Search for commits...
git diff <SHA>..<SHA>

# Two branches.
git diff <BRANCH_NAME> <BRANCH_NAME>

# Fetched remote branch with current branch.
git diff -b production/master

# Statistics between last ten commits.
git diff --stat HEAD~10 HEAD

See who last modified each line of a file with git blame.

git blame <FILENAME> --date short

# Start at line 10
git blame -L 10 <FILENAME>

# Lines 10 to 20
git blame -L 10,20 <FILENAME>
git blame -L 10,+11 <FILENAME>
git blame -L 20,-11 <FILENAME>

# Was change moved inside the file?
git blame -M <FILENAME>

# Was change copied from somewhere else?
git blame -C <FILENAME>

Make Git ignore specific files.

# To ignore local files in all projects.
# Setting will not be shared.
git config --global core.excludesfile ~/.gitignore

# To ignore files in a single local repository, use '.git/info/exclude'
# Setting will not be shared.
experiments/
*.mp4
logs/*.log

# To ignore in local and remote repositories, use '.gitignore' file.
# Setting can be shared.
logs/*.log

# You can see what you are ignoring.
git status --ignored

Make Git ignore specific local files. Useful if you have files you have changed but don't ever want to commit.

git update-index --assume-unchanged path/to/file/*
git update-index --no-assume-unchanged path/to/file/*

Git can rewrite history in various ways. It's useful when fixing broken commits, but you should never change history that you have pushed to a remote repository.

# Fixing just previous commit.
# Never amend pushed commits!
# Make changes you want and then run...
git commit --amend
# If you want to keep the same commit message.
git commit --amend -C HEAD

# To remove previous commit entirely from history.
git reset --hard HEAD~

# If not the last one you want to change, use interactive rebase.
git rebase -i HEAD~3
# Go to the commit you want to fix, write it as "edit", save and exit rebase.
# Make changes and stage them.
git commit --amend
git rebase --continue

# You can make a number of previous commits appear as one.
git reset --soft HEAD~2 # Go backward 2 commits.
git commit # Makes 2 previous commits appear as one single commit.

Revert is not just an action, it's a new commit. Reverting commits makes another commit in history which reverses the changes.

# Revert applies reverse commit to history, undoing changes.
git revert <COMMIT_ID>

# Revert without autocommit
git revert -n <COMMIT_ID>

git reset --hard and git revert are the two main ways you fix a mess you've made.

# I haven't committed the mess and all changes can disappear.
git reset --hard

# I haven't committed the mess but I want to keep somethings.
git add <SOMETHING>
git commit -m <MESSAGE>
git reset --hard

# I have committed the mess, it's the last commit but I haven't pushed it:
git reset --hard HEAD^

# I have committed the mess and I have pushed it:
git revert <COMMIT_ID>

# I only want to return earlier version of some of the files:
git checkout <COMMIT_ID> -- path/to/file
git commit -m 'Revert that one file'

Undoing to the version after last commit/checkout.

git checkout -- <FILENAME>
git checkout -- cache.

Move and rename files with git mv.

git mv <FROM> <TO>
git mv README.md README.rst

Remove files from version control with git rm. Also stops tracking them, but you might want to also ignore the files so Git stops suggesting adding them.

git rm -- '*.txt'
git commit -m "Remove all text files"

# Force remove
git rm -f -- '*.txt'

Remove directories with git rm -r.

git rm -r -- old/
git commit -m "Remove the old/ directory"

# You can get directory back if you have not committed...
git reset HEAD -- old/
git checkout -- old/

Remove untracked files from the current working directory.

# Run this in the root directory.
git clean -f

# To also remove directories.
git clean -fd

# To also remove ignored files.
git clean -fx

# To just remove ignored files.
git clean -fX

git clean -ndx # lists what ignored files would be deleted by clean
git clean -fdx # deletes them

Branch is simply a reference to a specific commit. Main branch is called master by default.

# List all existing branches.
git branch

# Switch HEAD branch.
git checkout <BRANCH_NAME>

# Creating a new branch based on current HEAD.
git branch <BRANCH_NAME>

# Delete a local branch.
git branch -d <BRANCH_NAME> # delete if merged

# Delete a branch from local and remote.
git branch -D <BRANCH_NAME> # delete even if not merged
git push origin --delete <BRANCH_NAME>

Shortcut to create and checkout a branch.

# First check that the remote repository has what you want.
git branch -a

# Create a new branch and checkout it.
git checkout -b <BRANCH_NAME> <OPTIONAL_STARTING_POINT_COMMIT_ID>
git checkout -b bugfix/issue-0004

# Copying a remote branch that you do not have.
git checkout -b <BRANCH_NAME> <REMOTE_NAME>/<BRANCH_NAME>
git checkout -b feature/map origin/feature/map

Manually merging a branch to master and deleting the branch.

git checkout master
git merge <BRANCH_NAME>
git branch -d <BRANCH_NAME>

Updating a long running branch from the master. For example after other critical branch was merged with the master.

git checkout <BRANCH_NAME>
git merge master

Creating a remote branch to a remote repository.

# Create it.
git checkout -b <REMOTE_BRANCH_NAME>
git push origin <REMOTE_BRANCH_NAME>

# See all remote branches
git branch -r

# See all branches
git branch -a

# Remove a remote branch which has not been merged into current branch.
git push origin <REMOTE_BRANCH_NAME>
git branch -D <REMOTE_BRANCH_NAME>

# Clean up remote branch stale references
git remote prune origin

You can change merge strategy.

git merge master -s <STRATEGY>

# Give me master but final state should match my current branch.
# Used when long-running branch that will replace the master.
git merge master -s ours

# This creates better merge diffs.
git merge master -s recursive -X patience

You can abort merges.

git merge --abort
# or...
git reset --merge

You can configure an external merge tool to help with Git merges.

# Starting the merge tool.
git mergetool

# Changing the default mergetool.
git config --global merge.tool "<COMMAND_NAME>"

You can use Git to format text files. Removes trailing whitespace, collapses newlines and adds a final newline.

git stripspace < file

Tagging is usually used for release versioning.

# List all tags.
git tag

# List specific tags.
git tag -l 'v1.4.2.*'
# v1.4.2.1
# v1.4.2.2

# Checkout a tagged version.
git checkout v0.0.1

# Add new tag.
git tag -a <TAG> -m <TAG_MESSAGE>
git tag -a v0.0.3 -m "version 0.0.3"

# Publish your tags.
git push --tags

Rebase is kind of a merge but cleans history.

# Fetch pulls changes but does no merging.
git fetch
git rebase

# Rebase to remote branch, remote master in this case.
git fetch
git rebase origin/master

# Local branch rebase
git checkout admin
git rebase master

# All ok? Merge changes from admin to master branch.
git checkout master
git merge admin

# Interactive rebase, you can change what to include and what to exclude.
git rebase -i <COMMIT_ID>

# To remove one single commit in history...
git rebase -i <COMMIT_ID>^
# And delete the line with <COMMIT_ID>

# Interactively rebase 5 last commits
git rebase -i HEAD~5

# Moving branches with rebase
git rebase --onto <DESTINATION_BRANCH> <SOURCE_BRANCH> <TO_BE_MOVED_BRANCH>

De--o--o--o
        \
         o--o--So--To

           To
           /
De--o--o--o
        \
         o--o--So

Temporarily hide changes with git stash.

# Stash the changes
git stash save <STASH_NAME>

# Copy changes from a stash
git stash apply

# Copy AND remove from stash
git stash pop

# List stashes
git stash list

# Delete stash
git stash drop <STASH_NAME>

# Delete all stashes
git stash clear

# Create branch from an existing stash
git stash branch <STASH_NAME>

Cherry-picking is taking a single commit from another branch.

# Get only one commit and merge it.
git cherry-pick <COMMIT_ID_OR_TAG_OR_BRANCH_NAME>

# Edit commit message.
git cherry-pick -e <COMMIT_ID_OR_TAG_OR_BRANCH_NAME>

# Do not commit.
git cherry-pick -n <COMMIT_ID_OR_TAG_OR_BRANCH_NAME>

You can use git bisect tool to find when a bug was introduced.

# Find bugs with bisect
git bisect start
git bisect bad
git bisect good
git bisect good <COMMIT_ID>
git bisect reset

Export a repository with git archive.

git archive --prefix=my-project --output my-project.tar && gzip my-project.tar

How to return data that you deleted by mistake.

git reset --hard HEAD^10
# OOPS, that was not right!

# To see reflog that records branch head changes.
# Search a point in commit history that you want to advance to.
git log -g
# Create a new branch named lost_data using commit SHA1.
git branch lost_data [SHA1]
# Merge lost_data to your master.
git merge lost_data

# This shows you all the objects that aren’t pointed to by another object.
git fsck --full

Optionally define a few aliases to make working faster.

# Aliases are stored in .gitconfig
git config --global alias.s status
git config --global alias.co checkout
git config --global alias.br branch
git config --global alias.mylog "log --pretty=format:' %h %s [%an]' --graph"

# Log commits one per line.
ls = log --pretty=format:"%C(yellow)%h%Cred%d\\ %Creset%s%C(yellow)\\ [%cn]" --decorate

# To generate change log from commit messages between two versions.
git log --no-merges --format="%an: %s" <COMMIT_SHA1>..<COMMIT_SHA2>

# Find file in project.
find = "!git ls-files | grep -i"

# Find text in code source.
grep = grep -Ii

# Shortcuts
cp = cherry-pick
st = status -s
cl = clone
ci = commit
co = checkout
br = branch
diff = diff --word-diff

rs = reset --soft
rs1 = reset --soft HEAD~
rs2 = reset --soft HEAD~2

rm = reset --mixed
rm1 = reset --mixed HEAD~
rm2 = reset --mixed HEAD~2

rh = reset --hard
rh1 = reset --hard HEAD~
rh2 = reset --hard HEAD~2

# Temporary file ignoring.
# You want to change a file but under no conditions want to push your edits.
assume = update-index --assume-unchanged path/to/file/*
unassume = update-index --no-assume-unchanged path/to/file/*
assumed  = "!git ls-files -v | grep ^h | cut -c 3-"

# Take a snapshot of your current working tree without removing the
# changes from your tree.
snapshot = !git stash save "snapshot: $(date)" && git stash apply "stash@{0}"

Sources