GitHub Advanced

Mastering Git and GitHub

Please note: This class and curriculum have now become a part of the two day GitHub for Developers course.

GitHub Advanced

Command line deep dive, problem solving techniques, and GitHub efficiencies



Understanding Git

Explore the structure and way change is preserved in Git.



Git data structure



Commit DAG



git log [branch]..[branch]


git log [branch]...[branch]


$ git log --left-right


$ git log --diff-filter=[A|M|D]


$ git branch --merged


git branch --no-merged


git rev-parse [commit-shorthand]


git name-rev [commit]

Lab
  1. Understand the way Git preserves history and data
  2. Use shorthands to navigate commit history
  3. Filter log option switches for refined version querying

Details

Git version control is composed of three fundamental object types

Object Type Purpose
Blob Store file content
Tree Preserve path and structure
Commit Serve as unique historical reference

These three data types use a SHA1 for unique identification and establish built-in data integrity from the commit, tree, and blob fingerprints.

Commitish

Historical commit points are simple to discuss using commitish rather than hexadecimal SHA1 identifiers. This shorthand works on the command line, as well as on GitHub.

Shorthand Explanation
HEAD Current commit
HEAD^2 Second parent if merge commit, otherwise second ancestor
HEAD~2 Second ancestor of the current commit
HEAD@{one.day.ago} Reachable by current branch from one day ago
HEAD@{today} All commits reachable by current branch made today

The log command is like a search engine. By default it shows all history, but one can refine the output to view commits by: author and committer, the time changes occurred, patch content, or even the message description. This is called revision selection.

Show only those commits made by a given Git user.name, or within a particular time range:

$ git log --author [author-name]
$ git log --since [integer].days.ago

Searching by a string or regular expression is often the most efficient way of finding history:

$ git log -S [string-in-patch]
$ git log -G [regex-pattern-in-patch]
$ git log --grep=[regex-in-message]

Filtering by file state, i.e. added (A), modified (M), or deleted (D):

$ git log --diff-filter=[A|M|D]
$ git log --follow --stat --diff-filter=[A|M|D] -- <filename>

Specifying commit ranges, as detailed on the Git-SCM.com web site:

$ git log --oneline --left-right master..other
$ git log --oneline --left-right master...other

Videos


Collaborating on change & releases

Cutting and creating releases on the command line and on GitHub.



git tag [tagname] [commit]


git tag -a [tagname] [commit]


git push [remote] [tagname]


git tag -d [tagname]


git describe


git tag -s -m "[message]" [tagname] [commit]

Lab
  1. Use the command line for branching strategies
  2. Understand best branch and collaboration workflows
  3. Apply tags to commits to indicate releases

Tags


Details

Branching patterns, best practices

Cutting releases

Generating a lightweight reference tag is simple:

$ git tag [TAGNAME] [commit]

Annotated tags generate a complete data object in the Git repository, storing the author, a timestamp, a specific SHA1 reference, and an optional GPG key for signing work.

$ git tag -a [TAG_NAME] [commit|branch]
$ git tag -a -m [TAG_NAME] [commit|branch]

The tag command offers a number of ways to understand what it represents, ways of replacing it, and even the option of eliminating it if necessary.

$ git tag -s -m[message] [TAGNAME]
$ git tag -f [TAGNAME]
$ git tag -d [TAGNAME]
$ git describe
$ git describe [SHA]
$ git tag -d 12345
$ git push origin :[tag-name-to-delete]

Ignoring & cleaning up files

Avoid tracking unimportant files.



$ vi .gitignore


$ git config core.excludesfile [path]


$ git add -f [path]


$ git clean -f
$ git clean -fd
$ git clean -fx

Lab
  1. Set ignore patterns to prevent accidental versioning
  2. Clean working directory of untracked files
  3. Tidy working directory of any .gitignore matched files

Details

Repository-specific ignores

$ vi .gitignore

System-wide ignores

$ git config core.excludesfile [path]

Listing ignored files

$ git config alias.show-ignored \
    "ls-files --exclude-standard
    --others --ignored"

Staging ignored files

$ git add -f [path]

Removing unwanted files

$ git clean -f
$ git clean -fd
$ git clean -fx

Video


Mastering shortcuts & efficiencies

Speed up your workflow.



$ git commit -a -m"[message]"


$ git commit --amend -m "[updated message]"


$ git checkout -b [branch] [base]


$ git stash


# Stage by patch
$ git add -p [file]

# Unstage by patch
git reset HEAD -p [file]

Lab
  1. Use built-in shortcuts for committing and branch switching
  2. Learn when to use stash and when to use longer-living branches
  3. Commit partial file content hunks with add and reset patch switches
  4. Avoiding repetitive conflict resolution with rerere

Details

Shortcuts

Add and commit along with the commit message:

$ git commit -am "[message]"

Commit, amend, and provide the commit message:

