Skip to content

Commit a645c6f

Browse files
committed
Initial prototype of branch support.
1 parent 9dbd394 commit a645c6f

File tree

17 files changed

+520
-175
lines changed

17 files changed

+520
-175
lines changed

Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ tokio-test = "0.4.2"
1212

1313
[dependencies]
1414
anyhow = "1.0.45"
15+
async-recursion = "0.3.2"
1516
comma-v = { path = "comma-v" }
1617
flexi_logger = { version = "0.19.6", features = ["async", "colors"] }
1718
flume = "0.10.9"

comma-v/Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ license = "Apache-2.0"
1010
chrono = "0.4.19"
1111
derive_more = "0.99.16"
1212
eq-macro = { path = "../eq-macro" }
13+
itertools = "0.10.1"
1314
nom = "7.1.0"
1415
thiserror = "1.0.30"
1516

comma-v/examples/extract-script.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ use std::{
22
fs,
33
io::{self, Write},
44
path::PathBuf,
5+
str::FromStr,
56
};
67

78
use comma_v::Num;
@@ -19,9 +20,8 @@ struct Opt {
1920
fn main() -> anyhow::Result<()> {
2021
let opt = Opt::from_args();
2122

22-
let revision: Vec<u8> = opt.revision.as_bytes().to_vec();
2323
let file = comma_v::parse(&fs::read(&opt.file)?)?;
24-
match file.delta_text.get(&Num::from(revision)) {
24+
match file.delta_text.get(&Num::from_str(&opt.revision)?) {
2525
Some(dt) => {
2626
io::stdout().write_all(&dt.text.0)?;
2727
}

comma-v/src/error.rs

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
use std::{num::ParseIntError, str::Utf8Error};
2+
3+
use nom::error::ErrorKind;
4+
use thiserror::Error;
5+
6+
#[derive(Debug, Error)]
7+
pub enum Error {
8+
#[error("not a branch")]
9+
NotBranch,
10+
11+
#[error("parse error of kind {kind:?} at location {location:?}")]
12+
ParseError { location: Vec<u8>, kind: ErrorKind },
13+
14+
#[error(transparent)]
15+
ParseInt(#[from] ParseIntError),
16+
17+
#[error(transparent)]
18+
ParseUtf8(#[from] Utf8Error),
19+
}

comma-v/src/lib.rs

Lines changed: 6 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,13 @@
1-
use nom::{error::ErrorKind, Finish};
2-
use thiserror::Error;
1+
use nom::Finish;
32

3+
mod error;
4+
mod num;
45
mod parser;
56
mod types;
6-
pub use types::*;
77

8-
#[derive(Debug, Error)]
9-
pub enum Error {
10-
#[error("parse error of kind {kind:?} at location {location:?}")]
11-
ParseError { location: Vec<u8>, kind: ErrorKind },
12-
}
8+
pub use error::Error;
9+
pub use num::Num;
10+
pub use types::*;
1311

1412
/// Parses a full RCS file.
1513
pub fn parse(input: &[u8]) -> Result<File, Error> {

comma-v/src/num.rs

Lines changed: 185 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,185 @@
1+
use std::{convert::TryFrom, fmt::Display, num::ParseIntError, str::FromStr};
2+
3+
use itertools::Itertools;
4+
5+
use crate::Error;
6+
7+
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
8+
pub enum Num {
9+
Branch(Vec<u64>),
10+
Commit(Vec<u64>),
11+
}
12+
13+
impl Num {
14+
pub fn contains(&self, other: &Num) -> Result<bool, Error> {
15+
if let Num::Branch(branch) = self {
16+
if let Num::Commit(other) = other {
17+
// for (prefix, max) in prefixes.iter() {
18+
// if other.len() < prefix.len() {
19+
// // This means that we're looking at a commit from before
20+
// // this branch was branched, but since we know the
21+
// // previous checks passed, that means the commit must be
22+
// // part of this branch.
23+
// return Ok(true);
24+
// }
25+
// if &other[0..prefix.len()] != prefix.as_slice() {
26+
// return Ok(false);
27+
// }
28+
// if let Some(max) = max {
29+
// if other[prefix.len()] > *max {
30+
// return Ok(false);
31+
// }
32+
// }
33+
// }
34+
35+
// return Ok(true);
36+
37+
if other.len() > (branch.len() + 1) {
38+
// Commit is deeper, and therefore cannot be on this branch.
39+
return Ok(false);
40+
}
41+
42+
// Check intermediate branches.
43+
for i in (0..branch.len() - 1).step_by(2) {
44+
if let Some(other_branch) = other.get(i) {
45+
if *other_branch != branch[i] {
46+
// The branch number doesn't match.
47+
return Ok(false);
48+
}
49+
if let Some(rev) = other.get(i + 1) {
50+
if *rev > branch[i + 1] {
51+
// The revision on the commit branch is after
52+
// the branch we're comparing against.
53+
return Ok(false);
54+
}
55+
} else {
56+
// This would imply the commit isn't really a
57+
// commit, since it has an odd number of entries,
58+
// and there's nothing sensible to be done.
59+
return Err(Error::NotBranch);
60+
}
61+
} else {
62+
// We're done; previous branches matched, and the
63+
// revision isn't as deep.
64+
return Ok(true);
65+
}
66+
}
67+
68+
// Check the leaf branch.
69+
if let Some(other_branch) = other.get(branch.len() - 1) {
70+
if *other_branch != branch[branch.len() - 1] {
71+
return Ok(false);
72+
}
73+
}
74+
75+
return Ok(true);
76+
}
77+
}
78+
79+
Err(Error::NotBranch)
80+
}
81+
82+
pub fn to_branch(&self) -> Self {
83+
match self {
84+
Num::Branch(_) => self.clone(),
85+
Num::Commit(parts) => Num::Branch(parts[0..parts.len() - 1].to_vec()),
86+
}
87+
}
88+
}
89+
90+
impl TryFrom<&[u8]> for Num {
91+
type Error = Error;
92+
93+
fn try_from(value: &[u8]) -> Result<Self, Self::Error> {
94+
Self::from_str(std::str::from_utf8(value)?)
95+
}
96+
}
97+
98+
impl FromStr for Num {
99+
type Err = Error;
100+
101+
fn from_str(s: &str) -> Result<Self, Self::Err> {
102+
match s
103+
.split('.')
104+
.filter_map(|part| match part.parse::<u64>() {
105+
// We want to strip out 0 components, as they indicate magic
106+
// revisions in CVS that we don't need direct knowledge of in
107+
// the importer.
108+
Ok(v) if v == 0 => None,
109+
Ok(v) => Some(Ok(v)),
110+
Err(e) => Some(Err(e)),
111+
})
112+
.collect::<Result<Vec<u64>, ParseIntError>>()
113+
{
114+
Ok(parts) if parts.len() % 2 == 0 => Ok(Self::Commit(parts)),
115+
Ok(parts) => Ok(Self::Branch(parts)),
116+
Err(e) => Err(e.into()),
117+
}
118+
}
119+
}
120+
121+
impl Display for Num {
122+
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
123+
match self {
124+
Self::Branch(parts) => fmt_u64_slice(f, parts.as_slice()),
125+
Self::Commit(parts) => fmt_u64_slice(f, parts.as_slice()),
126+
}
127+
}
128+
}
129+
130+
#[allow(unstable_name_collisions)]
131+
fn fmt_u64_slice(f: &mut std::fmt::Formatter, input: &[u64]) -> std::fmt::Result {
132+
write!(
133+
f,
134+
"{}",
135+
input
136+
.iter()
137+
.map(|part| part.to_string())
138+
.intersperse(String::from("."))
139+
.collect::<String>()
140+
)
141+
}
142+
143+
#[cfg(test)]
144+
mod test {
145+
use super::*;
146+
147+
#[test]
148+
fn test_num_contains() {
149+
// Contained because it's on this specific branch.
150+
assert!(num("1.1.2").contains(&num("1.1.2.1")).unwrap());
151+
assert!(num("1.1.2").contains(&num("1.1.2.2")).unwrap());
152+
153+
// Contained because it's an ancestor of this branch.
154+
assert!(num("1.1.2").contains(&num("1.1")).unwrap());
155+
156+
// Not contained because it's on a different branch.
157+
assert!(!num("1.1.2").contains(&num("1.1.3.1")).unwrap());
158+
159+
// Not contained because it's only on a descendant branch.
160+
assert!(!num("1.1.2").contains(&num("1.1.2.1.1.1")).unwrap());
161+
162+
// Not contained because it's after the branch was made.
163+
assert!(!num("1.1.2").contains(&num("1.2")).unwrap());
164+
}
165+
166+
#[test]
167+
fn test_num_parse() {
168+
assert_eq!(Num::from_str("1.1").unwrap(), Num::Commit(vec![1, 1]));
169+
assert_eq!(
170+
Num::from_str("1.2.3.4").unwrap(),
171+
Num::Commit(vec![1, 2, 3, 4])
172+
);
173+
174+
assert_eq!(Num::from_str("1.2.3").unwrap(), Num::Branch(vec![1, 2, 3]),);
175+
176+
assert_eq!(
177+
Num::from_str("1.2.0.3").unwrap(),
178+
Num::Branch(vec![1, 2, 3])
179+
);
180+
}
181+
182+
fn num(s: &str) -> Num {
183+
Num::from_str(s).unwrap()
184+
}
185+
}

comma-v/src/parser/mod.rs

Lines changed: 19 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ use nom::{
1010
IResult,
1111
};
1212

13-
use crate::types;
13+
use crate::{num, types};
1414

1515
mod char;
1616

@@ -121,7 +121,7 @@ fn admin(input: &[u8]) -> IResult<&[u8], types::Admin> {
121121
)(input)
122122
}
123123

124-
fn delta(input: &[u8]) -> IResult<&[u8], (types::Num, types::Delta)> {
124+
fn delta(input: &[u8]) -> IResult<&[u8], (num::Num, types::Delta)> {
125125
map(
126126
tuple((
127127
terminated(num, multispace1),
@@ -174,7 +174,7 @@ fn delta(input: &[u8]) -> IResult<&[u8], (types::Num, types::Delta)> {
174174
)(input)
175175
}
176176

177-
fn delta_text(input: &[u8]) -> IResult<&[u8], (types::Num, types::DeltaText)> {
177+
fn delta_text(input: &[u8]) -> IResult<&[u8], (num::Num, types::DeltaText)> {
178178
map(
179179
tuple((
180180
num,
@@ -193,16 +193,18 @@ fn desc(input: &[u8]) -> IResult<&[u8], types::Desc> {
193193

194194
#[cfg(test)]
195195
mod tests {
196+
use std::str::FromStr;
197+
196198
use chrono::DateTime;
197199

198-
use crate::types::Num;
200+
use crate::num::Num;
199201

200202
use super::*;
201203

202204
#[test]
203205
fn test_admin() {
204206
let have = admin(include_bytes!("fixtures/admin/input")).unwrap().1;
205-
assert_eq!(*have.head.unwrap(), b"1.1");
207+
assert_eq!(have.head.unwrap().to_string(), "1.1");
206208
assert!(have.branch.is_none());
207209
assert_eq!(have.access.len(), 0);
208210
assert_eq!(have.symbols.len(), 0);
@@ -216,7 +218,7 @@ mod tests {
216218
#[test]
217219
fn test_delta() {
218220
let (num, have) = delta(include_bytes!("fixtures/delta/input")).unwrap().1;
219-
assert_eq!(*num, b"1.2");
221+
assert_eq!(num.to_string(), "1.2");
220222
assert_eq!(
221223
have.date,
222224
DateTime::parse_from_rfc3339("2021-08-20T17:34:26+00:00")
@@ -228,11 +230,11 @@ mod tests {
228230
assert_eq!(
229231
have.branches,
230232
vec![
231-
Num::from(b"1.2.2.1".to_vec()),
232-
Num::from(b"1.2.4.1".to_vec())
233+
Num::from_str("1.2.2.1").unwrap(),
234+
Num::from_str("1.2.4.1").unwrap()
233235
]
234236
);
235-
assert_eq!(*have.next.unwrap(), b"1.1");
237+
assert_eq!(have.next.unwrap().to_string(), "1.1");
236238
assert!(have.commit_id.is_none());
237239
}
238240

@@ -241,12 +243,12 @@ mod tests {
241243
let (num, have) = delta_text(include_bytes!("fixtures/delta_text/input"))
242244
.unwrap()
243245
.1;
244-
assert_eq!(*num, b"1.1");
246+
assert_eq!(num.to_string(), "1.1");
245247
assert_eq!(*have.log, include_bytes!("fixtures/delta_text/log"),);
246248
assert_eq!(*have.text, include_bytes!("fixtures/delta_text/text"),);
247249

248250
let (num, have) = delta_text(b"1.2 log @@ text @@").unwrap().1;
249-
assert_eq!(*num, b"1.2");
251+
assert_eq!(num.to_string(), "1.2");
250252
assert_eq!(*have.log, b"");
251253
assert_eq!(*have.text, b"");
252254
}
@@ -263,11 +265,14 @@ mod tests {
263265
let have = file(include_bytes!("fixtures/file/input")).unwrap().1;
264266

265267
// We'll just spot check.
266-
assert_eq!(*have.admin.head.unwrap(), b"1.4");
268+
assert_eq!(have.admin.head.unwrap().to_string(), "1.4");
267269

268270
assert_eq!(have.delta.len(), 4);
269271
assert_eq!(
270-
have.delta.get(&types::Num(b"1.4".to_vec())).unwrap().date,
272+
have.delta
273+
.get(&num::Num::from_str("1.4").unwrap())
274+
.unwrap()
275+
.date,
271276
DateTime::parse_from_rfc3339("2021-08-11T19:08:27+00:00")
272277
.unwrap()
273278
.into(),
@@ -279,7 +284,7 @@ mod tests {
279284
assert_eq!(
280285
*have
281286
.delta_text
282-
.get(&types::Num(b"1.1".to_vec()))
287+
.get(&num::Num::from_str("1.1").unwrap())
283288
.unwrap()
284289
.text,
285290
b"d5 3\n"

0 commit comments

Comments
 (0)