diff --git a/Cargo.lock b/Cargo.lock index 363ab7c6894..33888296196 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1108,11 +1108,13 @@ dependencies = [ "but-core", "but-ctx", "but-db", + "but-testsupport", "chrono", "gitbutler-commit", "gix", "serde", "sha-1", + "tempfile", "uuid", ] diff --git a/crates/but-gerrit/Cargo.toml b/crates/but-gerrit/Cargo.toml index da4d8cad63f..3856b23ad19 100644 --- a/crates/but-gerrit/Cargo.toml +++ b/crates/but-gerrit/Cargo.toml @@ -8,7 +8,6 @@ rust-version.workspace = true [lib] doctest = false -test = false [dependencies] but-core.workspace = true @@ -24,3 +23,11 @@ bstr.workspace = true chrono.workspace = true serde.workspace = true gix = { workspace = true } + +[dev-dependencies] + +but-testsupport.workspace = true + +gix = { workspace = true, features = ["revision"] } + +tempfile = "3" diff --git a/crates/but-gerrit/src/lib.rs b/crates/but-gerrit/src/lib.rs index 237b25ba08f..35df503681c 100644 --- a/crates/but-gerrit/src/lib.rs +++ b/crates/but-gerrit/src/lib.rs @@ -203,6 +203,7 @@ fn mappings( push_output: PushOutput, ) -> anyhow::Result> { let mut mappings = vec![]; + let host = gerrit_host(repo); for id in candidate_ids { let commit = repo.find_commit(id)?; let msg = commit.message_bstr().to_string(); @@ -217,20 +218,46 @@ fn mappings( .change_id() .map(|change_id| (change_id, c.url.clone())) }); + if let Some((change_id, review_url)) = change_id_review_url { mappings.push(ChangeIdMapping { commit_id: id, change_id, review_url, }); + } else if let (Some(change_id), Some(host)) = (commit.change_id(), host.as_ref()) { + // Fallback: generate review URL if we have a change ID and a host + if let Ok(uuid) = uuid::Uuid::parse_str(&change_id) { + let gerrit_change_id = GerritChangeId::from(uuid); + let review_url = format!("https://{}/q/{}", host, gerrit_change_id); + mappings.push(ChangeIdMapping { + commit_id: id, + change_id, + review_url, + }); + } } } Ok(mappings) } +fn gerrit_host(repo: &gix::Repository) -> Option { + let name = repo.remote_default_name(gix::remote::Direction::Push); + let name = name + .as_ref() + .map(|n| n.as_ref()) + .unwrap_or(b"origin".as_bstr()); + let remote = repo.find_remote(name).ok()?; + let url = remote + .url(gix::remote::Direction::Push) + .or_else(|| remote.url(gix::remote::Direction::Fetch))?; + url.host().map(|h| h.to_string()) +} + #[cfg(test)] mod tests { use super::*; + #[test] fn output_is_41_characters_long() { let uuid = Uuid::new_v4(); diff --git a/crates/but-gerrit/tests/integration_test.rs b/crates/but-gerrit/tests/integration_test.rs new file mode 100644 index 00000000000..0b4a0b51189 --- /dev/null +++ b/crates/but-gerrit/tests/integration_test.rs @@ -0,0 +1,70 @@ +use but_ctx::Context; +use but_gerrit::{GerritChangeId, parse::PushOutput, record_push_metadata}; +use but_testsupport::CommandExt; + +#[test] +fn test_record_push_metadata_fallback_url() { + let temp_dir = tempfile::tempdir().unwrap(); + let gix_repo = gix::init(temp_dir.path()).unwrap(); + let mut ctx = Context::from_repo(gix_repo.clone()).unwrap(); + + // 1. Create a commit with GitButler headers + let change_uuid = uuid::Uuid::new_v4(); + + let tree_id = gix_repo + .write_object(gix::objs::Tree::empty()) + .unwrap() + .detach(); + let author = gix::actor::Signature { + name: "Author".into(), + email: "author@example.com".into(), + time: gix::date::Time::now_local_or_utc(), + }; + let committer = author.clone(); + + let commit_obj = gix::objs::Commit { + tree: tree_id, + parents: Default::default(), + author, + committer, + encoding: None, + message: "Test commit".into(), + extra_headers: vec![ + (b"gitbutler-headers-version".into(), b"2".into()), + ( + b"gitbutler-change-id".into(), + change_uuid.to_string().into(), + ), + ], + }; + let commit_id = gix_repo.write_object(&commit_obj).unwrap().detach(); + + // 2. Set up a remote so we can derive the host + but_testsupport::git(&gix_repo) + .args(["remote", "add", "origin", "https://gerrithost/project"]) + .run(); + + let gix_repo = gix::open(gix_repo.path()).unwrap(); + let candidate_ids = vec![commit_id]; + let push_output = PushOutput { + success: true, + warnings: vec![], + changes: vec![], // Empty changes to trigger fallback + processing_info: None, + }; + + record_push_metadata(&mut ctx, &gix_repo, candidate_ids, push_output).unwrap(); + + let mut db = ctx.db.get_mut().unwrap(); + let mut db = db.gerrit_metadata(); + let meta = db + .get(&change_uuid.to_string()) + .unwrap() + .expect("Metadata should be recorded"); + + let gerrit_change_id = GerritChangeId::from(change_uuid); + assert_eq!( + meta.review_url, + format!("https://gerrithost/q/{}", gerrit_change_id) + ); +}