Skip to content

Commit 6fa2e47

Browse files
committed
wip!: merging hunks
1 parent 6b0601f commit 6fa2e47

1 file changed

Lines changed: 109 additions & 10 deletions

File tree

src/commands/derive.rs

Lines changed: 109 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,69 @@
11
use anyhow::{Context, Result};
2-
use git2::{Repository, Signature, Sort};
2+
use git2::{Index, IndexEntry, IndexTime, Repository, Signature, Sort, Tree};
33
use regex::Regex;
4-
use std::path::PathBuf;
4+
use std::path::{Path, PathBuf};
55

66
use std::collections::{HashMap, HashSet};
77

8+
fn get_file_bytes_from_tree(repo: &Repository, tree: &Tree, path: &Path) -> Result<Vec<u8>> {
9+
match tree.get_path(path) {
10+
Ok(entry) => {
11+
let blob = repo.find_blob(entry.id())?;
12+
Ok(blob.content().to_vec())
13+
}
14+
Err(_) => Ok(Vec::new()),
15+
}
16+
}
17+
18+
fn split_lines_including_newline(bytes: &[u8]) -> Vec<Vec<u8>> {
19+
let mut out = Vec::new();
20+
let mut start = 0usize;
21+
22+
for (i, &b) in bytes.iter().enumerate() {
23+
if b == b'\n' {
24+
out.push(bytes[start..=i].to_vec());
25+
start = i + 1;
26+
}
27+
}
28+
29+
if start < bytes.len() {
30+
out.push(bytes[start..].to_vec());
31+
}
32+
out
33+
}
34+
35+
fn join_lines(lines: &[Vec<u8>]) -> Vec<u8> {
36+
let mut out = Vec::new();
37+
for line in lines {
38+
out.extend_from_slice(line);
39+
}
40+
out
41+
}
42+
43+
fn ensure_length(lines: &mut Vec<Vec<u8>>, len: usize) {
44+
while lines.len() < len {
45+
lines.push(b"\n".to_vec());
46+
}
47+
}
48+
49+
fn apply_additions(mut base: Vec<u8>, mut additions: Vec<(u32, Vec<u8>)>) -> Vec<u8> {
50+
additions.sort_by_key(|(ln, _)| *ln);
51+
let mut lines = split_lines_including_newline(&base);
52+
53+
for (new_lineno, content) in additions {
54+
let idx = (new_lineno as usize).saturating_sub(1);
55+
56+
ensure_length(&mut lines, idx);
57+
if idx >= lines.len() {
58+
lines.push(content);
59+
} else {
60+
lines.insert(idx, content);
61+
}
62+
}
63+
64+
join_lines(&lines)
65+
}
66+
867
pub fn derive(repo: &Repository, name: &str, features: &[String]) -> Result<()> {
968
println!("Deriving variant '{}' with features: {:?}", name, features);
1069
// create a new orphan branch
@@ -13,7 +72,6 @@ pub fn derive(repo: &Repository, name: &str, features: &[String]) -> Result<()>
1372
.write()
1473
.context("Failed to create empty tree.")?;
1574
let tree = repo.find_tree(tree_oid)?;
16-
println!("Treeid: {:?}", tree_oid);
1775

1876
let sig = Signature::now("user", "user@example.com")?;
1977

@@ -83,17 +141,58 @@ pub fn derive(repo: &Repository, name: &str, features: &[String]) -> Result<()>
83141
true
84142
}),
85143
)?;
86-
for (path, contents) in &additions {
87-
println!("Path: {:?}", path);
88-
for (no, line) in contents {
89-
print!("{:?}: {}", no, String::from_utf8_lossy(line));
144+
145+
// write changes to disk without touching the worktree
146+
if !additions.is_empty() {
147+
let variant_parent = repo.find_commit(variant_head_oid)?;
148+
let variant_tree = variant_parent.tree()?;
149+
150+
let mut index = Index::new()?;
151+
index.read_tree(&variant_tree)?;
152+
153+
for (path, contents) in additions {
154+
let base = get_file_bytes_from_tree(repo, &variant_tree, &path)?;
155+
let merged = apply_additions(base, contents);
156+
157+
let blob_oid = repo.blob(&merged)?;
158+
let path_string = path.to_string_lossy().replace('\\', "/");
159+
let entry = IndexEntry {
160+
ctime: IndexTime {
161+
seconds: 0,
162+
nanoseconds: 0,
163+
},
164+
mtime: IndexTime {
165+
seconds: 0,
166+
nanoseconds: 0,
167+
},
168+
dev: 0,
169+
ino: 0,
170+
mode: 0o100644,
171+
uid: 0,
172+
gid: 0,
173+
file_size: merged.len() as u32,
174+
id: blob_oid,
175+
flags: 0,
176+
flags_extended: 0,
177+
path: path_string.into_bytes(),
178+
};
179+
index.add(&entry)?;
90180
}
181+
let new_tree_oid = index.write_tree_to(repo)?;
182+
let new_tree = repo.find_tree(new_tree_oid)?;
183+
184+
let commit_msg = commit.message().unwrap();
185+
variant_head_oid = repo.commit(
186+
Some(&ref_name),
187+
&sig,
188+
&sig,
189+
&commit_msg,
190+
&new_tree,
191+
&[&variant_parent],
192+
)?;
91193
}
92194
}
93195

94-
// for each commit that is in the specified feature set, extract the delta
95-
// put together the delta
96-
// add a custom reference to the newly created branch to keep track of variants
97196
let var_ref_name = format!("refs/variants/{name}");
98197
let target_ref = repo.find_reference(&ref_name)?;
99198
let var_ref_target = target_ref.name().context("Branch has invalid UTF-8 name")?;

0 commit comments

Comments
 (0)