$ git commit --amend -m "[updated message]"

Checkout and create a branch:

$ git checkout -b [branch] [base]

Temporary changes

$ git stash
$ git stash save "<description>"
$ git stash --include-untracked
$ git stash list
$ git stash pop <name>
$ git stash drop <name>
$ git stash apply
$ git stash clear
$ git stash -p

Isolating Work

 Stage by patch
$ git add -p [file]

 Unstage by patch
git reset HEAD -p [file]

Avoiding repetitive conflicts

$ git config rerere.enable true

Capturing pieces of history

Craft and acquire commits with selective, as-needed commands.



# Generate new commit from specified commit
$ git cherry-pick [commit]

# List branches containing same patch
$ git cherry [comparison-branch]


# Stage the file from a specific commit
git checkout [commit] -- [path]

Lab
  1. Capture select commits and generate new history on separate branches
  2. Determine commit history existence in independent branches
  3. Capture path-specific change from a commit with multiple files

Details

Capturing changes from other branches or in orphaned commit history becomes a useful and timesaving operation. With cherry-pick, authorship and attribution fields remain, versioned changes are carried over, and a new commit is generated.

# Generate new commit on current branch with patch of specified commit
$ git cherry-pick [commit]

# List branches containing same patch
$ git cherry [comparison-branch]

Use checkout to get the functionality of cherry-pick without automatically creating a new commit.

# Stage the versioned file from a specific commit
git checkout [commit] -- [path]

Rewriting and crafting history

Rebase and reorder existing commits for improved historical context.



Rebase



Rebase



Rebase



$ git rebase [base-commit]


$ git pull --rebase


$ git config branch.autosetuprebase
$ git config branch.[master].rebase true


Rebase



Rebase



$ git rebase -i [base-commit]


# When commits contain `fixup!` or `squash!`
$ git rebase -i --autosquash [base-commit]

Lab
  1. Replay branch history with rebase
  2. Understand the side effects of rewriting history
  3. Rewrite history interactively with rebase -i
  4. Resolve merge conflicts during a rebase

Details

Use rebase to edit existing commit history. But pay close attention to how it differs from merge

Rebasing a branch

Re-playing branch-specific commits against a base is the most common use case for rebase.

$ git checkout <featurebranch>
$ git rebase master

Rebase configuration

pull can be configured to replay all local commits ahead of the incoming upstream ones.

$ git config pull.rebase true

Handling conflicts

Resolving a conflict during a rebase requires intervention, and then signaling that the conflict is resolved.

# fix conflicts
$ git add [conflict-free file]
$ git rebase --continue

Reordering History

The rebase -i command operates similarly to rebase in its plain form, but allows for reordering commits, discarding commits, and revising existing commits.

# Rewrite any commits back to specified base commit
$ git rebase -i <commit>

Rewriting shared repository history is typically inadvisable and is easily avoided by using remote branches as the base commit of a rebase.

$ git rebase -i [remote]/[branch]

Automatically arrange history and rebase -i steps with fixup! and squash!. Prefix messages of work-in-progress commits to efficiently collapse them during an interactive rebase.

$ git rebase -i --autosquash [ref]

Reviewing & synchronizing

Interact, investigate, and integrate remote repository histories.



$ git remote -v
$ git branch -vv


$ git remote show <remote-name>


# List refs of upstream
$ git ls-remote [remote]


git pull [remote] [pull-request-namespace]


# Retrieve, store as temporary branch
$ git fetch [remote] refs/pull/[num]/head


$ git show FETCH_HEAD


$ git merge --no-commit --no-ff FETCH_HEAD

Lab
  1. Review GitHub Pull Requests from the command line
  2. Get Pull Request history temporarily and with branches
  3. Customize fetch operations with refspecs

Details

Reviewing remote branches

$ git ls-remote origin
$ git fetch origin refs/pull/1/head

$ git show FETCH_HEAD
$ git merge --no-commit --no-ff FETCH_HEAD

Examining remote branches

$ git remote -v
$ git remote show <remote-name>
$ git ls-remote
$ git branch -vv

Retrieving arbitrary commits

To just retrieve the commits to FETCH_HEAD:

$ git fetch [remote] [pull-request-namespace]

To merge the retrieved commits into a branch:

$ git pull [remote] [pull-request-namespace]

Leveraging FETCH_HEAD

$ git fetch <URL> <branch>
$ git checkout FETCH_HEAD
$ git branch <newbranchname> FETCH_HEAD

What are refspecs?

Refspec examples

 Source and destination refspecs
$ git fetch [repo-url] [source]:[destination]

$ git fetch [repo-url] master
 * branch     master     -> FETCH_HEAD

$ git fetch origin refs/pull/1/head
 * branch     refs/pull/1/head -> FETCH_HEAD

Refspec to retrieve Pull Requests

