Git
Miscellaneous Git Concepts
Updated: 23 December 2025
Stage Files Using Glob
Git allows the use of globbing to work with files, using this knowledge we’re able to do something like stage files based on a glob pattern, so we can do something like stage all .js and .ts files with:
1git add **/*.{js,ts}You can create and test glob patterns on GlobTester
Revert Commits
From StackOverflow
Revert Single Commit
Say we have a commit with id b below, and we would like undo changes that were introduced in that commit but still retain our history as is, something like:
We want to go from this state:
1a -> b -> c -> d -> eTo this state:
1a -> b -> c -> d -> e -> b'Such that b' undoes the changes introduced by b, then we can use the following git command:
1git revert --no-commit HEAD~4Where HEAD~4 means the 4th last commit from the current HEAD (latest commit)
Revert Multiple Commits
Say we have a commit with id b below, and we would like undo changes that were introduced in that all changes since that commit but still retain our history as is, something like:
We want to go from this state:
1a -> b -> c -> d -> eTo this state:
1a -> b -> c -> d -> e -> (bcde)'Such that (bcde)' undoes the changes introduced by all commits from b to e, then we can use the following git command:
1git revert --no-commit HEAD~4..Where HEAD~4 means the 4th last commit from the current HEAD (latest commit) and the .. means a commit range till the latest commit
Submodules
Submodules allow you to include one git repository in another, for instance if we want to include a library in our codebase
Set Up a Test Repo
We can get started on a new test repository, just create a folder with some files and other folders in it and run:
1git initThereafter add and commit all the files in the repo:
1git add .2git commit -m "initial commit"Add a Submodule
Next, from the directory into which you want the submodule to be cloned into, you can run the following command:
1git submodule add https://github.com/nabeelvalley/YourRepository.gitIf we would like to change the name of the folder being cloned from the default, we can add a new name for the folder by adding it at the end of the clone command
1git submodule add https://github.com/nabeelvalley/YourRepository.git NewNameForSubmoduleDirectoryThis will clone the repository into a directory called NewNameForSubmoduleDirectory
You will also see a new .gitmodules file in your parent repo’s root directory created with the following:
1[submodule "MySubmodules/YourRepository"]2 path = MySubmodules/YourRepository3 url = https://github.com/nabeelvalley/YourRepository.git4
5[submodule "MySubmodules/NewNameForSubmoduleDirectory"]6 path = MySubmodules/NewNameForSubmoduleDirectory7 url = https://github.com/nabeelvalley/YourRepository.gitYou can see above an example of a submodule created with the default name as well as a renamed one
Next you will see that the new files need to be committed, you can do that with
1git add .2git commit -m "add submodules"Cloning a Project with Submodules
When cloning a project that has submodules you can do either of the following:
1git clone https://github.com/nabeelvalley/MyNewRepository.gitAnd then updating the submodules with:
1git submodule init2git submodule updateIf you want to init and update all nested submodules of the repository at once you can use:
1git submodule update --initAnd if you want to also update any further embedded submodules you can do:
1git submodule update --init --recursiveAlternatively if you are cloning the project for the first time you should be able to pull everything including the submodules with --recurse-submodules
1git clone --recurse-submodules https://github.com/nabeelvalley/MyNewRepository.gitPull Latest Changes from Submodule
To pull the latest changes from a submodule into the repository you can make use of the following command:
1git submodule update --remote --mergeThere’s a lot more you can do with submodules but these are the basics, more information is in the Git docs
It’s also relevant to note that when working on submodules you can kind of treat them as a normal git repository and work on them like you would if they were such
Clean Ignored Files
To remove files that are in your .gitignore but are not ignored by your repo, you can do the following:
1git rm -r --cached .Which will clean out the repo, and then you can restage and commit all the files that should be tracked with
1git add .2git commit -m ".gitignore fix"Create an Orphan/Unrelated Branch
Information from this Stack Overflow Answer
Sometimes it’s useful to start a completely fresh segment of work without carrying around previous changes, e.g. to test out a totally new application architecture
We can do this by using the following:
1git checkout --orphan NEW_BRANCH_NAME2git rm -rf .Then you can remove all old files, or do whatever work is required and then:
1git add .2git commit -m 'Initial commit on new branch'Using Git Flow
To init Git Flow in a repo use git flow for the help menu:
1> git flow2
3usage: git flow <subcommand>4
5Available subcommands are:6 init Initialize a new git repo with support for the branching model.7 feature Manage your feature branches.8 bugfix Manage your bugfix branches.9 release Manage your release branches.10 hotfix Manage your hotfix branches.11 support Manage your support branches.12 version Shows version information.13 config Manage your git-flow configuration.14 log Show log deviating from base branch.15
16Try 'git flow <subcommand> help' for details.To init a new Git Flow project:
1git flow initThis will then ask you to update the naming convention for your branching system, it uses the defaults as listed in the help menu above
The full log when runing the above command will look something like this:
1> git flow init2
3Initialized empty Git repository in C:/Users/NVALLEY/source/repos/gitglow/.git/4No branches exist yet. Base branches must be created now.5Branch name for production releases: [master]6Branch name for "next release" development: [develop]7
8How to name your supporting branch prefixes?9Feature branches? [] feature/10Bugfix branches? [] bugfix/11Release branches? [] release/12Hotfix branches? [] hotfix/13Support branches? [] support/14Version tag prefix? []15Hooks and filters directory? [<REPO PATH/.git/hooks]When using
inityou will also automatically be switched to thedevelopbranch if you’re working on an existing project
Now you can use the git flow <BRANCH TYPE> start <FUNCTION NAME> command to start a new feature branch for something like so:
1> git flow feature start save-user2
3Switched to a new branch 'feature/save-user'4
5Summary of actions:6- A new branch 'feature/save-user' was created, based on 'develop'7- You are now on branch 'feature/save-user'8Now, start committing on your feature. When done, use:9git flow feature finish save-userThe above will then add you to a feature called feature/save-user and you can then make some changes and commits on this branch
When you’re done with that you can use git flow <BRANCH TYPE> finish <FUNCTION NAME> to merge the work to develop
1> git flow feature finish save-user2
3Switched to branch 'develop'4Updating 012cac2..ecfd0495Fast-forward6 stuff.txt | Bin 0 -> 28 bytes7 1 file changed, 0 insertions(+), 0 deletions(-)8 create mode 100644 stuff.txt9Deleted branch feature/save-user (was ecfd049).10
11Summary of actions:12- The feature branch 'feature/save-user' was merged into 'develop'You can then continue to use the above methodology to manage branching, releases, etc.
Using Git from Another Tool
Sometimes it’s useful to use git from another tool/application. To get a more standard/parseable output from git commands you can add the --porcelain flag. For example, with git status below:
1> git status --porcelain2
3 M Random/git.md4?? Random/wsl.json5?? Random/wsl.mdAs opposed to:
1> git status2
3On branch master4Your branch is up to date with 'origin/master'.5
6Changes not staged for commit:7 (use "git add <file>..." to update what will be committed)8 (use "git restore <file>..." to discard changes in working directory)9 modified: git.md10
11Untracked files:12 (use "git add <file>..." to include in what will be committed)13 wsl.json14 wsl.mdConsistent Line Endings
You can setup consistent line endings for repositories that are shared between Windows and *nix systems by adding the following to a .gitattributes file
1* text=auto eol=lf2*.{cmd,[cC][mM][dD]} text eol=crlf3*.{bat,[bB][aA][tT]} text eol=crlfLocate your SSH Key on Windows
When using git with SSH you may have difficulties finding the location for the SSH keys to use, to find the SSH Key you need to navigate to %HOMEDRIVE%%HOMEPATH%\.ssh\ To figure out where this folder is you can do the following: start > run > %HOMEDRIVE%%HOMEPATH%. The SSH Keys being used should be located in here
Delete All Branches other than Master
Using grep and xargs you can do this using:
1git branch | grep -v "master" | xargs git branch -DGet File From Specific Branch or Commit
Show File
From this StackOverflow answer
If you’d simply like to view a file as it stands in another branch you can use:
1git show other_branch:path/to/fileSo for example, we can do the following to view a specific file from a specific branch
1git show feature/new-user:src/users/user.page.tsYou can also pipe this into bat for example to view the file with syntax highlighting
1git show feature/new-user:src/users/user.page.ts | bat -l tsCheckout File
If you’d like to checkout the file from into your current working tree you can use the following instead:
From this StackOverflow answer
To basically copy a version of a file from one branch or commit to another you can use git checkout with either providing the branch name or commit from which you want to get the file
1git checkout branch_or_commit_sha path/to/fileSo to get a specific file from a develop branch
1git checkout develop src/my-file.tsOr from a specific commit
1git checkout 211512 src/my-file.tsView Diff
If you’d like to compare the file to what you have in your current branch you can use git diff like so:
1git diff other_branch -- path/to/fileSo this may look like so:
1git diff feature/stuff -- src/stuff/my-stuff.htmlCheckout/Switch Branches
Checking out/switching are similar taks with
switchbeing the “newer” way to do things
There are some alternative methods for checking out branches depending on what you want to do:
Checkout/Switch from Current Branch
You can branch from your current branch with:
1git checkout -b feature/my-new-branchCheckout/Switch from a Different Branch
Or you can checkout from another branch (e.g. main) without switching to it first, this can be done with:
1## you may want to update your local copy of `main` first2git fetch origin main:main3
4## and then switch to it5git switch -c feature/my-new-branch mainCheckout/Switch to Previous Branch
When switching branches, we can easily go to the last branch we were on using:
1git switch -Get Commit Changes from another Branch (Cherry Picking)
Cherry picking allows us to get a specific commit from one branch and bring it into our current branch
1git cherry-pick commit_shaFor example, if we want to take the changes from commit 211512 into our branch you can use
1git cherry-pick 211512Automatically Set Upstream
When using git it can be annoying when pushing a new branch since it will always request that you setup the upstream/origin. You can configure git to do this automatically using the following command:
1git config --global push.autoSetupRemote trueEnable Case Sensitivity
To ensure that git is case sensitive on non-case-sensitive systems (Windows) you can use the following command:
1git config core.ignorecase falseFind Bad Commits using Bisect
- Checkout the branch where the bad commit exists
- Run
git bisect startto start a bisect session. If you’re looking for a PR you can also use the--first-parentflag here which will make it easier to find merges only for example - Checkout a commit that is known to be bad and run
git bisect bad - Checkout a commit that is known to be good and run
git bisect good - Then the bisect tool will automatically checkout another commit
- Do your checks
- If good then run
git bisect good, otherwise rungit bisect bad
The overall flow for this will be something like
1git bisect start # or git bisect start --first-parent2git bisect good # to label a commit as good3git bisect bad # to label a commit as bad4## the tool will checkout another commit, you then mark this as good or bad- The process will run until you find the last bad commit, you can then try to figure out what was changed in that commit.
If you need to view the changes made you can do git bisect log. You can also save this to a file with something like git bisect log | my-log-file.txt. You can then edit that file to make changes to the log if you made a mistake along the way and you can replay the changes using git bisect replay my-log-file.txt
Like so:
1git bisect log | my-log-file.txt2## edit the my-log-file.txt` to correct a mislabeling, etc.3git bisect replay my-log-file.txtGit User Configs
Often when using git you may need to have multiple accounts on the same machine and would like more specific control over config for different repositories - Often I would like to be able to set my email address differently for a specific repo or group of repos
To do this, you can use two solutions depending on your usecase:
Single Repository
For a repository you can update the .git/config file for that repository using the following command:
~/repos/my-repo/.git/config
1git config set user.email "repo@email.com"For a Subdirectory
Sometimes you may have multiple subdirectories in which you would like repositories within it to inherit a specific config, you can do this by:
- Adding a conditional includes in your global git config
~/.gitconfigfile
~/.gitignore
1## default value to be used outside of subdirectory2[user]3 name = Nabeel Valley4 email = nabeel@email.com5
6## link to subdirectory config7[includeIf "gitdir:~/repos/my-other-repos/"]8 path = ~/repos/my-other-repos/.gitconfigNote that for Windows you have to include the path as
C:/Users/my-user/repos/my-other-repos.in theincludeIfsection and thepathsection
- In the subdirecroty create a
.gitconfigfile that has specifies the config for the entire subdirectory:
~/repos/my-other-repos/.gitconfig
1## override value that applies to this subdirectory only2[user]3 email = other@email.comRebasing
Rebasing lets us modify and restructure commits. It’s can get kind of messy but is handy for cleaning up commit history for example
Simple Rebase
For a simple rebase, for example changing the target branch or moving some commits up to the latest HEAD of your main branch for example
Rebasing a branch to another branch (e.g. main) can be done like so:
1git rebase mainAnother common use of this is to change the target branch, e.g. if I had a branch that was originally targeting feature/wip but I want to change it to target main, I can do:
1git rebase --onto main feature/wipComplex Rebases
Interactive rebasing is the easiest way to go about it in my opinion, this is done by starting a rebase session
Say I’ve got a branch with the following history:
1f1d5c91 (HEAD -> example-feature) fix typo2b5cb5c3 add initial notes39ab331d (main) initial commitAnd I want to squash the last two commmits. I can do this using git rebase. When rebasing like this we can provide the branch we want to rebase on top of:
1git rebase main -iOr the commit from which we’d like to start rebasing:
1git rebase 9ab331d0 -i # this is the first commit, we want to rebase on top of thisWe can also reference this hash using:
1git rebase HEAD~2 -iOnce the rebase session has started, you’ll see an editor open with some content like this followed by some instruction on what the different keywords do:
1pick b5cb5c3 # add initial notes2pick f1d5c91 # fix typo3
4## Rebase 9ab331d..f1d5c91 onto 9ab331d (2 commands)5#6## Commands:7## p, pick <commit> = use commit8## r, reword <commit> = use commit, but edit the commit message9## e, edit <commit> = use commit, but stop for amending10## s, squash <commit> = use commit, but meld into previous commit11## f, fixup [-C | -c] <commit> = like "squash" but keep only the previous12## commit's log message, unless -C is used, in which case13## keep only this commit's message; -c is same as -C but14## opens the editor15## ...In our case, let’s say we want to combine the fix typo commit into the previous commit, we can do this by editing the rebase file with the relevant command. For this we can use fixup which will squash the commit into the previous one and keep the previous message. We can edit the rebase file to look like this:
1pick b5cb5c3 # add initial notes2fixup f1d5c91 # fix typoThen, save and close this file - the rebase process will run automatically and prompt you if required, e.g. for doing things like updating the commit messaage
Once we’re done rebasing, our log now looks like this:
17814a0e (HEAD -> example-feature) add initial notes29ab331d (main) initial commitNewer Git Stuff
From Git Tips and Tricks
Log changes to specific part of file
Log changes to a specific part of a file using:
1git log -L FROM,TO:path/to/fileFor example:
1git log -L 10,20:my/file.jsYou can also do this using the name of some symbol in your code and it will try to figure that out for you:
1git log -L SymbolName:path/to/fileGit Maintenance
Run the following in a repo to make git maintain the repo and keep things fast in the background using a CRON job (just run this in every repo)
1git maintenance startSearching for Specific Change
To search for a specific piece of text that was changed at some point in history (e.g. find some deleted text) you can use:
1git log -p --all -S 'search term here'Or with a Regex:
1git log -p --all -G 'some regex here'Speeding things Up
Some general commands that should help speed up git as per this GitTower Post
Running the following in a repo should speed up the behaviour of git in general
1git config feature.manyFiles true2git update-index --index-version 43git config core.fsmonitor true4git fsmonitor--daemon status5git config core.untrackedcache true6git config core.commitgraph true7git config fetch.writeCommitGraph trueFor more details for dealing with Large Git Repos there’s some nice info on the GitButler Site y
Automatically Applying Git Diffs/Patches
Sometimes it’s handy to take a git diff/patch from one place and apply it to another. A simple way to do this is using the following method:
- Pipe the git diff into a file:
git diff 12345..67890 --no-ext-diff | mychanges.patch(--no-ext-diffuses the default git diff format) - You can then apply the changes using
git applylike:cat mychanges.patch | git apply
Other handy commands here are git apply --stat mychanges.patch to get the status of the patch or git apply --check mychanges.patch to dry run/detect errors
Worktrees
Worktrees help to separate work by effectively having multiple copies of the repository on your drive while maintaining a single git index
Create a Worktree
Creating a worktree is done from the main repo directory, you can optionally give it a branch name with -b. If not provided this will just be the name of the worktree
1git worktree add ../experiments -b experiment/thing-im-tryingYou can then navigate to that directory with
1cd ../experimentsNote that you cannot have the same branch on multiple worktrees at the same time - each tree is linked to a specific branch
Since the worktrees are also using the same git index, it’s easy to do things like cherry-pick commits from branches in other worktrees without pushing it to a remote
Listing Worktrees
Git can let us view the worktree and will provide us with information about the path and branch checkout out at each tree
1git worktree listRemoving Worktrees
Removing a worktree will delete the directory but not the associated branches, this can be done with:
1git worktree remove ../experimentsReflog
Some good examples of using the reflog can be found in this tutorial
The reflog tracks all operations that were done in a repository and it enables us to move between those operations
We can use the following command to view the reflog
1git reflogAnd the log will look something like this:
1692a778 (HEAD -> example-feature) HEAD@{7}: commit: some content2d834109 (main) HEAD@{8}: checkout: moving from main to example-feature3d834109 (main) HEAD@{9}: merge example-feature: Fast-forward47814a0e HEAD@{10}: checkout: moving from example-feature to main5d834109 (main) HEAD@{11}: rebase (finish): returning to refs/heads/example-feature6d834109 (main) HEAD@{12}: rebase (fixup): Add content for special notes700e23a8 HEAD@{13}: rebase (reword): Add content for special notes80c6d933 HEAD@{14}: rebase: fast-forward97814a0e HEAD@{15}: rebase (start): checkout mainWe can then move between different points in the history using the checkout command:
1git checkout HEAD@{9}Which will get the state of the repository at that specific point in time
You can also checkout using time, like so:
1git checkout HEAD@{1.day.ago}Rerere
Git has a setting called rerere (reuse recorded resolution) which helps reduce the need to constantly re-resolve conflicts. This can be enabled with:
1git config --global rerere.enabled trueTools on Top of Git
LazyGit
LazyGit is a terminal UI for Git. Using it can be done by running the lazygit command and it’s the default git UI in LazyVim so it fits pretty well together
Additionally, lazygit allows for a custom difftool to be configured, this can be done through the lazygit config and can be done as per the docs on configuring a custom pager
Some diff tools that can be used with LazyGit (or even just your normal git installation)
- Difftastic provides syntax-based diffs and has the nicest output of any of the diff tools I’ve found so far
- Diff so Fancy provides syntax-based diffs, similar to Difftastic
- Delta is a “pretty” diff tool and can do syntax-based diffing using
diff-so-fancywith thedelta --diff-so-fancyflag provided
Note that the above diff tools can modify your global diff, to get this to work as normal you can stick on the
--no-ext-diffflag, e.g.git diff HEAD --no-ext-diffwill get you back to the normal git diff