Skip to content

Commit 7dffff0

Browse files
authored
Fix round-tripping commits that contain extra headers (#1389)
* Ignore .t.err files * Test round tripping commits w/ custom headers #1377 * Fix handling of commits with custom headers * Fix proxy version test * Lean more on existing traits - Fmt for Oid instead of hex::encode - Into instead of as_bstr() - ByteSlice instead of pointer deref and as_bstr() * Simplify unsigning when rewriting commits * Remove unnecessary `use` * Avoid `use`ing gix_object and fully qualify instead * Replace proxy test with (currently broken) filter test * Fix filter-based test for custom headers Avoid navigating `--reverse`'s behaviour by just applying two inverse filters explicitly. * Show Rust backtrace on crashes when running tests * Remove comment that was for my benefit only * Reword comment * Add multiline header to test case
1 parent f0bc12c commit 7dffff0

File tree

7 files changed

+179
-32
lines changed

7 files changed

+179
-32
lines changed

Cargo.lock

+2
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

josh-core/Cargo.toml

+2
Original file line numberDiff line numberDiff line change
@@ -12,8 +12,10 @@ edition = "2021"
1212
[dependencies]
1313
backtrace = "0.3.72"
1414
bitvec = "1.0.1"
15+
bstr = "1.9.0"
1516
git-version = "0.3.9"
1617
git2 = { workspace = true }
18+
gix-object = "0.42.2"
1719
glob = "0.3.1"
1820
hex = "0.4.3"
1921
indoc = "2.0.5"

josh-core/src/history.rs

+40-31
Original file line numberDiff line numberDiff line change
@@ -196,7 +196,7 @@ pub struct RewriteData<'a> {
196196
pub message: Option<String>,
197197
}
198198

