|
1 | | -// tree_writer.insert(".conflict-side-0", side0.id(), 0o040000)?; |
2 | | -// tree_writer.insert(".conflict-side-1", side1.id(), 0o040000)?; |
3 | | -// tree_writer.insert(".conflict-base-0", base_tree.id(), 0o040000)?; |
4 | | -// tree_writer.insert(".auto-resolution", resolved_tree_id, 0o040000)?; |
5 | | -// tree_writer.insert(".conflict-files", conflicted_files_blob, 0o100644)?; |
6 | | - |
7 | 1 | use std::ops::Deref; |
8 | 2 |
|
9 | | -use anyhow::Context; |
10 | | -use git2::MergeOptions; |
| 3 | +use anyhow::{Context, Result}; |
11 | 4 | use gitbutler_commit::commit_ext::CommitExt; |
| 5 | +use gitbutler_oxidize::git2_to_gix_object_id; |
12 | 6 |
|
13 | 7 | #[derive(Default)] |
14 | 8 | pub enum ConflictedTreeKey { |
@@ -40,68 +34,110 @@ impl Deref for ConflictedTreeKey { |
40 | 34 | } |
41 | 35 |
|
42 | 36 | pub trait RepositoryExt { |
43 | | - fn cherry_pick_gitbutler( |
44 | | - &self, |
| 37 | + /// Find the real tree of a commit, which is the tree of the commit if it's not in a conflicted state |
| 38 | + /// or the tree according to `side` if it is conflicted. |
| 39 | + /// |
| 40 | + /// Unless you want to find a particular side, you likely want to pass Default::default() |
| 41 | + /// as the [`side`](ConflictedTreeKey) which will give the automatically resolved resolution |
| 42 | + fn find_real_tree(&self, commit: &git2::Commit, side: ConflictedTreeKey) -> Result<git2::Tree>; |
| 43 | +} |
| 44 | + |
| 45 | +pub trait GixRepositoryExt { |
| 46 | + /// Cherry-pick, but understands GitButler conflicted states. |
| 47 | + /// Note that it will automatically resolve conflicts in *our* favor, so any tree produced |
| 48 | + /// here can be used. |
| 49 | + /// |
| 50 | + /// This method *should* always be used in favour of native functions. |
| 51 | + fn cherry_pick_gitbutler<'repo>( |
| 52 | + &'repo self, |
45 | 53 | head: &git2::Commit, |
46 | 54 | to_rebase: &git2::Commit, |
47 | | - merge_options: Option<&MergeOptions>, |
48 | | - ) -> Result<git2::Index, anyhow::Error>; |
49 | | - fn find_real_tree( |
50 | | - &self, |
51 | | - commit: &git2::Commit, |
| 55 | + ) -> Result<gix::merge::tree::Outcome<'repo>>; |
| 56 | + |
| 57 | + /// Find the real tree of a commit, which is the tree of the commit if it's not in a conflicted state |
| 58 | + /// or the tree according to `side` if it is conflicted. |
| 59 | + /// |
| 60 | + /// Unless you want to find a particular side, you likely want to pass Default::default() |
| 61 | + /// as the [`side`](ConflictedTreeKey) which will give the automatically resolved resolution |
| 62 | + fn find_real_tree<'repo>( |
| 63 | + &'repo self, |
| 64 | + commit_id: &gix::oid, |
52 | 65 | side: ConflictedTreeKey, |
53 | | - ) -> Result<git2::Tree, anyhow::Error>; |
| 66 | + ) -> Result<gix::Id<'repo>>; |
54 | 67 | } |
55 | 68 |
|
56 | 69 | impl RepositoryExt for git2::Repository { |
57 | | - /// cherry-pick, but understands GitButler conflicted states |
58 | | - /// |
59 | | - /// cherry_pick_gitbutler should always be used in favour of libgit2 or gitoxide |
60 | | - /// cherry pick functions |
61 | | - fn cherry_pick_gitbutler( |
62 | | - &self, |
| 70 | + fn find_real_tree(&self, commit: &git2::Commit, side: ConflictedTreeKey) -> Result<git2::Tree> { |
| 71 | + let tree = commit.tree()?; |
| 72 | + if commit.is_conflicted() { |
| 73 | + let conflicted_side = tree |
| 74 | + .get_name(&side) |
| 75 | + .context("Failed to get conflicted side of commit")?; |
| 76 | + self.find_tree(conflicted_side.id()) |
| 77 | + .context("failed to find subtree") |
| 78 | + } else { |
| 79 | + self.find_tree(tree.id()).context("failed to find subtree") |
| 80 | + } |
| 81 | + } |
| 82 | +} |
| 83 | + |
| 84 | +impl GixRepositoryExt for gix::Repository { |
| 85 | + fn cherry_pick_gitbutler<'repo>( |
| 86 | + &'repo self, |
63 | 87 | head: &git2::Commit, |
64 | 88 | to_rebase: &git2::Commit, |
65 | | - merge_options: Option<&MergeOptions>, |
66 | | - ) -> Result<git2::Index, anyhow::Error> { |
| 89 | + ) -> Result<gix::merge::tree::Outcome<'repo>> { |
67 | 90 | // we need to do a manual 3-way patch merge |
68 | 91 | // find the base, which is the parent of to_rebase |
69 | 92 | let base = if to_rebase.is_conflicted() { |
70 | 93 | // Use to_rebase's recorded base |
71 | | - self.find_real_tree(to_rebase, ConflictedTreeKey::Base)? |
| 94 | + self.find_real_tree( |
| 95 | + &git2_to_gix_object_id(to_rebase.id()), |
| 96 | + ConflictedTreeKey::Base, |
| 97 | + )? |
72 | 98 | } else { |
73 | 99 | let base_commit = to_rebase.parent(0)?; |
74 | 100 | // Use the parent's auto-resolution |
75 | | - self.find_real_tree(&base_commit, Default::default())? |
| 101 | + self.find_real_tree(&git2_to_gix_object_id(base_commit.id()), Default::default())? |
76 | 102 | }; |
77 | 103 | // Get the auto-resolution |
78 | | - let ours = self.find_real_tree(head, Default::default())?; |
| 104 | + let ours = self.find_real_tree(&git2_to_gix_object_id(head.id()), Default::default())?; |
79 | 105 | // Get the original theirs |
80 | | - let thiers = self.find_real_tree(to_rebase, ConflictedTreeKey::Theirs)?; |
| 106 | + let theirs = self.find_real_tree( |
| 107 | + &git2_to_gix_object_id(to_rebase.id()), |
| 108 | + ConflictedTreeKey::Theirs, |
| 109 | + )?; |
81 | 110 |
|
82 | | - self.merge_trees(&base, &ours, &thiers, merge_options) |
83 | | - .context("failed to merge trees for cherry pick") |
| 111 | + self.merge_trees( |
| 112 | + base, |
| 113 | + ours, |
| 114 | + theirs, |
| 115 | + gix::merge::blob::builtin_driver::text::Labels { |
| 116 | + ancestor: Some("base".into()), |
| 117 | + current: Some("ours".into()), |
| 118 | + other: Some("theirs".into()), |
| 119 | + }, |
| 120 | + self.tree_merge_options()? |
| 121 | + .with_tree_favor(Some(gix::merge::tree::TreeFavor::Ours)) |
| 122 | + .with_file_favor(Some(gix::merge::tree::FileFavor::Ours)), |
| 123 | + ) |
| 124 | + .context("failed to merge trees for cherry pick") |
84 | 125 | } |
85 | 126 |
|
86 | | - /// Find the real tree of a commit, which is the tree of the commit if it's not in a conflicted state |
87 | | - /// or the parent parent tree if it is in a conflicted state |
88 | | - /// |
89 | | - /// Unless you want to find a particular side, you likly want to pass Default::default() |
90 | | - /// as the ConfclitedTreeKey which will give the automatically resolved resolution |
91 | | - fn find_real_tree( |
92 | | - &self, |
93 | | - commit: &git2::Commit, |
| 127 | + fn find_real_tree<'repo>( |
| 128 | + &'repo self, |
| 129 | + commit_id: &gix::oid, |
94 | 130 | side: ConflictedTreeKey, |
95 | | - ) -> Result<git2::Tree, anyhow::Error> { |
96 | | - let tree = commit.tree()?; |
97 | | - if commit.is_conflicted() { |
| 131 | + ) -> Result<gix::Id<'repo>> { |
| 132 | + let commit = self.find_commit(commit_id)?; |
| 133 | + Ok(if commit.is_conflicted() { |
| 134 | + let tree = commit.tree()?; |
98 | 135 | let conflicted_side = tree |
99 | | - .get_name(&side) |
| 136 | + .find_entry(&*side) |
100 | 137 | .context("Failed to get conflicted side of commit")?; |
101 | | - self.find_tree(conflicted_side.id()) |
102 | | - .context("failed to find subtree") |
| 138 | + conflicted_side.id() |
103 | 139 | } else { |
104 | | - self.find_tree(tree.id()).context("failed to find subtree") |
105 | | - } |
| 140 | + commit.tree_id()? |
| 141 | + }) |
106 | 142 | } |
107 | 143 | } |
0 commit comments