Skip to content

Commit b2071eb

Browse files
committed
radicle-surf: refactor design: part 4 (WIP)
- Update design doc. - Refactor History API. - Add more test cases. Signed-off-by: Han Xu <[email protected]>
1 parent 7fe6993 commit b2071eb

File tree

7 files changed

+232
-276
lines changed

7 files changed

+232
-276
lines changed

radicle-surf/docs/refactor-design.md

Lines changed: 68 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -98,7 +98,9 @@ I think we should use `Rev` in our API as much as possible.
9898

9999
#### How to identify History
100100

101-
TBD
101+
Currently we are keeping the basic `History` type, and only change it from
102+
a TupleStruct to a regular Struct. The goal is to make it easier to refactor
103+
later.
102104

103105
### Code browsing
104106

@@ -143,18 +145,80 @@ The core API is:
143145

144146
```Rust
145147
imp RepositoryRef {
148+
/// Returns the diff between two revisions.
146149
pub fn diff(&self, from: &Rev, to: &Rev) -> Result<Diff, Error>;
150+
151+
/// Returns the diff between a revision and the initial state of the repo.
152+
pub fn initial_diff(&self, rev: &Rev) -> Result<Diff, Error> {
153+
154+
/// Returns the diff of a specific revision.
155+
pub fn diff_of_rev(&self, rev: &Rev) -> Result<Diff, Error>;
147156
}
148157
```
149158

150-
To help convert from `Oid` to `Rev`, we provide a helper method:
159+
### Retrieve the history
160+
161+
The user would be able to get the list of previous commits reachable from a
162+
particular commit or a ref.
163+
151164
```Rust
152165
imp RepositoryRef {
153-
/// Returns the Oid of `rev`.
154-
pub fn rev_oid(&self, rev: &Rev) -> Result<Oid, Error>;
166+
pub fn history(&self, rev: &Rev) -> Result<History, Error>;
155167
}
156168
```
157169

170+
`History` is defined as an iterator to support potentially very long histories.
171+
172+
```Rust
173+
pub struct History<'a> {
174+
repo: RepositoryRef<'a>,
175+
head: Commit,
176+
revwalk: git2::Revwalk<'a>,
177+
}
178+
179+
impl<'a> History<'a> {
180+
/// `head` is always valid. A history is guaranteed not emnpty.
181+
pub fn head(&self) -> &Commit;
182+
183+
/// Returns an iterator that only iterates commits that has the file `path`.
184+
pub fn has_file(self, path: file_system::Path) -> impl Iterator<Item = Commit> + 'a;
185+
186+
/// Reloads the history so that a caller can iterate it again.
187+
pub fn reload(&mut self);
188+
}
189+
```
190+
Note: the only way to create a new `History` to call `repo.history(rev)`.
191+
192+
- Alternative design:
193+
194+
One potential downside of define `History` as an iterator is that:
195+
`history.next()` takes a mutable history object. A different design is to use
196+
`History` as immutable object that produces an iterator on-demand:
197+
198+
```Rust
199+
pub struct History<'a> {
200+
repo: RepositoryRef<'a>,
201+
head: Commit,
202+
}
203+
204+
impl<'a> History<'a> {
205+
/// This method creats a new `RevWalk` internally and return an
206+
/// iterator for all commits in a history.
207+
pub fn iter(&self) -> impl Iterator<Item = Commit>;
208+
}
209+
```
210+
211+
In this design, `History` does not keep `RevWalk` in its state. It will create
212+
a new one when `iter()` is called. I like the immutable interface of this design
213+
but did not implement it in the current code mainly because the libgit2 doc says
214+
[creating a new `RevWalk` is relatively expensive](https://libgit2.org/libgit2/#HEAD/group/revwalk/git_revwalk_new).
215+
216+
### Retrieve a specific object: all its metadata
217+
218+
Git has four different types of objects: Blob, Tree, Commit and Tag. The `blob`
219+
and `tree` are low-level to git and so I don't think we should expose them in
220+
our API.
221+
158222
## Error handling
159223

160224
TBD

radicle-surf/src/commit.rs

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -109,6 +109,12 @@ impl From<&git::Commit> for Header {
109109
}
110110
}
111111

112+
impl From<git::Commit> for Header {
113+
fn from(commit: git::Commit) -> Self {
114+
Self::from(&commit)
115+
}
116+
}
117+
112118
#[cfg(feature = "serialize")]
113119
impl Serialize for Header {
114120
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
@@ -241,7 +247,7 @@ where
241247
};
242248

243249
let stats = repo.get_stats(&rev)?;
244-
let headers = repo.history(rev)?.iter().map(Header::from).collect();
250+
let headers = repo.history(&rev)?.map(Header::from).collect();
245251

246252
Ok(Commits { headers, stats })
247253
}

radicle-surf/src/object/tree.rs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -155,7 +155,8 @@ where
155155
entries.sort_by(|a, b| a.info.object_type.cmp(&b.info.object_type));
156156

157157
let last_commit = if path.is_root() {
158-
Some(commit::Header::from(repo.history(rev).unwrap().first()))
158+
let history = repo.history(&rev)?;
159+
Some(commit::Header::from(history.head()))
159160
} else {
160161
None
161162
};

radicle-surf/src/vcs.rs

