Skip to content

Commit 69c8232

Browse files
committed
add precompose_unicode related tests
* reading precomposes unicode * writing precomposes unicode of input names for good measure
1 parent eacb5a4 commit 69c8232

File tree

14 files changed

+393
-13
lines changed

14 files changed

+393
-13
lines changed

Cargo.lock

Lines changed: 1 addition & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

gix-ref/Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ gix-path = { version = "^0.10.3", path = "../gix-path" }
2525
gix-hash = { version = "^0.14.1", path = "../gix-hash" }
2626
gix-date = { version = "^0.8.3", path = "../gix-date" }
2727
gix-object = { version = "^0.40.1", path = "../gix-object" }
28+
gix-utils = { version = "^0.1.8", path = "../gix-utils" }
2829
gix-validate = { version = "^0.8.3", path = "../gix-validate" }
2930
gix-actor = { version = "^0.29.1", path = "../gix-actor" }
3031
gix-lock = { version = "^12.0.0", path = "../gix-lock" }

gix-ref/src/store/file/find.rs

Lines changed: 52 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -10,9 +10,19 @@ pub use error::Error;
1010
use crate::{
1111
file,
1212
store_impl::{file::loose, packed},
13-
BStr, BString, FullNameRef, PartialNameRef, Reference,
13+
BStr, BString, FullNameRef, PartialName, PartialNameRef, Reference,
1414
};
1515