$ git config --add remote.[upstream].fetch "+refs/pull/*/head:refs/remotes/[upstream]/pull/*"

Filtering histories & externalizing dependencies

Separate single, large repository histories into individual projects.



# Rewrite all history with
# respect to files in directory
$ git filter-branch
--subdirectory-filter [dir]
-- --all


# Rewrite history, applying Git command
# across all commits
$ git filter-branch --index-filter
'git rm --cached
--ignore-unmatch [file]' HEAD


$ git submodule add [repo-url] [folder]


$ git submodule init
$ git submodule update

# OR

$ git clone --recursive [url]

Lab
  1. Separate versioned content in a repository into a separate one
  2. Clean up unwanted history repository-wide with filter-branch
  3. Incorporate external repositories as dependencies with submodule

Details

# Rewrite all history with respect to files in [dir]
$ git filter-branch
    --subdirectory-filter [dir]
    -- --all

# Rewrite history, applying `git rm` across all commits
$ git filter-branch --index-filter
    'git rm --cached
    --ignore-unmatch [file]' HEAD

Adding submodules

Add a separate repository as a subdirectory:

$ git submodule add [repo-url] [folder]

Using submodules

For a freshly cloned repository with submodules

 Step-by-step
$ git submodule init
$ git submodule update

 Single process with option switches
$ git submodule update --init --recursive

Signing work

Verify authenticity with GPG keys



$ git commit --signoff


# Display signatures per commit
$ git log --show-signature


# Merge only if all signatures match public keys
$ git merge --verify-signatures

Lab
  1. Use a GPG key to sign a specific commit
  2. Verify a signed commit
  3. Use a GPG key to sign a Git tag
  4. Verify a signed tag

Details

Configuring GPG

$ gpg --list-keys
pub   1024D/627CBB21 2014-08-01
uid                  Matthew McCullough

 Use 627CBB21 as the signing key's ID
$ git config --global user.signingkey [ID]

Using GPG signatures on commits

$ git commit --gpg-sign
# or the shorthand invocation...
$ git commit -S

# Display signatures per commit
$ git log --show-signature

# Merge only if all signatures match public keys
$ git merge --verify-signatures

Using GPG signatures on tags

# Create a signed tag
$ git tag -s [tag-name] [commit]

# Verify signature of tag
$ git tag -v [tag-name]

Using GitHub CLI and the API

Use GitHub features directly from the command line.


Lab
  1. Create a GitHub (public or private) repository from the command line
  2. Create a tracking branch and open a Pull Request from the command line
  3. Clone a public, open source repository and Fork it from the command line

Details

Command line interface

 Create a new public repository on your GitHub account
$ gh create

 Create a new private repository on your GitHub account
$ gh create -p

 Open a Pull Request for the current branch
$ gh pull-request

 Create a fork of the cloned repository on your GitHub Account
$ gh fork

 Launch a web browser with the branch comparison view
$ gh compare

 Launch a web browser to the repository home page
$ gh browse

The GitHub API

 Anonymous
$ curl <URL>

 Pass credentials on CLI
$ curl -u <user:password> <URL>

 Use .netrc file
$ curl -n <URL>

There are a number of libraries for interfacing with the GitHub API, all of which are available at octokit.github.io


Diff & merge tool

Go beyond command line comparison with visual tooling.


Lab
  1. Setup a preferred visual tool for diff and merge operations
  2. Run the difftool to compare uncommitted file changes
  3. Generate a merge conflict and resolve using a mergetool

Details

Difftool execution:

$ git difftool --tool-help
$ git config diff.tool <tool-name-in-config>
$ git config difftool.prompt false
$ git config difftool.<tool-name>.cmd "<path [args]>"

A sample .gitconfig file:

[diff]
    tool = p4merge
[difftool "p4merge"]
    cmd = "/Applications/p4merge.app/Contents/Resources/launchp4merge $LOCAL $REMOTE"
[difftool]
    prompt = false

Mergetool execution:

$ git config --global merge.tool p4mergetool

$ git config --global mergetool.p4mergetool.cmd "/Applications/p4merge.app/Contents/Resources/launchp4merge \$PWD/\$BASE \$PWD/\$REMOTE \$PWD/\$LOCAL \$PWD/\$MERGED"

$ git config --global mergetool.p4mergetool.trustExitCode false

$ git config --global mergetool.keepBackup false

A sample .gitconfig file:

[merge]
    tool = Kaleidoscope
[mergetool "p4mergetool"]
    cmd = " /Applications/p4merge.app/Contents/Resources/launchp4merge $PWD/$BASE $PWD/$REMOTE $PWD/$LOCAL $PWD/$MERGED"
    keepBackup = false

Additional resources


Details

This course covers many advanced uses of Git and GitHub, and yet there is still more to explore. We’ve included some of the most useful resources for our students with insatiable appetites.

Advanced Git Videos

Tools

Git Documentation

Table of Contents