Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

resolve: try to resolve all conflicted files in fileset #5111

Open
wants to merge 2 commits into
base: main
Choose a base branch
from

Conversation

scott2000
Copy link
Contributor

If many files are conflicted, it would be nice to be able to resolve all conflicts at once without having to run jj resolve multiple times. This is especially nice for merge tools which try to automatically resolve conflicts without user input, but it can also be used for regular merge editors like VS Code.

Checklist

If applicable:

  • I have updated CHANGELOG.md
  • I have updated the documentation (README.md, docs/, demos/)
  • I have updated the config schema (cli/src/config-schema.json)
  • I have added tests to cover my changes

@arxanas
Copy link
Contributor

arxanas commented Dec 16, 2024

I recently learned that some commands support filesets. Is jj resolve one of those? Could we support this use-case by extending the fileset language, rather than adding a flag specifically to jj resolve? (Does jj resolve 'all()' work today?)

@martinvonz
Copy link
Member

We actually already support filsets in this command. It's just that the command would always stop after the first conflict before this PR (so even jj resolve 'all()' would just pick the first conflict it runs into).

@scott2000
Copy link
Contributor Author

scott2000 commented Dec 16, 2024

I recently learned that some commands support filesets. Is jj resolve one of those? Could we support this use-case by extending the fileset language, rather than adding a flag specifically to jj resolve? (Does jj resolve 'all()' work today?)

Yes, it already does support filesets, and the default is all() currently already. The issue is that it always picks a single file from the fileset, rather than using the whole fileset. The new --all flag just tells it to use the whole fileset, rather than picking a single file from the fileset. We could maybe make it so that if the user explicitly passes a fileset, it uses the whole fileset? I'm not sure if anyone relies on the existing behavior though.

Edit: nevermind, looks like @martinvonz already responded!

@arxanas
Copy link
Contributor

arxanas commented Dec 16, 2024

We actually already support filsets in this command. It's just that the command would always stop after the first conflict before this PR (so even jj resolve 'all()' would just pick the first conflict it runs into).

Is this because existing merge editor work on a single file at a time, so we have no choice but to resolve conflicts individually? (Is there a warning in the case that subsequent conflicts are ignored?)

For uniformity, should we have an all: modifier (like for revsets) for filesets?

  • Assuming that it's a limitation of the merge editor interface, and you don't inadvertently want to resolve multiple files at once, perhaps it makes sense to warn before trying to resolve conflicts for multiple files, in the same way that we warn for certain multiple-revision operations?
  • I could be convinced either way, but by default, I would be inclined to keep the DSLs similar.

@scott2000
Copy link
Contributor Author

scott2000 commented Dec 16, 2024

If we want the default behavior of picking a single file to be expressable with filesets, then we'd probably want a first(fileset) function to pick a single file from a fileset. I'm not sure if that would make the implementation of filesets more complicated though.

TBH, I'm not sure I've ever actually wanted the default behavior of only resolving a single, seemingly-random file though. Usually, I want to resolve every conflict, or I want to resolve a conflict in a specific file I specify. So maybe we could just change jj resolve to just resolve every file in the fileset and require the user to name a specific file if they only want to resolve a single file?

@arxanas
Copy link
Contributor

arxanas commented Dec 16, 2024

If we want the default behavior of picking a single file to be expressable with filesets, then we'd probably want a first(fileset) function to pick a single file from a fileset. I'm not sure if that would make the implementation of filesets more complicated though.

Makes sense to me, although implementing that is its whole own matter.

TBH, I'm not sure I've ever actually wanted the default behavior of only resolving a single, seemingly-random file though. Usually, I want to resolve every conflict, or I want to resolve a conflict in a specific file I specify. So maybe we could just change jj resolve to just resolve every file in the fileset and require the user to name a specific file if they only want to resolve a single file?

In both your workflow and my workflow, it sounds like resolving a single arbitrary file is probably not desirable:

  • In my own workflow, I typically get scared and confused when I try to finish up and I wasn't expecting the tool to launch more editors.
    • It used to be particularly difficult to resolve in cases where I was using a CLI editor to resolve conflicts, so there was no way to just go back to my terminal and type ctrl-c to cancel the whole process.
    • These days, I know how to make Vim exit with an error code, but there may be other users might share my old workflow of trying to trying to quit and then press ctrl-c really fast before the caller can launch another editor 👀.
  • Thererfore: I would prefer an error in the case that jj resolve would operate on multiple files, while you would prefer to just start resolving multiple files, but neither of us would want the current behavior.
  • But, in fairness: I don't use jj resolve that much vs just opening the conflicted files in my editor and letting them get auto-resolved, so I'll defer to anyone who specifically uses jj resolve.