16+
/// ### Finding References - notes about precomposed unicode.
17+
///
18+
/// Generally, ref names and the target of symbolic refs are stored as-is if [`Self::precompose_unicode`] is `false`.
19+
/// If `true`, refs are stored as precomposed unicode in `packed-refs`, but stored as is on disk as it is then assumed
20+
/// to be indifferent, i.e. `"a\u{308}"` is the same as `"ä"`.
21+
///
22+
/// This also means that when refs are packed for transmission to another machine, both their names and the target of
23+
/// symbolic references need to be precomposed.
24+
///
25+
/// Namespaces are left as is as they never get past the particular repository that uses them.
1626
impl file::Store {
1727
/// Find a single reference by the given `path` which is required to be a valid reference name.
1828
///
@@ -66,27 +76,62 @@ impl file::Store {
6676
partial_name: &PartialNameRef,
6777
packed: Option<&packed::Buffer>,
6878
) -> Result<Option<Reference>, Error> {
79+
fn decompose_if(mut r: Reference, input_changed_to_precomposed: bool) -> Reference {
80+
if input_changed_to_precomposed {
81+
use gix_object::bstr::ByteSlice;
82+
let decomposed = r
83+
.name
84+
.0
85+
.to_str()
86+
.ok()
87+
.map(|name| gix_utils::str::decompose(name.into()));
88+
if let Some(Cow::Owned(decomposed)) = decomposed {
89+
r.name.0 = decomposed.into();
90+
}
91+
}
92+
r
93+
}
6994
let mut buf = BString::default();
95+
let mut precomposed_partial_name_storage = packed.filter(|_| self.precompose_unicode).and_then(|_| {
96+
use gix_object::bstr::ByteSlice;
97+
let precomposed = partial_name.0.to_str().ok()?;
98+
let precomposed = gix_utils::str::precompose(precomposed.into());
99+
match precomposed {
100+
Cow::Owned(precomposed) => Some(PartialName(precomposed.into())),
101+
Cow::Borrowed(_) => None,
102+
}
103+
});
104+
let precomposed_partial_name = precomposed_partial_name_storage
105+
.as_ref()
106+
.map(std::convert::AsRef::as_ref);
70107
for inbetween in &["", "tags", "heads", "remotes"] {
71-
match self.find_inner(inbetween, partial_name, packed, &mut buf) {
72-
Ok(Some(r)) => return Ok(Some(r)),
108+
match self.find_inner(inbetween, partial_name, precomposed_partial_name, packed, &mut buf) {
109+
Ok(Some(r)) => return Ok(Some(decompose_if(r, precomposed_partial_name.is_some()))),
73110
Ok(None) => {
74111
continue;
75112
}
76113
Err(err) => return Err(err),
77114
}
78115
}
79116
if partial_name.as_bstr() != "HEAD" {
117+
if let Some(mut precomposed) = precomposed_partial_name_storage {
118+
precomposed = precomposed.join("HEAD".into()).expect("HEAD is valid name");
119+
precomposed_partial_name_storage = Some(precomposed);
120+
}
80121
self.find_inner(
81122
"remotes",
82123
partial_name
83124
.to_owned()
84125
.join("HEAD".into())
85126
.expect("HEAD is valid name")
86127
.as_ref(),
128+
precomposed_partial_name_storage
129+
.as_ref()
130+
.map(std::convert::AsRef::as_ref),
87131
None,
88132
&mut buf,
89133
)
134+
.map(|res| res.map(|r| decompose_if(r, precomposed_partial_name_storage.is_some())))
90135
} else {
91136
Ok(None)
92137
}
@@ -96,10 +141,13 @@ impl file::Store {
96141
&self,
97142
inbetween: &str,
98143
partial_name: &PartialNameRef,
144+
precomposed_partial_name: Option<&PartialNameRef>,
99145
packed: Option<&packed::Buffer>,
100146
path_buf: &mut BString,
101147
) -> Result<Option<Reference>, Error> {
102-
let full_name = partial_name.construct_full_name_ref(inbetween, path_buf);
148+
let full_name = precomposed_partial_name
149+
.unwrap_or(partial_name)
150+
.construct_full_name_ref(inbetween, path_buf);
103151
let content_buf = self.ref_contents(full_name).map_err(|err| Error::ReadFileContents {
104152
source: err,
105153
path: self.reference_path(full_name),

gix-ref/src/store/file/loose/mod.rs

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,9 @@ mod init {
3333
/// Create a new instance at the given `git_dir`, which commonly is a standard git repository with a
3434
/// `refs/` subdirectory.
3535
/// The `object_hash` defines which kind of hash we should recognize.
36-
/// `precompose_unicode` is used to set to the value of [`Self::precompose_unicode].
36+
///
37+
/// Note that if `precompose_unicode` is set, the `git_dir` is also expected to use precomposed unicode,
38+
/// or else some operations that strip prefixes will fail.
3739
pub fn at(
3840
git_dir: PathBuf,
3941
write_reflog: file::WriteReflog,
@@ -54,6 +56,9 @@ mod init {
5456

5557
/// Like [`at()`][file::Store::at()], but for _linked_ work-trees which use `git_dir` as private ref store and `common_dir` for
5658
/// shared references.
59+
///
60+
/// Note that if `precompose_unicode` is set, the `git_dir` and `common_dir` are also expected to use precomposed unicode,
61+
/// or else some operations that strip prefixes will fail.
5762
pub fn for_linked_worktree(
5863
git_dir: PathBuf,
5964
common_dir: PathBuf,

gix-ref/src/store/file/mod.rs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,10 @@ pub struct Store {
2929
pub namespace: Option<Namespace>,
3030
/// If set, we will convert decomposed unicode like `a\u308` into precomposed unicode like `ä` when reading
3131
/// ref names from disk.
32+
/// Note that this is an internal operation that isn't observable on the outside, but it's needed for lookups
33+
/// to packed-refs or symlinks to work correctly.
34+
/// Iterated references will be returned verbatim, thus when sending them over the wire they have to be precomposed
35+
/// as needed.
3236
pub precompose_unicode: bool,
3337
/// A packed buffer which can be mapped in one version and shared as such.
3438
/// It's updated only in one spot, which is prior to reading it based on file stamps.

gix-ref/src/store/file/overlay_iter.rs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -208,6 +208,9 @@ impl file::Store {
208208
/// Return a platform to obtain iterator over all references, or prefixed ones, loose or packed, sorted by their name.
209209
///
210210
/// Errors are returned similarly to what would happen when loose and packed refs where iterated by themselves.
211+
///
212+
/// Note that since packed-refs are storing refs as precomposed unicode if [`Self::precompose_unicode`] is true, for consistency
213+
/// we also return loose references as precomposed unicode.
211214
pub fn iter(&self) -> Result<Platform<'_>, packed::buffer::open::Error> {
212215
Ok(Platform {
213216
store: self,

gix-ref/src/store/file/packed.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,8 @@ impl file::Store {
1616
Ok(packed::Transaction::new_from_pack_and_lock(
1717
self.assure_packed_refs_uptodate()?,
1818
lock,
19+
self.precompose_unicode,
20+
self.namespace.clone(),
1921
))
2022
}
2123

gix-ref/src/store/file/transaction/mod.rs

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -11,14 +11,21 @@ use crate::{
1111
/// How to handle packed refs during a transaction
1212
#[derive(Default)]
1313
pub enum PackedRefs<'a> {
14-
/// Only propagate deletions of references. This is the default
14+
/// Only propagate deletions of references. This is the default.
15+
/// This means deleted references are removed from disk if they are loose and from the packed-refs file if they are present.
1516
#[default]
1617
DeletionsOnly,
17-
/// Propagate deletions as well as updates to references which are peeled, that is contain an object id
18+
/// Propagate deletions as well as updates to references which are peeled and contain an object id.
19+
///
20+
/// This means deleted references are removed from disk if they are loose and from the packed-refs file if they are present,
21+
/// while updates are also written into the loose file as well as into packed-refs, potentially creating an entry.
1822
DeletionsAndNonSymbolicUpdates(Box<dyn gix_object::Find + 'a>),
19-
/// Propagate deletions as well as updates to references which are peeled, that is contain an object id. Furthermore delete the
23+
/// Propagate deletions as well as updates to references which are peeled and contain an object id. Furthermore delete the
2024
/// reference which is originally updated if it exists. If it doesn't, the new value will be written into the packed ref right away.
2125
/// Note that this doesn't affect symbolic references at all, which can't be placed into packed refs.
26+
///
27+
/// Thus, this is similar to `DeletionsAndNonSymbolicUpdates`, but removes the loose reference after the update, leaving only their copy
28+
/// in `packed-refs`.
2229
DeletionsAndNonSymbolicUpdatesRemoveLooseSourceReference(Box<dyn gix_object::Find + 'a>),
2330
}
2431

gix-ref/src/store/file/transaction/prepare.rs

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -329,8 +329,13 @@ impl<'s, 'p> Transaction<'s, 'p> {
329329
self.store
330330
.assure_packed_refs_uptodate()?
331331
.map(|p| {
332-
buffer_into_transaction(p, packed_refs_lock_fail_mode)
333-
.map_err(Error::PackedTransactionAcquire)
332+
buffer_into_transaction(
333+
p,
334+
packed_refs_lock_fail_mode,
335+
self.store.precompose_unicode,
336+
self.store.namespace.clone(),
337+
)
338+
.map_err(Error::PackedTransactionAcquire)
334339
})
335340
.transpose()?
336341
};

gix-ref/src/store/general/init.rs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,9 @@ impl crate::Store {
2222
///
2323
/// `object_hash` defines the kind of hash to assume when dealing with refs.
2424
/// `precompose_unicode` is used to set to the value of [`crate::file::Store::precompose_unicode].
25+
///
26+
/// Note that if `precompose_unicode` is set, the `git_dir` is also expected to use precomposed unicode,
27+
/// or else some operations that strip prefixes will fail.
2528
pub fn at(
2629
git_dir: PathBuf,
2730
reflog_mode: WriteReflog,

gix-ref/src/store/packed/mod.rs

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ use gix_hash::ObjectId;
44
use gix_object::bstr::{BStr, BString};
55
use memmap2::Mmap;
66

7-
use crate::{file, transaction::RefEdit, FullNameRef};
7+
use crate::{file, transaction::RefEdit, FullNameRef, Namespace};
88

99
#[derive(Debug)]
1010
enum Backing {
@@ -38,6 +38,9 @@ pub(crate) struct Transaction {
3838
lock: Option<gix_lock::File>,
3939
#[allow(dead_code)] // It just has to be kept alive, hence no reads
4040
closed_lock: Option<gix_lock::Marker>,
41+
precompose_unicode: bool,
42+
/// The namespace to use when preparing or writing refs
43+
namespace: Option<Namespace>,
4144
}
4245

4346
/// A reference as parsed from the `packed-refs` file

gix-ref/src/store/packed/transaction.rs

Lines changed: 36 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,11 @@
1+
use std::borrow::Cow;
12
use std::{fmt::Formatter, io::Write};
23

34
use crate::{
45
file,
56
store_impl::{packed, packed::Edit},
67
transaction::{Change, RefEdit},
7-
Target,
8+
Namespace, Target,
89
};
910

1011
pub(crate) const HEADER_LINE: &[u8] = b"# pack-refs with: peeled fully-peeled sorted \n";
@@ -14,12 +15,16 @@ impl packed::Transaction {
1415
pub(crate) fn new_from_pack_and_lock(
1516
buffer: Option<file::packed::SharedBufferSnapshot>,
1617
lock: gix_lock::File,
18+
precompose_unicode: bool,
19+
namespace: Option<Namespace>,
1720
) -> Self {
1821
packed::Transaction {
1922
buffer,
2023
edits: None,
2124
lock: Some(lock),
2225
closed_lock: None,
26+
precompose_unicode,
27+
namespace,
2328
}
2429
}
2530
}
@@ -55,6 +60,32 @@ impl packed::Transaction {
5560
// Remove all edits which are deletions that aren't here in the first place
5661
let mut edits: Vec<Edit> = edits
5762
.into_iter()
63+
.map(|mut edit| {
64+
use gix_object::bstr::ByteSlice;
65+
if self.precompose_unicode {
66+
let precomposed = edit
67+
.name
68+
.0
69+
.to_str()
70+
.ok()
71+
.map(|name| gix_utils::str::precompose(name.into()));
72+
match precomposed {
73+
None | Some(Cow::Borrowed(_)) => edit,
74+
Some(Cow::Owned(precomposed)) => {
75+
edit.name.0 = precomposed.into();
76+
edit
77+
}
78+
}
79+
} else {
80+
edit
81+
}
82+
})
83+
.map(|mut edit| {
84+
if let Some(namespace) = &self.namespace {
85+
edit.name = namespace.clone().into_namespaced_name(edit.name.as_ref());
86+
}
87+
edit
88+
})
5889
.filter(|edit| {
5990
if let Change::Delete { .. } = edit.change {
6091
buffer.as_ref().map_or(true, |b| b.find(edit.name.as_ref()).is_ok())
@@ -227,13 +258,17 @@ fn write_edit(out: &mut dyn std::io::Write, edit: &Edit, lines_written: &mut i32
227258
pub(crate) fn buffer_into_transaction(
228259
buffer: file::packed::SharedBufferSnapshot,
229260
lock_mode: gix_lock::acquire::Fail,
261+
precompose_unicode: bool,
262+
namespace: Option<Namespace>,
230263
) -> Result<packed::Transaction, gix_lock::acquire::Error> {
231264
let lock = gix_lock::File::acquire_to_update_resource(&buffer.path, lock_mode, None)?;
232265
Ok(packed::Transaction {
233266
buffer: Some(buffer),
234267
lock: Some(lock),
235268
closed_lock: None,
236269
edits: None,
270+
precompose_unicode,
271+
namespace,
237272
})
238273
}
239274

0 commit comments

Comments
 (0)