Skip to content

Commit

Permalink
cli: add global option to not commit transaction
Browse files Browse the repository at this point in the history
This patch adds a global `--no-commit-transaction` flag that prevents
publishing of most operations, including the ones created by
`snapshot_working_copy()` and `finish_transaction()`. The operations
are still created as usual.

We may want to follow up with a `jj op publish/commit/adopt` command
for publishing an operation that was not committed.

Closes #2562
  • Loading branch information
martinvonz committed Sep 13, 2024
1 parent 136dcac commit c6b2f3d
Show file tree
Hide file tree
Showing 7 changed files with 140 additions and 9 deletions.
3 changes: 3 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,9 @@ to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).

* `jj op log` gained an option to include operation diffs.

* A new global flag `--no-commit-transaction` lets you run a command without
impacting the repo state or the working copy.

### Fixed bugs

* Fixed panic when parsing invalid conflict markers of a particular form.
Expand Down
61 changes: 56 additions & 5 deletions cli/src/cli_util.rs
Original file line number Diff line number Diff line change
Expand Up @@ -346,6 +346,23 @@ impl CommandHelper {
)?)
}

pub fn should_commit_transaction(&self) -> bool {
!self.global_args().no_commit_transaction
}

pub fn maybe_commit_transaction(
&self,
tx: Transaction,
description: impl Into<String>,
) -> Arc<ReadonlyRepo> {
let unpublished_op = tx.write(description);
if self.should_commit_transaction() {
unpublished_op.publish()
} else {
unpublished_op.leave_unpublished()
}
}

pub fn workspace_loader(&self) -> Result<&dyn WorkspaceLoader, CommandError> {
self.data
.maybe_workspace_loader
Expand Down Expand Up @@ -839,7 +856,11 @@ impl WorkspaceCommandHelper {
// state to it without updating working copy files.
locked_ws.locked_wc().reset(&new_git_head_commit)?;
tx.repo_mut().rebase_descendants(command.settings())?;
self.user_repo = ReadonlyUserRepo::new(tx.commit("import git head"));
self.user_repo = ReadonlyUserRepo::new(
self.env
.command
.maybe_commit_transaction(tx, "import git head"),
);
locked_ws.finish(self.user_repo.repo.op_id().clone())?;
if old_git_head.is_present() {
writeln!(
Expand Down Expand Up @@ -1489,9 +1510,15 @@ See https://martinvonz.github.io/jj/latest/working-copy/#stale-working-copy \
print_failed_git_export(ui, &refs)?;
}

self.user_repo = ReadonlyUserRepo::new(tx.commit("snapshot working copy"));
self.user_repo = ReadonlyUserRepo::new(
self.env
.command
.maybe_commit_transaction(tx, "snapshot working copy"),
);
}
if self.env.command.should_commit_transaction() {
locked_ws.finish(self.user_repo.repo.op_id().clone())?;
}
locked_ws.finish(self.user_repo.repo.op_id().clone())?;
Ok(())
}

Expand Down Expand Up @@ -1606,10 +1633,11 @@ See https://martinvonz.github.io/jj/latest/working-copy/#stale-working-copy \
print_failed_git_export(ui, &refs)?;
}

self.user_repo = ReadonlyUserRepo::new(tx.commit(description));
self.user_repo =
ReadonlyUserRepo::new(self.env.command.maybe_commit_transaction(tx, description));
self.report_repo_changes(ui, &old_repo)?;

if self.may_update_working_copy {
if self.may_update_working_copy && self.env.command.should_commit_transaction() {
if let Some(new_commit) = &maybe_new_wc_commit {
self.update_working_copy(ui, maybe_old_wc_commit.as_ref(), new_commit)?;
} else {
Expand Down Expand Up @@ -1738,6 +1766,14 @@ See https://martinvonz.github.io/jj/latest/working-copy/#stale-working-copy \
)?;
}

if !self.env.command.should_commit_transaction() {
writeln!(
fmt,
"Operation left uncommitted because --no-commit-transaction was requested: {}",
short_operation_hash(self.repo().op_id())
)?;
}

Ok(())
}

