Files
oam/knowledge base/git.md
2022-04-23 12:09:56 +02:00

20 KiB

Git-related useful commands

TL;DR

# Set your identity.
git config user.name "User Name"
git config --global user.email user@email.com

# Avoid issues when collaborating from different platforms.
git config --local core.autocrlf 'input'
git config --local core.autocrlf 'true'

# Create aliases.
git config --local alias.co checkout
git config --global alias.unstage 'reset HEAD --'

# Show git's configuration.
git config --list
git config --list --show-scope
git config --list --show-origin

# Render all current settings' values.
git config --list \
  | awk -F '=' '{print $1}' | uniq \
  | xargs -I {} sh -c 'printf "{}=" && git config --get {}'

# Create or reinitialize a repository.
git init
git init --initial-branch main path/to/repo
git init --bare path/to/repo.git

# Clone a repository.
git clone https://github.com:user/repo.git
git clone git@github.com:user/repo.git path/to/clone
git clone --recurse-submodules ssh@git.server:user/repo.git

# Show what files changed.
git status
git status --verbose

# Stage changes for commit.
git add .
git add --all
git add path/to/file

# Interactively review chunks of changes.
git add --patch path/to/file

# Show changes.
git diff
git diff --staged commit
git diff commit1..commit2
git diff branch1 branch2
git diff --word-diff=color

# Stash changes locally.
git stash

# Stash changes with a message.
git stash save "message"

# List all the stashed changes.
git stash list

# Apply the most recent change and remove them from the stash stack.
git stash pop

# Apply a stash, but don't remove it from the stack.
git stash apply stash@{6}

# List remotes.
git remote --verbose

# Add a new remote.
git remote add gitlab git@gitlab.com:user/repo.git

# Create a branch.
git branch new-branch
git switch -c new-branch
git checkout -b new-local-branch remote/existing-branch

# Rename a branch.
git branch --move old-name new-name

# Switch branches.
git switch branch
git checkout branch
git checkout -

# Get the current branch.
git branch --show-current         # git > v2.22
git rev-parse --abbrev-ref HEAD

# Delete local branches.
git branch --delete local-branch
git branch -D local-branch

# Delete remote branches.
git push origin :remote-branch
git push origin --delete remote-branch

# Delete both local and remote branches.
git branch --delete --remotes branch

# Sync the local branch list.
git fetch --prune

# Rebase the 'main' branch on top of the current branch.
git rebase main

# Rebase an upstream branch on top of a local branch.
git rebase remote/upstream-branch local-branch

# Rebase the current branch onto the *upstream* 'master' branch.
git pull --rebase=interactive origin master

# Commit changes.
git commit --message "message"
git commit --message "whatever" --gpg-sign
git commit --allow-empty --allow-empty-message
git commit --date="Jun 13 18:30:25 IST 2015"
git commit --date="`date --date='2 days ago'`"

# Edit the last commit's message.
git commit --amend

# Push committed changes.
git push
git push github gitlab
git push --all

# Create patches.
git diff > file.patch
git diff --output file.patch --cached
git format-patch -5 commit
git format-patch HEAD~3 -o dir
git format-patch HEAD~2 --stdout > single.patch

# Create a full patch of the unstaged changes.
git add . && git commit -m 'uncommitted' \
  && git format-patch HEAD~1 && git reset HEAD~1

# Apply a patch to the current index.
git apply file.patch

# Apply commits from a patch.
git am file.patch

# Change the last commit's author.
git config user.name "user name"
git config user.email user.email@mail.com
git commit --amend --reset-author

# Sign all commits from now on.
git config --global user.signingkey 'KEY_ID_IN_SHORT_FORMAT'
git config --local commit.gpgsign true

# List all tags.
git tag

# Create annotated tags.
git tag --annotate v0.1.0
git tag -as v1.2.0-r0 -m "signed annotated tag for v1.2.0 release 0"
git tag -a 1.1.9 9fceb02

# Create lightweight tags.
git tag v0.1.1-rc0
git tag 1.12.1 HEAD

# Push specific tags.
git push origin v1.5

# Push annotated tags only
git push --follow-tags

# Push all tags
git push --tags

# Visualize the repository's history.
git log --graph --full-history --all --color --decorate --oneline

# Delete local tags.
git tag -d v1.4-lw

# Delete remote tags.
git push origin --delete v1.4-lw

# Sync the local tags list.
git fetch --prune-tags

# Get the top-level directory of the current repository.
git rev-parse --show-toplevel

# Remove merged branches.
git fetch -p && awk '/origin/&&/gone/{print $1}' <(git branch -vv) \
  | xargs git branch -d

Configuration

# Required to be able to commit changes.
git config --local user.email 'me@me.info'
git config --local user.name 'Me'

