Skip to content

Remove some files from all commits

WARNING: This command will rewrite history. This tool is probably not already installed on your system. On Arch Linux, the package is called git-filter-repo.

git filter-repo --invert-paths --path filename

Rebase onto root commit

Use the --root option to Git to allow rebasing onto the root commit.

$ git rebase --root

The -i flag will do this interactively, of course. If there is not currently an upstream branch, this flag takes the branch name as a parameter.

$ git rebase -i trunk --root

Create an Empty Branch (And Push to the Remote)

$ git checkout --orphan <branchName>
# If there are files in the current repo, they will be added to the index.
$ git reset HEAD -- .
# Must create a commit to be able to push the branch
$ git commit --allow-empty -m "Initial commit on empty branch"
$ git push -u origin <branchName>

Checkout Other Branches After git clone --depth 1

$ git remote set-branches origin '*'

Sort Branches by Most Recently Updated

Replace refs/heads/ with refs/remotes/ to see remote branches.

$ git for-each-ref --sort=committerdate refs/heads/ --format='%(HEAD) %(color:yellow)%(refname:short)%(color:reset) - %(color:red)%(objectname:short)%(color:reset) - %(contents:subject) - %(authorname) (%(color:green)%(committerdate:relative)%(color:reset))'

Move Some Files to Another Repo, Preserving History

# Clone the repository we're moving files from:
git clone https://github.com/AmateurECE/myproject.git

# Remove the remote, to prevent accidentally pushing these changes:
cd myproject && git remote rm origin

# Use git filter-repo to rewrite git history, keeping only the commits
# that touch the desired files. Note that renames are NOT followed,
# so if we want to keep commits that were made on files before any
# renames occurred, we need to specify the old paths as well
# (e.g. --path olddir --path newdir). We can use git filter-repo --analyze
# and then observing .git/filter-repo/analysis/renames.txt to determine if
# we need to do this.
#
# Additionally, we will likely need to use --force because we removed the
# origin remote, which makes this look like a clone with history.
#
# If the files are not organized in the way that is required for the
# destination repository, we can use --path-rename here to rewrite these
# commits as if they were always at the right location.
git filter-repo --force --path olddir --path newdir --path CMakeLists.txt \
    --path-rename CMakeLists.txt:fsadaptor/CMakeLists.txt

# Remove a bunch of history and data from dead commits:
git reset --hard
git gc --aggressive
git prune
git clean -fd

# Now working in the destination repository.
cd ~/newproject

# Add the myproject checkout as a remote
git remote add myproject ~/myproject

# Merge the commits from myproject/remote-branch into the current branch of
# newproject. Note that this will not perform a fast-forward on the current
# branch. It will create a merge commit that merges all commits from myproject
# into the current branch. This is extremely useful because it prevents
# creating broken commits on the current branch if some of the intermediate
# commits in myproject would introduce build problems in the current branch.
# Observe this using git log --graph after the merge!
git merge myproject remote-branch --allow-unrelated-histories

# Remove the remote
git remote rm myproject

Sometimes, while attempting the merge, git will report:

$ git merge myproject remote-branch --allow-unrelated-histories
myproject: not something we can merge

This may happen, for example, if a path filter is not used, so some of the content appears to be identical, and the rest of the content is renamed (as an example).

To get around this, we can create a local branch from the remote:

$ git fetch myproject
$ git checkout -b merge-playground myproject/remote-branch
$ git switch main
$ git merge merge-playground

In these scenarios, we may be dealing with rename/rename conflicts. Playing around with different combinations of git add should produce the desired behavior.

$ git add new-name-1 new-name-2
$ git rm old-name
$ git checkout
etc...

Stash Operations

Show the contents of the stash using git stash show [-p]. There are also aliases for the stash refs in the local checkout, which allows use of git-diff to inspect the stash:

# View changes to `a.file` in the topmost stash entry
git diff stash@{0}^1 stash@{0} -- a.file

Running git stash show without -p will show the diff in "short form"--that is, only the paths of the files that have changed, and not the content. Finally, running git stash list will show the entries in the stash.

Sometimes, popping an entry from the stash will result in merge conflicts. To recover from this, one easy way is to run git reset --merge. NOTE, however, that this will return the index and the working tree to a completely clean state--any staged or unstaged changes that were present before running git stash pop will be lost.

As a general rule, use git stash push instead of git stash save. The push subcommand can take arguments similar to git diff, which allows to push, for example, only some files.

Deleting Remote Objects

# Deleting branches
git push -u remote --delete branch-name

Applying Patches

For patches generated from the git repository to which they are being applied, the easiest mechanism is git-am(1). Simply:

$ git am my-awesome-patch.patch

For more complicated use cases, git-apply(1) is the tool. Use this tool when some hunks will not apply cleanly, patches need to be applied to files in different directories, or the patch file does not contain a change that forms an entire commit. For example, the following would apply a patch written for ./Foo/a.cpp to ./Bar/a.cpp:

$ git apply \
    --rej # In case some hunks don't apply
    -v    # Be verbose, let us know if you're skipping hunks
    --directory=Bar # change to the "Bar" directory before applying
    -p2 # Strip not only the prefix ("a" or "b"), but also the first path
        # element ("Foo", in this case.)
    my-awesome-patch.patch

Preparing patches to submit to a mailing list

Some flags to git-format-patch which may be important to remember:

  • --reroll-count,-v: Specify the version of the patch (e.g. v2)
  • --no-stat,-p: Generates plain patches without any diffstat. This makes it easier for the kernel folks to read the patch.
  • --base=auto: Automatically compute the base commit and add its object name as a tag in the patch.
  • --cover-letter: Create a cover letter patch to provide "frontmatter" for the email.

After generating the patches, the cover letter template generated by git-format-patch needs to be filled in manually. Then, check the patches using scripts/checkpatch.pl.

Some flags to git-send-email which may be important to remember:

  • --to-cmd: Provide a command to obtain the addresses that should fill the To: field of the email. This is super useful for the kernel submission work flow. An example: --to-cmd="scripts/get_maintainer.pl my.patch"

Tags

Sometimes, git doesn't fetch tags when git pull is run. Ensure that you're fetching the correct remote by running git fetch <remote>. This helps in multi-remote scenarios, such as Linux kernel development. If this still doesn't work, try git fetch --tags <remote>.

Cloning with Submodules

$ git clone --recurse-submodules <repo>

Log Tricks

# View a simplified graph showing only branch and tag points:
git log --simplify-by-decoration --graph --format="%d"

Complicated Feature Branch Rebase Operations

Sometimes, we base a feature branch on a feature branch, and we need to rebase our branch onto main after our predecessor is merged, or we want to rebase a feature branch onto main after a different feature was merged to maintain a clean history.

We might guess this is the case if rebasing appears to create many merge conflicts, or if git log main..HEAD produces a lot of commits that should already be present in main (this might be the case if our predecessor feature branch was merged with a merge commit, instead of a squash or a fast-forward).

The git docs for rebase provide a helpful example here.

 o---o---o---o---o  master
         \
          o---o---o---o---o  next
                           \
                            o---o---o  topic

In this case, topic is our feature, and next is the feature we are based on. In this example, next has just been merged to master (not shown).

To perform this rebase, we must use the --onto switch:

git rebase --onto main next topic

After this rebase, don't forget to push using --force-with-lease, instead of --force, since the former is much safer!