feat: complete JjLib describe implementation
This commit is contained in:
@@ -8,4 +8,4 @@ mod description;
|
|||||||
pub use description::{Description, DescriptionError};
|
pub use description::{Description, DescriptionError};
|
||||||
|
|
||||||
mod message;
|
mod message;
|
||||||
pub use message::{CommitMessageError, ConventionalCommit};
|
pub use message::CommitMessageError;
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
use lazy_regex::regex_find;
|
|
||||||
|
|
||||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||||
#[repr(transparent)]
|
#[repr(transparent)]
|
||||||
|
|||||||
24
src/error.rs
24
src/error.rs
@@ -56,3 +56,27 @@ impl From<jj_lib::config::ConfigGetError> for Error {
|
|||||||
Self::FailedReadingConfig
|
Self::FailedReadingConfig
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl From<jj_lib::repo::RepoLoaderError> for Error {
|
||||||
|
fn from(error: jj_lib::repo::RepoLoaderError) -> Self {
|
||||||
|
Self::JjOperation {
|
||||||
|
context: format!("Failed to load repository: {}", error),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<jj_lib::backend::BackendError> for Error {
|
||||||
|
fn from(error: jj_lib::backend::BackendError) -> Self {
|
||||||
|
Self::JjOperation {
|
||||||
|
context: format!("Backend operation failed: {}", error),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<jj_lib::transaction::TransactionCommitError> for Error {
|
||||||
|
fn from(error: jj_lib::transaction::TransactionCommitError) -> Self {
|
||||||
|
Self::JjOperation {
|
||||||
|
context: format!("Transaction commit failed: {}", error),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,9 +1,9 @@
|
|||||||
use std::path::Path;
|
use std::path::Path;
|
||||||
|
|
||||||
use jj_lib::config::StackedConfig;
|
use jj_lib::config::StackedConfig;
|
||||||
|
use jj_lib::repo::{Repo, StoreFactories};
|
||||||
use jj_lib::settings::UserSettings;
|
use jj_lib::settings::UserSettings;
|
||||||
use jj_lib::workspace::{Workspace, default_working_copy_factories};
|
use jj_lib::workspace::{Workspace, default_working_copy_factories};
|
||||||
use jj_lib::repo::{Repo, StoreFactories};
|
|
||||||
|
|
||||||
use crate::error::Error;
|
use crate::error::Error;
|
||||||
use crate::jj::JjExecutor;
|
use crate::jj::JjExecutor;
|
||||||
@@ -46,15 +46,82 @@ impl JjExecutor for JjLib {
|
|||||||
let settings = UserSettings::from_config(config)?;
|
let settings = UserSettings::from_config(config)?;
|
||||||
let store_factories = StoreFactories::default();
|
let store_factories = StoreFactories::default();
|
||||||
let wc_factories = default_working_copy_factories();
|
let wc_factories = default_working_copy_factories();
|
||||||
match Workspace::load(&settings, &std::env::current_dir()?, &store_factories, &wc_factories) {
|
|
||||||
Ok(_) => Ok(true),
|
// Check if the directory exists first
|
||||||
Err(_) => Ok(false)
|
if !self.working_dir.exists() {
|
||||||
|
return Ok(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Try to load workspace from the working directory
|
||||||
|
// Walk up the directory tree until we find a repository or reach the root
|
||||||
|
let mut current_dir = self.working_dir.clone();
|
||||||
|
loop {
|
||||||
|
match Workspace::load(&settings, ¤t_dir, &store_factories, &wc_factories) {
|
||||||
|
Ok(_) => return Ok(true),
|
||||||
|
Err(_) => {
|
||||||
|
// Move up to parent directory
|
||||||
|
if !current_dir.pop() {
|
||||||
|
// Reached root directory
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(false)
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn describe(&self, _message: &str) -> Result<(), Error> {
|
async fn describe(&self, message: &str) -> Result<(), Error> {
|
||||||
// TODO: T018/T019 - Implement using jj-lib transactions
|
// Load the repository
|
||||||
todo!("T018/T019: Implement describe() using jj-lib")
|
let config = StackedConfig::with_defaults();
|
||||||
|
let settings = UserSettings::from_config(config)?;
|
||||||
|
let store_factories = StoreFactories::default();
|
||||||
|
let wc_factories = default_working_copy_factories();
|
||||||
|
|
||||||
|
let workspace = Workspace::load(
|
||||||
|
&settings,
|
||||||
|
&self.working_dir,
|
||||||
|
&store_factories,
|
||||||
|
&wc_factories,
|
||||||
|
)
|
||||||
|
.map_err(|_| Error::NotARepository)?;
|
||||||
|
|
||||||
|
let repo = workspace.repo_loader().load_at_head()?;
|
||||||
|
|
||||||
|
// Start a transaction
|
||||||
|
let mut tx = repo.start_transaction();
|
||||||
|
tx.set_tag("args".to_string(), "jj-cz describe".to_string());
|
||||||
|
|
||||||
|
// Get the current working copy commit (equivalent to @ revset)
|
||||||
|
let view = tx.repo().view();
|
||||||
|
let wc_commit_ids = view.wc_commit_ids();
|
||||||
|
|
||||||
|
if wc_commit_ids.is_empty() {
|
||||||
|
return Err(Error::JjOperation {
|
||||||
|
context: "No working copy commit found".to_string(),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get the first working copy commit (usually there's only one)
|
||||||
|
let wc_commit_id = wc_commit_ids.values().next().unwrap();
|
||||||
|
let wc_commit = tx.repo().store().get_commit(wc_commit_id)?;
|
||||||
|
|
||||||
|
// Rewrite the working copy commit with the new description
|
||||||
|
let commit_builder = tx
|
||||||
|
.repo_mut()
|
||||||
|
.rewrite_commit(&wc_commit)
|
||||||
|
.set_description(message);
|
||||||
|
|
||||||
|
// Write the modified commit
|
||||||
|
let _new_commit = commit_builder.write()?;
|
||||||
|
|
||||||
|
// Rebase descendants after the rewrite
|
||||||
|
tx.repo_mut().rebase_descendants()?;
|
||||||
|
|
||||||
|
// Finish the transaction
|
||||||
|
tx.commit("jj-cz: update commit description")?;
|
||||||
|
|
||||||
|
Ok(())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -72,8 +139,7 @@ mod tests {
|
|||||||
.output()?;
|
.output()?;
|
||||||
|
|
||||||
if !output.status.success() {
|
if !output.status.success() {
|
||||||
return Err(std::io::Error::new(
|
return Err(std::io::Error::other(
|
||||||
std::io::ErrorKind::Other,
|
|
||||||
format!(
|
format!(
|
||||||
"jj git init failed: {}",
|
"jj git init failed: {}",
|
||||||
String::from_utf8_lossy(&output.stderr)
|
String::from_utf8_lossy(&output.stderr)
|
||||||
@@ -91,12 +157,8 @@ mod tests {
|
|||||||
.output()?;
|
.output()?;
|
||||||
|
|
||||||
if !output.status.success() {
|
if !output.status.success() {
|
||||||
return Err(std::io::Error::new(
|
return Err(std::io::Error::other(
|
||||||
std::io::ErrorKind::Other,
|
format!("jj log failed: {}", String::from_utf8_lossy(&output.stderr)),
|
||||||
format!(
|
|
||||||
"jj log failed: {}",
|
|
||||||
String::from_utf8_lossy(&output.stderr)
|
|
||||||
),
|
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
Ok(String::from_utf8_lossy(&output.stdout).trim().to_string())
|
Ok(String::from_utf8_lossy(&output.stdout).trim().to_string())
|
||||||
@@ -219,7 +281,11 @@ mod tests {
|
|||||||
let test_message = "feat(scope): add new feature";
|
let test_message = "feat(scope): add new feature";
|
||||||
let result = jj_lib.describe(test_message).await;
|
let result = jj_lib.describe(test_message).await;
|
||||||
|
|
||||||
assert!(result.is_ok(), "describe() should succeed, got {:?}", result);
|
assert!(
|
||||||
|
result.is_ok(),
|
||||||
|
"describe() should succeed, got {:?}",
|
||||||
|
result
|
||||||
|
);
|
||||||
|
|
||||||
// Verify the commit description was updated
|
// Verify the commit description was updated
|
||||||
let actual_description =
|
let actual_description =
|
||||||
@@ -239,10 +305,14 @@ mod tests {
|
|||||||
|
|
||||||
let jj_lib = JjLib::with_working_dir(temp_dir.path());
|
let jj_lib = JjLib::with_working_dir(temp_dir.path());
|
||||||
|
|
||||||
let multiline_message = "feat: add feature\n\nThis is the body of the commit.\nIt spans multiple lines.";
|
let multiline_message =
|
||||||
|
"feat: add feature\n\nThis is the body of the commit.\nIt spans multiple lines.";
|
||||||
let result = jj_lib.describe(multiline_message).await;
|
let result = jj_lib.describe(multiline_message).await;
|
||||||
|
|
||||||
assert!(result.is_ok(), "describe() should succeed with multiline message");
|
assert!(
|
||||||
|
result.is_ok(),
|
||||||
|
"describe() should succeed with multiline message"
|
||||||
|
);
|
||||||
|
|
||||||
let actual_description = get_commit_description(temp_dir.path()).unwrap();
|
let actual_description = get_commit_description(temp_dir.path()).unwrap();
|
||||||
assert_eq!(actual_description, multiline_message);
|
assert_eq!(actual_description, multiline_message);
|
||||||
@@ -264,7 +334,10 @@ mod tests {
|
|||||||
|
|
||||||
// Then clear it with empty message
|
// Then clear it with empty message
|
||||||
let result = jj_lib.describe("").await;
|
let result = jj_lib.describe("").await;
|
||||||
assert!(result.is_ok(), "describe() should succeed with empty message");
|
assert!(
|
||||||
|
result.is_ok(),
|
||||||
|
"describe() should succeed with empty message"
|
||||||
|
);
|
||||||
|
|
||||||
let actual_description = get_commit_description(temp_dir.path()).unwrap();
|
let actual_description = get_commit_description(temp_dir.path()).unwrap();
|
||||||
assert_eq!(actual_description, "", "Description should be cleared");
|
assert_eq!(actual_description, "", "Description should be cleared");
|
||||||
@@ -281,7 +354,10 @@ mod tests {
|
|||||||
let special_message = "fix: handle \"quotes\" and 'apostrophes' & <brackets>";
|
let special_message = "fix: handle \"quotes\" and 'apostrophes' & <brackets>";
|
||||||
let result = jj_lib.describe(special_message).await;
|
let result = jj_lib.describe(special_message).await;
|
||||||
|
|
||||||
assert!(result.is_ok(), "describe() should handle special characters");
|
assert!(
|
||||||
|
result.is_ok(),
|
||||||
|
"describe() should handle special characters"
|
||||||
|
);
|
||||||
|
|
||||||
let actual_description = get_commit_description(temp_dir.path()).unwrap();
|
let actual_description = get_commit_description(temp_dir.path()).unwrap();
|
||||||
assert_eq!(actual_description, special_message);
|
assert_eq!(actual_description, special_message);
|
||||||
|
|||||||
@@ -58,7 +58,8 @@ mod tests {
|
|||||||
|
|
||||||
/// Check if is_repository() was called
|
/// Check if is_repository() was called
|
||||||
fn was_is_repo_called(&self) -> bool {
|
fn was_is_repo_called(&self) -> bool {
|
||||||
self.is_repo_called.load(std::sync::atomic::Ordering::SeqCst)
|
self.is_repo_called
|
||||||
|
.load(std::sync::atomic::Ordering::SeqCst)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Get all messages passed to describe()
|
/// Get all messages passed to describe()
|
||||||
|
|||||||
Reference in New Issue
Block a user