Skip to content

Git⚓︎

Config⚓︎

Cater for different users using git as user root⚓︎

In root's /.kshrc

SUDO_USER="${SUDO_USER:-'none'}"
if [ ${SUDO_USER} = "johndoe" ]; then
    export GIT_AUTHOR_NAME="John Doe"
    export GIT_COMMITTER_NAME="John Doe"
    export GIT_AUTHOR_EMAIL="[email protected]"
    export GIT_COMMITTER_EMAIL="[email protected]"
elif [ ${SUDO_USER} = "janedoe" ]; then
    export GIT_AUTHOR_NAME="Jane Doe"
    export GIT_COMMITTER_NAME="Jane Doe"
    export GIT_AUTHOR_EMAIL="[email protected]"
    export GIT_COMMITTER_EMAIL="[email protected]"
fi

Remotes⚓︎

Changing a remote's URL (https or ssh)⚓︎

$ git remote -v
origin  https://github.com/Kristijan/notes.git (fetch)
origin  https://github.com/Kristijan/notes.git (push)
$ git remote set-url origin https://github.com/somethingelse/notes.git
$ git remote -v
origin  https://github.com/somethingelse/notes.git (fetch)
origin  https://github.com/somethingelse/notes.git (push)

Add remote upstream after fork⚓︎

$ git remote -v
origin  ssh://[email protected]:7999/~kristijan/repo.git (fetch)
origin  ssh://[email protected]:7999/~kristijan/repo.git (push)
$ git remote add upstream ssh://[email protected]:7999/unix/repo.git
$ git remote -v
origin  ssh://[email protected]:7999/~kristijan/repo.git (fetch)
origin  ssh://[email protected]:7999/~kristijan/repo.git (push)
upstream        ssh://[email protected]:7999/unix/repo.git (fetch)
upstream        ssh://[email protected]:7999/unix/repo.git (push)

Pull down changes from both upstream and origin⚓︎

git remote update

Aliases⚓︎

Decorated oneline graph⚓︎

git config --global alias.lga "log --oneline --all --decorate --graph"

One line summary⚓︎

git config --global alias.lgo "log --color --pretty=format:'%C(yellow)%h%Creset - %C(cyan)%ad%Creset %<(80,trunc)%s   %C(cyan)%>(20,trunc)%an - %Cgreen%>(12)%ar%Creset' --date=short --abbrev-commit --all"

Commits⚓︎

Automatically commit all files being tracked⚓︎

git commit -a -m "commit message"

Squashing commits and rebase onto master⚓︎

$ git switch master
$ git pull
$ git switch <other_branch>
$ git rebase -i master
pick dedd08d commit1
squash 8e782a4 commit2
squash 036bca2 commit3
squash ff8a125 commit4
$ git push --force-with-lease

Find parent commits of a merge⚓︎

$ git log --format=raw -n1
commit 61713c9ac641091c20045e9d3bde34f31d8e3621
tree 9d8c97f166cc9082c1949f21ed7b61a4fc33a420
parent c507b242517ba651b4fae5254c607571f666ffe2
parent a12033cb2e0933849a374defd272bd71a06b62ed

The highlighted commit above should be where the branch was before the merge.

Create empty commit⚓︎

Create an empty commit to do things like trigger builds, etc...

git commit --allow-empty -m "Trigger build"

Show commit hash for each file⚓︎

$ git ls-files --sparse --full-name -z | xargs -0 -I FILE -P 20 git log -1 --date=iso-strict-local --format='%ad %>(14) %cr %<(5) %an  %h ./FILE' -- FILE | sort --general-numeric-sort
2021-06-05T14:40:30+10:00  2 years, 5 months ago  Kristian Milos  d280e68 ./docs/gpfs.md
2021-06-05T14:40:30+10:00  2 years, 5 months ago  Kristian Milos  d280e68 ./docs/index.md
2021-06-05T14:40:30+10:00  2 years, 5 months ago  Kristian Milos  d280e68 ./docs/nim.md
2021-06-05T14:40:30+10:00  2 years, 5 months ago  Kristian Milos  d280e68 ./docs/vios.md
2021-06-05T14:40:30+10:00  2 years, 5 months ago  Kristian Milos  d280e68 ./docs/wpar.md
2021-06-05T14:59:15+10:00  2 years, 5 months ago  Kristian Milos  170190d ./.github/workflows/ci.yml
...

Show all commit hashes for file⚓︎