199-
// takes everything from base except it's tree and replaces it with the tree
199+
// takes everything from base except its tree and replaces it with the tree
200200
// given
201201
pub fn rewrite_commit(
202202
repo: &git2::Repository,
@@ -205,40 +205,49 @@ pub fn rewrite_commit(
205205
rewrite_data: RewriteData,
206206
unsign: bool,
207207
) -> JoshResult<git2::Oid> {
208-
let message = rewrite_data
209-
.message
210-
.unwrap_or(base.message_raw().unwrap_or("no message").to_string());
211-
let tree = &rewrite_data.tree;
212-
213-
let a = base.author();
214-
let new_a = if let Some((author, email)) = rewrite_data.author {
215-
git2::Signature::new(&author, &email, &a.when())?
216-
} else {
217-
a
218-
};
208+
let odb = repo.odb()?;
209+
let odb_commit = odb.read(base.id())?;
210+
assert!(odb_commit.kind() == git2::ObjectType::Commit);
211+
212+
// gix_object uses byte strings for Oids, but in hex representation, not raw bytes. Its `Format` implementation
213+
// writes out hex-encoded bytes. Because CommitRef's reference lifetimes we have to this, before creating CommitRef
214+
let tree_id = format!("{}", rewrite_data.tree.id());
215+
let parent_ids = parents
216+
.iter()
217+
.map(|x| format!("{}", x.id()))
218+
.collect::<Vec<_>>();
219219

220-
let c = base.committer();
221-
let new_c = if let Some((committer, email)) = rewrite_data.committer {
222-
git2::Signature::new(&committer, &email, &c.when())?
223-
} else {
224-
c
225-
};
220+
let mut commit = gix_object::CommitRef::from_bytes(odb_commit.data())?;
221+
222+
commit.tree = tree_id.as_bytes().into();
223+
224+
commit.parents.clear();
225+
commit
226+
.parents
227+
.extend(parent_ids.iter().map(|x| x.as_bytes().into()));
226228

227-
let b = repo.commit_create_buffer(&new_a, &new_c, &message, tree, parents)?;
228-
229-
if let (false, Ok((sig, _))) = (unsign, repo.extract_signature(&base.id(), None)) {
230-
// Re-create the object with the original signature (which of course does not match any
231-
// more, but this is needed to guarantee perfect round-trips).
232-
let b = b
233-
.as_str()
234-
.ok_or_else(|| josh_error("non-UTF-8 signed commit"))?;
235-
let sig = sig
236-
.as_str()
237-
.ok_or_else(|| josh_error("non-UTF-8 signature"))?;
238-
return Ok(repo.commit_signed(b, sig, None)?);
229+
if let Some(ref msg) = rewrite_data.message {
230+
commit.message = msg.as_bytes().into();
239231
}
240232

241-
return Ok(repo.odb()?.write(git2::ObjectType::Commit, &b)?);
233+
if let Some((ref author, ref email)) = rewrite_data.author {
234+
commit.author.name = author.as_bytes().into();
235+
commit.author.email = email.as_bytes().into();
236+
}
237+
238+
if let Some((ref author, ref email)) = rewrite_data.committer {
239+
commit.committer.name = author.as_bytes().into();
240+
commit.committer.email = email.as_bytes().into();
241+
}
242+
243+
commit
244+
.extra_headers
245+
.retain(|(k, _)| *k != "gpgsig".as_bytes() || !unsign);
246+
247+
let mut b = vec![];
248+
gix_object::WriteTo::write_to(&commit, &mut b)?;
249+
250+
return Ok(odb.write(git2::ObjectType::Commit, &b)?);
242251
}
243252

244253
// Given an OID of an unfiltered commit and a filter,

run-tests.sh

+1
Original file line numberDiff line numberDiff line change
@@ -38,4 +38,5 @@ export GIT_CONFIG_GLOBAL=${CONFIG_FILE}
3838
git config --global init.defaultBranch master
3939

4040
cargo fmt
41+
export RUST_BACKTRACE=1
4142
python3 -m prysk "$@"

tests/.gitignore

+1
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
*.t.err
+132
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,132 @@
1+
$ export TESTTMP=${PWD}
2+
3+
$ cd ${TESTTMP}
4+
$ git init -q repo 1>/dev/null
5+
$ cd repo
6+
7+
$ echo "hello world" > hw.txt
8+
$ git add .
9+
$ git commit -m initial
10+
[master (root-commit) 7d7c929] initial
11+
1 file changed, 1 insertion(+)
12+
create mode 100644 hw.txt
13+
14+
$ mkdir subdir
15+
$ echo "hello moon" > subdir/hw.txt
16+
$ git add .
17+
$ git commit -m second
18+
[master bab39f4] second
19+
1 file changed, 1 insertion(+)
20+
create mode 100644 subdir/hw.txt
21+
22+
$ git diff ${EMPTY_TREE}..refs/heads/master
23+
diff --git a/hw.txt b/hw.txt
24+
new file mode 100644
25+
index 0000000..3b18e51
26+
--- /dev/null
27+
+++ b/hw.txt
28+
@@ -0,0 +1 @@
29+
+hello world
30+
diff --git a/subdir/hw.txt b/subdir/hw.txt
31+
new file mode 100644
32+
index 0000000..1b95c6e
33+
--- /dev/null
34+
+++ b/subdir/hw.txt
35+
@@ -0,0 +1 @@
36+
+hello moon
37+
38+
Write a custom header into the commit (h/t https://github.com/Byron/gitoxide/blob/68cbea8gix/tests/fixtures/make_pre_epoch_repo.sh#L12-L27)
39+
$ git cat-file -p @ | tee commit.txt
40+
tree 15e3a4de9f0b90057746be6658b0f321f4bcc470
41+
parent 7d7c9293be5483ccd1a24bdf33ad52cf07cda738
42+
author Josh <[email protected]> 1112911993 +0000
43+
committer Josh <[email protected]> 1112911993 +0000
44+
45+
second
46+
47+
$ patch -p1 <<EOF
48+
> diff --git a/commit.txt b/commit.txt
49+
> index 1758866..fe1998a 100644
50+
> --- a/commit.txt
51+
> +++ b/commit.txt
52+
> @@ -2,5 +2,9 @@ tree 15e3a4de9f0b90057746be6658b0f321f4bcc470
53+
> parent 7d7c9293be5483ccd1a24bdf33ad52cf07cda738
54+
> author Josh <[email protected]> 1112911993 +0000
55+
> committer Josh <[email protected]> 1112911993 +0000
56+
> +custom-header with
57+
> + multiline
58+
> + value
59+
> +another-header such that it sorts before custom-header
60+
>
61+
> second
62+
> EOF
63+
patching file commit.txt
64+
$ new_commit=$(git hash-object --literally -w -t commit commit.txt)
65+
$ echo $new_commit
66+
f2fd7b23a4a2318d534d122615a6e75196c3e3c4
67+
$ git update-ref refs/heads/master $new_commit
68+
69+
$ josh-filter --update refs/heads/filtered ':prefix=pre'
70+
71+
$ git diff ${EMPTY_TREE}..refs/heads/filtered
72+
diff --git a/pre/hw.txt b/pre/hw.txt
73+
new file mode 100644
74+
index 0000000..3b18e51
75+
--- /dev/null
76+
+++ b/pre/hw.txt
77+
@@ -0,0 +1 @@
78+
+hello world
79+
diff --git a/pre/subdir/hw.txt b/pre/subdir/hw.txt
80+
new file mode 100644
81+
index 0000000..1b95c6e
82+
--- /dev/null
83+
+++ b/pre/subdir/hw.txt
84+
@@ -0,0 +1 @@
85+
+hello moon
86+
87+
$ git cat-file -p refs/heads/filtered
88+
tree 6876aad1a2259b9d4c7c24e0e3ff908d3d580404
89+
parent 73007fa33b8628d6560b78e37191c07c9e001d3b
90+
author Josh <[email protected]> 1112911993 +0000
91+
committer Josh <[email protected]> 1112911993 +0000
92+
custom-header with
93+
multiline
94+
value
95+
another-header such that it sorts before custom-header
96+
97+
second
98+
99+
$ josh-filter --update refs/heads/re-filtered ':/pre' refs/heads/filtered
100+
101+
$ git show refs/heads/re-filtered
102+
commit f2fd7b23a4a2318d534d122615a6e75196c3e3c4
103+
Author: Josh <[email protected]>
104+
Date: Thu Apr 7 22:13:13 2005 +0000
105+
106+
second
107+
108+
diff --git a/subdir/hw.txt b/subdir/hw.txt
109+
new file mode 100644
110+
index 0000000..1b95c6e
111+
--- /dev/null
112+
+++ b/subdir/hw.txt
113+
@@ -0,0 +1 @@
114+
+hello moon
115+
116+
$ git cat-file -p refs/heads/re-filtered
117+
tree 15e3a4de9f0b90057746be6658b0f321f4bcc470
118+
parent 7d7c9293be5483ccd1a24bdf33ad52cf07cda738
119+
author Josh <[email protected]> 1112911993 +0000
120+
committer Josh <[email protected]> 1112911993 +0000
121+
custom-header with
122+
multiline
123+
value
124+
another-header such that it sorts before custom-header
125+
126+
second
127+
128+
$ git log --oneline --all --decorate
129+
63982dc (filtered) second
130+
f2fd7b2 (HEAD -> master, re-filtered) second
131+
73007fa initial
132+
7d7c929 initial

tests/proxy/get_version.t

+1-1
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
$ cd ${TESTTMP}
33

44
$ curl -s http://localhost:8002/version
5-
Version: r*.*.* (glob)
5+
Version: v*.*.* (glob)
66

77
$ bash ${TESTDIR}/destroy_test_env.sh
88
.

0 commit comments

Comments
 (0)