diff options
| -rw-r--r-- | src/git.rs | 251 |
1 files changed, 91 insertions, 160 deletions
| @@ -75,6 +75,7 @@ pub trait RepoActions { | |||
| 75 | branch_name: &str, | 75 | branch_name: &str, |
| 76 | patch_and_ancestors: Vec<nostr::Event>, | 76 | patch_and_ancestors: Vec<nostr::Event>, |
| 77 | ) -> Result<Vec<nostr::Event>>; | 77 | ) -> Result<Vec<nostr::Event>>; |
| 78 | fn create_commit_from_patch(&self, patch: &nostr::Event) -> Result<Oid>; | ||
| 78 | fn parse_starting_commits(&self, starting_commits: &str) -> Result<Vec<Sha1Hash>>; | 79 | fn parse_starting_commits(&self, starting_commits: &str) -> Result<Vec<Sha1Hash>>; |
| 79 | fn ancestor_of(&self, decendant: &Sha1Hash, ancestor: &Sha1Hash) -> Result<bool>; | 80 | fn ancestor_of(&self, decendant: &Sha1Hash, ancestor: &Sha1Hash) -> Result<bool>; |
| 80 | fn get_git_config_item(&self, item: &str, global: Option<bool>) -> Result<Option<String>>; | 81 | fn get_git_config_item(&self, item: &str, global: Option<bool>) -> Result<Option<String>>; |
| @@ -534,15 +535,99 @@ impl RepoActions for Repo { | |||
| 534 | for patch in &patches_to_apply { | 535 | for patch in &patches_to_apply { |
| 535 | let commit_id = get_commit_id_from_patch(patch)?; | 536 | let commit_id = get_commit_id_from_patch(patch)?; |
| 536 | // only create new commits - otherwise make them the tip | 537 | // only create new commits - otherwise make them the tip |
| 537 | if self.does_commit_exist(&commit_id)? { | 538 | if !self.does_commit_exist(&commit_id)? { |
| 538 | self.create_branch_at_commit(branch_name, &commit_id)?; | 539 | self.create_commit_from_patch(patch)?; |
| 539 | } else { | ||
| 540 | apply_patch(self, patch)?; | ||
| 541 | } | 540 | } |
| 541 | self.create_branch_at_commit(branch_name, &commit_id)?; | ||
| 542 | self.checkout(branch_name)?; | ||
| 542 | } | 543 | } |
| 543 | Ok(patches_to_apply) | 544 | Ok(patches_to_apply) |
| 544 | } | 545 | } |
| 546 | fn create_commit_from_patch(&self, patch: &nostr::Event) -> Result<Oid> { | ||
| 547 | let commit_id = get_commit_id_from_patch(patch)?; | ||
| 548 | if self.does_commit_exist(&commit_id)? { | ||
| 549 | return Ok(Oid::from_str(&commit_id)?); | ||
| 550 | } | ||
| 551 | let parent_commit_id = tag_value(patch, "parent-commit")?; | ||
| 552 | |||
| 553 | let parent_commit = self | ||
| 554 | .git_repo | ||
| 555 | .find_commit(Oid::from_str(&parent_commit_id)?) | ||
| 556 | .context("parrent commit doesnt exist")?; | ||
| 557 | let parent_tree = parent_commit.tree()?; | ||
| 558 | |||
| 559 | // let mut apply_opts = git2::ApplyOptions::new(); | ||
| 560 | // apply_opts.check(false); | ||
| 545 | 561 | ||
| 562 | let mut index = self.git_repo.apply_to_tree( | ||
| 563 | &parent_tree, | ||
| 564 | &git2::Diff::from_buffer(patch.content.as_bytes())?, | ||
| 565 | // Some(&mut apply_opts), | ||
| 566 | None, | ||
| 567 | )?; | ||
| 568 | let tree = self | ||
| 569 | .git_repo | ||
| 570 | .find_tree(index.write_tree_to(&self.git_repo)?)?; | ||
| 571 | |||
| 572 | let pgp_sig = if let Ok(pgp_sig) = tag_value(patch, "commit-pgp-sig") { | ||
| 573 | if pgp_sig.is_empty() { | ||
| 574 | None | ||
| 575 | } else { | ||
| 576 | Some(pgp_sig) | ||
| 577 | } | ||
| 578 | } else { | ||
| 579 | None | ||
| 580 | }; | ||
| 581 | |||
| 582 | let mut applied_oid = if let Some(pgp_sig) = pgp_sig { | ||
| 583 | let commit_buff = self.git_repo.commit_create_buffer( | ||
| 584 | &extract_sig_from_patch_tags(&patch.tags, "author")?, | ||
| 585 | &extract_sig_from_patch_tags(&patch.tags, "committer")?, | ||
| 586 | tag_value(patch, "description")?.as_str(), | ||
| 587 | &tree, | ||
| 588 | &[&parent_commit], | ||
| 589 | )?; | ||
| 590 | self.git_repo | ||
| 591 | .commit_signed(commit_buff.as_str().unwrap(), pgp_sig.as_str(), None) | ||
| 592 | .context("failed to create signed commit")? | ||
| 593 | } else { | ||
| 594 | self.git_repo | ||
| 595 | .commit( | ||
| 596 | Some("HEAD"), | ||
| 597 | &extract_sig_from_patch_tags(&patch.tags, "author")?, | ||
| 598 | &extract_sig_from_patch_tags(&patch.tags, "committer")?, | ||
| 599 | tag_value(patch, "description")?.as_str(), | ||
| 600 | &tree, | ||
| 601 | &[&parent_commit], | ||
| 602 | ) | ||
| 603 | .context("failed to create unsigned commit")? | ||
| 604 | }; | ||
| 605 | // I beleive this was added to address a bug where commit author / committer | ||
| 606 | // were identical when in a scenario when they should be different but I dont | ||
| 607 | // think we have a test case for it. surely we should be using the | ||
| 608 | // extract_sig_from_patch_tags outputs to address this? | ||
| 609 | if !applied_oid.to_string().eq(&commit_id) { | ||
| 610 | let commit = self.git_repo.find_commit(applied_oid)?; | ||
| 611 | applied_oid = commit | ||
| 612 | .amend( | ||
| 613 | None, | ||
| 614 | Some(&commit.author()), | ||
| 615 | Some(&commit.committer()), | ||
| 616 | None, | ||
| 617 | None, | ||
| 618 | None, | ||
| 619 | ) | ||
| 620 | .context("cannot amend commit to produce new oid")?; | ||
| 621 | } | ||
| 622 | if !applied_oid.to_string().eq(&commit_id) { | ||
| 623 | bail!( | ||
| 624 | "when applied the patch commit id ({}) doesn't match the one specified in the event tag ({})", | ||
| 625 | applied_oid.to_string(), | ||
| 626 | get_commit_id_from_patch(patch)?, | ||
| 627 | ); | ||
| 628 | } | ||
| 629 | Ok(applied_oid) | ||
| 630 | } | ||
| 546 | fn parse_starting_commits(&self, starting_commits: &str) -> Result<Vec<Sha1Hash>> { | 631 | fn parse_starting_commits(&self, starting_commits: &str) -> Result<Vec<Sha1Hash>> { |
| 547 | let revspec = self | 632 | let revspec = self |
| 548 | .git_repo | 633 | .git_repo |
| @@ -726,139 +811,6 @@ fn git_sig_to_tag_vec(sig: &git2::Signature) -> Vec<String> { | |||
| 726 | ] | 811 | ] |
| 727 | } | 812 | } |
| 728 | 813 | ||
| 729 | fn apply_patch(git_repo: &Repo, patch: &nostr::Event) -> Result<()> { | ||
| 730 | // check parent commit matches head | ||
| 731 | if !git_repo | ||
| 732 | .get_head_commit()? | ||
| 733 | .to_string() | ||
| 734 | .eq(&tag_value(patch, "parent-commit")?) | ||
| 735 | { | ||
| 736 | bail!( | ||
| 737 | "patch parent ({}) doesnt match current head ({})", | ||
| 738 | tag_value(patch, "parent-commit")?, | ||
| 739 | git_repo.get_head_commit()? | ||
| 740 | ); | ||
| 741 | } | ||
| 742 | |||
| 743 | let diff_from_patch = git2::Diff::from_buffer(patch.content.as_bytes()).unwrap(); | ||
| 744 | |||
| 745 | let mut apply_opts = git2::ApplyOptions::new(); | ||
| 746 | apply_opts.check(false); | ||
| 747 | |||
| 748 | git_repo.git_repo.apply( | ||
| 749 | &diff_from_patch, | ||
| 750 | git2::ApplyLocation::WorkDir, | ||
| 751 | Some(&mut apply_opts), | ||
| 752 | )?; | ||
| 753 | // stage and commit | ||
| 754 | let prev_oid = git_repo.git_repo.head().unwrap().peel_to_commit()?; | ||
| 755 | |||
| 756 | let mut index = git_repo.git_repo.index()?; | ||
| 757 | index.add_all(["."], git2::IndexAddOption::DEFAULT, None)?; | ||
| 758 | index.write()?; | ||
| 759 | |||
| 760 | let pgp_sig = if let Ok(pgp_sig) = tag_value(patch, "commit-pgp-sig") { | ||
| 761 | if pgp_sig.is_empty() { | ||
| 762 | None | ||
| 763 | } else { | ||
| 764 | Some(pgp_sig) | ||
| 765 | } | ||
| 766 | } else { | ||
| 767 | None | ||
| 768 | }; | ||
| 769 | |||
| 770 | if let Some(pgp_sig) = pgp_sig { | ||
| 771 | let commit_buff = git_repo.git_repo.commit_create_buffer( | ||
| 772 | &extract_sig_from_patch_tags(&patch.tags, "author")?, | ||
| 773 | &extract_sig_from_patch_tags(&patch.tags, "committer")?, | ||
| 774 | tag_value(patch, "description")?.as_str(), | ||
| 775 | &git_repo.git_repo.find_tree(index.write_tree()?)?, | ||
| 776 | &[&prev_oid], | ||
| 777 | )?; | ||
| 778 | let gpg_commit_id = git_repo.git_repo.commit_signed( | ||
| 779 | commit_buff.as_str().unwrap(), | ||
| 780 | pgp_sig.as_str(), | ||
| 781 | None, | ||
| 782 | )?; | ||
| 783 | git_repo.git_repo.reset( | ||
| 784 | &git_repo.git_repo.find_object(gpg_commit_id, None)?, | ||
| 785 | git2::ResetType::Mixed, | ||
| 786 | None, | ||
| 787 | )?; | ||
| 788 | if gpg_commit_id | ||
| 789 | .to_string() | ||
| 790 | .eq(&get_commit_id_from_patch(patch)?) | ||
| 791 | { | ||
| 792 | return Ok(()); | ||
| 793 | } | ||
| 794 | } else { | ||
| 795 | git_repo.git_repo.commit( | ||
| 796 | Some("HEAD"), | ||
| 797 | &extract_sig_from_patch_tags(&patch.tags, "author")?, | ||
| 798 | &extract_sig_from_patch_tags(&patch.tags, "committer")?, | ||
| 799 | tag_value(patch, "description")?.as_str(), | ||
| 800 | &git_repo.git_repo.find_tree(index.write_tree()?)?, | ||
| 801 | &[&prev_oid], | ||
| 802 | )?; | ||
| 803 | } | ||
| 804 | validate_patch_applied(git_repo, patch) | ||
| 805 | } | ||
| 806 | |||
| 807 | fn validate_patch_applied(git_repo: &Repo, patch: &nostr::Event) -> Result<()> { | ||
| 808 | // end of stage and commit | ||
| 809 | // check commit applied | ||
| 810 | if git_repo | ||
| 811 | .get_head_commit()? | ||
| 812 | .to_string() | ||
| 813 | .eq(&tag_value(patch, "parent-commit")?) | ||
| 814 | { | ||
| 815 | bail!("applying patch failed"); | ||
| 816 | } | ||
| 817 | |||
| 818 | let mut revwalk = git_repo.git_repo.revwalk().context("revwalk error")?; | ||
| 819 | revwalk.push_head().context("revwalk.push_head")?; | ||
| 820 | |||
| 821 | for (i, oid) in revwalk.enumerate() { | ||
| 822 | if i == 0 { | ||
| 823 | let old_commit = git_repo | ||
| 824 | .git_repo | ||
| 825 | .find_commit(oid.context("cannot get oid in revwalk")?) | ||
| 826 | .context("cannot find newly added commit oid")?; | ||
| 827 | // create commit using amend which relects the original commit id | ||
| 828 | let updated_commit_oid = old_commit | ||
| 829 | .amend( | ||
| 830 | None, | ||
| 831 | Some(&old_commit.author()), | ||
| 832 | Some(&old_commit.committer()), | ||
| 833 | None, | ||
| 834 | None, | ||
| 835 | None, | ||
| 836 | ) | ||
| 837 | .context("cannot amend commit to produce new oid")?; | ||
| 838 | // replace the commit with the wrong oid with the newly created one with the | ||
| 839 | // correct oid | ||
| 840 | git_repo | ||
| 841 | .git_repo | ||
| 842 | .head() | ||
| 843 | .context("cannot get head of git_repo")? | ||
| 844 | .set_target(updated_commit_oid, "ref commit with fix committer details") | ||
| 845 | .context("cannot update branch with fixed commit")?; | ||
| 846 | |||
| 847 | if !updated_commit_oid | ||
| 848 | .to_string() | ||
| 849 | .eq(&get_commit_id_from_patch(patch)?) | ||
| 850 | { | ||
| 851 | bail!( | ||
| 852 | "when applied the patch commit id ({}) doesn't match the one specified in the event tag ({})", | ||
| 853 | updated_commit_oid.to_string(), | ||
| 854 | get_commit_id_from_patch(patch)?, | ||
| 855 | ) | ||
| 856 | } | ||
| 857 | } | ||
| 858 | } | ||
| 859 | Ok(()) | ||
| 860 | } | ||
| 861 | |||
| 862 | fn extract_sig_from_patch_tags<'a>( | 814 | fn extract_sig_from_patch_tags<'a>( |
| 863 | tags: &'a [nostr::Tag], | 815 | tags: &'a [nostr::Tag], |
| 864 | tag_name: &str, | 816 | tag_name: &str, |
| @@ -1674,7 +1626,7 @@ mod tests { | |||
| 1674 | } | 1626 | } |
| 1675 | } | 1627 | } |
| 1676 | 1628 | ||
| 1677 | mod apply_patch { | 1629 | mod create_commit_from_patch { |
| 1678 | 1630 | ||
| 1679 | use test_utils::TEST_KEY_1_SIGNER; | 1631 | use test_utils::TEST_KEY_1_SIGNER; |
| 1680 | 1632 | ||
| @@ -1704,31 +1656,10 @@ mod tests { | |||
| 1704 | test_repo.populate()?; | 1656 | test_repo.populate()?; |
| 1705 | let git_repo = Repo::from_path(&test_repo.dir)?; | 1657 | let git_repo = Repo::from_path(&test_repo.dir)?; |
| 1706 | println!("{:?}", &patch_event); | 1658 | println!("{:?}", &patch_event); |
| 1707 | apply_patch(&git_repo, &patch_event)?; | 1659 | git_repo.create_commit_from_patch(&patch_event)?; |
| 1708 | let commit_id = tag_value(&patch_event, "commit")?; | 1660 | let commit_id = tag_value(&patch_event, "commit")?; |
| 1709 | // does commit with id exist? | 1661 | // does commit with id exist? |
| 1710 | assert!(git_repo.does_commit_exist(&commit_id)?); | 1662 | assert!(git_repo.does_commit_exist(&commit_id)?); |
| 1711 | // is commit head | ||
| 1712 | assert_eq!( | ||
| 1713 | test_repo | ||
| 1714 | .git_repo | ||
| 1715 | .head()? | ||
| 1716 | .peel_to_commit()? | ||
| 1717 | .id() | ||
| 1718 | .to_string(), | ||
| 1719 | commit_id, | ||
| 1720 | ); | ||
| 1721 | // applied to current checked branch (head hasn't moved to specific commit) | ||
| 1722 | assert_eq!( | ||
| 1723 | test_repo | ||
| 1724 | .git_repo | ||
| 1725 | .head()? | ||
| 1726 | .shorthand() | ||
| 1727 | .context("an object without a shorthand is checked out")? | ||
| 1728 | .to_string(), | ||
| 1729 | "main", | ||
| 1730 | ); | ||
| 1731 | |||
| 1732 | Ok(()) | 1663 | Ok(()) |
| 1733 | } | 1664 | } |
| 1734 | 1665 | ||