@scott2000
Copy link
Contributor Author

These days, I know how to make Vim exit with an error code, but there may be other users might share my old workflow of trying to trying to quit and then press ctrl-c really fast before the caller can launch another editor 👀.

I think in practice, this shouldn't be an issue because we treat an unmodified or blank output file as an error, so it would stop launching more editors if someone closes the editor without making any changes. That being said, it does feel a bit strange to launch the editor multiple times without the user requesting it explicitly, so it might be nice to not launch multiple editors by default anyway.

At some point, I'm thinking that we probably will want to support merge tools which can merge whole directories at once instead of being invoked with each file individually.

@martinvonz
Copy link
Member

So maybe we could just change jj resolve to just resolve every file in the fileset and require the user to name a specific file if they only want to resolve a single file?

I'm also for that.

@martinvonz
Copy link
Member

Oh, @ilyagr might also have thoughts. He wrote most of the current implementation.

@scott2000 scott2000 marked this pull request as draft December 17, 2024 03:17
@scott2000 scott2000 changed the title resolve: add --all option resolve: try to resolve all conflicted files in fileset Dec 17, 2024
@scott2000 scott2000 marked this pull request as ready for review December 17, 2024 18:08
@scott2000 scott2000 force-pushed the resolve-all branch 2 times, most recently from dbbe3df to ecf54fa Compare December 18, 2024 20:40
"Stopping due to error after resolving {i} conflicts"
)?;
print_error_sources(ui, Some(&err))?;
break;
Copy link
Member

@martinvonz martinvonz Dec 19, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Perhaps we should still return the error here to make it more predictable for scripts? Otherwise the exit code is a bit hard to interpret. Alternatively, we could make it not an error even after no conflicts were resolved. What do you think? It might be unlikely that scripts call jj resolve (without --list), so maybe I'm worrying about nothing.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah, I was considering that as well. I mainly chose to make it a warning instead of an error because I didn't want any previous successful resolutions to be discarded (so returning an error immediately isn't an option), and I thought it looked strange having the error message be at the end of the command after the transaction commits successfully instead of being before the transaction commits. Maybe I could make it so that it prints the error immediately, but doesn't actually exit with the error code until after the transaction commits?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe I could make it so that it prints the error immediately, but doesn't actually exit with the error code until after the transaction commits?

SGTM, thanks.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hmm it ended up being more difficult to print the error immediately than I thought. It seems necessary to return a CommandError from the command, because calling std::process::exit would skip finalizing the pager, but all the current CommandError variants print a message at the end of the command. I'm not sure that printing the error immediately would be worth the complexity, so I currently changed it to just return the error like normal, but just wait until after committing the transaction.

I think it might be clear enough that some conflicts were successfully resolved, since I added an error message saying Stopped due to error after resolving <count> conflicts. What do you think?

cli/src/commands/resolve.rs Outdated Show resolved Hide resolved
Comment on lines 329 to 344
pub fn edit_file(
&self,
ui: &Ui,
path_converter: &RepoPathUiConverter,
tree: &MergedTree,
repo_path: &RepoPath,
repo_paths: &[&RepoPath],
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just a comment. Perhaps, this will be edit(ui, tree, matcher) at some point, but more refactoring will be needed.

@scott2000 scott2000 force-pushed the resolve-all branch 3 times, most recently from 0b8147c to fdb52c6 Compare December 21, 2024 18:49
I'm going to change the merge tools to accept multiple files, and this
will make it easier to pass all the required data about each file.
If many files are conflicted, it would be nice to be able to resolve all
conflicts at once without having to run `jj resolve` multiple times.
This is especially nice for merge tools which try to automatically
resolve conflicts without user input, but it is also good for regular
merge editors like VS Code.

This change makes the behavior of `jj resolve` more consistent with
other commands which accept filesets since it will use the entire
fileset instead of picking an arbitrary file from the fileset.

Since we don't support passing directories to merge tools yet, the
current implementation just calls the merge tool repeatedly in a loop
until every file is resolved, or until an error occurs. If an error
occurs after successfully resolving at least one file, it is downgraded
to a warning instead. This means the user can just close the editor at
any point to cancel resolution on all remaining files.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants