-
Notifications
You must be signed in to change notification settings - Fork 345
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
A new module is added to jj_lib which exposes a function get_annotation_for_file. This annotates the given file line by line with commit information according to the commit that made the most recent change to the line. Similarly, a new command is added to the CLI called `jj file annotate` which accepts a file path. It then prints out line by line the commit information for the line and the line itself. Specific commit information can be configured via the templates.annotate_commit_summary config variable
- Loading branch information
Showing
9 changed files
with
636 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,94 @@ | ||
// 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 jj_lib::annotate::get_annotation_for_file; | ||
use jj_lib::annotate::AnnotateResults; | ||
use jj_lib::commit::Commit; | ||
use jj_lib::repo::Repo; | ||
use tracing::instrument; | ||
|
||
use crate::cli_util::CommandHelper; | ||
use crate::cli_util::RevisionArg; | ||
use crate::command_error::user_error; | ||
use crate::command_error::CommandError; | ||
use crate::templater::TemplateRenderer; | ||
use crate::ui::Ui; | ||
|
||
/// Show the source change for each line of the target file. | ||
/// | ||
/// Annotates a revision line by line. Each line includes the source change that | ||
/// introduced the associated line. A path to the desired file must be provided. | ||
/// The per-line prefix for each line can be customized via | ||
/// template with the `templates.annotate_commit_summary` config variable. | ||
#[derive(clap::Args, Clone, Debug)] | ||
pub(crate) struct FileAnnotateArgs { | ||
/// the file to annotate | ||
#[arg(value_hint = clap::ValueHint::AnyPath)] | ||
path: String, | ||
/// an optional revision to start at | ||
#[arg(long, short)] | ||
revision: Option<RevisionArg>, | ||
} | ||
|
||
#[instrument(skip_all)] | ||
pub(crate) fn cmd_file_annotate( | ||
ui: &mut Ui, | ||
command: &CommandHelper, | ||
args: &FileAnnotateArgs, | ||
) -> Result<(), CommandError> { | ||
let workspace_command = command.workspace_helper(ui)?; | ||
let repo = workspace_command.repo(); | ||
let starting_commit = workspace_command | ||
.resolve_single_rev(ui, args.revision.as_ref().unwrap_or(&RevisionArg::AT))?; | ||
let file_path = workspace_command.parse_file_path(&args.path)?; | ||
let file_value = starting_commit.tree()?.path_value(&file_path)?; | ||
let ui_path = workspace_command.format_file_path(&file_path); | ||
if file_value.is_absent() { | ||
return Err(user_error(format!("No such path: {ui_path}"))); | ||
} | ||
if file_value.is_tree() { | ||
return Err(user_error(format!( | ||
"Path exists but is not a regular file: {ui_path}" | ||
))); | ||
} | ||
|
||
let annotate_commit_summary_text = command | ||
.settings() | ||
.config() | ||
.get_string("templates.annotate_commit_summary")?; | ||
let template = workspace_command.parse_commit_template(ui, &annotate_commit_summary_text)?; | ||
|
||
let annotations = get_annotation_for_file(repo.as_ref(), &starting_commit, &file_path)?; | ||
|
||
render_annotations(repo.as_ref(), ui, &template, &annotations)?; | ||
Ok(()) | ||
} | ||
|
||
fn render_annotations( | ||
repo: &dyn Repo, | ||
ui: &mut Ui, | ||
template_render: &TemplateRenderer<Commit>, | ||
results: &AnnotateResults, | ||
) -> Result<(), CommandError> { | ||
ui.request_pager(); | ||
let mut formatter = ui.stdout_formatter(); | ||
for (line_no, (commit_id, line)) in results.file_annotations.iter().enumerate() { | ||
let commit = repo.store().get_commit(commit_id)?; | ||
template_render.format(&commit, formatter.as_mut())?; | ||
write!(formatter, " {:>4}: ", line_no + 1)?; | ||
formatter.write_all(line)?; | ||
} | ||
|
||
Ok(()) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,148 @@ | ||
// 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 std::fs::OpenOptions; | ||
use std::io::Write; | ||
use std::path::Path; | ||
|
||
use crate::common::TestEnvironment; | ||
|
||
fn append_to_file(file_path: &Path, contents: &str) { | ||
let mut options = OpenOptions::new(); | ||
options.append(true); | ||
let mut file = options.open(file_path).unwrap(); | ||
writeln!(file, "{contents}").unwrap(); | ||
} | ||
|
||
#[test] | ||
fn test_annotate_linear() { | ||
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("file.txt"), "line1\n").unwrap(); | ||
test_env.jj_cmd_ok(&repo_path, &["describe", "-m=initial"]); | ||
|
||
test_env.jj_cmd_ok(&repo_path, &["new", "-m=next"]); | ||
append_to_file(&repo_path.join("file.txt"), "new text from new commit"); | ||
|
||
let stdout = test_env.jj_cmd_success(&repo_path, &["file", "annotate", "file.txt"]); | ||
insta::assert_snapshot!(stdout, @r###" | ||
qpvuntsm 8934c772 [email protected] 2001-02-03 08:05:08 1: line1 | ||
kkmpptxz 41ae16e6 [email protected] 2001-02-03 08:05:10 2: new text from new commit | ||
"###); | ||
} | ||
|
||
#[test] | ||
fn test_annotate_merge() { | ||
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("file.txt"), "line1\n").unwrap(); | ||
test_env.jj_cmd_ok(&repo_path, &["describe", "-m=initial"]); | ||
test_env.jj_cmd_ok(&repo_path, &["branch", "create", "initial"]); | ||
|
||
test_env.jj_cmd_ok(&repo_path, &["new", "-m=commit1"]); | ||
append_to_file(&repo_path.join("file.txt"), "new text from new commit 1"); | ||
test_env.jj_cmd_ok(&repo_path, &["branch", "create", "commit1"]); | ||
|
||
test_env.jj_cmd_ok(&repo_path, &["new", "-m=commit2", "initial"]); | ||
append_to_file(&repo_path.join("file.txt"), "new text from new commit 2"); | ||
test_env.jj_cmd_ok(&repo_path, &["branch", "create", "commit2"]); | ||
|
||
// create a (conflicted) merge | ||
test_env.jj_cmd_ok(&repo_path, &["new", "-m=merged", "commit1", "commit2"]); | ||
// resolve conflicts | ||
std::fs::write( | ||
repo_path.join("file.txt"), | ||
"line1\nnew text from new commit 1\nnew text from new commit 2\n", | ||
) | ||
.unwrap(); | ||
|
||
let stdout = test_env.jj_cmd_success(&repo_path, &["file", "annotate", "file.txt"]); | ||
insta::assert_snapshot!(stdout, @r###" | ||
qpvuntsm 8934c772 [email protected] 2001-02-03 08:05:08 1: line1 | ||
zsuskuln 712ba14a [email protected] 2001-02-03 08:05:11 2: new text from new commit 1 | ||
royxmykx b0571bd9 [email protected] 2001-02-03 08:05:13 3: new text from new commit 2 | ||
"###); | ||
} | ||
|
||
#[test] | ||
fn test_annotate_conflicted() { | ||
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("file.txt"), "line1\n").unwrap(); | ||
test_env.jj_cmd_ok(&repo_path, &["describe", "-m=initial"]); | ||
test_env.jj_cmd_ok(&repo_path, &["branch", "create", "initial"]); | ||
|
||
test_env.jj_cmd_ok(&repo_path, &["new", "-m=commit1"]); | ||
append_to_file(&repo_path.join("file.txt"), "new text from new commit 1"); | ||
test_env.jj_cmd_ok(&repo_path, &["branch", "create", "commit1"]); | ||
|
||
test_env.jj_cmd_ok(&repo_path, &["new", "-m=commit2", "initial"]); | ||
append_to_file(&repo_path.join("file.txt"), "new text from new commit 2"); | ||
test_env.jj_cmd_ok(&repo_path, &["branch", "create", "commit2"]); | ||
|
||
// create a (conflicted) merge | ||
test_env.jj_cmd_ok(&repo_path, &["new", "-m=merged", "commit1", "commit2"]); | ||
test_env.jj_cmd_ok(&repo_path, &["new"]); | ||
|
||
let stdout = test_env.jj_cmd_success(&repo_path, &["file", "annotate", "file.txt"]); | ||
insta::assert_snapshot!(stdout, @r###" | ||
qpvuntsm 8934c772 [email protected] 2001-02-03 08:05:08 1: line1 | ||
yostqsxw 7b90c9f6 [email protected] 2001-02-03 08:05:15 2: <<<<<<< Conflict 1 of 1 | ||
yostqsxw 7b90c9f6 [email protected] 2001-02-03 08:05:15 3: %%%%%%% Changes from base to side #1 | ||
yostqsxw 7b90c9f6 [email protected] 2001-02-03 08:05:15 4: +new text from new commit 1 | ||
yostqsxw 7b90c9f6 [email protected] 2001-02-03 08:05:15 5: +++++++ Contents of side #2 | ||
royxmykx b0571bd9 [email protected] 2001-02-03 08:05:13 6: new text from new commit 2 | ||
yostqsxw 7b90c9f6 [email protected] 2001-02-03 08:05:15 7: >>>>>>> Conflict 1 of 1 ends | ||
"###); | ||
} | ||
|
||
#[test] | ||
fn test_annotate_merge_one_sided_conflict_resolution() { | ||
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("file.txt"), "line1\n").unwrap(); | ||
test_env.jj_cmd_ok(&repo_path, &["describe", "-m=initial"]); | ||
test_env.jj_cmd_ok(&repo_path, &["branch", "create", "initial"]); | ||
|
||
test_env.jj_cmd_ok(&repo_path, &["new", "-m=commit1"]); | ||
append_to_file(&repo_path.join("file.txt"), "new text from new commit 1"); | ||
test_env.jj_cmd_ok(&repo_path, &["branch", "create", "commit1"]); | ||
|
||
test_env.jj_cmd_ok(&repo_path, &["new", "-m=commit2", "initial"]); | ||
append_to_file(&repo_path.join("file.txt"), "new text from new commit 2"); | ||
test_env.jj_cmd_ok(&repo_path, &["branch", "create", "commit2"]); | ||
|
||
// create a (conflicted) merge | ||
test_env.jj_cmd_ok(&repo_path, &["new", "-m=merged", "commit1", "commit2"]); | ||
// resolve conflicts | ||
std::fs::write( | ||
repo_path.join("file.txt"), | ||
"line1\nnew text from new commit 1\n", | ||
) | ||
.unwrap(); | ||
|
||
let stdout = test_env.jj_cmd_success(&repo_path, &["file", "annotate", "file.txt"]); | ||
insta::assert_snapshot!(stdout, @r###" | ||
qpvuntsm 8934c772 [email protected] 2001-02-03 08:05:08 1: line1 | ||
zsuskuln 712ba14a [email protected] 2001-02-03 08:05:11 2: new text from new commit 1 | ||
"###); | ||
} |
Oops, something went wrong.