Skip to content

Commit 6367c7d

Browse files
committed
feat!: Add gix-shallow crate and use it from gix and gix-protocol
That way it's easier to reuse shallow-handling code from plumbing crates. Note that this is a breaking change as `gix-protocol` now uses the `gix-shallow::Update` type, which doesn't implement a formerly public `from_line()` method anymore. Now it is available as `fetch::response::shallow_update_from_line()`.
1 parent d41cc0b commit 6367c7d

File tree

16 files changed

+199
-131
lines changed

16 files changed

+199
-131
lines changed

Diff for: Cargo.lock

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

Diff for: Cargo.toml

+1
Original file line numberDiff line numberDiff line change
@@ -304,6 +304,7 @@ members = [
304304
"gix-ref/tests",
305305
"gix-config/tests",
306306
"gix-traverse/tests",
307+
"gix-shallow"
307308
]
308309

309310
[workspace.dependencies]

Diff for: gix-protocol/Cargo.toml

+1
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,7 @@ gix-features = { version = "^0.39.1", path = "../gix-features", features = [
5252
] }
5353
gix-transport = { version = "^0.43.1", path = "../gix-transport" }
5454
gix-hash = { version = "^0.15.1", path = "../gix-hash" }
55+
gix-shallow = { version = "^0.1.0", path = "../gix-shallow" }
5556
gix-date = { version = "^0.9.2", path = "../gix-date" }
5657
gix-credentials = { version = "^0.25.1", path = "../gix-credentials" }
5758
gix-utils = { version = "^0.1.13", path = "../gix-utils" }

Diff for: gix-protocol/src/fetch/response/async_io.rs

+2-1
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ use gix_transport::{client, Protocol};
44

