Expand on git branching & merging features with more advanced git commands and concepts
Saving changes is a two-step process in Git:
# Stages changes to the staging area
git add <files>
# Commits a snapshot of changes to the local repo
git commit
# Add just these file(s)
git add app.js
# Add all changed files in this directory and sub-dirs
git add .
# Glob and stage a pattern of files
git add **/*.js
# Unstage the changes to app.js
git reset app.js
# Commits the staged snapshot - will open editor for a message
git commit
# Commits with a message
git commit -m "Make a meaningful change"
Twofer - commit and add in one command:
# Stage & commit all changed files with a message
git commit -am "FH-12345 - My files on disk are perfect"
A branch in git is simply a reference to a commit
# List all local branches
git branch
# Create a new branch locally
git branch mynewbranch [myoptionalbasebranch]
# What commit is a branch pointing at?
git rev-parse mynewbranch
# What commits are on a branch?
git log mynewbranch [--pretty --graph --oneline --decorate]
# What branches point at a particular commit?
git branch --points-at e29b7ee
# Delete a branch
git branch -d mynewbranch
# Really delete a branch, even if it has changes that aren't on any other branches
git branch -D mynewbranch
There are also "remote-tracking" branches These are read-only copies of what is on a "remote", at the time that we last "fetched" (updated the information we store locally about branches on the remote).
# Get new information from all remotes
git remote update [--prune] # or: git fetch --all [--prune]
# List all remote-tracking branches
git branch -r
# List all branches (local and remote-tracking)
git branch -a
# Track a remote branch
git branch [branchname] -u origin/anybranch
# Now status will show the relationship with
git status [-sb]
stdout: "Your branch is ahead of 'origin/anybranch' by 3 commits"
# Delete a branch from a remote
git push origin --delete mybranchname
# Clone the forked repository
git clone git@github.com:<github_username>/git-tutorial.git
Why rewrite history?
Rebasing and Merging are two methods that achieve the same goal - the integration of changes from one branch into another branch
cd git-tutorial # From earlier
git fetch
git checkout rebase
# The rebase branch was branched from master a while ago
# It's out of date (master has moved on)
# And there's bad commits in our history that we're going to fixup
git log --pretty=oneline
# First, lets interactively rebase our last 4 commits
git rebase -i HEAD~4 # Note the lack of a branch
# Fix up the commits - think about re-ordering or (s)quashing
# the version bumps and (r)eword the commit messages.
# Also, delete commits that are of no value
git rebase origin/master
# Alternatively:
# We can do both a history rewrite and a rebase via:
git rebase -i origin/master
Move changes in a dirty workspace away to the side
# You have a dirty workspace
$ git status -s
D .eslintrc.json
?? .eslintrc.yaml
# "stash" those changes
$ git stash save "Experimental work with eslint"
Saved working directory and index state On master: Experimental work with eslint
HEAD is now at 645ba47 Merge pull request #884 from...
# See what stashes you have in this repository
$ git stash list
stash@{0}: On master: Experimental work with eslint
stash@{1}: kubernetes client test stuff
stash@{2}: WIP on master: d09a007 Merge pull request #768 from...
# Your workspace is now clean...is it?
$ git status -s
?? .eslintrc.yaml # this file isn't tracked by git yet, so not stashed by default
# Restore top/first stash (stash@{0}) & remove it from the list
$ git stash pop # == apply && drop
# Back to normal
$ git status -s
D .eslintrc.json
?? .eslintrc.yaml
# 'save' is the default stash action -- needed if you want to include a message
$ git stash save -u "Stash all including untracked"
# Bring it (or another stash) back
$ git stash apply stash@{0}
# Remove the stash if it's no longer needed
git stash drop stash@{0}
# Or make a new branch & restore your changes there
git stash branch <branchname> [stash@<revision>]
# LW tags are files on disk with a pointer to a commit
ls .git/refs/tags/
# Cat to reveal pointer to a commit
cat git/refs/tags/v99.0.0
# Show this commit
git show 659220
Creating a tag
# On a branch or any kind of git-ref
git tag v1.2.3-your-name
# Check filesystem (ls .git/refs/tags)
# Git does not push tags by default to a remote
# Or `git push --tags`
git push origin v1.2.3-your-name
# Checkout a tag
git checkout v99.0.0
# Create a branch from a tag
git checkout -b version-nine-nine v99.0.0
# Delete tags /!\ Careful now /!\
# Local only
git tag -d v99.0.0
# Delete from remote
git push origin :refs/tags/v99.0.0
# A little bit about annotated tags
git tag -s v0.99.0-annotated -m "my tag v0.99.0-annotated"
git show v0.99.0-annotated
Apply the changes introduced by some existing commits
Our main use-case for git-cherry-pick is for taking some bugfix commits from one branch, and applying them to another (e.g. a release branch and master)
# Ensure that you're on the branch you want to copy commits to
# and the working directory is clean
$ git status
On branch cp-example
nothing to commit, working tree clean
# Bring a commit from a different branch over
git cherry-pick dea2da
# Notice that the changes, author details, and commit message are the same
# But the commit sha1 is different (and maybe the tree object)
$ git log -1 # or git show
commit b2d900443cb628f8d5c6accc271e7b66aaadb9ca
Author: Jason Madigan <jason@jasonmadigan.com>
Date: Thu Dec 1 16:42:17 2016 +0000
name change
Do something before or after a particular action
# everything you need to know about each hook
man githooks
.git/hooks/
in your repoExample pre-commit hook to lint code, stored in .git/hooks/pre-commit
#!/usr/bin/bash --login
eslint ./lib
Config is layered: system, global (user), local (repo)
# System
cat /etc/gitconfig
# Global (user)
cat ~/.gitconfig # or ~/.git/config
# repo-specific
cat ./.git/config
Set defaults for all your projects in ~/.gitconfig, either by editing the file directly, or by using the config subcommand. E.g.:
# Set a sane editor (otherwise uses value from $EDITOR)
$ git config --global core.editor emacs
# Set a custom commit message template
git config --global commit.template ~/.git-commit-template
Gerard's current ~/.git-commit-template file: https://git.io/vQJzx
Most interesting is the user's global config
[user]
name = Gerard Ryan
email = gerard@ryan.lt
signingkey = 8A617903604095DC
[core]
excludesfile = ~/.gitignore
quotepath = false
autocrlf = input
safecrlf = warn
editor = emacsclient -t -a emacs
[hub]
protocol = ssh
[commit]
template = /home/grdryn/.git-commit-template
gpgsign = true
[gpg]
program = gpg2
Aliases are really useful
[alias]
br = branch
co = checkout
ci = commit -a
d = diff --color-words
st = status
lol = log --graph --decorate --all --abbrev-commit --pretty=oneline
lg = log --graph --all --pretty=format:'%Cred%h%Creset -%C(yellow)%d%Creset %s %Cgreen(%cr)%Creset' --abbrev-commit --date=relative
scrub = !git reset --hard && git clean -fd
Lots of other useful aliases and config customization examples here:
https://github.com/matthewmccullough/dotfiles/blob/master/gitconfig
In the git-tutorial
repo:
git checkout bisect
# Our bisect branch is a little ahead of of master (500 commits)
# At some point, a commit slipped in that means our app doesn't start
# and our tests fail.
# Lets use git bisect to find the broken commit
git bisect start
# Step 1 - find a commit where things are working
# I have no idea, so lets cast a wide net (across 500 commits)
git bisect good cddd186 # update to 3.0.0
git bisect bad bf0c5b1 # update to 502.0.0
# Run `npm test` to see if our tests fail
# If they fail - run `git bisect bad`
# If they pass - run `git bisect good`
# When done, reset via `git bisect reset` & patch the bug
$ tree -L 1 ./.git
./.git
├── branches
├── COMMIT_EDITMSG # Text of most recent commit message
├── config # Local configuration specific to this repo
├── description
├── FETCH_HEAD # Info about what was fetched from remotes
├── HEAD # Special ref that points to current ref in ./git/refs
├── hooks # Dir containing scripts that run at specific events
├── index # Binary representation of the index/cache/staging area
├── info
├── logs # Dir containing info used by reflog
├── objects # Dir containing all "objects" (loose or packed)
├── ORIG_HEAD
├── packed-refs # (compressed)
├── refs # Branch, tag, remote, stash info
└── rr-cache
COMMIT_EDITMSG
can be useful with the hub command to create a pull-request on github with a single command
# Create a PR against master branch of the repo in the feedhenry org
# with the contents of the most recent commit message as the description
git pull-request -b feedhenry:master -F ./.git/COMMIT_EDITMSG
# What type of object is it?
$ git cat-file -t $(git rev-parse HEAD)
commit
# What size is it (in bytes)?
$ git cat-file -s e29b7eef219e38d005b1360214322a336a56729e
1079
# Pretty-print it
$ git cat-file -p $(git rev-parse HEAD)^
tree 2320f5bb8cf059571ed69d705ba792c7939296f5
parent e67aeb6f2a1c521f62106ede0b53f3a30ce57a36
parent d4c13babe4fb19550455d96cd067a19f745d999e
author Jason Madigan <jmadigan@redhat.com> 1480413570 +0000
committer GitHub <noreply@github.com> 1480413570 +0000
Merge pull request #2 from fheng/readme-updates
README updates
Porcelain provides a more user-friendly interface to the plumbing https://git.io/v1Bt5
# Create a new branch from master & checkout
git co -b ungit master
# Make a new blob object
blob_sha1=$(echo "Let's ungit" | git hash-object -w --stdin)
# Add the blob as a file in the index
git update-index --add --cacheinfo 100644 ${blob_sha1} ungit.txt
# Make a tree object from the current index
tree_sha1=$(git write-tree)
# Make a commit object with the newly-created tree
commit_sha1=$(echo "Ungit all the things" | git commit-tree -p HEAD ${tree_sha1})
# Update the current branch with that new commit
git merge ${commit_sha1}
# WTF, where's my ungit.txt?!
ls && git status
Git commands that change the state of the local repo will write an entry to the reflog
git reflog # git log -g --abbrev-commit --pretty=oneline
a21f4c4 HEAD@{0}: merge a21f4c412fa194021faa859193380a26b54a024d: Fast-forward
c838b24 HEAD@{1}: merge c838b2447de706f53eaabed16c371abbb6d82b03: Fast-forward
e29b7ee HEAD@{2}: checkout: moving from cp-conflict-example to ungit
bf196ea HEAD@{3}: commit (cherry-pick): Bumping version to 2.0.8
e29b7ee HEAD@{4}: checkout: moving from cp-example to cp-conflict-example
b2d9004 HEAD@{5}: checkout: moving from master to cp-example
e29b7ee HEAD@{6}: checkout: moving from cp-example to master
b2d9004 HEAD@{7}: cherry-pick: name change
e29b7ee HEAD@{8}: checkout: moving from master to cp-example
e29b7ee HEAD@{9}: clone: from git@github.com:fheng/git-tutorial.git
# Checkout a previous state
git checkout HEAD@{2} # detached head, consider creating a branch here if important
# Reset current branch to an earlier state
git reset [--hard] HEAD@{4}