$ git ls-files --sparse --full-name -z | xargs -0 -I FILE -P 20 git log --date=iso-strict-local --format='%ad %>(14) %cr %<(5) %an  %h ./FILE' -- FILE | sort --general-numeric-sort
2023-01-11T17:19:25+11:00   10 months ago  Kristian Milos  cac3a2a ./docs/git.md
2023-03-17T16:09:35+11:00    7 months ago  Kristian Milos  9633d50 ./docs/aix.md
2023-03-23T12:41:12+11:00    7 months ago  Kristian Milos  0c1ce12 ./docs/aix.md
2023-06-09T12:00:20+10:00    5 months ago  Kristian Milos  30b8b9b ./docs/git.md
2023-06-15T17:06:15+10:00    4 months ago  Kristian Milos  229804b ./docs/aix.md
2023-06-15T17:14:56+10:00    4 months ago  Kristian Milos  358c456 ./docs/aix.md
...

Show commit count for each file⚓︎

This will list which files have been in commits the most.

$ git log --pretty=format: --name-only | sed '/^$/d' | sort | uniq -c | sort -g
   1 docs/CNAME
   1 docs/gpfs.md
   1 docs/nim.md
   1 docs/vios.md
   1 docs/wpar.md
   2 .gitignore
   4 docs/hmc.md
   4 docs/index.md
   4 docs/powerha.md
   7 .github/workflows/ci.yml
  10 docs/aix.md
  11 mkdocs.yml
  15 docs/git.md

Branches⚓︎

Create new branch and switch⚓︎

git switch -c <new-branch>

Create new branch from another branch and switch⚓︎

git switch -c <new-branch> <start-point>

Switch back and forth between two different branches⚓︎

git switch -

Update a branch with changes from master⚓︎

git fetch origin master
git rebase origin/master

Reset local branch to match remote⚓︎

git fetch origin
git reset --hard origin/master

Rename a branch⚓︎

git branch -m <new_name>

Rename branch if already pushed to remote⚓︎

git branch -m <new_name>
git push -u origin <new_name>
git push origin --delete <old_name>

Delete a branch⚓︎

git branch -d <branch>

Delete a remote branch⚓︎

git push origin -d <branch>

List all branches and the commit they point to⚓︎

git branch -av

Delete local remote tracking branch if the remote branch has been merged⚓︎

git checkout master
git remote update origin --prune
git branch --merged | grep -v master | xargs git branch -d

Logs⚓︎

Condensed summary⚓︎

git log --summary

Decorated graph⚓︎

git log --oneline --all --decorate --graph

Details of a specific commit⚓︎

git show <commit> [-s]

List of commits per user⚓︎

git shortlog -sne
    16  Kristian Milos <[email protected]>
     2  Goat Man <[email protected]>
     2  Mwark <[email protected]>

List of commits per user after a certain date⚓︎

git shortlog -sne --after 2023-06-01
    16  Kristian Milos <[email protected]>
     2  Goat Man <[email protected]>

Blame⚓︎

Show blame from a specific line in a file⚓︎

This will show a git blame of 10 lines commencing at line 14.

git blame -L 14,+10 -- files/to/blame.txt

Diff⚓︎

Diff from staged files⚓︎

git diff --staged

Show committed files to be pushed to remote⚓︎

git diff --stat --cached <remote/branch>

Diff between local and remote file⚓︎

git diff origin/branchname -- location/of/file.txt

Diff between two branches⚓︎

git diff <branch1>..<branch2>

Diff between file in two different branches⚓︎

git diff <branch1>..<branch2> -- location/of/file.txt

Diff of filenames between two commits/branches⚓︎

git diff --name-only <commit1/branch1> <commit2/branch2>
git diff --name-status <commit1/branch1> <commit2/branch2>
git diff --stat <commit1/branch1> <commit2/branch2>

Diff contents of a single file between two commits⚓︎

git diff 9e9384392 61713c9ac <filename>

Difference between .. and ... in a git diff⚓︎

foo..bar or foo bar⚓︎

git diff foo..bar is the same as git diff foo bar, both will show you the difference between the tips of the two branches foo and bar.

%%{init: { 'theme': 'dark', 'gitGraph': {'mainBranchOrder': 1, 'parallelCommits': true}} }%%
gitGraph:
    commit id: 'a'
    commit id: 'b'
    commit id: 'c'
    branch feature1 order: 0
    branch feature2 order: 2
    switch feature1
    commit id: 'd'
    commit id: 'e'
    commit id: 'foo' type: HIGHLIGHT
    switch feature2
    commit id: 'g'
    commit id: 'h'
    commit id: 'bar' type: HIGHLIGHT

