Skip to content

Commit de3fd23

Browse files
committed
fix: do not write empty packs and indices (#1404)
Failure can occour on Windows as it's likely such a pack or index is already opened during negotiation. Then, when an empty fetch via `--depth 1` is repeated, a temporary file would be renamed onto one an mmapped pack or index, causing a failure. Now packs or indices aren't written anymore if they are empty.
1 parent ea3f0ee commit de3fd23

File tree

3 files changed

+50
-39
lines changed

3 files changed

+50
-39
lines changed

gix-pack/src/bundle/write/mod.rs

+30-24
Original file line numberDiff line numberDiff line change
@@ -300,31 +300,37 @@ impl crate::Bundle {
300300
)?;
301301
drop(pack_entries_iter);
302302

303-
let data_path = directory.join(format!("pack-{}.pack", outcome.data_hash.to_hex()));
304-
let index_path = data_path.with_extension("idx");
305-
let keep_path = data_path.with_extension("keep");
303+
if outcome.num_objects == 0 {
304+
WriteOutcome {
305+
outcome,
306+
data_path: None,
307+
index_path: None,
308+
keep_path: None,
309+
}
310+
} else {
311+
let data_path = directory.join(format!("pack-{}.pack", outcome.data_hash.to_hex()));
312+
let index_path = data_path.with_extension("idx");
313+
let keep_path = data_path.with_extension("keep");
306314

307-
std::fs::write(&keep_path, b"")?;
308-
Arc::try_unwrap(data_file)
309-
.expect("only one handle left after pack was consumed")
310-
.into_inner()
311-
.into_inner()
312-
.map_err(|err| Error::from(err.into_error()))?
313-
.persist(&data_path)?;
314-
index_file
315-
.persist(&index_path)
316-
.map_err(|err| {
317-
progress.info(format!(
318-
"pack file at {} is retained despite failing to move the index file into place. You can use plumbing to make it usable.",
319-
data_path.display()
320-
));
321-
err
322-
})?;
323-
WriteOutcome {
324-
outcome,
325-
data_path: Some(data_path),
326-
index_path: Some(index_path),
327-
keep_path: Some(keep_path),
315+
std::fs::write(&keep_path, b"")?;
316+
Arc::try_unwrap(data_file)
317+
.expect("only one handle left after pack was consumed")
318+
.into_inner()
319+
.into_inner()
320+
.map_err(|err| Error::from(err.into_error()))?
321+
.persist(&data_path)?;
322+
index_file
323+
.persist(&index_path)
324+
.map_err(|err| {
325+
gix_features::trace::warn!("pack file at \"{}\" is retained despite failing to move the index file into place. You can use plumbing to make it usable.",data_path.display());
326+
err
327+
})?;
328+
WriteOutcome {
329+
outcome,
330+
data_path: Some(data_path),
331+
index_path: Some(index_path),
332+
keep_path: Some(keep_path),
333+
}
328334
}
329335
}
330336
None => WriteOutcome {

gix-pack/tests/pack/data/output/count_and_entries.rs

+13-8
Original file line numberDiff line numberDiff line change
@@ -322,13 +322,18 @@ fn traversals() -> crate::Result {
322322

323323
#[test]
324324
fn empty_pack_is_allowed() {
325-
write_and_verify(
326-
db(DbKind::DeterministicGeneratedContent).unwrap(),
327-
vec![],
328-
hex_to_id("029d08823bd8a8eab510ad6ac75c823cfd3ed31e"),
329-
None,
330-
)
331-
.unwrap();
325+
assert_eq!(
326+
write_and_verify(
327+
db(DbKind::DeterministicGeneratedContent).unwrap(),
328+
vec![],
329+
hex_to_id("029d08823bd8a8eab510ad6ac75c823cfd3ed31e"),
330+
None,
331+
)
332+
.unwrap_err()
333+
.to_string(),
334+
"pack data directory should be set",
335+
"empty packs are not actually written as they would be useless"
336+
);
332337
}
333338

334339
fn write_and_verify(
@@ -392,7 +397,7 @@ fn write_and_verify(
392397
pack::bundle::write::Options::default(),
393398
)?
394399
.data_path
395-
.expect("directory set"),
400+
.ok_or("pack data directory should be set")?,
396401
object_hash,
397402
)?;
398403
// TODO: figure out why these hashes change, also depending on the machine, even though they are indeed stable.

gix-pack/tests/pack/index.rs

+7-7
Original file line numberDiff line numberDiff line change
@@ -108,7 +108,7 @@ mod version {
108108
}
109109
}
110110

111-
#[cfg(feature = "internal-testing-gix-features-parallel")]
111+
#[cfg(feature = "gix-features-parallel")]
112112
mod any {
113113
use std::{fs, io, sync::atomic::AtomicBool};
114114

@@ -133,7 +133,7 @@ mod version {
133133
index_path: &&str,
134134
data_path: &&str,
135135
) -> Result<(), Box<dyn std::error::Error>> {
136-
let pack_iter = pack::data::input::BytesToEntriesIter::new_from_header(
136+
let mut pack_iter = pack::data::input::BytesToEntriesIter::new_from_header(
137137
io::BufReader::new(fs::File::open(fixture_path(data_path))?),
138138
*mode,
139139
*compressed,
@@ -148,12 +148,12 @@ mod version {
148148
desired_kind,
149149
|| {
150150
let file = std::fs::File::open(fixture_path(data_path))?;
151-
let map = unsafe { memmap2::MmapOptions::map_copy_read_only(&file)? };
151+
let map = unsafe { memmap2::MmapOptions::new().map_copy_read_only(&file)? };
152152
Ok((slice_map, map))
153153
},
154-
pack_iter,
154+
&mut pack_iter,
155155
None,
156-
progress::Discard,
156+
&mut progress::Discard,
157157
&mut actual,
158158
&AtomicBool::new(false),
159159
gix_hash::Kind::Sha1,
@@ -210,7 +210,7 @@ mod version {
210210
assert_eq!(outcome.index_version, desired_kind);
211211
assert_eq!(
212212
outcome.index_hash,
213-
gix_hash::ObjectId::from(&expected[end_of_pack_hash..end_of_index_hash])
213+
gix_hash::ObjectId::try_from(&expected[end_of_pack_hash..end_of_index_hash])?
214214
);
215215
Ok(())
216216
}
@@ -227,7 +227,7 @@ mod version {
227227
#[test]
228228
fn lookup_missing() {
229229
let file = index::File::at(&fixture_path(INDEX_V2), gix_hash::Kind::Sha1).unwrap();
230-
let prefix = gix_hash::Prefix::new(gix_hash::ObjectId::null(gix_hash::Kind::Sha1), 7).unwrap();
230+
let prefix = gix_hash::Prefix::new(&gix_hash::Kind::Sha1.null(), 7).unwrap();
231231
assert!(file.lookup_prefix(prefix, None).is_none());
232232

233233
let mut candidates = 1..1;

0 commit comments

Comments
 (0)