GIT

GIT version management snippets.

Sane Global Settings

Rebasing when pulling makes the branch history cleaner, avoiding pull merge commits. Prune on fetch automatically clean Git objects in your repository locally whenever you fetch changes from remote, minimizes the number of branches on your local machine. Highlight moved code block sections.

git config --global pull.rebase true
git config --global fetch.prune true
git config --global diff.colorMoved zebra

Reference

Revert Changes and Keep Commit History

git revert --no-edit {LAST GOOD COMMIT}..{LAST BAD COMMIT}

Force Repository to Previous Commit

Should use Revert Changes and Keep Commit History.

git reset --hard {COMMIT ID}
git push --force origin master

Danger

This is data destructive. Ensure any files are cleaned up before committing.

Currently checked out versions of this repository will break.

Force Pull from Master Repository

Forces local repository to be in sync with master, discarding all local changes and unpushed commits.

git fetch --all
git reset --hard origin/master
Apply to a branch only
git checkout {BRANCH}
git fetch --all
git branch {BRANCH}-backup
git reset --hard origin/{BRANCH}

Reference

Revert Entire Directory to HEAD

git checkout -- {DIR}
git clean -fd {DIR}

Note

{DIR}/.. can be used to target all files and directories within that directory.

Add Tag to Previous Commit

git pull --tags
git tag {TAG} {COMMIT}
git push --tags

Squash Commits to a Single Commit (Rebase)

This will squash a series of commits into a single commit, which is useful to cleanup multiple commits before pushing upstream.

git rebase --interactive {COMMIT}

Note

The COMMIT is the last commit that should be collapsed (e.g. rolled into a single commit).

Editor will appear with rebase configuration. Generally use pick for the first commit and squash for the remaining commits.

git rebase --continue

Note

If done correctly, this will show all commit messages that were rolled up. Update as needed and commit as normal.

Modify Specific Historical Commit

Warning

This will re-write commit history from the changed commit; if tags, releases are used they will need to be deleted and re-created. Existing clones will break. Be careful.

Rebase to one commit before the intended change
git rebase --interactive '{COMMIT}^'

Note

Set edit for the desired commit; save and exit. Make desired changes.

Finish rebasing to HEAD
git commit --all --amend --no-edit
git rebase --continue
Reset affected tags
git tag -l
git rev-list -n 1 {TAG}
git tag -d {TAG}
git push --delete origin {TAG}
git tag {TAG} {NEW COMMIT HASH}
git push --tags

Note

Ensure affected releases, tags are removed before pushing the changed repo.

git push -f

Reference

Remove Tracked Files without Deleting Them

Single file.
git rm --cached {FILE TO REMOVE FROM COMMIT}
Multiple files.
git rm --cached {FILE1} {FILEN}
All contents of a directory.
git rm -r --cached {DIR}

Create a Branch

Multiple branches can be made to focus changes on specific efforts. These are cut from the current branch; most cases this should be master.

List, create, and move to new branch.
git branch -a
git checkout -b {NEW BRANCH}

Use git normally.

Merging Branches

Completed branches can be merged back into any branch, typically master.

List branches, switch to master, and merge.
git branch -a
git checkout master
git merge {BRANCH} --no-ff

Note