# Avoid issues when collaborating from different platforms.
# 'input' on unix, 'true' on windows, 'false' only if you know what you are doing.
git config --local core.autocrlf 'input'

# Sign commits by default.
# Get the GPG key short ID with `gpg --list-keys --keyid-format short`.
git config --local user.signingkey 'KEY_ID_IN_SHORT_FORMAT'
git config --local commit.gpgsign true

# Pull submodules by default.
git config --global submodule.recurse true

To show the current configuration use the --list option:

git config --list
git config --list --show-scope
git config --list --global --show-origin

The configuration is shown in full for the requested scope (or all if not specified), but it might include the same setting multiple times if it shows up in multiple scopes.
Render the current value of a setting using the --get option:

# Get the current user.name value.
git config --get user.name

# Render all current settings' values.
# Gets the settings names, then requests the current value for each.
git config --list \
  | awk -F '=' '{print $1}' | sort -u \
  | xargs -I {} sh -c 'printf "{}=" && git config --get {}'

Manage changes

# Show changes relative to the current index (not yet staged).
git diff

# Show changes in the staged files only
git diff --staged

# Show changes relative to 'commit' (defaults to HEAD if not given)
# Alias of `--staged`
git diff --cached commit

# Show changes relative to 'branch'
git diff branch

# Show changes between commits
# Separating the commits with `..` is optional
git diff commit1 commit2

# Show changes between branches
# Separating the branches with `..` is optional
git diff branch1 branch2

# Show a word diff using 'mode' to delimit changed words for emphasis
# 'mode' defaults to 'plain'
# 'mode' must be one of 'color', 'none', 'plain' or 'porcelain'
git diff --word-diff=porcelain

Create a patch

Just save the output from git diff to get a patch file:

# Just the current changes.
# No staged nor committed files.
git diff > file.patch

# Staged files only.
git diff --output file.patch --cached 

The output from git diff just shows changes to text files by default, no metadata or other information about commits or branches.
To get a whole commit with all its metadata and binary changes, use git format-patch:

# Include 5 commits starting with 'commit' and going backwards.
git format-patch -5 commit

# Include 3 commits starting from HEAD and save the patches in 'dir'.
git format-patch HEAD~3 -o dir

# Include 2 commits from HEAD and save them as a single file.
git format-patch HEAD~2 --stdout > single.patch

# Create a full patch of the unstaged changes.
git add . && git commit -m 'uncommitted' \
  && git format-patch HEAD~1 && git reset HEAD~1

Apply a patch

Use git apply to apply a patch file to the current index:

git apply file.patch

The changes from the patch are unstaged and no commits are created.
To apply all commits from a patch, use git am on a patch created with git format-patch:

git am file.patch

The commits are applied one after the other and registered in the repository's logs.

The stash stack

The stash is a changelist separated from the one in the current working directory.
git stash will save the current changes there and cleans the working directory. You can (re-)apply changes from the stash at any time:

# Stash changes locally.
git stash

# Stash changes with a message.
git stash save "message"

# List all the stashed changes.
git stash list

# Apply the most recent change and remove them from the stash stack.
git stash pop

# Apply a stash, but don't remove it from the stack.
git stash apply stash@{6}

Branches

Checkout an existing remote branch

This creates a local branch tracking an existing remote branch.

$ git checkout -b local-branch remote/existing-branch
Branch 'local-branch' set up to track remote branch 'existing-branch' from 'remote'.
Switched to a new branch 'local-branch'

Delete a branch

# Delete local branches.
git branch --delete local-branch
git branch -D local-branch

# Delete remote branches.
git push origin :feat-branch
git push origin --delete feat-branch

# Delete both local and remote branches.
git branch --delete --remotes feat-branch

Delete all branches already merged on master

Already present in oh-my-zsh's git module as the gbda alias

Command source [here][prune local branches that do not exist on remote anymore].

git fetch -p && awk '/origin/&&/gone/{print $1}' <(git branch -vv) | xargs git branch -d
git branch --no-color --merged | command grep -vE "^(\*|\s*(master|develop|dev)\s*$)" | command xargs -n 1 git branch -d

Merge the master branch into a feature branch

git stash pull
git checkout master
git pull
git checkout feature
git pull
git merge --no-ff master
git stash pop
git checkout feature
git pull origin master

Rebase a branch on top of another

git rebase takes the commits in a branch and appends them on top of the commits in a different branch. The commits to rebase are previously saved into a temporary area and then reapplied to the new branch, one by one, in order.

# Rebase main on top of the current branch.
git rebase main

# Rebase an upstream branch on top of a local branch.
git rebase remote/upstream-branch local-branch

# Rebase the current branch onto the *upstream* 'master' branch.
git pull --rebase=interactive origin master

Tags

Annotated tags are stored as full objects in git's database:

# Create annotated tags.
git tag --annotate v0.1.0