55
use crate::fetch::{
66
response,
7+
response::shallow_update_from_line,
78
response::{Acknowledgement, ShallowUpdate, WantedRef},
89
Response,
910
};
@@ -132,7 +133,7 @@ impl Response {
132133
}
133134
}
134135
"shallow-info" => {
135-
if parse_v2_section(&mut line, reader, &mut shallows, ShallowUpdate::from_line).await? {
136+
if parse_v2_section(&mut line, reader, &mut shallows, shallow_update_from_line).await? {
136137
break 'section false;
137138
}
138139
}

Diff for: gix-protocol/src/fetch/response/blocking_io.rs

+2-1
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ use std::io;
22

33
use gix_transport::{client, Protocol};
44

5+
use crate::fetch::response::shallow_update_from_line;
56
use crate::fetch::{
67
response,
78
response::{Acknowledgement, ShallowUpdate, WantedRef},
@@ -128,7 +129,7 @@ impl Response {
128129
}
129130
}
130131
"shallow-info" => {
131-
if parse_v2_section(&mut line, reader, &mut shallows, ShallowUpdate::from_line)? {
132+
if parse_v2_section(&mut line, reader, &mut shallows, shallow_update_from_line)? {
132133
break 'section false;
133134
}
134135
}

Diff for: gix-protocol/src/fetch/response/mod.rs

+14-24
Original file line numberDiff line numberDiff line change
@@ -59,15 +59,7 @@ pub enum Acknowledgement {
5959
Nak,
6060
}
6161

62-
/// A shallow line received from the server.
63-
#[derive(PartialEq, Eq, Debug, Hash, Ord, PartialOrd, Clone, Copy)]
64-
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
65-
pub enum ShallowUpdate {
66-
/// Shallow the given `id`.
67-
Shallow(gix_hash::ObjectId),
68-
/// Don't shallow the given `id` anymore.
69-
Unshallow(gix_hash::ObjectId),
70-
}
62+
pub use gix_shallow::Update as ShallowUpdate;
7163

7264
/// A wanted-ref line received from the server.
7365
#[derive(PartialEq, Eq, Debug, Hash, Ord, PartialOrd, Clone)]
@@ -79,21 +71,19 @@ pub struct WantedRef {
7971
pub path: BString,
8072
}
8173

82-
impl ShallowUpdate {
83-
/// Parse a `ShallowUpdate` from a `line` as received to the server.
84-
pub fn from_line(line: &str) -> Result<ShallowUpdate, Error> {
85-
match line.trim_end().split_once(' ') {
86-
Some((prefix, id)) => {
87-
let id = gix_hash::ObjectId::from_hex(id.as_bytes())
88-
.map_err(|_| Error::UnknownLineType { line: line.to_owned() })?;
89-
Ok(match prefix {
90-
"shallow" => ShallowUpdate::Shallow(id),
91-
"unshallow" => ShallowUpdate::Unshallow(id),
92-
_ => return Err(Error::UnknownLineType { line: line.to_owned() }),
93-
})
94-
}
95-
None => Err(Error::UnknownLineType { line: line.to_owned() }),
74+
/// Parse a `ShallowUpdate` from a `line` as received to the server.
75+
pub fn shallow_update_from_line(line: &str) -> Result<ShallowUpdate, Error> {
76+
match line.trim_end().split_once(' ') {
77+
Some((prefix, id)) => {
78+
let id = gix_hash::ObjectId::from_hex(id.as_bytes())
79+
.map_err(|_| Error::UnknownLineType { line: line.to_owned() })?;
80+
Ok(match prefix {
81+
"shallow" => ShallowUpdate::Shallow(id),
82+
"unshallow" => ShallowUpdate::Unshallow(id),
83+
_ => return Err(Error::UnknownLineType { line: line.to_owned() }),
84+
})
9685
}
86+
None => Err(Error::UnknownLineType { line: line.to_owned() }),
9787
}
9888
}
9989

@@ -236,7 +226,7 @@ impl Response {
236226
}
237227
None => acks.push(ack),
238228
},
239-
Err(_) => match ShallowUpdate::from_line(peeked_line) {
229+
Err(_) => match shallow_update_from_line(peeked_line) {
240230
Ok(shallow) => {
241231
shallows.push(shallow);
242232
}

Diff for: gix-shallow/Cargo.toml

+28
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
lints.workspace = true
2+
3+
[package]
4+
name = "gix-shallow"
5+
version = "0.1.0"
6+
repository = "https://github.com/GitoxideLabs/gitoxide"
7+
authors = ["Sebastian Thiel <[email protected]>"]
8+
license = "MIT OR Apache-2.0"
9+
description = "Handle files specifying the shallow boundary"
10+
edition = "2021"
11+
include = ["src/**/*", "LICENSE-*"]
12+
rust-version = "1.65"
13+
14+
[lib]
15+
doctest = false
16+
test = false
17+
18+
[features]
19+
## Data structures implement `serde::Serialize` and `serde::Deserialize`.
20+
serde = ["dep:serde", "gix-hash/serde"]
21+
22+
[dependencies]
23+
gix-hash = { version = "^0.15.1", path = "../gix-hash" }
24+
gix-lock = { version = "^15.0.0", path = "../gix-lock" }
25+
26+
thiserror = "2.0.0"
27+
bstr = { version = "1.5.0", default-features = false }
28+
serde = { version = "1.0.114", optional = true, default-features = false, features = ["std", "derive"] }

Diff for: gix-shallow/LICENSE-APACHE

+1
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
../LICENSE-APACHE

Diff for: gix-shallow/LICENSE-MIT

+1
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
../LICENSE-MIT

Diff for: gix-shallow/src/lib.rs

+122
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,122 @@
1+
//! [Read](read()) and [write](write()) shallow files, while performing typical operations on them.
2+
#![deny(missing_docs, rust_2018_idioms)]
3+
4+
/// An instruction on how to
5+
#[derive(PartialEq, Eq, Debug, Hash, Ord, PartialOrd, Clone, Copy)]
6+
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
7+
pub enum Update {
8+
/// Shallow the given `id`.
9+
Shallow(gix_hash::ObjectId),
10+
/// Don't shallow the given `id` anymore.
11+
Unshallow(gix_hash::ObjectId),
12+
}
13+
14+
/// Return a list of shallow commits as unconditionally read from `shallow_file`.
15+
///
16+
/// The list of shallow commits represents the shallow boundary, beyond which we are lacking all (parent) commits.
17+
/// Note that the list is never empty, as `Ok(None)` is returned in that case indicating the repository
18+
/// isn't a shallow clone.
19+
pub fn read(shallow_file: &std::path::Path) -> Result<Option<Vec<gix_hash::ObjectId>>, read::Error> {
20+
use bstr::ByteSlice;
21+
let buf = match std::fs::read(shallow_file) {
22+
Ok(buf) => buf,
23+
Err(err) if err.kind() == std::io::ErrorKind::NotFound => return Ok(None),
24+
Err(err) => return Err(err.into()),
25+
};
26+
27+
let mut commits = buf
28+
.lines()
29+
.map(gix_hash::ObjectId::from_hex)
30+
.collect::<Result<Vec<_>, _>>()?;
31+
32+
commits.sort();
33+
if commits.is_empty() {
34+
Ok(None)
35+
} else {
36+
Ok(Some(commits))
37+
}
38+
}
39+
40+
///
41+
pub mod write {
42+
pub(crate) mod function {
43+
use std::io::Write;
44+
45+
use super::Error;
46+
use crate::Update;
47+
48+
/// Write the [previously obtained](crate::read()) (possibly non-existing) `shallow_commits` to the shallow `file`
49+
/// after applying all `updates`.
50+
///
51+
/// If this leaves the list of shallow commits empty, the file is removed.
52+
///
53+
/// ### Deviation
54+
///
55+
/// Git also prunes the set of shallow commits while writing, we don't until we support some sort of pruning.
56+
pub fn write(
57+
mut file: gix_lock::File,
58+
shallow_commits: Option<Vec<gix_hash::ObjectId>>,
59+
updates: &[Update],
60+
) -> Result<(), Error> {
61+
let mut shallow_commits = shallow_commits.unwrap_or_default();
62+
for update in updates {
63+
match update {
64+
Update::Shallow(id) => {
65+
shallow_commits.push(*id);
66+
}
67+
Update::Unshallow(id) => shallow_commits.retain(|oid| oid != id),
68+
}
69+
}
70+
if shallow_commits.is_empty() {
71+
std::fs::remove_file(file.resource_path())?;
72+
drop(file);
73+
return Ok(());
74+
}
75+
76+
if shallow_commits.is_empty() {
77+
if let Err(err) = std::fs::remove_file(file.resource_path()) {
78+
if err.kind() != std::io::ErrorKind::NotFound {
79+
return Err(err.into());
80+
}
81+
}
82+
} else {
83+
shallow_commits.sort();
84+
let mut buf = Vec::<u8>::new();
85+
for commit in shallow_commits {
86+
commit.write_hex_to(&mut buf).map_err(Error::Io)?;
87+
buf.push(b'\n');
88+
}
89+
file.write_all(&buf).map_err(Error::Io)?;
90+
file.flush()?;
91+
}
92+
file.commit()?;
93+
Ok(())
94+
}
95+
}
96+
97+
/// The error returned by [`write()`](crate::write()).
98+
#[derive(Debug, thiserror::Error)]
99+
#[allow(missing_docs)]
100+
pub enum Error {
101+
#[error(transparent)]
102+
Commit(#[from] gix_lock::commit::Error<gix_lock::File>),
103+
#[error("Could not remove an empty shallow file")]
104+
RemoveEmpty(#[from] std::io::Error),
105+
#[error("Failed to write object id to shallow file")]
106+
Io(std::io::Error),
107+
}
108+
}
109+
pub use write::function::write;
110+
111+
///
112+
pub mod read {
113+
/// The error returned by [`read`](crate::read()).
114+
#[derive(Debug, thiserror::Error)]
115+
#[allow(missing_docs)]
116+
pub enum Error {
117+
#[error("Could not open shallow file for reading")]
118+
Io(#[from] std::io::Error),
119+
#[error("Could not decode a line in shallow file as hex-encoded object hash")]
120+
DecodeHash(#[from] gix_hash::decode::Error),
121+
}
122+
}

Diff for: gix/Cargo.toml

+1
Original file line numberDiff line numberDiff line change
@@ -328,6 +328,7 @@ gix-dir = { version = "^0.10.0", path = "../gix-dir", optional = true }
328328
gix-config = { version = "^0.42.0", path = "../gix-config" }
329329
gix-odb = { version = "^0.65.0", path = "../gix-odb" }
330330
gix-hash = { version = "^0.15.1", path = "../gix-hash" }
331+
gix-shallow = { version = "^0.1.0", path = "../gix-shallow" }
331332
gix-object = { version = "^0.46.0", path = "../gix-object" }
332333
gix-actor = { version = "^0.33.1", path = "../gix-actor" }
333334
gix-pack = { version = "^0.55.0", path = "../gix-pack", default-features = false, features = [

Diff for: gix/src/remote/connection/fetch/error.rs

+1-1
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,7 @@ pub enum Error {
2929
source: std::io::Error,
3030
},
3131
#[error(transparent)]
32-
ShallowOpen(#[from] crate::shallow::open::Error),
32+
ShallowOpen(#[from] crate::shallow::read::Error),
3333
#[error("Server lack feature {feature:?}: {description}")]
3434
MissingServerFeature {
3535
feature: &'static str,

Diff for: gix/src/remote/connection/fetch/receive_pack.rs

+5-1
Original file line numberDiff line numberDiff line change
@@ -325,7 +325,11 @@ where
325325

326326
if let Some(shallow_lock) = shallow_lock {
327327
if !previous_response.shallow_updates().is_empty() {
328-
crate::shallow::write(shallow_lock, shallow_commits, previous_response.shallow_updates())?;
328+
gix_shallow::write(
329+
shallow_lock,
330+
shallow_commits.map(|v| (**v).to_owned()),
331+
previous_response.shallow_updates(),
332+
)?;
329333
}
330334
}
331335
(write_pack_bundle, Some(outcome::Negotiate { graph, rounds }))

Diff for: gix/src/repository/shallow.rs

+2-21
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
11
use std::{borrow::Cow, path::PathBuf};
22

33
use crate::{
4-
bstr::ByteSlice,
54
config::tree::{gitoxide, Key},
65
Repository,
76
};
@@ -22,28 +21,10 @@ impl Repository {
2221
/// isn't a shallow clone.
2322
///
2423
/// The shared list is shared across all clones of this repository.
25-
pub fn shallow_commits(&self) -> Result<Option<crate::shallow::Commits>, crate::shallow::open::Error> {
24+
pub fn shallow_commits(&self) -> Result<Option<crate::shallow::Commits>, crate::shallow::read::Error> {
2625
self.shallow_commits.recent_snapshot(
2726
|| self.shallow_file().metadata().ok().and_then(|m| m.modified().ok()),
28-
|| {
29-
let buf = match std::fs::read(self.shallow_file()) {
30-
Ok(buf) => buf,
31-
Err(err) if err.kind() == std::io::ErrorKind::NotFound => return Ok(None),
32-
Err(err) => return Err(err.into()),
33-
};
34-
35-
let mut commits = buf
36-
.lines()
37-
.map(gix_hash::ObjectId::from_hex)
38-
.collect::<Result<Vec<_>, _>>()?;
39-
40-
commits.sort();
41-
if commits.is_empty() {
42-
Ok(None)
43-
} else {
44-
Ok(Some(commits))
45-
}
46-
},
27+
|| gix_shallow::read(&self.shallow_file()),
4728
)
4829
}
4930

Diff for: gix/src/revision/walk.rs

+1-1
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ pub enum Error {
1111
#[error(transparent)]
1212
SimpleTraversal(#[from] gix_traverse::commit::simple::Error),
1313
#[error(transparent)]
14-
ShallowCommits(#[from] crate::shallow::open::Error),
14+
ShallowCommits(#[from] crate::shallow::read::Error),
1515
#[error(transparent)]
1616
ConfigBoolean(#[from] crate::config::boolean::Error),
1717
}

0 commit comments

Comments
 (0)