Skip to content

Latest commit

 

History

History
1035 lines (883 loc) · 34.3 KB

git-notes.org

File metadata and controls

1035 lines (883 loc) · 34.3 KB

Notes on using Git

-*- mode: org; comment-column: 0; -*-

This file contains a list of git commands that I’ve found handy in situations I’ve faced over time. These are commands that I have used (successfully). I’m not claiming that this is the only way or even the right way to handle a particular situation that you may face. YMMV.

Along with commands, there are also some general git patterns that I have noted down. I hope this file is of help to you, or at least teaches you something about git that you didn’t know beforehand

The commands are ordered by type rather than difficulty, so you are going to find intermediate and slightly advanced commands intermingled with the easy ones. I mean for this document to be used as a cheat-sheet, not a tutorial, but if you are confused about something shoot me a mail

Basics: Adding a remote to git repo

Initialize a git repository

git init
  

My recommendation is to always create an empty commit after your initialize the repo.

Create an empty commit

git commit -m "Empty initial commit" --allow-empty
  

This trick is useful when initializing the git repository, since the initial commit has special properties which stop it from being used in rebase operations.

Mark everything in the current dir for committing to git

git add .
  

Create a git commit

git commit -m
  

Add a new remote (public url) to your git repository

git remote add origin [email protected]:vedang/reponame.git
  

Branching

The default commit is HEAD.

git checkout -b <branchname> <commit>
  

Delete a branch

git checkout -d <branchname>
  

Rename a branch

git checkout -m <branchname> <newbranchname>
  

Merge branch <branchname> with current branch

git merge <branchname>
  

Push a local branch to remote

git push <remotename> <localbranchname>:<remotebranchname>
  

Delete branch on remote. Literally, push nothing into <branchname>

git push <remotename> :<branchname>
  

Create an empty branch

git symbolic-ref HEAD refs/heads/newbranch
rm .git/index
git clean -fdx
  

There is a much simpler way to do this in modern Git (above v1.7.2). This is as follows:

git checkout --orphan newbranch
git rm -rf .
  

Does my branch have the latest master?

git branch -r --merged origin/branchname | rg origin/master
  

Was my branch merged into master?

     git branch -r --merged origin/master | rg origin/branchname

** Create a separate repository from a range of commits restricted to a sub-directory
:PROPERTIES:
:CREATED:  [2022-03-21 Mon 13:20]
:ID:       6baa94fc-2e01-4fe1-b6d0-6f43a5aa0183
:END:
     git filter-branch --subdirectory-filter trunk HEAD
     git remote add neworigin /path/to/trunk
     git push neworigin master
  

Basically, what this snippet is doing is the following:

  • Create a new repository from a sub-directory named trunk.
  • Automatically remove commits that did not affect the sub-directory

This can also be done using git-subtree on git 1.7.11+ Refer to Subtree (1.7.11+)

Getting back original master

git checkout masters_original_HEAD
git checkout -b tmp
git merge -s ours master
git checkout master
git merge tmp
git reset --soft HEAD^
  

The final step removes the merge-commit and reverts the local repo back to state before the filter-branch.

Change the upstream target for a branch

git checkout <branch_name>
git branch --set-upstream-to=origin/<branch_name>
  

Merging and Rebasing

$ git merge -s recursive -Xours <branchname>

$ git merge -s recursive -Xtheirs <branchname>

$ git checkout develop $ git merge -s ours master $ git checkout master $ git merge develop

[~/testing (develop *+|MERGING u+7)] $ git status

#

#

#

#

#

$ git checkout –ours README.txt $ git add README.txt $ git commit

$ git remote add -f projA /path/to/projA $ git merge -s ours –no-commit projA/master $ git read-tree –prefix=subdirA/ -u projA/master $ git commit -m “merging projA into subdirA”

$ git rebase -p <branchname>

$ git merge-base <branch1> <branch2>

#

$ git revert M -m 2

Tagging

$ git tag -u <key-id> <tagname> <commitid>

$ git push <remotename> <tagname>

$ git push <remotename> <branchname> –tags

