martinvonz / jj

A Git-compatible VCS that is both simple and powerful
https://martinvonz.github.io/jj/
Apache License 2.0
9.02k stars 313 forks source link

FR: `jj file` subcommand for file operations #3812

Open arxanas opened 5 months ago

arxanas commented 5 months ago

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

Describe the solution you'd like

Commands of forms like this should one day be grouped together under a single subcommand:

Describe alternatives you've considered

Additional context N/A

fowles commented 5 months ago

jj file chmod too, please

fowles commented 4 months ago

I am interested in adding a move/rename command. I would be willing to do the work, but need a few pointer in the code base for where to poke.

Also, I would appreciate some indication that this work would be accepted before I go and start doing it.

martinvonz commented 4 months ago

I am interested in adding a move/rename command. I would be willing to do the work, but need a few pointer in the code base for where to poke.

That command will not be very useful until we support copies and renames (but maybe still a tiny bit useful?). Do you mean that you wanted to implement that feature too? Or do you see a use for the command that I'm not seeing?

I think our canonical bug for copy/rename support is #3386. @torquestomp has started working on a design for it (and maybe I'll take that over from him). I don't think there's much work to parallelize in the beginning but maybe there will be a little later, I'm not sure.

fowles commented 4 months ago

I am willing to start with the command stuff as that is going to be a good deal simpler. If I finish the command stuff I can take a crack at implementing the underlying thing as follow. Since git auto-detects copies, it seems like it would be useful as it stands.

arxanas commented 4 months ago
ilyagr commented 4 months ago

One command that I keep wondering about adding is a command that "moves" a file in one side of a conflict.

When jj doesn't realize a move-edit conflict happened (which it currently never does), jj presents it as a delete-edit conflict and a new file. The reason I want that command is that it would let the user turn that mess into an edit-edit conflict at the new path.

martinvonz commented 4 months ago

I am willing to start with the command stuff as that is going to be a good deal simpler. If I finish the command stuff I can take a crack at implementing the underlying thing as follow. Since git auto-detects copies, it seems like it would be useful as it stands.

It seems like a regular mv would usually have the same effect. Ilya pointed out one possible use case for a jj mv command. Another is that you can run it on an arbitrary commit. But other than those two, it wouldn't help with renames AFAICT. Again, that doesn't mean it won't be useful; I'm just trying to make sure you don't expect the command to help more with renames than it actually will. In particular, note that git mv has no effect on renames (which you seem to know already since you mentioned auto-detection).

  • For moving/renaming files, there's an interesting question of what to do when the source or destination would be ignored.

When would the source or destination be ignored?

fowles commented 4 months ago

When would the source or destination be ignored?

I think when it is in the .gitignore file.

martinvonz commented 4 months ago

When would the source or destination be ignored?

I think when it is in the .gitignore file.

Ah, that kind of ignored path :) Makes sense.

Good question. If we don't give such paths any special treatment, then we'd just update the commit as usual, and when update the working copy to match (as we always do at the end of a command), we'd leave the ignored file alone, which might mean that the file appears modified afterwards. For attempting to move an ignored-and-untracked file, the command would just error out because the file doesn't exist in the commit, but it would be nice if we printed an error if the command was run on the working-copy commit.

fowles commented 4 months ago

Do folks want me to deprecate the existing file cat at the same time?

ilyagr commented 4 months ago

When jj doesn't realize a move-edit conflict happened (which it currently never does), jj presents it as a delete-edit conflict and a new file. The reason I want that command is that it would let the user turn that mess into an edit-edit conflict at the new path.

Replying to myself, I realized that there's a natural way to implement this as part of normal jj file move/cp commands.

If you jj file move a b, and b already exists, jj file move can create a conflict with sides a and b and the base being no file. This is as though there was a separate commit where you created a file called b but with value a from nothing, and then rebased that commit on top of the current one.

If b happened to be a conflicted path with an edit-deletion conflict, this will turn b into a more complicated conflict that would immediately simplify to an edit-edit conflict. Similarly, if b is a normal file and a was an edit-deletion conflict, it should also work correctly.

There could also be a jj file move a b --overwrite that disables that behavior, but that would be very similar to using the Unix mv command (unless a is conflicted, in which case the Unix mv a b command would do the wrong thing -- for technical reasons, the resulting file b would be considered non-conflicted even though it has conflict markers. There are few usecases where people would want to use it in that way, though.)


Update: I have a bit of unease to picking the conflict base in what I describe above. Another option would be for jj file move a b to create a conflict at b with sides old_value_of_b, a, and base value_of_b_in_parent_commit, or with base value_of_a_in_parent_commit. This seems tempting theoretically, but I'm not sure which to pick, so I'd rather just pick an absence of a file as bases unless we get a better idea.


More generally, I feel like a lot of jj file commands have to do with conflicts in my mind, perhaps except for file cat. I'm thinking of noting this in the help for jj file, but keeping it called jj file (as opposed to jj conflict), since all the commands seem useful for not-conflicts, especially if we have move tracking one day.

