Skip to content

FR: support --no-renames for git diffs and a diff.renames config equivalent #8549

@botovq

Description

@botovq

Is your feature request related to a problem? Please describe.

To interact with other VCS it is sometimes easiest to apply a diff generated with git or jj using the good old POSIX patch utility. patch generally ignores metadata it doesn't understand. Some implementations are unaware of the git-specific 'rename from/rename to' and 'copy from/copy to' (or similarity index) part of diffs. The result is they don't actually rename/copy the old file. This can result in obscure build failures that may take some time to debug.

The --no-renames flag and the diff.renames config of git avoid this problem: a rename is split into two diffs against /dev/null, one that removes (or at least empties) the old file and one that creates the new one. Similarly, a copy is a diff from /dev/null that creates the copy. That's something all patch utilities I've ever used handle correctly. The obvious downside of is that it significantly increases the size of the diff.

Describe the solution you'd like

  • Support --no-renames in commands that output git-style unified diffs via --git or -p.
  • A config option akin to git's diff.renames that makes such output the default.
    I'm only concerned with the display aspect of this config option here, not with possible implications on git's rebase or merge behavior.

Describe alternatives you've considered
None.

Additional context
I expect this to be rather easy but I currently lack the time to tackle this and push it over the line. If --no-renames is given, skip the writeln!() here:

jj/cli/src/diff_util.rs

Lines 1743 to 1751 in e155cb3

if let Some(op) = path.copy_operation() {
let operation = match op {
CopyOperation::Copy => "copy",
CopyOperation::Rename => "rename",
};
// TODO: include similarity index?
writeln!(formatter, "{operation} from {left_path_string}")?;
writeln!(formatter, "{operation} to {right_path_string}")?;
}

and don't do the // no content hunks so as to output the diffs against /dev/null as appropriate for copy and rename mode:

jj/cli/src/diff_util.rs

Lines 1766 to 1777 in e155cb3

if left_part.content.contents == right_part.content.contents {
continue; // no content hunks
}
let left_path = match left_part.mode {
Some(_) => format!("a/{left_path_string}"),
None => "/dev/null".to_owned(),
};
let right_path = match right_part.mode {
Some(_) => format!("b/{right_path_string}"),
None => "/dev/null".to_owned(),
};

Some thought needs to be given on permissions to match git's behavior. The bulk of the work will be plumbing to add the option where needed and tests.

Metadata

Metadata

Assignees

No one assigned

    Labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions