Git

Miscellaneous Git Concepts

Updated: 30 October 2024

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:

Terminal window
1
git 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:

1
a -> b -> c -> d -> e

To this state:

1
a -> b -> c -> d -> e -> b'

Such that b' undoes the changes introduced by b, then we can use the following git command:

Terminal window
1
git revert --no-commit HEAD~4

Where 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:

1
a -> b -> c -> d -> e

To this state:

1
a -> 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:

1
git 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:

Terminal window
1
git init

Thereafter add and commit all the files in the repo:

Terminal window
1
git add .
2
git 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:

Terminal window
1
git submodule add https://github.com/nabeelvalley/YourRepository.git

If 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

Terminal window
1
git submodule add https://github.com/nabeelvalley/YourRepository.git NewNameForSubmoduleDirectory

This 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/YourRepository
3
url = https://github.com/nabeelvalley/YourRepository.git
4
5
[submodule "MySubmodules/NewNameForSubmoduleDirectory"]
6
path = MySubmodules/NewNameForSubmoduleDirectory
7
url = https://github.com/nabeelvalley/YourRepository.git

You 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

Terminal window
1
git add .
2
git commit -m "add submodules"

Cloning a Project with Submodules

When cloning a project that has submodules you can do either of the following:

Terminal window
1
git clone https://github.com/nabeelvalley/MyNewRepository.git

And then updating the submodules with:

Terminal window
1
git submodule init
2
git submodule update

If you want to init and update all nested submodules of the repository at once you can use:

Terminal window
1
git submodule update --init

And if you want to also update any further embedded submodules you can do:

Terminal window
1
git submodule update --init --recursive

Alternatively if you are cloning the project for the first time you should be able to pull everything including the submodules with --recurse-submodules

Terminal window
1
git clone --recurse-submodules https://github.com/nabeelvalley/MyNewRepository.git

Pull Latest Changes from Submodule

To pull the latest changes from a submodule into the repository you can make use of the following command:

Terminal window
1
git submodule update --remote --merge

There’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:

Terminal window
1
git rm -r --cached .

Which will clean out the repo, and then you can restage and commit all the files that should be tracked with

Terminal window
1
git add .
2
git 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:

Terminal window
1
git checkout --orphan NEW_BRANCH_NAME
2
git rm -rf .

Then you can remove all old files, or do whatever work is required and then:

Terminal window
1
git add .
2
git commit -m 'Initial commit on new branch'

Using Git Flow

To init Git Flow in a repo use git flow for the help menu:

Terminal window
1
> git flow
2
3
usage: git flow <subcommand>
4
5
Available 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
16
Try 'git flow <subcommand> help' for details.

To init a new Git Flow project:

Terminal window
1
git flow init

This 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:

Terminal window
1
> git flow init
2
3
Initialized empty Git repository in C:/Users/NVALLEY/source/repos/gitglow/.git/
4
No branches exist yet. Base branches must be created now.
5
Branch name for production releases: [master]
6
Branch name for "next release" development: [develop]
7
8
How to name your supporting branch prefixes?
9
Feature branches? [] feature/
10
Bugfix branches? [] bugfix/
11
Release branches? [] release/
12
Hotfix branches? [] hotfix/
13
Support branches? [] support/
14
Version tag prefix? []
15
Hooks and filters directory? [<REPO PATH/.git/hooks]

When using init you will also automatically be switched to the develop branch 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:

Terminal window
1
> git flow feature start save-user
2
3
Switched to a new branch 'feature/save-user'
4
5
Summary of actions:
6
- A new branch 'feature/save-user' was created, based on 'develop'
7
- You are now on branch 'feature/save-user'
8
Now, start committing on your feature. When done, use:
9
git flow feature finish save-user

The 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

Terminal window
1
> git flow feature finish save-user
2
3
Switched to branch 'develop'
4
Updating 012cac2..ecfd049
5
Fast-forward
6
stuff.txt | Bin 0 -> 28 bytes
7
1 file changed, 0 insertions(+), 0 deletions(-)
8
create mode 100644 stuff.txt
9
Deleted branch feature/save-user (was ecfd049).
10
11
Summary 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:

Terminal window
1
> git status --porcelain
2
3
M Random/git.md
4
?? Random/wsl.json
5
?? Random/wsl.md

