Skip to content

Commit

Permalink
cli: add jj operation show command
Browse files Browse the repository at this point in the history
  • Loading branch information
bnjmnt4n committed Jul 6, 2024
1 parent dcd8381 commit 8b1ebd4
Show file tree
Hide file tree
Showing 5 changed files with 319 additions and 0 deletions.
3 changes: 3 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,9 @@ to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
* New command `jj operation diff` that can compare changes made between two
operations.

* New command `jj operation show` that can show the changes made in a single
operation.

### Fixed bugs

## [0.19.0] - 2024-07-03
Expand Down
4 changes: 4 additions & 0 deletions cli/src/commands/operation/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -16,13 +16,15 @@ mod abandon;
mod diff;
mod log;
mod restore;
mod show;
pub mod undo;

use abandon::{cmd_op_abandon, OperationAbandonArgs};
use clap::Subcommand;
use diff::{cmd_op_diff, OperationDiffArgs};
use log::{cmd_op_log, OperationLogArgs};
use restore::{cmd_op_restore, OperationRestoreArgs};
use show::{cmd_op_show, OperationShowArgs};
use undo::{cmd_op_undo, OperationUndoArgs};

use crate::cli_util::CommandHelper;
Expand All @@ -39,6 +41,7 @@ pub enum OperationCommand {
Diff(OperationDiffArgs),
Log(OperationLogArgs),
Restore(OperationRestoreArgs),
Show(OperationShowArgs),
Undo(OperationUndoArgs),
}

Expand All @@ -52,6 +55,7 @@ pub fn cmd_operation(
OperationCommand::Diff(args) => cmd_op_diff(ui, command, args),
OperationCommand::Log(args) => cmd_op_log(ui, command, args),
OperationCommand::Restore(args) => cmd_op_restore(ui, command, args),
OperationCommand::Show(args) => cmd_op_show(ui, command, args),
OperationCommand::Undo(args) => cmd_op_undo(ui, command, args),
}
}
Expand Down
123 changes: 123 additions & 0 deletions cli/src/commands/operation/show.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,123 @@
// Copyright 2024 The Jujutsu Authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// https://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

use itertools::Itertools;
use jj_lib::op_walk;

use super::diff::show_op_diff;
use crate::cli_util::{CommandHelper, LogContentFormat};
use crate::command_error::{user_error, CommandError};
use crate::diff_util::DiffFormatArgs;
use crate::operation_templater::OperationTemplateLanguage;
use crate::ui::Ui;

/// Show changes to the repository in an operation
#[derive(clap::Args, Clone, Debug)]
pub struct OperationShowArgs {
/// Show repository changes in this operation, compared to its parent(s)
#[arg(visible_alias = "op", default_value = "@")]
operation: String,
/// Don't show the graph, show a flat list of modified changes
#[arg(long)]
no_graph: bool,
/// Show patch of modifications to changes
///
/// If the previous version has different parents, it will be temporarily
/// rebased to the parents of the new version, so the diff is not
/// contaminated by unrelated changes.
#[arg(long, short = 'p')]
patch: bool,
#[command(flatten)]
diff_format: DiffFormatArgs,
}

