diff options
Diffstat (limited to 'src/git.rs')
| -rw-r--r-- | src/git.rs | 967 |
1 files changed, 954 insertions, 13 deletions
| @@ -6,6 +6,8 @@ use anyhow::{bail, Context, Result}; | |||
| 6 | use git2::{Oid, Revwalk}; | 6 | use git2::{Oid, Revwalk}; |
| 7 | use nostr::prelude::{sha1::Hash as Sha1Hash, Hash}; | 7 | use nostr::prelude::{sha1::Hash as Sha1Hash, Hash}; |
| 8 | 8 | ||
| 9 | use crate::sub_commands::prs::list::tag_value; | ||
| 10 | |||
| 9 | pub struct Repo { | 11 | pub struct Repo { |
| 10 | git_repo: git2::Repository, | 12 | git_repo: git2::Repository, |
| 11 | } | 13 | } |
| @@ -36,14 +38,26 @@ pub trait RepoActions { | |||
| 36 | fn does_commit_exist(&self, commit: &str) -> Result<bool>; | 38 | fn does_commit_exist(&self, commit: &str) -> Result<bool>; |
| 37 | fn get_head_commit(&self) -> Result<Sha1Hash>; | 39 | fn get_head_commit(&self) -> Result<Sha1Hash>; |
| 38 | fn get_commit_parent(&self, commit: &Sha1Hash) -> Result<Sha1Hash>; | 40 | fn get_commit_parent(&self, commit: &Sha1Hash) -> Result<Sha1Hash>; |
| 41 | fn get_commit_message(&self, commit: &Sha1Hash) -> Result<String>; | ||
| 42 | /// returns vector ["name", "email", "unixtime"] | ||
| 43 | /// eg ["joe bloggs", "joe@pm.me", "12176,-300"] | ||
| 44 | fn get_commit_author(&self, commit: &Sha1Hash) -> Result<Vec<String>>; | ||
| 45 | /// returns vector ["name", "email", "unixtime"] | ||
| 46 | /// eg ["joe bloggs", "joe@pm.me", "12176,-300"] | ||
| 47 | fn get_commit_comitter(&self, commit: &Sha1Hash) -> Result<Vec<String>>; | ||
| 39 | fn get_commits_ahead_behind( | 48 | fn get_commits_ahead_behind( |
| 40 | &self, | 49 | &self, |
| 41 | base_commit: &Sha1Hash, | 50 | base_commit: &Sha1Hash, |
| 42 | latest_commit: &Sha1Hash, | 51 | latest_commit: &Sha1Hash, |
| 43 | ) -> Result<(Vec<Sha1Hash>, Vec<Sha1Hash>)>; | 52 | ) -> Result<(Vec<Sha1Hash>, Vec<Sha1Hash>)>; |
| 44 | fn make_patch_from_commit(&self, commit: &Sha1Hash) -> Result<String>; | 53 | fn make_patch_from_commit(&self, commit: &Sha1Hash) -> Result<String>; |
| 45 | fn checkout(&self, ref_name: &str) -> Result<()>; | 54 | fn checkout(&self, ref_name: &str) -> Result<Sha1Hash>; |
| 46 | fn create_branch_at_commit(&self, branch_name: &str, commit: &str) -> Result<()>; | 55 | fn create_branch_at_commit(&self, branch_name: &str, commit: &str) -> Result<()>; |
| 56 | fn apply_patch_chain( | ||
| 57 | &self, | ||
| 58 | branch_name: &str, | ||
| 59 | patch_and_ancestors: Vec<nostr::Event>, | ||
| 60 | ) -> Result<Vec<nostr::Event>>; | ||
| 47 | } | 61 | } |
| 48 | 62 | ||
| 49 | impl RepoActions for Repo { | 63 | impl RepoActions for Repo { |
| @@ -122,7 +136,7 @@ impl RepoActions for Repo { | |||
| 122 | } | 136 | } |
| 123 | 137 | ||
| 124 | fn does_commit_exist(&self, commit: &str) -> Result<bool> { | 138 | fn does_commit_exist(&self, commit: &str) -> Result<bool> { |
| 125 | if let Ok(c) = self.git_repo.find_commit(Oid::from_str(commit)?) { | 139 | if self.git_repo.find_commit(Oid::from_str(commit)?).is_ok() { |
| 126 | Ok(true) | 140 | Ok(true) |
| 127 | } else { | 141 | } else { |
| 128 | Ok(false) | 142 | Ok(false) |
| @@ -148,6 +162,34 @@ impl RepoActions for Repo { | |||
| 148 | Ok(oid_to_sha1(&parent_oid)) | 162 | Ok(oid_to_sha1(&parent_oid)) |
| 149 | } | 163 | } |
| 150 | 164 | ||
| 165 | fn get_commit_message(&self, commit: &Sha1Hash) -> Result<String> { | ||
| 166 | Ok(self | ||
| 167 | .git_repo | ||
| 168 | .find_commit(sha1_to_oid(commit)?) | ||
| 169 | .context(format!("could not find commit {commit}"))? | ||
| 170 | .message_raw() | ||
| 171 | .context("commit message has unusual characters in (not valid utf-8)")? | ||
| 172 | .to_string()) | ||
| 173 | } | ||
| 174 | |||
| 175 | fn get_commit_author(&self, commit: &Sha1Hash) -> Result<Vec<String>> { | ||
| 176 | let commit = self | ||
| 177 | .git_repo | ||
| 178 | .find_commit(sha1_to_oid(commit)?) | ||
| 179 | .context(format!("could not find commit {commit}"))?; | ||
| 180 | let sig = commit.author(); | ||
| 181 | Ok(git_sig_to_tag_vec(&sig)) | ||
| 182 | } | ||
| 183 | |||
| 184 | fn get_commit_comitter(&self, commit: &Sha1Hash) -> Result<Vec<String>> { | ||
| 185 | let commit = self | ||
| 186 | .git_repo | ||
| 187 | .find_commit(sha1_to_oid(commit)?) | ||
| 188 | .context(format!("could not find commit {commit}"))?; | ||
| 189 | let sig = commit.committer(); | ||
| 190 | Ok(git_sig_to_tag_vec(&sig)) | ||
| 191 | } | ||
| 192 | |||
| 151 | fn make_patch_from_commit(&self, commit: &Sha1Hash) -> Result<String> { | 193 | fn make_patch_from_commit(&self, commit: &Sha1Hash) -> Result<String> { |
| 152 | let c = self | 194 | let c = self |
| 153 | .git_repo | 195 | .git_repo |
| @@ -225,7 +267,7 @@ impl RepoActions for Repo { | |||
| 225 | Ok((ahead, behind)) | 267 | Ok((ahead, behind)) |
| 226 | } | 268 | } |
| 227 | 269 | ||
| 228 | fn checkout(&self, ref_name: &str) -> Result<()> { | 270 | fn checkout(&self, ref_name: &str) -> Result<Sha1Hash> { |
| 229 | let (object, reference) = self.git_repo.revparse_ext(ref_name)?; | 271 | let (object, reference) = self.git_repo.revparse_ext(ref_name)?; |
| 230 | 272 | ||
| 231 | self.git_repo.checkout_tree(&object, None)?; | 273 | self.git_repo.checkout_tree(&object, None)?; |
| @@ -236,7 +278,9 @@ impl RepoActions for Repo { | |||
| 236 | // this is a commit, not a reference | 278 | // this is a commit, not a reference |
| 237 | None => self.git_repo.set_head_detached(object.id()), | 279 | None => self.git_repo.set_head_detached(object.id()), |
| 238 | }?; | 280 | }?; |
| 239 | Ok(()) | 281 | let oid = self.git_repo.head()?.peel_to_commit()?.id(); |
| 282 | |||
| 283 | Ok(oid_to_sha1(&oid)) | ||
| 240 | } | 284 | } |
| 241 | 285 | ||
| 242 | fn create_branch_at_commit(&self, branch_name: &str, commit: &str) -> Result<()> { | 286 | fn create_branch_at_commit(&self, branch_name: &str, commit: &str) -> Result<()> { |
| @@ -244,11 +288,66 @@ impl RepoActions for Repo { | |||
| 244 | .branch( | 288 | .branch( |
| 245 | branch_name, | 289 | branch_name, |
| 246 | &self.git_repo.find_commit(Oid::from_str(commit)?)?, | 290 | &self.git_repo.find_commit(Oid::from_str(commit)?)?, |
| 247 | false, | 291 | true, |
| 248 | ) | 292 | ) |
| 249 | .context("branch could not be created")?; | 293 | .context("branch could not be created")?; |
| 250 | Ok(()) | 294 | Ok(()) |
| 251 | } | 295 | } |
| 296 | /* returns patches applied */ | ||
| 297 | fn apply_patch_chain( | ||
| 298 | &self, | ||
| 299 | branch_name: &str, | ||
| 300 | patch_and_ancestors: Vec<nostr::Event>, | ||
| 301 | ) -> Result<Vec<nostr::Event>> { | ||
| 302 | // filter out existing ancestors | ||
| 303 | let mut patches_to_apply: Vec<nostr::Event> = patch_and_ancestors | ||
| 304 | .into_iter() | ||
| 305 | .filter(|e| { | ||
| 306 | !self | ||
| 307 | .does_commit_exist(&tag_value(e, "commit").unwrap()) | ||
| 308 | .unwrap() | ||
| 309 | }) | ||
| 310 | .collect(); | ||
| 311 | |||
| 312 | let parent_commit_id = tag_value( | ||
| 313 | if let Ok(last_patch) = patches_to_apply.last().context("no patches") { | ||
| 314 | last_patch | ||
| 315 | } else { | ||
| 316 | self.checkout(branch_name).context("latest commit in pr doesnt connect with an existing commit. Try a git pull first.")?; | ||
| 317 | return Ok(vec![]); | ||
| 318 | }, | ||
| 319 | "parent-commit", | ||
| 320 | )?; | ||
| 321 | |||
| 322 | // check patches can be applied | ||
| 323 | if !self.does_commit_exist(&parent_commit_id)? { | ||
| 324 | bail!("cannot find parent commit ({parent_commit_id}). run git pull and try again.") | ||
| 325 | } | ||
| 326 | |||
| 327 | // check for rebase or changes | ||
| 328 | if let Ok(current_tip) = self.get_tip_of_local_branch(branch_name) { | ||
| 329 | if !current_tip.to_string().eq(&parent_commit_id) { | ||
| 330 | // TODO: either changes have been made on the local branch or | ||
| 331 | // the latest commit in the pr has rebased onto a newer commit | ||
| 332 | // that you havn't pulled yet ask user whether | ||
| 333 | // they want to proceed | ||
| 334 | } | ||
| 335 | } | ||
| 336 | |||
| 337 | // checkout branch | ||
| 338 | if !self.get_checked_out_branch_name()?.eq(&branch_name) { | ||
| 339 | self.create_branch_at_commit(branch_name, &parent_commit_id)?; | ||
| 340 | } | ||
| 341 | self.checkout(branch_name)?; | ||
| 342 | |||
| 343 | // apply commits | ||
| 344 | patches_to_apply.reverse(); | ||
| 345 | |||
| 346 | for patch in &patches_to_apply { | ||
| 347 | apply_patch(self, patch)?; | ||
| 348 | } | ||
| 349 | Ok(patches_to_apply) | ||
| 350 | } | ||
| 252 | } | 351 | } |
| 253 | 352 | ||
| 254 | fn oid_to_u8_20_bytes(oid: &Oid) -> [u8; 20] { | 353 | fn oid_to_u8_20_bytes(oid: &Oid) -> [u8; 20] { |
| @@ -285,8 +384,142 @@ fn sha1_to_oid(hash: &Sha1Hash) -> Result<Oid> { | |||
| 285 | Oid::from_bytes(hash.as_byte_array()).context("Sha1Hash bytes failed to produce a valid Oid") | 384 | Oid::from_bytes(hash.as_byte_array()).context("Sha1Hash bytes failed to produce a valid Oid") |
| 286 | } | 385 | } |
| 287 | 386 | ||
| 387 | fn git_sig_to_tag_vec(sig: &git2::Signature) -> Vec<String> { | ||
| 388 | vec![ | ||
| 389 | sig.name().unwrap_or("").to_string(), | ||
| 390 | sig.email().unwrap_or("").to_string(), | ||
| 391 | format!("{},{}", sig.when().seconds(), sig.when().offset_minutes()), | ||
| 392 | ] | ||
| 393 | } | ||
| 394 | |||
| 395 | fn apply_patch(git_repo: &Repo, patch: &nostr::Event) -> Result<()> { | ||
| 396 | // check parent commit matches head | ||
| 397 | if !git_repo | ||
| 398 | .get_head_commit()? | ||
| 399 | .to_string() | ||
| 400 | .eq(&tag_value(patch, "parent-commit")?) | ||
| 401 | { | ||
| 402 | bail!( | ||
| 403 | "patch parent ({}) doesnt match current head ({})", | ||
| 404 | tag_value(patch, "parent-commit")?, | ||
| 405 | git_repo.get_head_commit()? | ||
| 406 | ); | ||
| 407 | } | ||
| 408 | |||
| 409 | let diff_from_patch = git2::Diff::from_buffer(patch.content.as_bytes()).unwrap(); | ||
| 410 | |||
| 411 | let mut apply_opts = git2::ApplyOptions::new(); | ||
| 412 | apply_opts.check(false); | ||
| 413 | |||
| 414 | git_repo.git_repo.apply( | ||
| 415 | &diff_from_patch, | ||
| 416 | git2::ApplyLocation::WorkDir, | ||
| 417 | Some(&mut apply_opts), | ||
| 418 | )?; | ||
| 419 | // stage and commit | ||
| 420 | let prev_oid = git_repo.git_repo.head().unwrap().peel_to_commit()?; | ||
| 421 | |||
| 422 | let mut index = git_repo.git_repo.index()?; | ||
| 423 | index.add_all(["."], git2::IndexAddOption::DEFAULT, None)?; | ||
| 424 | index.write()?; | ||
| 425 | |||
| 426 | git_repo.git_repo.commit( | ||
| 427 | Some("HEAD"), | ||
| 428 | &extract_sig_from_patch_tags(&patch.tags, "author")?, | ||
| 429 | &extract_sig_from_patch_tags(&patch.tags, "committer")?, | ||
| 430 | tag_value(patch, "description")?.as_str(), | ||
| 431 | &git_repo.git_repo.find_tree(index.write_tree()?)?, | ||
| 432 | &[&prev_oid], | ||
| 433 | )?; | ||
| 434 | // end of stage and commit | ||
| 435 | // check commit applied | ||
| 436 | if git_repo | ||
| 437 | .get_head_commit()? | ||
| 438 | .to_string() | ||
| 439 | .eq(&tag_value(patch, "parent-commit")?) | ||
| 440 | { | ||
| 441 | bail!("applying patch failed"); | ||
| 442 | } | ||
| 443 | |||
| 444 | let mut revwalk = git_repo.git_repo.revwalk().context("revwalk error")?; | ||
| 445 | revwalk.push_head().context("revwalk.push_head")?; | ||
| 446 | |||
| 447 | for (i, oid) in revwalk.enumerate() { | ||
| 448 | if i == 0 { | ||
| 449 | let old_commit = git_repo | ||
| 450 | .git_repo | ||
| 451 | .find_commit(oid.context("cannot get oid in revwalk")?) | ||
| 452 | .context("cannot find newly added commit oid")?; | ||
| 453 | // create commit using amend which relects the original commit id | ||
| 454 | let updated_commit_oid = old_commit | ||
| 455 | .amend( | ||
| 456 | None, | ||
| 457 | Some(&old_commit.author()), | ||
| 458 | Some(&old_commit.committer()), | ||
| 459 | None, | ||
| 460 | None, | ||
| 461 | None, | ||
| 462 | ) | ||
| 463 | .context("cannot ammend commit to produce new oid")?; | ||
| 464 | // replace the commit with the wrong oid with the newly created one with the | ||
| 465 | // correct oid | ||
| 466 | git_repo | ||
| 467 | .git_repo | ||
| 468 | .head() | ||
| 469 | .context("cannot get head of git_repo")? | ||
| 470 | .set_target(updated_commit_oid, "ref commit with fix committer details") | ||
| 471 | .context("cannot update branch with fixed commit")?; | ||
| 472 | |||
| 473 | if !updated_commit_oid | ||
| 474 | .to_string() | ||
| 475 | .eq(&tag_value(patch, "commit")?) | ||
| 476 | { | ||
| 477 | bail!( | ||
| 478 | "when applied the patch commit id ({}) doesn't match the one specified in the event tag ({})", | ||
| 479 | updated_commit_oid.to_string(), | ||
| 480 | tag_value(patch, "commit")?, | ||
| 481 | ) | ||
| 482 | } | ||
| 483 | } | ||
| 484 | } | ||
| 485 | Ok(()) | ||
| 486 | } | ||
| 487 | |||
| 488 | fn extract_sig_from_patch_tags<'a>( | ||
| 489 | tags: &'a [nostr::Tag], | ||
| 490 | tag_name: &str, | ||
| 491 | ) -> Result<git2::Signature<'a>> { | ||
| 492 | let v = tags | ||
| 493 | .iter() | ||
| 494 | .find(|t| t.as_vec()[0].eq(tag_name)) | ||
| 495 | .context(format!("tag '{tag_name}' not present in patch"))? | ||
| 496 | .as_vec(); | ||
| 497 | if v.len() != 4 { | ||
| 498 | bail!("tag '{tag_name}' is incorrectly formatted") | ||
| 499 | } | ||
| 500 | let git_time: Vec<&str> = v[3].split(',').collect(); | ||
| 501 | if git_time.len() != 2 { | ||
| 502 | bail!("tag '{tag_name}' time is incorrectly formatted") | ||
| 503 | } | ||
| 504 | git2::Signature::new( | ||
| 505 | v[1].as_str(), | ||
| 506 | v[2].as_str(), | ||
| 507 | &git2::Time::new( | ||
| 508 | git_time[0] | ||
| 509 | .parse() | ||
| 510 | .context("tag time is incorrectly formatted")?, | ||
| 511 | git_time[1] | ||
| 512 | .parse() | ||
| 513 | .context("tag time offset is incorrectly formatted")?, | ||
| 514 | ), | ||
| 515 | ) | ||
| 516 | .context("failed to create git signature") | ||
| 517 | } | ||
| 518 | |||
| 288 | #[cfg(test)] | 519 | #[cfg(test)] |
| 289 | mod tests { | 520 | mod tests { |
| 521 | use std::fs; | ||
| 522 | |||
| 290 | use test_utils::git::GitTestRepo; | 523 | use test_utils::git::GitTestRepo; |
| 291 | 524 | ||
| 292 | use super::*; | 525 | use super::*; |
| @@ -308,16 +541,177 @@ mod tests { | |||
| 308 | Ok(()) | 541 | Ok(()) |
| 309 | } | 542 | } |
| 310 | 543 | ||
| 544 | mod get_commit_message { | ||
| 545 | use super::*; | ||
| 546 | fn run(message: &str) -> Result<()> { | ||
| 547 | let test_repo = GitTestRepo::default(); | ||
| 548 | test_repo.populate()?; | ||
| 549 | std::fs::write(test_repo.dir.join("t100.md"), "some content")?; | ||
| 550 | let oid = test_repo.stage_and_commit(message)?; | ||
| 551 | |||
| 552 | let git_repo = Repo::from_path(&test_repo.dir)?; | ||
| 553 | |||
| 554 | assert_eq!(message, git_repo.get_commit_message(&oid_to_sha1(&oid))?,); | ||
| 555 | Ok(()) | ||
| 556 | } | ||
| 557 | #[test] | ||
| 558 | fn one_liner() -> Result<()> { | ||
| 559 | run("add t100.md") | ||
| 560 | } | ||
| 561 | |||
| 562 | #[test] | ||
| 563 | fn multiline() -> Result<()> { | ||
| 564 | run("add t100.md\r\nanother line\r\nthird line") | ||
| 565 | } | ||
| 566 | |||
| 567 | #[test] | ||
| 568 | fn trailing_newlines() -> Result<()> { | ||
| 569 | run("add t100.md\r\n\r\n\r\n\r\n\r\n\r\n") | ||
| 570 | } | ||
| 571 | |||
| 572 | #[test] | ||
| 573 | fn unicode_characters() -> Result<()> { | ||
| 574 | run("add t100.md ❤️") | ||
| 575 | } | ||
| 576 | } | ||
| 577 | |||
| 578 | mod get_commit_author { | ||
| 579 | use super::*; | ||
| 580 | |||
| 581 | static NAME: &str = "carole"; | ||
| 582 | static EMAIL: &str = "carole@pm.me"; | ||
| 583 | |||
| 584 | fn prep(time: &git2::Time) -> Result<Vec<String>> { | ||
| 585 | let test_repo = GitTestRepo::default(); | ||
| 586 | test_repo.populate()?; | ||
| 587 | fs::write(test_repo.dir.join("x1.md"), "some content")?; | ||
| 588 | let oid = test_repo.stage_and_commit_custom_signature( | ||
| 589 | "add x1.md", | ||
| 590 | Some(&git2::Signature::new(NAME, EMAIL, time)?), | ||
| 591 | None, | ||
| 592 | )?; | ||
| 593 | |||
| 594 | let git_repo = Repo::from_path(&test_repo.dir)?; | ||
| 595 | git_repo.get_commit_author(&oid_to_sha1(&oid)) | ||
| 596 | } | ||
| 597 | |||
| 598 | #[test] | ||
| 599 | fn name() -> Result<()> { | ||
| 600 | let res = prep(&git2::Time::new(5000, 0))?; | ||
| 601 | assert_eq!(NAME, res[0]); | ||
| 602 | Ok(()) | ||
| 603 | } | ||
| 604 | |||
| 605 | #[test] | ||
| 606 | fn email() -> Result<()> { | ||
| 607 | let res = prep(&git2::Time::new(5000, 0))?; | ||
| 608 | assert_eq!(EMAIL, res[1]); | ||
| 609 | Ok(()) | ||
| 610 | } | ||
| 611 | |||
| 612 | mod time { | ||
| 613 | use super::*; | ||
| 614 | |||
| 615 | #[test] | ||
| 616 | fn no_offset() -> Result<()> { | ||
| 617 | let res = prep(&git2::Time::new(5000, 0))?; | ||
| 618 | assert_eq!("5000,0", res[2]); | ||
| 619 | Ok(()) | ||
| 620 | } | ||
| 621 | #[test] | ||
| 622 | fn positive_offset() -> Result<()> { | ||
| 623 | let res = prep(&git2::Time::new(5000, 300))?; | ||
| 624 | assert_eq!("5000,300", res[2]); | ||
| 625 | Ok(()) | ||
| 626 | } | ||
| 627 | #[test] | ||
| 628 | fn negative_offset() -> Result<()> { | ||
| 629 | let res = prep(&git2::Time::new(5000, -300))?; | ||
| 630 | assert_eq!("5000,-300", res[2]); | ||
| 631 | Ok(()) | ||
| 632 | } | ||
| 633 | } | ||
| 634 | |||
| 635 | mod extract_sig_from_patch_tags { | ||
| 636 | use super::*; | ||
| 637 | |||
| 638 | fn test(time: git2::Time) -> Result<()> { | ||
| 639 | assert_eq!( | ||
| 640 | extract_sig_from_patch_tags( | ||
| 641 | &[nostr::Tag::Generic( | ||
| 642 | nostr::TagKind::Custom("author".to_string()), | ||
| 643 | prep(&time)?, | ||
| 644 | )], | ||
| 645 | "author", | ||
| 646 | )? | ||
| 647 | .to_string(), | ||
| 648 | git2::Signature::new(NAME, EMAIL, &time)?.to_string(), | ||
| 649 | ); | ||
| 650 | Ok(()) | ||
| 651 | } | ||
| 652 | |||
| 653 | #[test] | ||
| 654 | fn no_offset() -> Result<()> { | ||
| 655 | test(git2::Time::new(5000, 0)) | ||
| 656 | } | ||
| 657 | |||
| 658 | #[test] | ||
| 659 | fn positive_offset() -> Result<()> { | ||
| 660 | test(git2::Time::new(5000, 300)) | ||
| 661 | } | ||
| 662 | |||
| 663 | #[test] | ||
| 664 | fn negative_offset() -> Result<()> { | ||
| 665 | test(git2::Time::new(5000, -300)) | ||
| 666 | } | ||
| 667 | } | ||
| 668 | } | ||
| 669 | |||
| 670 | mod get_commit_comitter { | ||
| 671 | use super::*; | ||
| 672 | |||
| 673 | static NAME: &str = "carole"; | ||
| 674 | static EMAIL: &str = "carole@pm.me"; | ||
| 675 | |||
| 676 | fn prep(time: &git2::Time) -> Result<Vec<String>> { | ||
| 677 | let test_repo = GitTestRepo::default(); | ||
| 678 | test_repo.populate()?; | ||
| 679 | fs::write(test_repo.dir.join("x1.md"), "some content")?; | ||
| 680 | let oid = test_repo.stage_and_commit_custom_signature( | ||
| 681 | "add x1.md", | ||
| 682 | None, | ||
| 683 | Some(&git2::Signature::new(NAME, EMAIL, time)?), | ||
| 684 | )?; | ||
| 685 | |||
| 686 | let git_repo = Repo::from_path(&test_repo.dir)?; | ||
| 687 | git_repo.get_commit_comitter(&oid_to_sha1(&oid)) | ||
| 688 | } | ||
| 689 | |||
| 690 | #[test] | ||
| 691 | fn name() -> Result<()> { | ||
| 692 | let res = prep(&git2::Time::new(5000, 0))?; | ||
| 693 | assert_eq!(NAME, res[0]); | ||
| 694 | Ok(()) | ||
| 695 | } | ||
| 696 | |||
| 697 | #[test] | ||
| 698 | fn email() -> Result<()> { | ||
| 699 | let res = prep(&git2::Time::new(5000, 0))?; | ||
| 700 | assert_eq!(EMAIL, res[1]); | ||
| 701 | Ok(()) | ||
| 702 | } | ||
| 703 | } | ||
| 704 | |||
| 311 | mod does_commit_exist { | 705 | mod does_commit_exist { |
| 312 | use super::*; | 706 | use super::*; |
| 313 | 707 | ||
| 314 | #[test] | 708 | #[test] |
| 315 | fn existing_commits_results_in_true() -> Result<()> { | 709 | fn existing_commits_results_in_true() -> Result<()> { |
| 316 | let test_repo = GitTestRepo::default(); | 710 | let test_repo = GitTestRepo::default(); |
| 317 | let oid = test_repo.populate()?; | 711 | test_repo.populate()?; |
| 318 | let git_repo = Repo::from_path(&test_repo.dir)?; | 712 | let git_repo = Repo::from_path(&test_repo.dir)?; |
| 319 | 713 | ||
| 320 | assert!(git_repo.does_commit_exist(&"431b84edc0d2fa118d63faa3c2db9c73d630a5ae")?); | 714 | assert!(git_repo.does_commit_exist("431b84edc0d2fa118d63faa3c2db9c73d630a5ae")?); |
| 321 | Ok(()) | 715 | Ok(()) |
| 322 | } | 716 | } |
| 323 | 717 | ||
| @@ -325,10 +719,10 @@ mod tests { | |||
| 325 | fn correctly_formatted_hash_that_doesnt_correspond_to_an_existing_commit_results_in_false() | 719 | fn correctly_formatted_hash_that_doesnt_correspond_to_an_existing_commit_results_in_false() |
| 326 | -> Result<()> { | 720 | -> Result<()> { |
| 327 | let test_repo = GitTestRepo::default(); | 721 | let test_repo = GitTestRepo::default(); |
| 328 | let oid = test_repo.populate()?; | 722 | test_repo.populate()?; |
| 329 | let git_repo = Repo::from_path(&test_repo.dir)?; | 723 | let git_repo = Repo::from_path(&test_repo.dir)?; |
| 330 | 724 | ||
| 331 | assert!(!git_repo.does_commit_exist(&"000004edc0d2fa118d63faa3c2db9c73d630a5ae")?); | 725 | assert!(!git_repo.does_commit_exist("000004edc0d2fa118d63faa3c2db9c73d630a5ae")?); |
| 332 | Ok(()) | 726 | Ok(()) |
| 333 | } | 727 | } |
| 334 | 728 | ||
| @@ -336,10 +730,10 @@ mod tests { | |||
| 336 | fn incorrectly_formatted_hash_that_doesnt_correspond_to_an_existing_commit_results_in_error() | 730 | fn incorrectly_formatted_hash_that_doesnt_correspond_to_an_existing_commit_results_in_error() |
| 337 | -> Result<()> { | 731 | -> Result<()> { |
| 338 | let test_repo = GitTestRepo::default(); | 732 | let test_repo = GitTestRepo::default(); |
| 339 | let oid = test_repo.populate()?; | 733 | test_repo.populate()?; |
| 340 | let git_repo = Repo::from_path(&test_repo.dir)?; | 734 | let git_repo = Repo::from_path(&test_repo.dir)?; |
| 341 | 735 | ||
| 342 | assert!(!git_repo.does_commit_exist(&"00").is_err()); | 736 | assert!(git_repo.does_commit_exist("00").is_ok()); |
| 343 | Ok(()) | 737 | Ok(()) |
| 344 | } | 738 | } |
| 345 | } | 739 | } |
| @@ -633,7 +1027,7 @@ mod tests { | |||
| 633 | let branch_name = "test-name-1"; | 1027 | let branch_name = "test-name-1"; |
| 634 | git_repo.create_branch_at_commit(branch_name, &ahead_1_oid.to_string())?; | 1028 | git_repo.create_branch_at_commit(branch_name, &ahead_1_oid.to_string())?; |
| 635 | 1029 | ||
| 636 | assert!(test_repo.checkout(&branch_name).is_ok()); | 1030 | assert!(test_repo.checkout(branch_name).is_ok()); |
| 637 | Ok(()) | 1031 | Ok(()) |
| 638 | } | 1032 | } |
| 639 | 1033 | ||
| @@ -654,8 +1048,555 @@ mod tests { | |||
| 654 | let branch_name = "test-name-1"; | 1048 | let branch_name = "test-name-1"; |
| 655 | git_repo.create_branch_at_commit(branch_name, &ahead_1_oid.to_string())?; | 1049 | git_repo.create_branch_at_commit(branch_name, &ahead_1_oid.to_string())?; |
| 656 | 1050 | ||
| 657 | assert_eq!(test_repo.checkout(&branch_name)?, ahead_1_oid); | 1051 | assert_eq!(test_repo.checkout(branch_name)?, ahead_1_oid); |
| 1052 | Ok(()) | ||
| 1053 | } | ||
| 1054 | |||
| 1055 | mod when_branch_already_exists { | ||
| 1056 | use super::*; | ||
| 1057 | |||
| 1058 | #[test] | ||
| 1059 | fn when_new_tip_specified_it_is_updated() -> Result<()> { | ||
| 1060 | let test_repo = GitTestRepo::default(); | ||
| 1061 | test_repo.populate()?; | ||
| 1062 | // create feature branch and add 2 commits | ||
| 1063 | test_repo.create_branch("feature")?; | ||
| 1064 | test_repo.checkout("feature")?; | ||
| 1065 | std::fs::write(test_repo.dir.join("t3.md"), "some content")?; | ||
| 1066 | let ahead_1_oid = test_repo.stage_and_commit("add t3.md")?; | ||
| 1067 | std::fs::write(test_repo.dir.join("t4.md"), "some content")?; | ||
| 1068 | let ahead_2_oid = test_repo.stage_and_commit("add t4.md")?; | ||
| 1069 | |||
| 1070 | let git_repo = Repo::from_path(&test_repo.dir)?; | ||
| 1071 | |||
| 1072 | let branch_name = "test-name-1"; | ||
| 1073 | git_repo.create_branch_at_commit(branch_name, &ahead_1_oid.to_string())?; | ||
| 1074 | |||
| 1075 | git_repo.create_branch_at_commit(branch_name, &ahead_2_oid.to_string())?; | ||
| 1076 | assert_eq!(test_repo.checkout(branch_name)?, ahead_2_oid); | ||
| 1077 | Ok(()) | ||
| 1078 | } | ||
| 1079 | |||
| 1080 | #[test] | ||
| 1081 | fn when_same_tip_is_specified_it_doesnt_error() -> Result<()> { | ||
| 1082 | let test_repo = GitTestRepo::default(); | ||
| 1083 | test_repo.populate()?; | ||
| 1084 | // create feature branch and add 2 commits | ||
| 1085 | test_repo.create_branch("feature")?; | ||
| 1086 | test_repo.checkout("feature")?; | ||
| 1087 | std::fs::write(test_repo.dir.join("t3.md"), "some content")?; | ||
| 1088 | let ahead_1_oid = test_repo.stage_and_commit("add t3.md")?; | ||
| 1089 | std::fs::write(test_repo.dir.join("t4.md"), "some content")?; | ||
| 1090 | test_repo.stage_and_commit("add t4.md")?; | ||
| 1091 | |||
| 1092 | let git_repo = Repo::from_path(&test_repo.dir)?; | ||
| 1093 | |||
| 1094 | let branch_name = "test-name-1"; | ||
| 1095 | git_repo.create_branch_at_commit(branch_name, &ahead_1_oid.to_string())?; | ||
| 1096 | |||
| 1097 | git_repo.create_branch_at_commit(branch_name, &ahead_1_oid.to_string())?; | ||
| 1098 | assert_eq!(test_repo.checkout(branch_name)?, ahead_1_oid); | ||
| 1099 | Ok(()) | ||
| 1100 | } | ||
| 1101 | } | ||
| 1102 | } | ||
| 1103 | |||
| 1104 | mod apply_patch { | ||
| 1105 | use test_utils::TEST_KEY_1_KEYS; | ||
| 1106 | |||
| 1107 | use super::*; | ||
| 1108 | use crate::sub_commands::prs::create::generate_patch_event; | ||
| 1109 | |||
| 1110 | fn generate_patch_from_head_commit(test_repo: &GitTestRepo) -> Result<nostr::Event> { | ||
| 1111 | let original_oid = test_repo.git_repo.head()?.peel_to_commit()?.id(); | ||
| 1112 | let git_repo = Repo::from_path(&test_repo.dir)?; | ||
| 1113 | generate_patch_event( | ||
| 1114 | &git_repo, | ||
| 1115 | &git_repo.get_root_commit("main")?, | ||
| 1116 | &oid_to_sha1(&original_oid), | ||
| 1117 | nostr::EventId::all_zeros(), | ||
| 1118 | &TEST_KEY_1_KEYS, | ||
| 1119 | ) | ||
| 1120 | } | ||
| 1121 | fn test_patch_applies_to_repository(patch_event: nostr::Event) -> Result<()> { | ||
| 1122 | let test_repo = GitTestRepo::default(); | ||
| 1123 | test_repo.populate()?; | ||
| 1124 | let git_repo = Repo::from_path(&test_repo.dir)?; | ||
| 1125 | println!("{:?}", &patch_event); | ||
| 1126 | apply_patch(&git_repo, &patch_event)?; | ||
| 1127 | let commit_id = tag_value(&patch_event, "commit")?; | ||
| 1128 | // does commit with id exist? | ||
| 1129 | assert!(git_repo.does_commit_exist(&commit_id)?); | ||
| 1130 | // is commit head | ||
| 1131 | assert_eq!( | ||
| 1132 | test_repo | ||
| 1133 | .git_repo | ||
| 1134 | .head()? | ||
| 1135 | .peel_to_commit()? | ||
| 1136 | .id() | ||
| 1137 | .to_string(), | ||
| 1138 | commit_id, | ||
| 1139 | ); | ||
| 1140 | // applied to current checked branch (head hasn't moved to specific commit) | ||
| 1141 | assert_eq!( | ||
| 1142 | test_repo | ||
| 1143 | .git_repo | ||
| 1144 | .head()? | ||
| 1145 | .shorthand() | ||
| 1146 | .context("an object without a shorthand is checked out")? | ||
| 1147 | .to_string(), | ||
| 1148 | "main", | ||
| 1149 | ); | ||
| 1150 | |||
| 658 | Ok(()) | 1151 | Ok(()) |
| 659 | } | 1152 | } |
| 1153 | |||
| 1154 | mod patch_created_as_commit_with_matching_id { | ||
| 1155 | use test_utils::git::joe_signature; | ||
| 1156 | |||
| 1157 | use super::*; | ||
| 1158 | |||
| 1159 | #[test] | ||
| 1160 | fn simple_signature_author_committer_same_as_git_user_0_unixtime_no_pgp_signature() | ||
| 1161 | -> Result<()> { | ||
| 1162 | let source_repo = GitTestRepo::default(); | ||
| 1163 | source_repo.populate()?; | ||
| 1164 | fs::write(source_repo.dir.join("x1.md"), "some content")?; | ||
| 1165 | source_repo.stage_and_commit("add x1.md")?; | ||
| 1166 | |||
| 1167 | test_patch_applies_to_repository(generate_patch_from_head_commit(&source_repo)?) | ||
| 1168 | } | ||
| 1169 | |||
| 1170 | #[test] | ||
| 1171 | fn signature_with_specific_author_time() -> Result<()> { | ||
| 1172 | let source_repo = GitTestRepo::default(); | ||
| 1173 | source_repo.populate()?; | ||
| 1174 | fs::write(source_repo.dir.join("x1.md"), "some content")?; | ||
| 1175 | source_repo.stage_and_commit_custom_signature( | ||
| 1176 | "add x1.md", | ||
| 1177 | Some(&git2::Signature::new( | ||
| 1178 | joe_signature().name().unwrap(), | ||
| 1179 | joe_signature().email().unwrap(), | ||
| 1180 | &git2::Time::new(5000, 0), | ||
| 1181 | )?), | ||
| 1182 | None, | ||
| 1183 | )?; | ||
| 1184 | |||
| 1185 | test_patch_applies_to_repository(generate_patch_from_head_commit(&source_repo)?) | ||
| 1186 | } | ||
| 1187 | |||
| 1188 | #[test] | ||
| 1189 | fn author_name_and_email_not_current_git_user() -> Result<()> { | ||
| 1190 | let source_repo = GitTestRepo::default(); | ||
| 1191 | source_repo.populate()?; | ||
| 1192 | fs::write(source_repo.dir.join("x1.md"), "some content")?; | ||
| 1193 | source_repo.stage_and_commit_custom_signature( | ||
| 1194 | "add x1.md", | ||
| 1195 | Some(&git2::Signature::new( | ||
| 1196 | "carole", | ||
| 1197 | "carole@pm.me", | ||
| 1198 | &git2::Time::new(0, 0), | ||
| 1199 | )?), | ||
| 1200 | None, | ||
| 1201 | )?; | ||
| 1202 | |||
| 1203 | test_patch_applies_to_repository(generate_patch_from_head_commit(&source_repo)?) | ||
| 1204 | } | ||
| 1205 | |||
| 1206 | #[test] | ||
| 1207 | fn comiiter_name_and_email_not_current_git_user_or_author() -> Result<()> { | ||
| 1208 | let source_repo = GitTestRepo::default(); | ||
| 1209 | source_repo.populate()?; | ||
| 1210 | fs::write(source_repo.dir.join("x1.md"), "some content")?; | ||
| 1211 | source_repo.stage_and_commit_custom_signature( | ||
| 1212 | "add x1.md", | ||
| 1213 | Some(&git2::Signature::new( | ||
| 1214 | "carole", | ||
| 1215 | "carole@pm.me", | ||
| 1216 | &git2::Time::new(0, 0), | ||
| 1217 | )?), | ||
| 1218 | Some(&git2::Signature::new( | ||
| 1219 | "bob", | ||
| 1220 | "bob@pm.me", | ||
| 1221 | &git2::Time::new(0, 0), | ||
| 1222 | )?), | ||
| 1223 | )?; | ||
| 1224 | |||
| 1225 | test_patch_applies_to_repository(generate_patch_from_head_commit(&source_repo)?) | ||
| 1226 | } | ||
| 1227 | |||
| 1228 | // TODO: pgp signature | ||
| 1229 | |||
| 1230 | #[test] | ||
| 1231 | fn unique_author_and_commiter_details() -> Result<()> { | ||
| 1232 | let source_repo = GitTestRepo::default(); | ||
| 1233 | source_repo.populate()?; | ||
| 1234 | fs::write(source_repo.dir.join("x1.md"), "some content")?; | ||
| 1235 | source_repo.stage_and_commit_custom_signature( | ||
| 1236 | "add x1.md", | ||
| 1237 | Some(&git2::Signature::new( | ||
| 1238 | "carole", | ||
| 1239 | "carole@pm.me", | ||
| 1240 | &git2::Time::new(5000, 0), | ||
| 1241 | )?), | ||
| 1242 | Some(&git2::Signature::new( | ||
| 1243 | "bob", | ||
| 1244 | "bob@pm.me", | ||
| 1245 | &git2::Time::new(1000, 0), | ||
| 1246 | )?), | ||
| 1247 | )?; | ||
| 1248 | |||
| 1249 | test_patch_applies_to_repository(generate_patch_from_head_commit(&source_repo)?) | ||
| 1250 | } | ||
| 1251 | } | ||
| 1252 | } | ||
| 1253 | |||
| 1254 | mod apply_patch_chain { | ||
| 1255 | use test_utils::TEST_KEY_1_KEYS; | ||
| 1256 | |||
| 1257 | use super::*; | ||
| 1258 | use crate::sub_commands::prs::create::generate_pr_and_patch_events; | ||
| 1259 | |||
| 1260 | static BRANCH_NAME: &str = "add-example-feature"; | ||
| 1261 | // returns original_repo, pr_event, patch_events | ||
| 1262 | fn generate_test_repo_and_events() -> Result<(GitTestRepo, nostr::Event, Vec<nostr::Event>)> | ||
| 1263 | { | ||
| 1264 | let original_repo = GitTestRepo::default(); | ||
| 1265 | let oid3 = original_repo.populate_with_test_branch()?; | ||
| 1266 | let oid2 = original_repo.git_repo.find_commit(oid3)?.parent_id(0)?; | ||
| 1267 | let oid1 = original_repo.git_repo.find_commit(oid2)?.parent_id(0)?; | ||
| 1268 | // TODO: generate pr and patch events | ||
| 1269 | let git_repo = Repo::from_path(&original_repo.dir)?; | ||
| 1270 | |||
| 1271 | let mut events = generate_pr_and_patch_events( | ||
| 1272 | "title", | ||
| 1273 | "description", | ||
| 1274 | BRANCH_NAME, | ||
| 1275 | &git_repo, | ||
| 1276 | &vec![oid_to_sha1(&oid1), oid_to_sha1(&oid2), oid_to_sha1(&oid3)], | ||
| 1277 | &TEST_KEY_1_KEYS, | ||
| 1278 | )?; | ||
| 1279 | |||
| 1280 | events.reverse(); | ||
| 1281 | |||
| 1282 | Ok((original_repo, events.pop().unwrap(), events)) | ||
| 1283 | } | ||
| 1284 | |||
| 1285 | mod when_branch_and_commits_dont_exist { | ||
| 1286 | use super::*; | ||
| 1287 | |||
| 1288 | mod when_branch_root_is_tip_of_main { | ||
| 1289 | use super::*; | ||
| 1290 | |||
| 1291 | #[test] | ||
| 1292 | fn branch_gets_created_with_name_specified_in_pr() -> Result<()> { | ||
| 1293 | let (_, _, patch_events) = generate_test_repo_and_events()?; | ||
| 1294 | let test_repo = GitTestRepo::default(); | ||
| 1295 | test_repo.populate()?; | ||
| 1296 | let git_repo = Repo::from_path(&test_repo.dir)?; | ||
| 1297 | git_repo.apply_patch_chain(BRANCH_NAME, patch_events)?; | ||
| 1298 | assert!( | ||
| 1299 | git_repo | ||
| 1300 | .get_local_branch_names()? | ||
| 1301 | .contains(&BRANCH_NAME.to_string()) | ||
| 1302 | ); | ||
| 1303 | Ok(()) | ||
| 1304 | } | ||
| 1305 | |||
| 1306 | #[test] | ||
| 1307 | fn branch_checked_out() -> Result<()> { | ||
| 1308 | let (_, _, patch_events) = generate_test_repo_and_events()?; | ||
| 1309 | let test_repo = GitTestRepo::default(); | ||
| 1310 | test_repo.populate()?; | ||
| 1311 | let git_repo = Repo::from_path(&test_repo.dir)?; | ||
| 1312 | git_repo.apply_patch_chain(BRANCH_NAME, patch_events)?; | ||
| 1313 | assert_eq!( | ||
| 1314 | git_repo.get_checked_out_branch_name()?, | ||
| 1315 | BRANCH_NAME.to_string(), | ||
| 1316 | ); | ||
| 1317 | Ok(()) | ||
| 1318 | } | ||
| 1319 | |||
| 1320 | #[test] | ||
| 1321 | fn patches_get_created_as_commits() -> Result<()> { | ||
| 1322 | let (original_repo, _, patch_events) = generate_test_repo_and_events()?; | ||
| 1323 | let test_repo = GitTestRepo::default(); | ||
| 1324 | test_repo.populate()?; | ||
| 1325 | let git_repo = Repo::from_path(&test_repo.dir)?; | ||
| 1326 | git_repo.apply_patch_chain(BRANCH_NAME, patch_events)?; | ||
| 1327 | assert_eq!( | ||
| 1328 | test_repo.git_repo.head()?.peel_to_commit()?.id(), | ||
| 1329 | original_repo.git_repo.head()?.peel_to_commit()?.id(), | ||
| 1330 | ); | ||
| 1331 | Ok(()) | ||
| 1332 | } | ||
| 1333 | |||
| 1334 | #[test] | ||
| 1335 | fn branch_tip_is_most_recent_patch() -> Result<()> { | ||
| 1336 | let (original_repo, _, patch_events) = generate_test_repo_and_events()?; | ||
| 1337 | let test_repo = GitTestRepo::default(); | ||
| 1338 | test_repo.populate()?; | ||
| 1339 | let git_repo = Repo::from_path(&test_repo.dir)?; | ||
| 1340 | git_repo.apply_patch_chain(BRANCH_NAME, patch_events)?; | ||
| 1341 | assert_eq!( | ||
| 1342 | git_repo.get_tip_of_local_branch(BRANCH_NAME)?, | ||
| 1343 | oid_to_sha1(&original_repo.git_repo.head()?.peel_to_commit()?.id(),), | ||
| 1344 | ); | ||
| 1345 | Ok(()) | ||
| 1346 | } | ||
| 1347 | |||
| 1348 | #[test] | ||
| 1349 | fn previously_checked_out_branch_tip_does_not_change() -> Result<()> { | ||
| 1350 | let (_, _, patch_events) = generate_test_repo_and_events()?; | ||
| 1351 | let test_repo = GitTestRepo::default(); | ||
| 1352 | test_repo.populate()?; | ||
| 1353 | let existing_branch = test_repo.get_checked_out_branch_name()?; | ||
| 1354 | let git_repo = Repo::from_path(&test_repo.dir)?; | ||
| 1355 | let previous_tip_of_existing_branch = | ||
| 1356 | git_repo.get_tip_of_local_branch(existing_branch.as_str())?; | ||
| 1357 | git_repo.apply_patch_chain(BRANCH_NAME, patch_events)?; | ||
| 1358 | assert_eq!( | ||
| 1359 | previous_tip_of_existing_branch, | ||
| 1360 | git_repo.get_tip_of_local_branch(existing_branch.as_str())?, | ||
| 1361 | ); | ||
| 1362 | Ok(()) | ||
| 1363 | } | ||
| 1364 | |||
| 1365 | #[test] | ||
| 1366 | fn returns_all_patches_applied() -> Result<()> { | ||
| 1367 | let (_, _, patch_events) = generate_test_repo_and_events()?; | ||
| 1368 | let test_repo = GitTestRepo::default(); | ||
| 1369 | test_repo.populate()?; | ||
| 1370 | let git_repo = Repo::from_path(&test_repo.dir)?; | ||
| 1371 | let res = git_repo.apply_patch_chain(BRANCH_NAME, patch_events)?; | ||
| 1372 | assert_eq!(res.len(), 3); | ||
| 1373 | Ok(()) | ||
| 1374 | } | ||
| 1375 | } | ||
| 1376 | |||
| 1377 | mod when_branch_root_is_tip_behind_main { | ||
| 1378 | use super::*; | ||
| 1379 | |||
| 1380 | #[test] | ||
| 1381 | fn branch_gets_created_with_name_specified_in_pr() -> Result<()> { | ||
| 1382 | let (_, _, patch_events) = generate_test_repo_and_events()?; | ||
| 1383 | let test_repo = GitTestRepo::default(); | ||
| 1384 | test_repo.populate()?; | ||
| 1385 | std::fs::write(test_repo.dir.join("m3.md"), "some content")?; | ||
| 1386 | test_repo.stage_and_commit("add m3.md")?; | ||
| 1387 | let git_repo = Repo::from_path(&test_repo.dir)?; | ||
| 1388 | git_repo.apply_patch_chain(BRANCH_NAME, patch_events)?; | ||
| 1389 | assert!( | ||
| 1390 | git_repo | ||
| 1391 | .get_local_branch_names()? | ||
| 1392 | .contains(&BRANCH_NAME.to_string()) | ||
| 1393 | ); | ||
| 1394 | Ok(()) | ||
| 1395 | } | ||
| 1396 | |||
| 1397 | #[test] | ||
| 1398 | fn branch_checked_out() -> Result<()> { | ||
| 1399 | let (_, _, patch_events) = generate_test_repo_and_events()?; | ||
| 1400 | let test_repo = GitTestRepo::default(); | ||
| 1401 | test_repo.populate()?; | ||
| 1402 | std::fs::write(test_repo.dir.join("m3.md"), "some content")?; | ||
| 1403 | test_repo.stage_and_commit("add m3.md")?; | ||
| 1404 | let git_repo = Repo::from_path(&test_repo.dir)?; | ||
| 1405 | git_repo.apply_patch_chain(BRANCH_NAME, patch_events)?; | ||
| 1406 | assert_eq!( | ||
| 1407 | git_repo.get_checked_out_branch_name()?, | ||
| 1408 | BRANCH_NAME.to_string(), | ||
| 1409 | ); | ||
| 1410 | Ok(()) | ||
| 1411 | } | ||
| 1412 | |||
| 1413 | #[test] | ||
| 1414 | fn branch_tip_is_most_recent_patch() -> Result<()> { | ||
| 1415 | let (original_repo, _, patch_events) = generate_test_repo_and_events()?; | ||
| 1416 | let test_repo = GitTestRepo::default(); | ||
| 1417 | test_repo.populate()?; | ||
| 1418 | std::fs::write(test_repo.dir.join("m3.md"), "some content")?; | ||
| 1419 | test_repo.stage_and_commit("add m3.md")?; | ||
| 1420 | let git_repo = Repo::from_path(&test_repo.dir)?; | ||
| 1421 | git_repo.apply_patch_chain(BRANCH_NAME, patch_events)?; | ||
| 1422 | assert_eq!( | ||
| 1423 | git_repo.get_tip_of_local_branch(BRANCH_NAME)?, | ||
| 1424 | oid_to_sha1(&original_repo.git_repo.head()?.peel_to_commit()?.id(),), | ||
| 1425 | ); | ||
| 1426 | Ok(()) | ||
| 1427 | } | ||
| 1428 | |||
| 1429 | #[test] | ||
| 1430 | fn previously_checked_out_branch_tip_does_not_change() -> Result<()> { | ||
| 1431 | let (_, _, patch_events) = generate_test_repo_and_events()?; | ||
| 1432 | let test_repo = GitTestRepo::default(); | ||
| 1433 | test_repo.populate()?; | ||
| 1434 | std::fs::write(test_repo.dir.join("m3.md"), "some content")?; | ||
| 1435 | test_repo.stage_and_commit("add m3.md")?; | ||
| 1436 | let existing_branch = test_repo.get_checked_out_branch_name()?; | ||
| 1437 | let git_repo = Repo::from_path(&test_repo.dir)?; | ||
| 1438 | let previous_tip_of_existing_branch = | ||
| 1439 | git_repo.get_tip_of_local_branch(existing_branch.as_str())?; | ||
| 1440 | git_repo.apply_patch_chain(BRANCH_NAME, patch_events)?; | ||
| 1441 | assert_eq!( | ||
| 1442 | previous_tip_of_existing_branch, | ||
| 1443 | git_repo.get_tip_of_local_branch(existing_branch.as_str())?, | ||
| 1444 | ); | ||
| 1445 | Ok(()) | ||
| 1446 | } | ||
| 1447 | |||
| 1448 | #[test] | ||
| 1449 | fn returns_all_patches_applied() -> Result<()> { | ||
| 1450 | let (_, _, patch_events) = generate_test_repo_and_events()?; | ||
| 1451 | let test_repo = GitTestRepo::default(); | ||
| 1452 | test_repo.populate()?; | ||
| 1453 | let git_repo = Repo::from_path(&test_repo.dir)?; | ||
| 1454 | let res = git_repo.apply_patch_chain(BRANCH_NAME, patch_events)?; | ||
| 1455 | assert_eq!(res.len(), 3); | ||
| 1456 | Ok(()) | ||
| 1457 | } | ||
| 1458 | } | ||
| 1459 | |||
| 1460 | // TODO when_pr_root_is_tip_ahead_of_main_and_doesnt_exist | ||
| 1461 | } | ||
| 1462 | |||
| 1463 | mod when_branch_and_first_commits_exists { | ||
| 1464 | use super::*; | ||
| 1465 | |||
| 1466 | mod when_branch_already_checked_out { | ||
| 1467 | use super::*; | ||
| 1468 | |||
| 1469 | #[test] | ||
| 1470 | fn branch_tip_is_most_recent_patch() -> Result<()> { | ||
| 1471 | let (original_repo, _, mut patch_events) = generate_test_repo_and_events()?; | ||
| 1472 | let test_repo = GitTestRepo::default(); | ||
| 1473 | test_repo.populate()?; | ||
| 1474 | let git_repo = Repo::from_path(&test_repo.dir)?; | ||
| 1475 | git_repo.apply_patch_chain(BRANCH_NAME, vec![patch_events.pop().unwrap()])?; | ||
| 1476 | git_repo.apply_patch_chain(BRANCH_NAME, patch_events)?; | ||
| 1477 | |||
| 1478 | assert_eq!( | ||
| 1479 | git_repo.get_tip_of_local_branch(BRANCH_NAME)?, | ||
| 1480 | oid_to_sha1(&original_repo.git_repo.head()?.peel_to_commit()?.id(),), | ||
| 1481 | ); | ||
| 1482 | Ok(()) | ||
| 1483 | } | ||
| 1484 | |||
| 1485 | #[test] | ||
| 1486 | fn returns_all_patches_applied() -> Result<()> { | ||
| 1487 | let (_, _, mut patch_events) = generate_test_repo_and_events()?; | ||
| 1488 | let test_repo = GitTestRepo::default(); | ||
| 1489 | test_repo.populate()?; | ||
| 1490 | let git_repo = Repo::from_path(&test_repo.dir)?; | ||
| 1491 | git_repo.apply_patch_chain(BRANCH_NAME, vec![patch_events.pop().unwrap()])?; | ||
| 1492 | let res = git_repo.apply_patch_chain(BRANCH_NAME, patch_events)?; | ||
| 1493 | assert_eq!(res.len(), 2); | ||
| 1494 | Ok(()) | ||
| 1495 | } | ||
| 1496 | } | ||
| 1497 | mod when_branch_not_checked_out { | ||
| 1498 | use super::*; | ||
| 1499 | |||
| 1500 | #[test] | ||
| 1501 | fn branch_tip_is_most_recent_patch() -> Result<()> { | ||
| 1502 | let (original_repo, _, mut patch_events) = generate_test_repo_and_events()?; | ||
| 1503 | let test_repo = GitTestRepo::default(); | ||
| 1504 | test_repo.populate()?; | ||
| 1505 | let git_repo = Repo::from_path(&test_repo.dir)?; | ||
| 1506 | git_repo.apply_patch_chain(BRANCH_NAME, vec![patch_events.pop().unwrap()])?; | ||
| 1507 | git_repo.checkout("main")?; | ||
| 1508 | git_repo.apply_patch_chain(BRANCH_NAME, patch_events)?; | ||
| 1509 | |||
| 1510 | assert_eq!( | ||
| 1511 | git_repo.get_tip_of_local_branch(BRANCH_NAME)?, | ||
| 1512 | oid_to_sha1(&original_repo.git_repo.head()?.peel_to_commit()?.id(),), | ||
| 1513 | ); | ||
| 1514 | Ok(()) | ||
| 1515 | } | ||
| 1516 | |||
| 1517 | #[test] | ||
| 1518 | fn branch_checked_out() -> Result<()> { | ||
| 1519 | let (_, _, mut patch_events) = generate_test_repo_and_events()?; | ||
| 1520 | let test_repo = GitTestRepo::default(); | ||
| 1521 | test_repo.populate()?; | ||
| 1522 | let git_repo = Repo::from_path(&test_repo.dir)?; | ||
| 1523 | git_repo.apply_patch_chain(BRANCH_NAME, vec![patch_events.pop().unwrap()])?; | ||
| 1524 | git_repo.checkout("main")?; | ||
| 1525 | git_repo.apply_patch_chain(BRANCH_NAME, patch_events)?; | ||
| 1526 | |||
| 1527 | assert_eq!( | ||
| 1528 | git_repo.get_checked_out_branch_name()?, | ||
| 1529 | BRANCH_NAME.to_string(), | ||
| 1530 | ); | ||
| 1531 | Ok(()) | ||
| 1532 | } | ||
| 1533 | |||
| 1534 | #[test] | ||
| 1535 | fn returns_all_patches_applied() -> Result<()> { | ||
| 1536 | let (_, _, mut patch_events) = generate_test_repo_and_events()?; | ||
| 1537 | let test_repo = GitTestRepo::default(); | ||
| 1538 | test_repo.populate()?; | ||
| 1539 | let git_repo = Repo::from_path(&test_repo.dir)?; | ||
| 1540 | git_repo.apply_patch_chain(BRANCH_NAME, vec![patch_events.pop().unwrap()])?; | ||
| 1541 | git_repo.checkout("main")?; | ||
| 1542 | let res = git_repo.apply_patch_chain(BRANCH_NAME, patch_events)?; | ||
| 1543 | assert_eq!(res.len(), 2); | ||
| 1544 | Ok(()) | ||
| 1545 | } | ||
| 1546 | } | ||
| 1547 | // TODO when branch ahead (rebased or user commits) | ||
| 1548 | } | ||
| 1549 | mod when_branch_exists_and_is_up_to_date { | ||
| 1550 | use super::*; | ||
| 1551 | |||
| 1552 | mod when_branch_already_checked_out { | ||
| 1553 | use super::*; | ||
| 1554 | |||
| 1555 | #[test] | ||
| 1556 | fn returns_all_patches_applied_0() -> Result<()> { | ||
| 1557 | let (_, _, patch_events) = generate_test_repo_and_events()?; | ||
| 1558 | let test_repo = GitTestRepo::default(); | ||
| 1559 | test_repo.populate()?; | ||
| 1560 | let git_repo = Repo::from_path(&test_repo.dir)?; | ||
| 1561 | git_repo.apply_patch_chain(BRANCH_NAME, patch_events.clone())?; | ||
| 1562 | let res = git_repo.apply_patch_chain(BRANCH_NAME, patch_events)?; | ||
| 1563 | assert_eq!(res.len(), 0); | ||
| 1564 | Ok(()) | ||
| 1565 | } | ||
| 1566 | } | ||
| 1567 | mod when_branch_not_checked_out { | ||
| 1568 | use super::*; | ||
| 1569 | |||
| 1570 | #[test] | ||
| 1571 | fn branch_checked_out() -> Result<()> { | ||
| 1572 | let (_, _, patch_events) = generate_test_repo_and_events()?; | ||
| 1573 | let test_repo = GitTestRepo::default(); | ||
| 1574 | test_repo.populate()?; | ||
| 1575 | let git_repo = Repo::from_path(&test_repo.dir)?; | ||
| 1576 | git_repo.apply_patch_chain(BRANCH_NAME, patch_events.clone())?; | ||
| 1577 | git_repo.checkout("main")?; | ||
| 1578 | git_repo.apply_patch_chain(BRANCH_NAME, patch_events)?; | ||
| 1579 | |||
| 1580 | assert_eq!( | ||
| 1581 | git_repo.get_checked_out_branch_name()?, | ||
| 1582 | BRANCH_NAME.to_string(), | ||
| 1583 | ); | ||
| 1584 | Ok(()) | ||
| 1585 | } | ||
| 1586 | |||
| 1587 | #[test] | ||
| 1588 | fn returns_all_patches_applied_0() -> Result<()> { | ||
| 1589 | let (_, _, patch_events) = generate_test_repo_and_events()?; | ||
| 1590 | let test_repo = GitTestRepo::default(); | ||
| 1591 | test_repo.populate()?; | ||
| 1592 | let git_repo = Repo::from_path(&test_repo.dir)?; | ||
| 1593 | git_repo.apply_patch_chain(BRANCH_NAME, patch_events.clone())?; | ||
| 1594 | git_repo.checkout("main")?; | ||
| 1595 | let res = git_repo.apply_patch_chain(BRANCH_NAME, patch_events)?; | ||
| 1596 | assert_eq!(res.len(), 0); | ||
| 1597 | Ok(()) | ||
| 1598 | } | ||
| 1599 | } | ||
| 1600 | } | ||
| 660 | } | 1601 | } |
| 661 | } | 1602 | } |