-
Notifications
You must be signed in to change notification settings - Fork 350
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 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
1,371 additions
and
731 deletions.
There are no files selected for viewing
Large diffs are not rendered by default.
Oops, something went wrong.
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,89 @@ | ||
// 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, AnnotateResults}; | ||
use jj_lib::repo::{ReadonlyRepo, Repo}; | ||
use jj_lib::settings::UserSettings; | ||
use tracing::instrument; | ||
|
||
use crate::cli_util::{CommandHelper, RevisionArg, WorkspaceCommandHelper}; | ||
use crate::command_error::CommandError; | ||
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. | ||
/// This command will fail if the file is in a conflicted state currently or | ||
/// in previous changes. 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 AnnotateArgs { | ||
/// 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_annotate( | ||
ui: &mut Ui, | ||
command: &CommandHelper, | ||
args: &AnnotateArgs, | ||
) -> Result<(), CommandError> { | ||
let workspace_command = command.workspace_helper(ui)?; | ||
let repo = workspace_command.repo(); | ||
let starting_commit = | ||
workspace_command.resolve_single_rev(args.revision.as_ref().unwrap_or(&RevisionArg::AT))?; | ||
let file_path = workspace_command.parse_file_path(&args.path)?; | ||
|
||
let annotations = get_annotation_for_file(&file_path, repo, &starting_commit); | ||
if let Err(e) = annotations { | ||
eprintln!("{}", e); | ||
return Ok(()); | ||
} | ||
let annotations = annotations.unwrap(); | ||
render_annotations( | ||
repo, | ||
ui, | ||
command.settings(), | ||
&workspace_command, | ||
&annotations, | ||
)?; | ||
Ok(()) | ||
} | ||
|
||
fn render_annotations( | ||
repo: &ReadonlyRepo, | ||
ui: &mut Ui, | ||
settings: &UserSettings, | ||
workspace_command: &WorkspaceCommandHelper, | ||
results: &AnnotateResults, | ||
) -> Result<(), CommandError> { | ||
let annotate_commit_summary_text = settings | ||
.config() | ||
.get_string("templates.annotate_commit_summary")?; | ||
let template = workspace_command.parse_commit_template(&annotate_commit_summary_text)?; | ||
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.format(&commit, formatter.as_mut())?; | ||
writeln!(formatter, " {}: {}", line_no + 1, 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,114 @@ | ||
// 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, &["annotate", "file.txt"]); | ||
insta::assert_snapshot!(stdout, @r###" | ||
qpvuntsm 8934c772 Test User 2001-02-03 08:05:08 1: line1 | ||
kkmpptxz 41ae16e6 Test User 2001-02-03 08:05:09 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, &["annotate", "file.txt"]); | ||
insta::assert_snapshot!(stdout, @r###" | ||
qpvuntsm 8934c772 Test User 2001-02-03 08:05:08 1: line1 | ||
zsuskuln 712ba14a Test User 2001-02-03 08:05:10 2: new text from new commit 1 | ||
royxmykx b0571bd9 Test User 2001-02-03 08:05:12 3: new text from new commit 2 | ||
"###); | ||
} | ||
|
||
#[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, &["annotate", "file.txt"]); | ||
insta::assert_snapshot!(stdout, @r###" | ||
qpvuntsm 8934c772 Test User 2001-02-03 08:05:08 1: line1 | ||
zsuskuln 712ba14a Test User 2001-02-03 08:05:10 2: new text from new commit 1 | ||
"###); | ||
} |
Oops, something went wrong.