Git usage

From wikinotes

basics

branches

## branches
git branch        f-mynewfeature      ## create branch
git checkout      f-mynewfeature      ## checkout branch
git checkout  HEAD --force            ## checkout branch, 
git checkout $(git merge-base @ origin/HEAD)  # checkout commit before branch start 
git remote show origin | grep 'HEAD branch' | cut -wf4  # get name of master/main branch

files

## files
git add                 path/to/file  ## add file to branch's stage
git add      --all      :/            ## add all changed files to branch's stage

git checkout HEAD       path/to/file  ## checkout a file from an older commit
git checkout f-mybranch path/to/file  ## checkout a file from an older commit

git commit                            ## commit current stage to branch
git commit   --amend                  ## add current changes to last commit
git rm                  path/to/file  ## remove file from git
git mv    path/to/file  path/to/other ## move a file (history continues to track file)

stage

git checkout HEAD            path/to/file
git checkout f-mynewfeature  path/to/file
git add                      path/to/file
git add      --all           :/
git commit
git push

git diff     HEAD..HEAD~2    path/to/file
git clean    -fd                            # remove untracked files

stash

git stash
  # what to stash
  -au `# all changes`
  -S  `# only-staged changes`
  -k  `# stash, but keep index`
  # optional message
  -m  'msg' `# description of stash`

git stash list
git stash pop
git stash drop 0

log

## log
git log
git log --oneline
git log --abbrev-commit                      # use short commit hashes
git log --grep  'Deprecated'                 # search commit messages for 'Deprecated'
git log -G 'def foo'                         # search commit diffs for changes involving 'def foo'
git log /path/to/file
git log -L${start_ln},${end_ln}:${filepath}  # view log for all changes to this selection of lines
git log -L${function}:${filepath}
git log --all --full-history -- ${filepath}          # view log for a deleted file
git log --format="%H" --full-history -- ${filepath}  # list commits for a deleted file
git log -p --full-history --no-merges -- ${filepath} # list commits with diffs for a deleted file

status

## status
git status
git diff

clone

git clone https://github/blah.git
git clone ssh://git@server:8888/path/to/repo
git clone ssh://server:/path/to/repo          ## NOTE: if you have configured your ~/.ssh/config with all info, you only need to specify machine
git clone file:////home/you/src
git clone --depth=1 https://github.com/user/blah  # clone only latest checkout

push

git push $remote $commit:$remote_branch           # push specific commit to branch
git push -f -u origin local_branch:remote_branch  # replace remote-branch with a different local-branch

pull

git pull  # pull changes from branch

# set local head to match a rebased remote-branch
git fetch
git reset --hard origin/master

# convert shallow clone to full clone
git fetch --unshallow     # this
git fetch --depth=10000   # or this

submodules

# Adding a submodule to a project
cd  ../my_git_project
git submodule add ssh://gitbox:/home/git/_python/supercli
git submodule update --init

# manage submodules
git submodule update  # update to recorded revision
git submodule deinit  # un-clone dir

# remove submodule
vim .gitmodules                 # remove your submodule
git config --edit                # remove your submodule
git rm --cached your/submodule  # remove your submodule's path
git submodule sync

multiple urls for a submodule:

#### .git/modules/..submodule_name.../config
[remote "origin"]
   url = ssh://user@box1:2222/path/to/repo
   url = ssh://user@box2:2222/path/to/other/repo
####

patches

Create patch from unstaged changes

# in a repo with unstaged changes
cd /path/to/projectroot
git diff --binary > description.patch

cd /path/to/other/projectroot
git apply description.patch

Squash all differences between branches into a patch

git merge needs_changes has_changes  # VERY IMPORTANT!
git checkout -b deleteme has_changes
git reset --soft needs_changes
git diff --binary HEAD > ~/description.patch
git checkout -b recv_patch needs_changes
git apply ~/description.patch

search

# ==========
# Searching
# ==========
git log \
  -p \                  # print diff
  -m \                  # include merge commit diffs
  -G '\$\(document\)' \ # regex commit+diff
  --abbrev-commit \     # short commit hashes
  --name-only \         # when used with `-p`, only filenames are shown instead of full diffs


git log -G "regex"                         # search commits, whose text, or diff contains this regex
git log --abbrev-commit -p -m -G "regex"   # seearch log, show diffs, (even commit diffs), matching regex
git log --abbrev-commit -p -m -- file.txt  # show commits/diffs for all commits related to file

git rev-list --all | xargs git grep <regex>      # search all changesets for keyword
git grep <regex> $(git rev-list --max-count=50)  # search last 50 changesets

# show all commits involving file
gitk    --all --first-parent --remotes --reflog --author-date-order -- filename
git log --all --first-parent --remotes --reflog --author-date-order -- filename