--no-ff retains all commit messages from the branch. Leave this off to squish the commit (it may be helpful to get branch log for merge message git --no-pager log > /tmp/git.log.

You may reset the merge before committing with no data loss with get merge --abort.

Reference

Deleting Branches

Git will throw an error if deleting a branch with commits that has not been merged.

Delete merged branch.
git branch -a
git branch -d {BRANCH}

Create Worktree

Allows the use of multiple branches simultaneously.

Create a new worktree from a branch
git branch -a
git checkout -b {NEW BRANCH}
git checkout master
git worktree add ../{WORKTREE NAME} {BRANCH}

Merge Worktree

Works like normal branch merging. Execute merge from master worktree.

See Merging Branches.

Removing Worktree

After the branch is merged worktree can be removed.

Remove worktree.
cd {MASTER WORKTREE}
git worktree remove {WORKTREE}

Then delete branch as normal. See Deleting Branches.

Migrate git stash to another machine

Export a stash as a patch to import in another git client.

Export stash 0 to a patch.
git stash show "stash@{0}" -p > changes.patch
Import patch to client.
git apply changes.patch
Patch can be reverted if there are issues.
git apply changes.patch --reverse

Repo git hooks

Hooks are located in .git/hooks but are not versioned. This enables repository tracked hooks.

See .git/hooks for examples. Custom pre-commit example. Hooks must have exact names.

Create repo hooks
mkdir {REPO}/.githooks
Set local gitconfig hookspath to custom location
git config -f .gitconfig core.hooksPath .githooks
0755 user user .githooks/pre-commit
#!/usr/bin/env bash
#
# Called by "git commit" with no arguments.  The hook should
# exit with non-zero status after issuing an appropriate message if
# it wants to stop the commit. See .git/hooks for examples.

# Unset variables produce errors
set -u

if git rev-parse --verify HEAD >/dev/null 2>&1
then
	against=HEAD
else
	# Initial commit: diff against an empty tree object
	against=4b825dc642cb6eb9a060e54bf8d69288fbee4904
fi

# Redirect output to stderr.
exec 1>&2

EXIT_STATUS=0

# Check that vault files are encrypted.
# read: -r do not allow backslashes to escape characters; -d delimiter
while IFS= read -r -d $'\0' file; do
	[[ "$file" != *.vault &&
		 "$file" != *.vault.yml &&
		 "$file" != *vault ]] && continue
	# cut gets symbols 1-2
	file_status=$(git status --porcelain -- "$file" 2>&1 | cut -c1-2)
	file_status_index=${file_status:0:1}
	file_status_worktree=${file_status:1:1}
	[[ "$file_status_worktree" != ' ' ]] && {
		echo "ERROR: *.vault file is modified in worktree but not added to the index: $file"
		echo "Can not check if it is properly encrypted. Use git add or git stash to fix this."
		EXIT_STATUS=1
	}
	# check is neither required nor possible for deleted files
	[[ "$file_status_index" = 'D' ]] && continue
	head -1 "$file" | grep --quiet '^\$ANSIBLE_VAULT;' || {
		echo "ERROR: non-encrypted *.vault file: $file"
		EXIT_STATUS=1
	}
done < <(git diff --cached --name-only -z "$against")

exit $EXIT_STATUS
Use a Makefile or manually run on repo checkout to setup.
git config --local include.path ../.gitconfig

Note

Command executes from .git directory, hence going up a directory to read the config.

List All Respositories for An Organization/User

Useful for determining if there are new repositories to sync.

List all repositories for an organzation.
curl "https://api.github.com/orgs/{ORGANIZATION NAME}/repos?per_page=1000&page=1" | jq -r '.[] | .name' | sort
List all repositories for a user.
curl "https://api.github.com/users/{USER}/repos?per_page=1000&page=1" | jq -r '.[] | .name' | sort

Pull Latest Tarball Release from Github

Useful for projects that have periodic releases but are not in OS packages.

0755 user user update_latest_tarball
#!/bin/bash
#
# Args:
#   1: github username
#   2: repository name
#   3: tarball download target (absolute path)
#   4: tarball extract target (absolute path)
RELEASE_DATA=$(curl --silent "https://api.github.com/repos/${1}/${2}/releases/latest")
TAG=$(echo ${RELEASE_DATA} | jq -r .tag_name)
URL=$(echo ${RELEASE_DATA} | jq -r '.assets[0].browser_download_url')
NAME=$(echo ${RELEASE_DATA} | jq -r '.assets[0].name')
TARBALL=${3}/${NAME}

if [ ! -f "${TARBALL}" ]; then
  curl --silent --location ${URL} --output ${TARBALL}
  tar xf ${TARBALL} --strip-components=1 -C ${4}
fi