As opposed to:

Terminal window
1
> git status
2
3
On branch master
4
Your branch is up to date with 'origin/master'.
5
6
Changes 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.md
10
11
Untracked files:
12
(use "git add <file>..." to include in what will be committed)
13
wsl.json
14
wsl.md

Consistent 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

.gitattributes
1
* text=auto eol=lf
2
*.{cmd,[cC][mM][dD]} text eol=crlf
3
*.{bat,[bB][aA][tT]} text eol=crlf

Locate 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:

Terminal window
1
git branch | grep -v "master" | xargs git branch -D

Get File From Specific Branch or Commit

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

Terminal window
1
git checkout branch_or_commit_sha path/to/file

So to get a specific file from a develop branch

Terminal window
1
git checkout develop src/my-file.ts

Or from a specific commit

Terminal window
1
git checkout 211512 src/my-file.ts

Checkout Previous Branch

When switching branches, we can easily go to the last branch we were on using:

Terminal window
1
git checkout -

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

Terminal window
1
git cherry-pick commit_sha

For example, if we want to take the changes from commit 211512 into our branch you can use

Terminal window
1
git cherry-pick 211512

Automatically 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:

Terminal window
1
git config --global push.autoSetupRemote true

Enable Case Sensitivity

To ensure that git is case sensitive on non-case-sensitive systems (Windows) you can use the following command:

Terminal window
1
git config core.ignorecase false

Find Bad Commits using Bisect

  1. Checkout the branch where the bad commit exists
  2. Run git bisect start to start a bisect session. If you’re looking for a PR you can also use the --first-parent flag here which will make it easier to find merges only for example
  3. Checkout a commit that is known to be bad and run git bisect bad
  4. Checkout a commit that is known to be good and run git bisect good
  5. Then the bisect tool will automatically checkout another commit
  • Do your checks
  • If good then run git bisect good, otherwise run git bisect bad

The overall flow for this will be something like

Terminal window
1
git bisect start # or git bisect start --first-parent
2
git bisect good # to label a commit as good
3
git bisect bad # to label a commit as bad
4
# the tool will checkout another commit, you then mark this as good or bad
  1. 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:

Terminal window
1
git bisect log | my-log-file.txt
2
# edit the my-log-file.txt` to correct a mislabeling, etc.
3
git bisect replay my-log-file.txt

Git User Configs

Refer to this StackOverflow for more information

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

Terminal window
1
git 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:

  1. Adding a conditional includes in your global git config ~/.gitconfig file

~/.gitignore

1
# default value to be used outside of subdirectory
2
[user]
3
name = Nabeel Valley
4
email = nabeel@email.com
5
6
# link to subdirectory config
7
[includeIf "gitdir:~/repos/my-other-repos/"]
8
path = ~/repos/my-other-repos/.gitconfig

Note that for Windows you have to include the path as C:/Users/my-user/repos/my-other-repos. in the includeIf section and the path section

  1. In the subdirecroty create a .gitconfig file that has specifies the config for the entire subdirectory:

~/repos/my-other-repos/.gitconfig

1
# override value that applies to this subdirectory only
2
[user]
3
email = other@email.com

Newer Git Stuff

From Git Tips and Tricks

Log changes to specific part of file

Log changes to a specific part of a file using:

Terminal window
1
git log -L FROM,TO:path/to/file

For example:

Terminal window
1
git log -L 10,20:my/file.js

You can also do this using the name of some symbol in your code and it will try to figure that out for you:

Terminal window
1
git log -L SymbolName:path/to/file

Git 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)

Terminal window
1
git maintenance start

Searching for Specific Change

StackOverflow Question

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:

Terminal window
1
git log -p --all -S 'search term here'

Or with a Regex:

Terminal window
1
git 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

Terminal window
1
git config feature.manyFiles true
2
git update-index --index-version 4
3
git config core.fsmonitor true
4
git fsmonitor--daemon status
5
git config core.untrackedcache true
6
git config core.commitgraph true
7
git config fetch.writeCommitGraph true

For more details for dealing with Large Git Repos there’s some nice info on the GitButler Site y

Tools 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-fancy with the delta --diff-so-fancy flag provided