Lines changed: 1 addition & 115 deletions
Original file line numberDiff line numberDiff line change
@@ -15,120 +15,6 @@
1515
// You should have received a copy of the GNU General Public License
1616
// along with this program. If not, see <https://www.gnu.org/licenses/>.
1717

18-
//! A model of a general VCS History.
19-
20-
use nonempty::NonEmpty;
18+
//! A model of a general VCS.
2119
2220
pub mod git;
23-
24-
/// A non-empty bag of artifacts which are used to
25-
/// derive a [`crate::file_system::Directory`] view. Examples of artifacts
26-
/// would be commits in Git or patches in Pijul.
27-
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord)]
28-
pub struct History<A>(pub NonEmpty<A>);
29-
30-
impl<A> History<A> {
31-
/// Create a new `History` consisting of one artifact.
32-
pub fn new(a: A) -> Self {
33-
History(NonEmpty::new(a))
34-
}
35-
36-
/// Push an artifact to the end of the `History`.
37-
pub fn push(&mut self, a: A) {
38-
self.0.push(a)
39-
}
40-
41-
/// Iterator over the artifacts.
42-
pub fn iter(&self) -> impl Iterator<Item = &A> {
43-
self.0.iter()
44-
}
45-
46-
/// Get the firest artifact in the `History`.
47-
pub fn first(&self) -> &A {
48-
self.0.first()
49-
}
50-
51-
/// Get the length of `History` (aka the artefacts count)
52-
pub fn len(&self) -> usize {
53-
self.0.len()
54-
}
55-
56-
/// Check if `History` is empty
57-
pub fn is_empty(&self) -> bool {
58-
self.0.is_empty()
59-
}
60-
61-
/// Given that the `History` is topological order from most
62-
/// recent artifact to least recent, `find_suffix` gets returns
63-
/// the history up until the point of the given artifact.
64-
///
65-
/// This operation may fail if the artifact does not exist in
66-
/// the given `History`.
67-
pub fn find_suffix(&self, artifact: &A) -> Option<Self>
68-
where
69-
A: Clone + PartialEq,
70-
{
71-
let new_history: Option<NonEmpty<A>> = NonEmpty::from_slice(
72-
&self
73-
.iter()
74-
.cloned()
75-
.skip_while(|current| *current != *artifact)
76-
.collect::<Vec<_>>(),
77-
);
78-
79-
new_history.map(History)
80-
}
81-
82-
/// Apply a function from `A` to `B` over the `History`
83-
pub fn map<F, B>(self, f: F) -> History<B>
84-
where
85-
F: FnMut(A) -> B,
86-
{
87-
History(self.0.map(f))
88-
}
89-
90-
/// Find an artifact in the `History`.
91-
///
92-
/// The function provided should return `Some` if the item is the desired
93-
/// output and `None` otherwise.
94-
pub fn find<F, B>(&self, f: F) -> Option<B>
95-
where
96-
F: Fn(&A) -> Option<B>,
97-
{
98-
self.iter().find_map(f)
99-
}
100-
101-
/// Find an atrifact in the given `History` using the artifacts ID.
102-
///
103-
/// This operation may fail if the artifact does not exist in the history.
104-
pub fn find_in_history<Identifier, F>(&self, identifier: &Identifier, id_of: F) -> Option<A>
105-
where
106-
A: Clone,
107-
F: Fn(&A) -> Identifier,
108-
Identifier: PartialEq,
109-
{
110-
self.iter()
111-
.find(|artifact| {
112-
let current_id = id_of(artifact);
113-
*identifier == current_id
114-
})
115-
.cloned()
116-
}
117-
118-
/// Find all occurences of an artifact in a bag of `History`s.
119-
pub fn find_in_histories<Identifier, F>(
120-
histories: Vec<Self>,
121-
identifier: &Identifier,
122-
id_of: F,
123-
) -> Vec<Self>
124-
where
125-
A: Clone,
126-
F: Fn(&A) -> Identifier + Copy,
127-
Identifier: PartialEq,
128-
{
129-
histories
130-
.into_iter()
131-
.filter(|history| history.find_in_history(identifier, id_of).is_some())
132-
.collect()
133-
}
134-
}

radicle-surf/src/vcs/git/reference.rs

Lines changed: 1 addition & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ use std::{fmt, str};
1919

2020
use thiserror::Error;
2121

22-
use crate::vcs::git::{repo::RepositoryRef, BranchName, Namespace, TagName};
22+
use crate::vcs::git::{BranchName, Namespace, TagName};
2323
use radicle_git_ext::Oid;
2424
pub(super) mod glob;
2525

@@ -93,15 +93,6 @@ impl Ref {
9393

9494
ref_namespace
9595
}
96-
97-
/// We try to find a [`git2::Reference`] based off of a `Ref` by turning the
98-
/// ref into a fully qualified ref (e.g. refs/remotes/**/master).
99-
pub fn find_ref<'a>(
100-
&self,
101-
repo: &RepositoryRef<'a>,
102-
) -> Result<git2::Reference<'a>, git2::Error> {
103-
repo.repo_ref.find_reference(&self.to_string())
104-
}
10596
}
10697

10798
impl fmt::Display for Ref {

0 commit comments

Comments
 (0)