# Create and sign annotated tags.
git tag -as v1.2.0-r0 -m "signed annotated tag for v1.2.0 release 0"

# Tag specific commits.
git tag -a 1.1.9 9fceb02

# Push all annotated tags only.
git push --follow-tags

while lightweight tags are stored as a pointer to a specific commit:

# Create lightweight tags.
git tag v0.1.1-rc0
git tag 1.12.1 HEAD

Type-generic tag operations:

# Push specific tags.
git push origin v1.5

# Push all tags
git push --tags

# Delete specific local tags only.
git tag -d v1.4-lw

# Delete specific remote tags only.
git push origin --delete v1.4-lw

LFS

  1. install the LFS extension for git

    # Ubuntu
    apt install git-lfs
    
  2. enable the extension in the repository

    $ cd "${REPOSITORY}"
    [repository-root]$ git install lfs
    
  3. configure file tracking

    [repository-root]$ git lfs track "*.exe"
    [repository-root]$ git lfs track "enormous_file.*"
    
  • add the .gitattributes file to the traced files

    [repository-root]$ git add .gitattributes
    [repository-root]$ git commit -m "lfs configured"
    

Submodules

See Git Submodules: Adding, Using, Removing, Updating.

  • add a submodule to an existing repository:

    git submodule add https://github.com/ohmyzsh/ohmyzsh lib/ohmyzsh
    
  • clone a repository with submodules:

    git clone --recursive keybase://public/bananas/dotfiles
    git clone --recurse-submodules ohmyzsh keybase://public/bananas/dotfiles
    
  • update an existing repository with submodules:

    git pull --recurse-submodules
    

To delete a submodule the procedure is more complicated:

  1. de-init the submodule:

    git submodule deinit lib/ohmyzsh
    

    this wil also remove the entry from $REPO_ROOT/.git/config

  2. remove the submodule from the index:

    git rm -rf lib/ohmyzsh
    

    this wil also remove the entry from $REPO_ROOT/.gitmodules

  3. commit the changes

Crypt

FIXME

Remove a file from a commit

See remove files from git commit.

Remove a file from the repository

  1. unstage the file using git reset specify the HEAD as source

    git reset HEAD superSecretFile
    
  2. remove it from the index using git rm with the --cached option

    git rm --cached superSecretFile
    
  3. check the file is no longer in the index

    $ git ls-files | grep superSecretFile
    $
    
  4. add it to .gitignore or remove it from the disk

  5. amend the most recent commit from your repository

    git commit --amend
    

Remotes management

# add a remote
git remote add gitlab git@gitlab.com:user/my-awesome-repo.git

# add other push urls to an existing remote
git remote set-url --push --add origin https://exampleuser@example.com/path/to/repo1

# change a remote
git remote set-url origin git@github.com:user/new-repo-name.git

Push to multiple git remotes with the one command

To always push to repo1, repo2, and repo3, but always pull only from repo1, set up the remote 'origin' as follows:

git remote add origin https://exampleuser@example.com/path/to/repo1
git remote set-url --push --add origin https://exampleuser@example.com/path/to/repo1
git remote set-url --push --add origin https://exampleuser@example.com/path/to/repo2
git remote set-url --push --add origin https://exampleuser@example.com/path/to/repo3
[remote "origin"]
    url = https://exampleuser@example.com/path/to/repo1
    pushurl = https://exampleuser@example.com/path/to/repo1
    pushurl = https://exampleuser@example.com/path/to/repo2
    pushurl = https://exampleuser@example.com/path/to/repo3
    fetch = +refs/heads/*:refs/remotes/origin/*

To only pull from repo1 but push to repo1 and repo2 for a specific branch specialBranch:

[remote "origin"]
    url = ssh://git@aaa.xxx.com:7999/yyy/repo1.git
    fetch = +refs/heads/*:refs/remotes/origin/*
    ...
[remote "specialRemote"]
    url = ssh://git@aaa.xxx.com:7999/yyy/repo1.git
    pushurl = ssh://git@aaa.xxx.com:7999/yyy/repo1.git
    pushurl = ssh://git@aaa.xxx.com:7999/yyy/repo2.git
    fetch = +refs/heads/*:refs/remotes/origin/*
    ...
[branch "specialBranch"]
    remote = origin
    pushRemote = specialRemote
    ...

See https://git-scm.com/docs/git-config#git-config-branchltnamegtremote.

Troubleshooting

Debug

When everything else fails, enable tracing:

export GIT_TRACE=1

GPG cannot sign a commit

error: gpg failed to sign the data
fatal: failed to write commit object

If gnupg2 and gpg-agent 2.x are used, be sure to set the environment variable GPG_TTY, specially zsh users with Powerlevel10k with Instant Prompt enabled.

export GPG_TTY=$(tty)

Further readings