$ git tag -s <tagname> <commitid>

$ git fetch –tags origin

$ git tag -l

$ git tag –list –sort=version:refname

$ git tag -a

$ git describe –match v*

$ git describe –tags `git rev-list –tags –max-count=1`

$ git show v2.5:fs/locks.c

$ git rev-parse name

$ git tag -d 12345

$ git push origin :refs/tags/12345

$ git tag –contains <SHA>

Listing and Display

$ git diff –name-only rev1 rev2

$ git show –pretty=”format:” –name-only <commitid>

$ git log –pretty=oneline

$ git log –author=foo

$ git log –pretty=format:’%C(bold red)%h%Creset -%C(bold yellow)%d%Creset %s %C(bold green)(%cr)%Creset %C(bold blue)[%an]%Creset’ –graph

$ git log –author=$USER –format=”- %B” –since=-7days –reverse

$ git diff –name-status master..branch

$ git log -p <filename>

$ git shortlog master..branch

$ git log –left-right –graph –cherry-pick –oneline branch1…branch2

$ git log –branches –not –remotes

$ git log –pretty=format:’%C(bold red)%h%Creset -%C(bold yellow)%d%Creset %s %C(bold green)(%cr)%Creset %C(bold blue)[%an]%Creset’ –graph

$ git log –branches –not –remotes –simplify-by-decoration –decorate –oneline

$ git log @{u}..

$ git diff origin/master..HEAD

$ git diff rev1:filename rev2:filename

Patches and Pull Requests

Create a patch between two ids

git diff [commit-id-before] [commit-id-after] > my.patch

A better approach is to Create a ready-to-send patch with the last 3 commits

Create a ready-to-send patch with the last 3 commits

git format-patch -3

Note: In Magit, creating patches is tied to the W key.

Create a patch of the commits present in this branch but not in master

git format-patch master --stdout > diff-with-master.patch
  

Send a patch to the mailing list using git-send-email

This is surprisingly simpler than I thought it would be. Here is an excellent guide: https://git-send-email.io/, maintained by sourcehut.

The important thing to remember is to keep the email entirely in plain-text.

Apply commits selectively from one branch to another.

git format-patch -k -s --stdout R1..R2 | git am -3 -k
  

Here, the flags mean the following:

  • -k: keep subject
  • -s: signoff
  • -3: use diff3 in case of conflict

There is also an easier way of doing this:

git cherry-pick R1..R2
  

Check the stats of the patch

git apply --stat diff-with-master.patch
  

Check if the patch will apply cleanly

git apply --check diff-with-master.patch
  

Apply the patch to the master branch

git am -3 --signoff < diff-with-master.patch
  

Create a beautiful pull request for submitting to another repository

The aim here is to inform another author of your public repository, what they can find there and what they should do to pull those changes into their own repository. The way to do this:
  • Push all the changes to your public repository, preferably in a well-named branch.
  • Run the following command to generate a well-formatted message:
    git request-pull master https://github.com/vedang/pdf-tools/ feature/render-improvements
        
    • Here, master is the upstream branch that you want to generate a PR against.
    • https://github.com/vedang/pdf-tools/ is your public repository
    • feature/render-improvements is your branch (which contains the changes you want to submit)

Generating release notes for your open source work

  • Generate the pull request as shown above
    git request-pull master https://github.com/vedang/clj_fdb/ dev
          
  • Copy the changes as mentioned in the output above
  • Create a changelogs file.

Submodules

Adding another repository as a submodule

git submodule add path_to_git_repo local_dir &&
git submodule init &&
git submodule update;
  

Pulling from the remote and updating a submodule

  • Git submodule update keeps the submodule in headless state. When you want to bring the submodule up-to-date, remember to checkout to a branch first.
    • NOTE: This is not true with the latest versions of git. These versions will checkout the default branch in the submodule for you
  • Updating all submodules while pulling upstream changes:
    1. Get all the changes
      git pull --recurse-submodules #requires git 1.7.3+
              
    2. Checkout the proper SHA-1
      git submodule update --recursive
              