Expand Down Expand Up @@ -2610,6 +2646,21 @@ pub struct GlobalArgs {
/// implies `--ignore-working-copy`.
#[arg(long, global = true)]
pub ignore_working_copy: bool,
/// Run the command as usual but don't commit any transactions
///
/// When this option is given, the operations will still be created as
/// usual but they will not be committed/promoted to the operation log. The
/// working copy will also not be updated.
///
/// The command will print the resulting operation id. You can pass that to
/// e.g. `jj --at-op` to inspect the resulting repo state, or you can pass
/// it to `jj op restore` to restore the repo to that state.
///
/// Note that this does *not* prevent side effects outside the repo. For
/// example, `jj git push --no-commit-transaction` will still perform
/// the push.
#[arg(long, global = true)]
pub no_commit_transaction: bool,
/// Allow rewriting immutable commits
///
/// By default, Jujutsu prevents rewriting commits in the configured set of
Expand Down
6 changes: 4 additions & 2 deletions cli/src/commands/file/track.rs
Original file line number Diff line number Diff line change
Expand Up @@ -62,7 +62,9 @@ pub(crate) fn cmd_file_track(
if num_rebased > 0 {
writeln!(ui.status(), "Rebased {num_rebased} descendant commits")?;
}
let repo = tx.commit("track paths");
locked_ws.finish(repo.op_id().clone())?;
if command.should_commit_transaction() {
let repo = tx.commit("track paths");
locked_ws.finish(repo.op_id().clone())?;
}
Ok(())
}
6 changes: 4 additions & 2 deletions cli/src/commands/file/untrack.rs
Original file line number Diff line number Diff line change
Expand Up @@ -107,7 +107,9 @@ Make sure they're ignored, then try again.",
if num_rebased > 0 {
writeln!(ui.status(), "Rebased {num_rebased} descendant commits")?;
}
let repo = tx.commit("untrack paths");
locked_ws.finish(repo.op_id().clone())?;
if command.should_commit_transaction() {
let repo = tx.commit("untrack paths");
locked_ws.finish(repo.op_id().clone())?;
}
Ok(())
}
3 changes: 3 additions & 0 deletions cli/src/commands/git/init.rs
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,9 @@ pub fn cmd_git_init(
command: &CommandHelper,
args: &GitInitArgs,
) -> Result<(), CommandError> {
if command.global_args().no_commit_transaction {
return Err(cli_error("--no-commit-transaction is not respected"));
}
if command.global_args().ignore_working_copy {
return Err(cli_error("--ignore-working-copy is not respected"));
}
Expand Down
7 changes: 7 additions & 0 deletions cli/tests/[email protected]
Original file line number Diff line number Diff line change
Expand Up @@ -159,6 +159,13 @@ To get started, see the tutorial at https://martinvonz.github.io/jj/latest/tutor
By default, Jujutsu snapshots the working copy at the beginning of every command. The working copy is also updated at the end of the command, if the command modified the working-copy commit (`@`). If you want to avoid snapshotting the working copy and instead see a possibly stale working copy commit, you can use `--ignore-working-copy`. This may be useful e.g. in a command prompt, especially if you have another process that commits the working copy.
Loading the repository at a specific operation with `--at-operation` implies `--ignore-working-copy`.
* `--no-commit-transaction` — Run the command as usual but don't commit any transactions
When this option is given, the operations will still be created as usual but they will not be committed/promoted to the operation log. The working copy will also not be updated.
The command will print the resulting operation id. You can pass that to e.g. `jj --at-op` to inspect the resulting repo state, or you can pass it to `jj op restore` to restore the repo to that state.
Note that this does *not* prevent side effects outside the repo. For example, `jj git push --no-commit-transaction` will still perform the push.
* `--ignore-immutable` — Allow rewriting immutable commits
By default, Jujutsu prevents rewriting commits in the configured set of immutable commits. This option disables that check and lets you rewrite any commit but the root commit.
Expand Down
63 changes: 63 additions & 0 deletions cli/tests/test_global_opts.rs
Original file line number Diff line number Diff line change
Expand Up @@ -147,6 +147,68 @@ fn test_ignore_working_copy() {
"###);
}

#[test]
fn test_no_commit_transaction() {
let test_env = TestEnvironment::default();
test_env.jj_cmd_ok(test_env.env_root(), &["git", "init", "repo"]);

let repo_path = test_env.env_root().join("repo");

std::fs::write(repo_path.join("file1"), "initial").unwrap();
test_env.jj_cmd_ok(&repo_path, &["commit", "-m=initial"]);
let op_log_stdout = test_env.jj_cmd_success(&repo_path, &["op", "log"]);
let working_copy_stdout = test_env.jj_cmd_success(&repo_path, &["debug", "working-copy"]);

// Modify the working copy and run a mutating operation. With
// --no-commit-transaction, the working copy gets snapshotted and the operation
// gets created, but there's no new operation in the operation log, and the
// working copy state is not updated.
std::fs::write(repo_path.join("file2"), "initial").unwrap();
let (stdout, stderr) = test_env.jj_cmd_ok(&repo_path, &["squash", "--no-commit-transaction"]);
insta::assert_snapshot!(stdout, @"");
insta::assert_snapshot!(stderr, @r###"
Operation left uncommitted because --no-commit-transaction was requested: 3fa0e7ceb0e4
"###);
let first_line = stderr.split('\n').next().unwrap();
let op_id_hex = first_line[first_line.len()-12..].to_string();
let stdout = test_env.jj_cmd_success(&repo_path, &["op", "log", "--ignore-working-copy"]);
assert_eq!(stdout, op_log_stdout);
let stdout = test_env.jj_cmd_success(&repo_path, &["debug", "working-copy"]);
assert_eq!(stdout, working_copy_stdout);

// We can see the resulting log and op log with --at-op
let stdout = test_env.jj_cmd_success(&repo_path, &["log", "-s", "--at-op", op_id_hex.as_str()]);
insta::assert_snapshot!(stdout, @r###"
@ mzvwutvl [email protected] 2001-02-03 08:05:11 e4e6953f
│ (empty) (no description set)
○ qpvuntsm [email protected] 2001-02-03 08:05:11 a2280cba
│ initial
│ A file1
│ A file2
◆ zzzzzzzz root() 00000000
"###);
let stdout = test_env.jj_cmd_success(&repo_path, &["op", "log", "--at-op", op_id_hex.as_str()]);
insta::assert_snapshot!(stdout, @r###"
@ 3fa0e7ceb0e4 [email protected] 2001-02-03 04:05:11.000 +07:00 - 2001-02-03 04:05:11.000 +07:00
│ squash commits into dc5f5c36813feca46c1e26ea80c9634ea2fdb9e6
│ args: jj squash --no-commit-transaction
○ 63a798419dc4 [email protected] 2001-02-03 04:05:11.000 +07:00 - 2001-02-03 04:05:11.000 +07:00
│ snapshot working copy
│ args: jj squash --no-commit-transaction
○ 04139fd4890a [email protected] 2001-02-03 04:05:08.000 +07:00 - 2001-02-03 04:05:08.000 +07:00
│ commit a38d281287b19f33abe36fedb6c2df1370b90f54
│ args: jj commit '-m=initial'
○ 0db616e9e09a [email protected] 2001-02-03 04:05:08.000 +07:00 - 2001-02-03 04:05:08.000 +07:00
│ snapshot working copy
│ args: jj commit '-m=initial'
○ b51416386f26 [email protected] 2001-02-03 04:05:07.000 +07:00 - 2001-02-03 04:05:07.000 +07:00
│ add workspace 'default'
○ 9a7d829846af [email protected] 2001-02-03 04:05:07.000 +07:00 - 2001-02-03 04:05:07.000 +07:00
│ initialize repo
○ 000000000000 root()
"###);
}

#[test]
fn test_repo_arg_with_init() {
let test_env = TestEnvironment::default();
Expand Down Expand Up @@ -608,6 +670,7 @@ fn test_help() {
Global Options:
-R, --repository <REPOSITORY> Path to repository to operate on
--ignore-working-copy Don't snapshot the working copy, and don't update it
--no-commit-transaction Run the command as usual but don't commit any transactions
--ignore-immutable Allow rewriting immutable commits
--at-operation <AT_OPERATION> Operation to load the repo at [aliases: at-op]
--debug Enable debug logging
Expand Down

0 comments on commit c6b2f3d

Please sign in to comment.