Skip to content

Source control command list: git, hg, jj

Rain edited this page Jan 21, 2025 · 30 revisions

The most common use of this crate is with version control systems. The examples below cover three such systems: Git, Mercurial, and Jujutsu.

Considerations

This section covers some of the considerations you should keep in mind when working with paths in a version control system.

Null-separated paths

Paths as produced by tools like git diff --name-only are typically separated by newline characters (\n). However, on Unix platforms filenames can themselves have newlines embedded in them, so source control systems often end up quoting newlines and other "unusual" characters.

A robust, lossless way to retrieve a list of paths is by separating them with null characters (i.e. NUL or \0: a byte with a value of zero). Both Unix and Windows guarantee that a path can never have embedded null characters. This crate is geared towards supporting these kinds of outputs. This means that this doesn't handle any kind of escaping or quoting, instead assuming that the paths are already correctly formatted.

Both Git and Mercurial support null-separated paths. Jujutsu as of version 0.25.0 does not, but will hopefully support them in the future. (With current versions, commands like jj file list will return paths separated by newlines and will not perform any kind of quoting or escaping on them, so this crate is compatible with most real-world paths.)

Windows path separators

Unix platforms always use forward slashes (/) as the path separator. Windows platforms use \ as the main separator, but also supports / as an alternative in most situations.

On Windows, Git always returns file paths with forward slashes. (This is related to Git's history as a Unix-first tool.) Mercurial and Jujutsu, both being native Windows utilities, always return paths with backslashes.

Independently of the system in use, based on your requirements, you might prefer:

  • Paths with forward slashes (Unix-style or Git-style) on all platforms.
  • Paths using the native separator on each platform.

This crate provides a way to convert any of these representations to one of your choosing.

Current directory

In all of the recipes below, paths are returned with respect to the current directory. In most cases, you will want to run the below commands from the root of the repository.

Recipes

Here's a set of recipes for how to make such paths in Git, Mercurial, and Jujutsu.

First, you need to decide:

  1. What query do you want to perform? In general, you may either want to list all files in the working directory or in a revision, or to perform a diff of some kind.

  2. If you're performing a diff, what are you diffing from and to? There are three possible answers here:

    1. Between two revisions.
    2. From the merge base or nearest/greatest/lowest common ancestor of two revisions, to a particular revision.
    3. Between a revision and the working directory.

    In general, if you're concerned with what changed in a branch, you'll want to use the merge base. If you're concerned with what changed in a commit, you'll want to diff a commit against its parent.

  3. If the working directory is part of what you're doing, do you only want to consider tracked files, or do you want to include untracked files as well? (In general, most users will exclude ignored files.)

    Jujutsu does not have a notion of untracked files.

  4. Do you need to filter by directory? Most commands here let you filter to a directory by passing in -- path/to/directory to the command.

    However, doing so might cause paths to be returned relative to the directory rather than relative to the root of the repo. You're encouraged to test commands out against your own repositories to see how this works.

(This is all a bit complicated! Feel free to take a break at this point if this is too much.)

Commands to run

Here's how this breaks down:

List all files in the working directory, excluding untracked files:

  • Git: git ls-files -z produces null-separated paths with forward slashes.
  • Mercurial: hg files --print0 produces null-separated paths with the native separator.
  • Jujutsu: jj file list produces newline-separated paths with the native separator.

List all files in the working directory, including untracked files:

  • Git: git ls-files -z --cached --others --exclude-standard produces null-separated paths with forward slashes.
  • Mercurial: hg status --print0 -mardu --no-status produces null-separated paths with the native separator.
  • Jujutsu: Jujutsu does not have a notion of untracked files.

List all files present in revision $REV:

  • Git: git ls-tree -z -r --name-only $REV produces null-separated paths with forward slashes.
  • Mercurial: hg status --print0 --no-status --rev null --rev $REV produces null-separated paths with the native separator.
  • Jujutsu: jj file list --revision $REV produces newline-separated paths with the native separator.

List changed files from a revision $REV to the working directory, excluding untracked files:

  • Git: git diff-index -m -z --name-only $REV produces null-separated paths with forward slashes.
  • Mercurial: hg status --no-status --print0 --rev $REV produces null-separated paths with the native separator.
  • Jujutsu: jj diff --from $REV --name-only produces newline-separated paths with the native separator.

List changed files from a revision $REV to the working directory, including untracked files:

  • Git: There isn't a single command to do this. Instead, first obtain the diff excluding untracked files, then combine with the list of untracked files obtained from git ls-files -z --others --exclude-standard.
  • Mercurial: hg status --no-status --print0 -mardu --rev $REV produces null-separated paths with the native separator.
  • Jujutsu: Jujutsu does not have a notion of untracked files.

List changed files from the merge base of a revision $REV to the working directory, excluding untracked files:

  • Git: git diff-index -m --merge-base -z --name-only $REV produces null-separated paths with forward slashes.
  • Mercurial: hg status --no-status --print0 --rev 'ancestor($REV,.)' produces null-separated paths with the native separator.
  • Jujutsu: jj diff --from 'fork_point(@ | $REV)' --name-only produces newline-separated paths with the native separator.

List changed files from the merge base of a revision $REV to the working directory, including untracked files:

  • Git: There isn't a single command to do this. Instead, first obtain the diff excluding untracked files, then combine with the list of untracked files obtained from git ls-files -z --others --exclude-standard.
  • Mercurial: hg status --no-status --print0 -mardu --rev 'ancestor($REV,.)' produces null-separated paths with the native separator.
  • Jujutsu: Jujutsu does not have a notion of untracked files.

List changed files in a revision compared to its parent(s):

  • Git: git diff-tree -z --name-only $REV produces null-separated paths with forward slashes.
  • Mercurial: hg status --no-status --print0 --change $REV produces null-separated paths with the native separator.
  • Jujutsu: jj diff --revision $REV --name-only produces newline-separated paths with the native separator.

List changed files from $OLD_REV to $NEW_REV:

  • Git: git diff-tree -z --name-only $OLD_REV $NEW_REV produces null-separated paths with forward slashes.
  • Mercurial: hg status --no-status --print0 --rev $OLD_REV --rev $NEW_REV produces null-separated paths with the native separator.
  • Jujutsu: jj diff --from $OLD_REV --to $NEW_REV --name-only produces newline-separated paths with the native separator.

List changed files from the merge base of $OLD_REV and $NEW_REV, to $NEW_REV:

  • Git: git diff-tree -z --name-only --merge-base $OLD_REV $NEW_REV produces null-separated paths with forward slashes.
  • Mercurial: hg status --no-status --print0 --rev 'ancestor($OLD_REV,$NEW_REV)' --rev $NEW_REV produces null-separated paths with the native separator.
  • Jujutsu: jj diff --from 'fork_point($OLD_REV | $NEW_REV)' --to $NEW_REV --name-only produces newline-separated paths with the native separator.

Advanced detail

Clone this wiki locally