foo...bar or $(git merge-base foo bar) bar⚓︎

git diff foo...bar (or $(git merge-base foo bar) bar) will show you the difference between the "merge base" of the two branches and the tip of bar. The "merge base" is usually the last commit in common between those two branches, so this command will show you the changes that your work on bar has introduced, while ignoring everything that has been done on foo in the meantime.

%%{init: { 'theme': 'dark', 'gitGraph': {'mainBranchOrder': 1, 'parallelCommits': true}} }%%
gitGraph:
    commit id: 'a'
    commit id: 'b'
    commit id: 'merge base' type: HIGHLIGHT
    branch feature1 order: 0
    branch feature2 order: 2
    switch feature1
    commit id: 'd'
    commit id: 'e'
    commit id: 'foo'
    switch feature2
    commit id: 'g'
    commit id: 'h'
    commit id: 'bar' type: HIGHLIGHT

Tags⚓︎

Tag an existing commit⚓︎

git log --pretty=oneline
git tag -m "Package version 1.5.0.22" -a v1.5.0.22 <commit_id>

Push all tags to remotte⚓︎

git push origin --tags

Push a single tag to remote⚓︎

git push origin v1.5.0.22

Delete local tag⚓︎

git tag -d v1.5.0.22

Delete remote tag⚓︎

git push --delete origin v1.5.0.22

Update local tags from remote⚓︎

git fetch --tags --force

Show the commit a tag is pointing to⚓︎

git rev-list -n 1 <tag>

Reset, Restore, & Remove⚓︎

Unstage a file⚓︎

git reset <file>

Undo last commit that hasn't been pushed⚓︎

git reset HEAD^

Discard all branch changes and refresh from remote⚓︎

git reset --hard origin/master

Unstaging a file, but leaving its actual changes untouched⚓︎

git restore --staged myFile.txt

Discard local changes in a certain file⚓︎

git restore myFile.txt

Undo all local changes in the working copy⚓︎

git restore .

Restore any previous version of a specific file⚓︎

git restore --source master index.html
git restore --source 6bcf266b index.html

Remove a staged file⚓︎

git rm <file>

git showing unstaged changes⚓︎

git rm --cached -r .
git reset --hard

Miscellaneous⚓︎

Using git bisect to find bad commits⚓︎

  1. Start the bisect and provide both the 'good' and 'bad' commit hashes.

    $ git bisect start
    status: waiting for both good and bad commits
    $ git bisect good 72f09f
    status: waiting for bad commit, 1 good commit known
    $ git bisect bad fd1bbb5
    Bisecting: 11 revisions left to test after this (roughly 4 steps)
    [adf2e0caec46fa82eea3ae4754fd0bea8d137e3d] fixed shellcheck SC2015 violation
    
  2. Run your tests, marking each failed test as 'bad'.

    $ git bisect bad
    Bisecting: 2 revisions left to test after this (roughly 2 steps)
    [de1031a7d43aeb355482890159f53437094bf7a9] make sure to use /usr/bin/infocmp to probe if tmux-256color is available system wide (3), fixes #618
    $ git bisect bad
    Bisecting: 0 revisions left to test after this (roughly 1 step)
    [044d6336e84034c14760a2fe178f604a89626bfb] make sure to use /usr/bin/infocmp to probe if tmux-256color is available system wide (2), fixes #617
    
  3. When your test passes, mark the commit as 'good'

    $ git bisect good
    044d6336e84034c14760a2fe178f604a89626bfb is the first bad commit
    commit 044d6336e84034c14760a2fe178f604a89626bfb
    Author: Gregory Pakosz <[email protected]>
    Date:   Sat Jan 21 21:57:51 2023 +0100
    
        make sure to use /usr/bin/infocmp to probe if tmux-256color is available system wide (2), fixes #617
    
        some operating systems like NixOS don't have /usr/bin/infocmp
    
    .tmux.conf | 15 ++++++++++++++-
    1 file changed, 14 insertions(+), 1 deletion(-)
    

    The highlighted commit above should be where the bug/issue was introduced.

  4. Once complete, clean up the bisection state and return to the original HEAD.

    $ git bisect reset
    Previous HEAD position was 2cf4d9a make sure to use /usr/bin/infocmp to probe if tmux-256color is available system wide
    Switched to branch 'master'
    Your branch is up to date with 'origin/master'.
    