# show all commits involving file, including diffs for merge commits
for f in `git rev-list --all --first-parent --remotes --reflog --author-date-order -- your_filename.rb | tr '\n' ' '`; do echo "========\n$f\n========"; git show -m --color=always $f | cat  ; done | less -Ri

# ============
# Show Changes
# ============
git show aaaaaaaa       # show commit message/changes (except merges)
git show -U 30 aaaaaaaa # show commit message/changes and 30 lines surrounding
git diff aaaaaaaa^!     # show changes (always works)

tags

You can tag releases of your git repository with versions so that they are easy to revert.

Listing Tags

git tag            # list all tags
git tag -l '1.*'   # list matching tags

Creating Tags

# create lightweight tag 1.1.1
git tag 1.1.1

# create annotated tag 
# (a real git commit, can be GPG signed, has commit message)  
git tag \
  -a 1.1.1 \
  -m "my released 1.1.1"

Tag Info

git show 1.1.1  # print info about tag 1.1.1

show

git show master:/path/to/file.rb  # show file without changing branches
git show 1.1.1                    # show tag 1.1.1

commits

git diff ${COMMIT}~ ${COMMIT}                          # show changes introduced by a commit (since commit before it)
git rev-list -1 --before='2021-08-16T18:00:00' master  # find commit(s) before a specific datetime

git cherry-pick ${oldest_commit}^..${newest_commit}    # cherry-pick a range of commits

diff

# print all files changed on branch
{
  git diff main... --name-only \
    && git diff --staged --name-only \
    && git diff HEAD --name-only; 
}

difftool

Pro tip, you can use vimdiff as a diff tool

git difftool --tool vimdiff2

Within vim you can use:

  • 1,20diffput to put everything from line 1-20 into the other buffer
  • do pull block from other side
  • dp push block to other side

Advanced

filter-branch

WARNING:

git's documentation says not to use this command.

use git-filter-repo instead.

Remove target file anywhere it is found in entire git history.

git filter-branch \
  -f \
  --index-filter 'git rm --cached --ignore-unmatch *.sql'

git filter-branch \
  -d /your/temp/workdir  `# temporary working dir for filtered branch` \
  --prune-empty          `# delete commits that are now empty as result of oper` \
  --index-filter         `# executes command on every commit` \
      "git rm --cached -f --ignore-unmatch oops.iso"   \
  --tag-name-filter cat  `# rename tags (references to files) so history is consistent` \
  -- --all               `# run this on every commit in the entire git history`

See

rebase

See https://git-rebase.io/

git log --nameonly origin/master..HEAD   # count number of log entries.
git rebase -i HEAD~3                     # rebase last 3 commits (1-indexed)
  • editor will open. Change letter in front of each commit to desired operation
  • change commit, then git rebase --continue to continue working through commits

to split up a commit

git rebase -i HEAD~1
# 'e' in front of commit
git reset HEAD^

# now git add, or use git-gui to add lines you want in your new commits

If you've accidentally dropped commits, find them again with git reflog

git reflog                    # find commit by checking it out, search code
git checkout my-branch
git reset --hard 1258f0d0aae  # set your branch to point to this commit

blame

git blame helps you find who wrote a section of a file, so you can ask them for details about how it works.

git blame filename          # find last author to modify any line in file
git blame filename -L 0,10  # find last author to modify lines 0-10

revert

# before push

# after push (revert branch)
git revert HEAD~5..HEAD  # revert everything from last 5x commits in single commit

bisect

Start a git bisect, and indicate range

git bisect start
git bisect bad   [commit]  # indicate known bad commit (defaults to current)
git bisect good  [commit]  # indicate known good commit (defaults to current)

Git will then continuously check midpoints and you indicate if the commit is good or bad

git bisect good  # mark as working
git bisect bad   # mark as not working

Eventually, it will inform you the branch where the regression was introduced.
Use git show to see the changes introduced.


If your regression is scriptable, you can have git automatically perform the checks (using the exit-code to identify good commits)

# git bisect start... etc
git bisect run testme.sh

Workflows

Tracking commits ahead/behind

git log --oneline @{u}.. | wc -l  # num commits ahead
git log --oneline ..@{u} | wc -l  # num commits behind

Replace History With another Repo

Say you want to publish a private repo to github once you feel good about it.

# clone the git repo whose history you want to replace
git remote add home git://foo.com/repo
git pull home master --allow-unrelated-histories
git update-ref -d HEAD
git checkout HEAD --force
git clean -fd

merge conflict

git merge ${BRANCH}
git diff ORIG_HEAD MERGE_HEAD file.txt  # show diff of conflict

squash commit history (rolling N commits)

Untested, but considering for some high traffic repos keeping a rolling log of say 15 commits.

See https://stackoverflow.com/questions/1657017/how-to-squash-all-git-commits-into-one

# set head at beginning of history, retaining changes from all commits
git reset $(git commit-tree HEAD^{tree} -m "A new start")