1use grit_lib::commit_trailers;
10use grit_lib::config::ConfigSet;
11use grit_lib::merge_file::MergeFavor;
12use grit_lib::merge_trees::{merge_trees_three_way, WhitespaceMergeOptions};
13use grit_lib::objects::{parse_commit, serialize_commit, CommitData, ObjectKind};
14use grit_lib::refs;
15use grit_lib::repo::init_repository;
16use grit_lib::rev_parse::resolve_revision;
17use grit_lib::write_tree::write_tree_from_index;
18
19fn commit_from_tree(
20 repo: &grit_lib::repo::Repository,
21 tree: grit_lib::objects::ObjectId,
22 parents: &[grit_lib::objects::ObjectId],
23 message: &str,
24) -> grit_lib::error::Result<grit_lib::objects::ObjectId> {
25 let commit = CommitData {
26 tree,
27 parents: parents.to_vec(),
28 author: "Example <example@example.com> 1700000000 +0000".to_owned(),
29 committer: "Example <example@example.com> 1700000000 +0000".to_owned(),
30 author_raw: Vec::new(),
31 committer_raw: Vec::new(),
32 encoding: None,
33 message: message.to_owned(),
34 raw_message: None,
35 };
36 repo.odb
37 .write(ObjectKind::Commit, &serialize_commit(&commit))
38}
39
40fn tree_of_commit(
41 repo: &grit_lib::repo::Repository,
42 commit_oid: grit_lib::objects::ObjectId,
43) -> grit_lib::error::Result<grit_lib::objects::ObjectId> {
44 let obj = repo.odb.read(&commit_oid)?;
45 Ok(parse_commit(&obj.data)?.tree)
46}
47
48fn main() -> grit_lib::error::Result<()> {
49 let root = tempfile::tempdir()?;
50 let repo = init_repository(root.path(), false, "main", None, "files")?;
51
52 use grit_lib::index::{Index, IndexEntry, MODE_REGULAR};
53
54 let blob_a = repo.odb.write(ObjectKind::Blob, b"base\n")?;
56 let mut index = Index::new();
57 index.add_or_replace(IndexEntry {
58 ctime_sec: 0,
59 ctime_nsec: 0,
60 mtime_sec: 0,
61 mtime_nsec: 0,
62 dev: 0,
63 ino: 0,
64 mode: MODE_REGULAR,
65 uid: 0,
66 gid: 0,
67 size: 0,
68 oid: blob_a,
69 flags: 7,
70 flags_extended: None,
71 path: b"base.txt".to_vec(),
72 base_index_pos: 0,
73 });
74 repo.write_index(&mut index)?;
75 let index = repo.load_index()?;
76 let tree_a = write_tree_from_index(&repo.odb, &index, "")?;
77 let commit_a = commit_from_tree(&repo, tree_a, &[], "initial\n")?;
78 refs::write_ref(&repo.git_dir, "refs/heads/main", &commit_a)?;
79
80 let blob_pick = repo.odb.write(ObjectKind::Blob, b"hello from topic\n")?;
82 let mut index = Index::new();
83 index.add_or_replace(IndexEntry {
84 ctime_sec: 0,
85 ctime_nsec: 0,
86 mtime_sec: 0,
87 mtime_nsec: 0,
88 dev: 0,
89 ino: 0,
90 mode: MODE_REGULAR,
91 uid: 0,
92 gid: 0,
93 size: 0,
94 oid: blob_a,
95 flags: 7,
96 flags_extended: None,
97 path: b"base.txt".to_vec(),
98 base_index_pos: 0,
99 });
100 index.add_or_replace(IndexEntry {
101 ctime_sec: 0,
102 ctime_nsec: 0,
103 mtime_sec: 0,
104 mtime_nsec: 0,
105 dev: 0,
106 ino: 0,
107 mode: MODE_REGULAR,
108 uid: 0,
109 gid: 0,
110 size: 0,
111 oid: blob_pick,
112 flags: 9,
113 flags_extended: None,
114 path: b"picked.txt".to_vec(),
115 base_index_pos: 0,
116 });
117 repo.write_index(&mut index)?;
118 let index = repo.load_index()?;
119 let tree_b = write_tree_from_index(&repo.odb, &index, "")?;
120 let commit_b = commit_from_tree(&repo, tree_b, &[commit_a], "add picked file\n")?;
121 refs::write_ref(&repo.git_dir, "refs/heads/topic", &commit_b)?;
122
123 let head = resolve_revision(&repo, "main")?;
125 let picked = resolve_revision(&repo, "topic")?;
126 let picked_obj = repo.odb.read(&picked)?;
127 let picked_data = parse_commit(&picked_obj.data)?;
128 let parent = picked_data.parents.first().copied().ok_or_else(|| {
129 grit_lib::error::Error::CorruptObject("picked commit has no parent".into())
130 })?;
131
132 let base_tree = tree_of_commit(&repo, parent)?;
133 let ours_tree = tree_of_commit(&repo, head)?;
134 let theirs_tree = picked_data.tree;
135
136 let merged = merge_trees_three_way(
137 &repo,
138 base_tree,
139 ours_tree,
140 theirs_tree,
141 MergeFavor::default(),
142 WhitespaceMergeOptions::default(),
143 grit_lib::merge_trees::TreeMergeConflictPresentation {
144 label_ours: "HEAD",
145 label_theirs: grit_lib::merge_trees::TheirsConflictLabel::Fixed("picked"),
146 label_base: "parent of picked commit",
147 style: grit_lib::merge_file::ConflictStyle::Merge,
148 checkout_merge: false,
149 },
150 )?;
151
152 if !merged.conflict_content.is_empty() {
153 return Err(grit_lib::error::Error::Message(format!(
154 "merge produced {} conflict path(s); this example expects a clean pick",
155 merged.conflict_content.len()
156 )));
157 }
158
159 let new_tree = write_tree_from_index(&repo.odb, &merged.index, "")?;
160 let config = ConfigSet::load_repo_local_only(&repo.git_dir)?;
161 let msg = commit_trailers::finalize_cherry_pick_message(
162 &picked_data.message,
163 true,
164 false,
165 "Example",
166 "example@example.com",
167 &config,
168 &picked.to_hex(),
169 );
170 let new_commit = commit_from_tree(&repo, new_tree, &[head], &msg)?;
171 refs::write_ref(&repo.git_dir, "refs/heads/main", &new_commit)?;
172
173 println!("cherry-picked {} onto {}", picked, head);
174 println!("new main: {new_commit}");
175 let out = repo.odb.read(&new_commit)?;
176 println!("message:\n{}", parse_commit(&out.data)?.message);
177
178 Ok(())
179}