Getting all the submodules in a repository while cloning it

git clone --recursive <path-to-remote-repo>

Deleting a Submodule

  1. Delete the relevant section from the .gitmodules file.
  2. Delete the relevant section from .git/config.
  3. Run:
    git rm --cached path_to_submodule # (no trailing slash)
        
  4. Commit and delete the now untracked submodule files.

Subtree (1.7.11+)

  • The primary difference between submodules and subtrees is in the intent behind their usage.
    • If you are adding your own child repo to a parent repo, and you plan to actively develop the child repo from inside the parent repo, use submodules.
    • If you are adding an external child repo to a parent repo, and you never plan to contribute changes back to the child repo from inside the parent repo, use subtrees.
  • Submodules : a pointer to a specific commit in another repository
    • This means its trivial to push changes back, but you have to be careful about pulling changes. The submodule is an entirely different repo.
  • Subtrees: a full copy of a repository pulled into a parent repository.
    • This means it’s trivial to pull changes, but you have to be careful about pushing changes. The subtree is a copy of a different repo, we don’t know anything about that repo in the parent. We only benefit from having the content.

Adding a repository as a subtree to your current repository

git subtree add --prefix=path/in/curr/repo --squash \
    git://github.com/yourname/your-repo.git master
  

Pulling changes from subtree

git subtree pull --prefix=path/in/curr/repo --squash \
    git://github.com/yourname/your-repo.git master
  

Pushing the changes that you have made in a subtree back upstream

git subtree split --prefix=subdir/ --annotate='(split)' -b split_dir_latest
git push [email protected]:yourname/your-repo.git split_dir_latest:master
  

Git subtree guarantees that previously seen commits with retain the same SHAs which makes this operation relatively straightforward.

Splitting out code from an existing repository into a new repository

git subtree split --prefix=subdir/ --annotate='(split)' -b split_branch
git push [email protected]:yourname/your-repo.git split_branch:master
  

Worktree (2.5+)

Use Git Worktrees for working on multiple things in parallel

Use Git Worktrees (2.5+) for working on multiple things in parallel. Suppose I want to run tests on the work I’ve done on feature/s
[v@hm ~/src/m (feature/s $ u+1)]
$ git worktree add -b s-tests ../mtesting feature/s
Enter ../mtesting (identifier mtesting)
Branch s-tests set up to track local branch feature/s by rebasing.
Switched to a new branch 's-tests'

[v@hm ~/src/m (feature/s $ u+1)]
$ cd ../mtesting

Voila! I have a new directory with a new branch that is linked to my original repository!

[v@hm ~/src/mtesting (s-tests $ u=)]
$ make testclj

# Now I can start my tests here and go back to my work in the original
# repository without worrying about breaking anything!
[v@hm ~/src/mtesting (s-tests $ u=)]
$ cd ../m
[v@hm ~/src/m (feature/s $ u+1)]
$

List all existing git worktrees

git worktree list

Using Joe Armstrong’s method for re-writing from scratch with git-worktrees

  1. Create the following folder structure to get started. For example, here we are developing the automerge-clj project.
    mkdir -p [work-dir]/automerge-clj/v1/
    cd !$
    clojure -Tnew lib :name me.vedang/automerge-clj
    cd automerge-clj
    git init
        
  2. In the step above, the clojure new command creates another automerge-clj directory inside our v1 directory. This is going to be the master repo that will drive our prototyping.
  3. Create an Initial commit of the bare-minimum skeleton. This gives us a good starting point to come back to.
  4. Now we make as many commits in automerge-clj/v1/automerge-clj as we want.
  5. Once we feel the need to start a new prototype
    cd [work-dir]/automerge-clj/v1/automerge-clj
    git worktree add ../../v2
    cd ../../v2 # Here, git will have automatically created a new branch for us, called v2.
        
  6. We need to create an orphaned branch at the starting point of the repo in order to start from scratch again. You can also do a git reset --hard with the rev-list command to start from a previous commit.
    1. To start from scratch, discarding even the initial commit:
      git checkout --orphan auto-v2
      git rm -rf .
              
    2. To reset the current branch to a previous commit
      git reset --hard $(git rev-list HEAD | tail -n 1)
              
  7. Caveat: note that it’s v1/automerge-clj/deps.edn (for example) vs just v2/deps.edn. This is a gotcha but I don’t bother working around it. I find I get used to it almost immediately.
  8. When done with prototypes, or when you want to clean up space, you can just
    # from the "main" v1/project directory
    git worktree list
    git worktree remove path
        