List of files/blobs sorted by size⚓︎

$ git rev-list --objects --all |
  git cat-file --batch-check='%(objecttype) %(objectname) %(objectsize) %(rest)' |
  sed -n 's/^blob //p' |
  sort --numeric-sort --key=2 |
  cut -c 1-12,41- |
  $(command -v gnumfmt || echo numfmt) --field=2 --to=iec-i --suffix=B --padding=7 --round=nearest
...
...
...
83367b100bdf   50KiB docs/aix.md
e0f87ab30a21   50KiB docs/aix.md
815a3edec4d5   50KiB docs/aix.md
a11c5026a642   53KiB docs/aix.md
bd015cca4288   53KiB docs/aix.md
9c8f5774ab55   56KiB git/index.html
6c8a85661a40   58KiB powerha/index.html
11c2602bcedb   60KiB gpfs/index.html
3b7b4ecfc742   66KiB hmc/index.html

Search for a string in a file across all branches⚓︎

The below script takes two parameters. The first being the search string, and the second being the file to search. On a successful match, the script will print the name of the checked-out branch.

Disk space

The script will checkout every remote branch. In large repositories, this can consume a lot of disk space.

#!/bin/bash
# Search for a string in a file across all branches

if [ "$#" -ne 2 ]; then
    printf "%s\n" "$0 <search string> <file name>"
    exit 1
fi

printf "%s\n\n" "Listing branches containing \"${1}\" in file \"${2}\""

# Fetch all remote branches
git fetch --all

# Loop through each remote branch
git branch -r | awk -F'/' '!/HEAD/{sub(/^[^\/]*\//, ""); print}' | while read -r branch; do
  # Switch to branch
  git switch --quiet "${branch}"

  # Check if the file contains the string
  if git grep --quiet "${1}" -- "${2}"; then
    echo "${branch}"
  fi

  # Checkout back to the original branch
  git switch - --quiet
done

Commit a change to a file in every remote branch⚓︎

Disk space

The script will checkout every remote branch. In large repositories, this can consume a lot of disk space.

#!/bin/bash
# Commit a change to a file in every remote branch

# Fetch all remote branches
git fetch --all

# Loop through each remote branch
git branch -r | awk -F'/' '!/HEAD/ && !/production/ && !/main/{sub(/^[^\/]*\//, ""); print}' | while read -r branch; do
  # Switch to branch
  git switch --quiet "${branch}"

  # Update Puppetfile
  gsed -i '/mod '\''unix\/mwark'\''/,/mod /{//!d;};/mod '\''unix\/mwark'\''/d' Puppetfile
  gsed -i '/mod '\''unix\/goats'\''/,/mod /{//!d;};/mod '\''unix\/goats'\''/d' Puppetfile

  # Commit and push changes (if any)
  if ! git diff --quiet; then
    git add Puppetfile
    git commit -m "Remove unix/mwark and unix/goats modules"
    git push
  fi

  # Checkout back to the original branch
  git switch - --quiet
done

Show ignored files⚓︎

$ git status --short --ignored
 M spec/classes/init_spec.rb
 M spec/classes/xormon_agent_spec.rb
 M spec/spec_helper.rb
?? spec/fixtures/facts/aix-7.2-powerpc.facts
?? spec/fixtures/facts/aix-7.3-powerpc.facts
!! .ruby-lsp/.gitignore
!! .ruby-lsp/Gemfile
!! .ruby-lsp/Gemfile.lock
!! .ruby-lsp/main_lockfile_hash
!! .ruby-lsp/needs_update
!! .vscode/extensions.json
!! Gemfile.lock
!! bin/metadata-json-lint
!! bin/puppet
!! bin/puppet-lint
!! bin/rake
!! bin/rspec
!! bin/rubocop
!! spec/fixtures/manifests/site.pp
!! spec/fixtures/modules/cron_core/
!! spec/fixtures/modules/quadlets/
!! spec/fixtures/modules/stdlib/
!! spec/fixtures/modules/systemd/
!! spec/fixtures/modules/xormon
!! update_report.txt

Find out which .gitignore (global, local, etc...) is causing a file to be ignored⚓︎

$ git check-ignore -v spec/fixtures/facts/aix-7.2-powerpc.facts
/Users/kristian/.gitignore:48:spec/fixtures/*     spec/fixtures/facts/aix-7.2-powerpc.facts