yuja commented 4 months ago

Do we also rename jj files to jj file list? It would be confusing if there were jj file and jj files.

I'm not sure about jj diff. On one hand, it's superior version of jj files. However, it can also be seen as a subset of jj show/jj log. In any case, I think the top-level jj diff alias should be preserved.

ilyagr commented 4 months ago

jj file list sounds great to me!

jj diff feels like it should not be in jj file to me, to the point that I was wondering why I felt so strongly about it. One way to make sense of this that I like is to declare that jj file should be restricted to operations that work on a single commit's tree, while jj diff compares trees from more than one commit.

This also justifies a vague feeling I had that we could make jj resolve and alias for jj file resolve. I don't think making this change is very important, but we discussed this on Discord briefly, and now I have an answer to the question of why I feel that resolve should be in jj file and diffedit shouldn't.


One version of jj file move I described (as an aside) in https://github.com/martinvonz/jj/issues/3812#issuecomment-2168957224 would also look at a parent commit's tree. Perhaps this is a reason to lean away from that version of jj file move.

fowles commented 4 months ago

I am happy to rename files to file list but I would like ot do that in a separate PR.

fowles commented 4 months ago

https://github.com/fowles/jj/tree/file_list is built on top of https://github.com/fowles/jj/tree/file_cli (https://github.com/martinvonz/jj/issues/3812), so I have that ready to go next

arxanas commented 4 months ago

I'll add them to the top-level issue.

ilyagr commented 4 months ago

(This is all an aside)

Another thing in common for jj file commands is that they very much treat a commit as a snapshot as opposed to a patch.

In theory, commands like diff, show, diffedit, squash could be moved to jj patch, e.g. jj diff could be an alias for jj patch diff. I'm not sure this is useful, but it would be consistent. One case where this would be mildly useful is that jj patch show sounds better to me than jj show, but that's not a sufficient reason create jj patch IMO.

Perhaps abandon and rebase would belong to jj patch too.

It's also curious that most, but not all, of these potential jj patch commands work not only on one patch but also on the diff between arbitrary two commits.

joyously commented 4 months ago

In theory, commands like diff, show, diffedit, squash could be moved to jj patch, e.g. jj diff could be an alias for jj patch diff. I'm not sure this is useful, but it would be consistent. One case where this would be mildly useful is that jj patch show sounds better to me than jj show, but that's not a sufficient reason create jj patch IMO.

I kind of like this concept. It reminds me of how some Git help (i don't remember where) was organized: commands that affect the graph and commands that affect files.

yuja commented 4 months ago
  • jj file print(jj file show?) (alias jj file cat)

iirc, someone said they like show, and I also vote for show.

  • jj file chmod (but chmod is a Unix-ism so we might want to use a different term)

maybe jj file set-mode? (assuming we'll add jj git remote set-url.)

ilyagr commented 4 months ago

maybe jj file set-mode?

I wonder whether jj file kind or jj file mode would work. It could print the file mode without an argument of what to set the mode to.

Or it could be jj file kind set/ jj file kind get.

Though, I feel like all of these are harder to remember than chmod. So, I'd keep chmod as an alias probably.


I'm slightly itching to add Unix-y aliases to all of these (e.g. jj file ls for jj file list, cat for print/show, etc.). Is this something we want? I feel like it will be easier to remember, but for commands other than chmod, the current names are OK too.

I also initially considered Windows-y aliases (jj file dir), but the alias for print/show would be type, which I think is confusing.

ilyagr commented 4 months ago

Also, did we consciously decide to go with jj file print instead of jj file show?

While print is OK, I still like show quite a bit better; it feels clearer. I'm happy to keep print as an alias, or get rid of it. If we go with UNIXy aliases for everything, it would also have jj file cat as an alias.

fowles commented 4 months ago

I personally like ls and cat as aliases, but I don't feel strongly.

martinvonz commented 4 months ago

Also, did we consciously decide to go with jj file print instead of jj file show?

I don't think we did. I'm fine with renaming it to jj file show. We should do that before the release next week if we're going to do it at all, so we don't have 0.19.0 renaming jj print to jj file print and then 0.20. renaming it again.

I wonder whether jj file kind or jj file mode would work. It could print the file mode without an argument of what to set the mode to.

Probably better to have separate commands for query and mutation. That's an argument I've heard before at least.

yuja commented 4 months ago

maybe jj file set-mode?

I wonder whether jj file kind or jj file mode would work. It could print the file mode without an argument of what to set the mode to.

I expect jj file list will gain a flag or template to show file modes.

jj file mode <set|get> might be okay, but I prefer somewhat flattened structure.

I'm slightly itching to add Unix-y aliases to all of these (e.g. jj file ls for jj file list, cat for print/show, etc.). Is this something we want? I feel like it will be easier to remember, but for commands other than chmod, the current names are OK too.

I like Unix-y aliases, but I would rather want them in top-level (i.e. jj cat and jj ls), which is easier to remember and can be defined by user.