Adding, Removing & Committing

Writing a good commit message

The commit message is your note to yourself, about what your snapshot contains. Refer to a Note about Commit Messages by Tim Pope on how to write a good commit message. I write the following in my messages:
  1. Brief explanation of what I’ve done and how I’ve tested it.
  2. Brief note on why I’ve done it this way.
  3. (Sometimes) list of pending stuff that I need to do as next steps / to complete the task / to add to the functionality.

Interactively stage changes to file

git add -p

Selectively apply a commit from one branch to another

git cherry-pick <commit-id>

Reuse the commit message used in the latest commit, during amend

So that you don’t have to retype the commit message
git commit --amend -C HEAD

Delete all untracked files in the repository

# -d : delete untracked directories
# -x : delete ignored files.
# -f : force. This command is disabled otherwise.
git clean -dxf <path>

Selectively copy a file from a different branch into the current branch

git checkout <branch_name> <filename>

Debugging, Recovery & Undoing

$ git bisect start $ git bisect good v2.6.18 $ git bisect bad master

$ git bisect good # or $ git bisect bad

$ git bisect reset

$ git bisect run my_script

$ git fsck –lost-found –no-reflog

git reflog

$ gitk v1.5.0.. <filenames/directorynames>

$ gitk –since=”2 weeks ago” – <filename>

$ gitk –max-count=100 –all – <filename>

$ gitk –all $( git fsck –no-reflog | awk ’dangling commit {print $3}’ )

$ git reset –soft HEAD@{1}

$ git commit -C HEAD@{1}

$ git reset –soft HEAD^1

$ git stash

$ git pull origin <branchname>

$ git stash pop

$ git log -g # Walks reflog and shows you a history of your actions

$ git reflog show –no-abbrev <branch name>

$ git checkout -m <file>

Searching

Search for regex in the entire repo.

git grep regex
  

Search for search_regex only in files that match file_regex

git grep search_regex -- 'file_regex'
  

Find all the refs (tags, branches) that a given commit belongs to

Note that this is only available for Git versions 2.7 and above
git for-each-ref --contains SHA1
  

Misc

$ git apply –whitespace=fix

$ git filter-branch –commit-filter ’ if [ “$GIT_AUTHOR_EMAIL” = “original_email_address” ]; then GIT_AUTHOR_NAME=”FirstName LastName”; GIT_AUTHOR_EMAIL=”new_email_address”; git commit-tree “$@”; else git commit-tree “$@”; fi’ HEAD

$ git commit –amend –reset-author

$ git daemon –reuseaddr –verbose –base-path=. –export-all ./.git

$ git config –global alias.serve ‘!git daemon –reuseaddr –verbose –base-path=. –export-all ./.git’

$ git clone git://192.168.254.135/ project

Connecting to SSH over IPV6 causes a problem

debug2: resolving “github.com” port 22 debug2: ssh_connect_direct debug1: Connecting to github.com [64:ff9b::dea:d226] port 22. debug1: connect to address 64:ff9b::dea:d226 port 22: Connection timed out debug1: Connecting to github.com [13.234.176.102] port 22. debug1: Connection established.

Extra configuration for a better Git experience

$ git config –global user.name “FirstName LastName”

$ git config –global user.email “[email protected]

$ git config –global color.ui “auto”

$ git config –global rerere.enabled 1

$ git config –global branch.autosetuprebase always

$ git config branch.*branch-name*.rebase true

$ git config –global branch.autosetupmerge always

$ git config –global merge.summary true

$ git config –global user.signingkey <keyid>

$ git config –list