pub fn cmd_op_show(
ui: &mut Ui,
command: &CommandHelper,
args: &OperationShowArgs,
) -> Result<(), CommandError> {
let workspace_command = command.workspace_helper(ui)?;
let repo = workspace_command.repo();
let repo_loader = &repo.loader();
let head_op_str = &command.global_args().at_operation;
let head_ops = if head_op_str == "@" {
// If multiple head ops can't be resolved without merging, let the
// current op be empty. Beware that resolve_op_for_load() will eliminate
// redundant heads whereas get_current_head_ops() won't.
let current_op = op_walk::resolve_op_for_load(repo_loader, head_op_str).ok();
if let Some(op) = current_op {
vec![op]
} else {
op_walk::get_current_head_ops(
repo_loader.op_store(),
repo_loader.op_heads_store().as_ref(),
)?
}
} else {
vec![op_walk::resolve_op_for_load(repo_loader, head_op_str)?]
};
let current_op_id = match &*head_ops {
[op] => Some(op.id()),
_ => None,
};
let op = op_walk::resolve_op_for_load(repo_loader, &args.operation)?;
let parents: Vec<_> = op.parents().try_collect()?;
if parents.is_empty() {
return Err(user_error("Cannot show the root operation"));
}
let parent_op = repo_loader.merge_operations(command.settings(), parents, None)?;
let parent_repo = repo_loader.load_at(&parent_op)?;
let repo = repo_loader.load_at(&op)?;

// Create a new transaction beginning from `repo`.
let mut workspace_command =
command.for_loaded_repo(ui, command.load_workspace()?, repo.clone())?;
let tx = workspace_command.start_transaction();

let with_content_format = LogContentFormat::new(ui, command.settings())?;

// TODO: Should we make this customizable via clap arg?
let template;
{
let language = OperationTemplateLanguage::new(
repo_loader.op_store().root_operation_id(),
current_op_id,
command.operation_template_extensions(),
);
let text = command.settings().config().get_string("templates.op_log")?;
template = command
.parse_template(
ui,
&language,
&text,
OperationTemplateLanguage::wrap_operation,
)?
.labeled("op_log");
}

ui.request_pager();
template.format(&op, ui.stdout_formatter().as_mut())?;

show_op_diff(
ui,
command,
tx,
&parent_repo,
&repo,
!args.no_graph,
&with_content_format,
&args.diff_format,
args.patch,
)
}
35 changes: 35 additions & 0 deletions cli/tests/[email protected]
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,7 @@ This document contains the help content for the `jj` command-line program.
* [`jj operation diff`↴](#jj-operation-diff)
* [`jj operation log`↴](#jj-operation-log)
* [`jj operation restore`↴](#jj-operation-restore)
* [`jj operation show`↴](#jj-operation-show)
* [`jj operation undo`↴](#jj-operation-undo)
* [`jj parallelize`↴](#jj-parallelize)
* [`jj prev`↴](#jj-prev)
Expand Down Expand Up @@ -1242,6 +1243,7 @@ For information about the operation log, see https://github.com/martinvonz/jj/bl
* `diff` — Compare changes to the repository between two operations
* `log` — Show the operation log
* `restore` — Create a new operation that restores the repo to an earlier state
* `show` — Show changes to the repository in an operation
* `undo` — Create a new operation that undoes an earlier operation
Expand Down Expand Up @@ -1341,6 +1343,39 @@ This restores the repo to the state at the specified operation, effectively undo
## `jj operation show`
Show changes to the repository in an operation
**Usage:** `jj operation show [OPTIONS] [OPERATION]`
###### **Arguments:**
* `<OPERATION>` — Show repository changes in this operation, compared to its parent(s)
Default value: `@`
###### **Options:**
* `--no-graph` — Don't show the graph, show a flat list of modified changes
* `-p`, `--patch` — Show patch of modifications to changes
If the previous version has different parents, it will be temporarily rebased to the parents of the new version, so the diff is not contaminated by unrelated changes.
* `-s`, `--summary` — For each path, show only whether it was modified, added, or deleted
* `--stat` — Show a histogram of the changes
* `--types` — For each path, show only its type before and after
The diff is shown as two letters. The first letter indicates the type before and the second letter indicates the type after. '-' indicates that the path was not present, 'F' represents a regular file, `L' represents a symlink, 'C' represents a conflict, and 'G' represents a Git submodule.
* `--name-only` — For each path, show only its path
Typically useful for shell commands like: `jj diff -r @- --name_only | xargs perl -pi -e's/OLD/NEW/g`
* `--git` — Show a Git-format diff
* `--color-words` — Show a word-level diff with changes indicated only by color
* `--tool <TOOL>` — Generate diff by external command
* `--context <CONTEXT>` — Number of lines of context to show
## `jj operation undo`
Create a new operation that undoes an earlier operation
Expand Down
154 changes: 154 additions & 0 deletions cli/tests/test_operations.rs
Original file line number Diff line number Diff line change
Expand Up @@ -900,6 +900,160 @@ fn test_op_diff() {
"###);
}

#[test]
fn test_op_show() {
let test_env = TestEnvironment::default();
let git_repo_path = test_env.env_root().join("git-repo");
let git_repo = init_bare_git_repo(&git_repo_path);
test_env.jj_cmd_ok(test_env.env_root(), &["git", "clone", "git-repo", "repo"]);
let repo_path = test_env.env_root().join("repo");

// Overview of op log.
let stdout = test_env.jj_cmd_success(&repo_path, &["op", "log"]);
insta::assert_snapshot!(&stdout, @r###"
@ 984d5ceb039f [email protected] 2001-02-03 04:05:07.000 +07:00 - 2001-02-03 04:05:07.000 +07:00
│ check out git remote's default branch
│ args: jj git clone git-repo repo
◉ 817baaeefcbb [email protected] 2001-02-03 04:05:07.000 +07:00 - 2001-02-03 04:05:07.000 +07:00
│ fetch from git remote into empty repo
│ args: jj git clone git-repo repo
◉ 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()
"###);

// The root operation is empty.
let stderr = test_env.jj_cmd_failure(&repo_path, &["op", "show", "0000000"]);
insta::assert_snapshot!(&stderr, @r###"
Error: Cannot show the root operation
"###);

// Showing the latest operation.
let stdout = test_env.jj_cmd_success(&repo_path, &["op", "show", "@"]);
insta::assert_snapshot!(&stdout, @r###"
984d5ceb039f [email protected] 2001-02-03 04:05:07.000 +07:00 - 2001-02-03 04:05:07.000 +07:00
check out git remote's default branch
args: jj git clone git-repo repo
Changed commits:
○ Change sqpuoqvxutmz
+sqpuoqvx 9708515f (empty) (no description set)
○ Change qpvuntsmwlqt
-qpvuntsm hidden 230dd059 (empty) (no description set)
Changed local branches:
branch-1:
+ ulyvmwyz 1d843d1f branch-1 | Commit 1
- (absent)
Changed remote branches:
branch-1@origin:
+ (tracked) ulyvmwyz 1d843d1f branch-1 | Commit 1
- (untracked) ulyvmwyz 1d843d1f branch-1 | Commit 1
"###);
// `jj op show @` should behave identically to `jj op show`.
let stdout_without_op_id = test_env.jj_cmd_success(&repo_path, &["op", "show"]);
assert_eq!(stdout, stdout_without_op_id);

// Showing a given operation.
let stdout = test_env.jj_cmd_success(&repo_path, &["op", "show", "@-"]);
insta::assert_snapshot!(&stdout, @r###"
817baaeefcbb [email protected] 2001-02-03 04:05:07.000 +07:00 - 2001-02-03 04:05:07.000 +07:00
fetch from git remote into empty repo
args: jj git clone git-repo repo
Changed commits:
○ Change tqyxmsztkvot
+tqyxmszt 3e785984 branch-3@origin | Commit 3
○ Change yuvsmzqkmpws
+yuvsmzqk 3d9189bc branch-2@origin | Commit 2
○ Change ulyvmwyzwuwt
+ulyvmwyz 1d843d1f branch-1@origin | Commit 1
Changed remote branches:
branch-1@origin:
+ (untracked) ulyvmwyz 1d843d1f branch-1@origin | Commit 1
- (untracked) (absent)
branch-2@origin:
+ (untracked) yuvsmzqk 3d9189bc branch-2@origin | Commit 2
- (untracked) (absent)
branch-3@origin:
+ (untracked) tqyxmszt 3e785984 branch-3@origin | Commit 3
- (untracked) (absent)
"###);

// Create a conflicted branch using a concurrent operation.
test_env.jj_cmd_ok(
&repo_path,
&[
"branch",
"set",
"branch-1",
"-r",
"branch-2@origin",
"--at-op",
"@-",
],
);
let (_, stderr) = test_env.jj_cmd_ok(&repo_path, &["log"]);
insta::assert_snapshot!(&stderr, @r###"
Concurrent modification detected, resolving automatically.
"###);
// Showing a merge operation is empty.
let stdout = test_env.jj_cmd_success(&repo_path, &["op", "show"]);
insta::assert_snapshot!(&stdout, @r###"
6c131cd79314 [email protected] 2001-02-03 04:05:14.000 +07:00 - 2001-02-03 04:05:14.000 +07:00
resolve concurrent operations
args: jj log
"###);

// Test fetching from git remote.
modify_git_repo(git_repo);
let (stdout, stderr) = test_env.jj_cmd_ok(&repo_path, &["git", "fetch"]);
insta::assert_snapshot!(&stdout, @r###"
"###);
insta::assert_snapshot!(&stderr, @r###"
branch: branch-1@origin [updated] tracked
branch: branch-2@origin [updated] untracked
branch: branch-3@origin [deleted] untracked
Abandoned 1 commits that are no longer reachable.
"###);
let stdout = test_env.jj_cmd_success(&repo_path, &["op", "show"]);
insta::assert_snapshot!(&stdout, @r###"
84466f397d80 [email protected] 2001-02-03 04:05:16.000 +07:00 - 2001-02-03 04:05:16.000 +07:00
fetch from git remote(s) origin
args: jj git fetch
Changed commits:
○ Change qzxslznxxpoz
+qzxslznx d487febd branch-2@origin | Commit 5
○ Change slvtnnzxztqy
+slvtnnzx 4f856199 branch-1?? branch-1@origin | Commit 4
○ Change tqyxmsztkvot
-tqyxmszt hidden 3e785984 Commit 3
Changed local branches:
branch-1:
+ (added) slvtnnzx 4f856199 branch-1?? branch-1@origin | Commit 4
+ (added) yuvsmzqk 3d9189bc branch-1?? | Commit 2
- (added) ulyvmwyz 1d843d1f Commit 1
- (added) yuvsmzqk 3d9189bc branch-1?? | Commit 2
Changed remote branches:
branch-1@origin:
+ (tracked) slvtnnzx 4f856199 branch-1?? branch-1@origin | Commit 4
- (tracked) ulyvmwyz 1d843d1f Commit 1
branch-2@origin:
+ (untracked) qzxslznx d487febd branch-2@origin | Commit 5
- (untracked) yuvsmzqk 3d9189bc branch-1?? | Commit 2
branch-3@origin:
+ (untracked) (absent)
- (untracked) tqyxmszt hidden 3e785984 Commit 3
"###);
}

fn init_bare_git_repo(git_repo_path: &Path) -> git2::Repository {
let git_repo = git2::Repository::init_bare(git_repo_path).unwrap();
let git_blob_oid = git_repo.blob(b"some content").unwrap();
Expand Down

0 comments on commit 8b1ebd4

Please sign in to comment.