Skip to content

Commit 207b554

Browse files
committed
Merge remote-tracking branch 'han/new-design-2'
* han/new-design-2: refactor design: part 2
2 parents 6a3fac7 + c3d9a2b commit 207b554

File tree

15 files changed

+417
-1730
lines changed

15 files changed

+417
-1730
lines changed

radicle-surf/docs/refactor-design.md

Lines changed: 127 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -1,47 +1,153 @@
11
# An updated design for radicle-surf
22

3-
Now we have ported the `radicle-surf` crate from its own github repo to be part of the `radicle-git` repo. We are taking this opportunity to refactor its design as well. Intuitively, `radicle-surf` provides an API so that one can use it to create a github-like UI for a git repo:
3+
## Introduction
44

5-
- Given a commit (or other types of ref), list the content: i.e. files and directories.
6-
- Generate a diff between two commits.
7-
- List the history of the commits.
8-
- List refs: Branches, Tags.
5+
Now we have ported the `radicle-surf` crate from its own github repo to be
6+
part of the `radicle-git` repo. We are taking this opportunity to refactor
7+
its design as well. Intuitively, `radicle-surf` provides an API so that one
8+
can use it to create a GitHub-like UI for a git repo:
99

10-
The main goals of the changes are:
10+
1. Code browsing: given a specific commit/ref, browse files and directories.
11+
2. Diff between two revisions that resolve into two commits.
12+
3. Retrieve the history of the commits.
13+
4. Retrieve a specific object: all its metadata.
14+
5. Retrieve the refs: Branches, Tags, Remotes, Notes and user-defined
15+
"categories", where a category is: refs/<category>/<...>.
1116

12-
- make API simpler whenever possible.
13-
- address open issues in the original `radicle-surf` repo as much as possible.
14-
- not to shy away from being `git` specific. (i.e. not to consider supporting other VCS systems)
17+
## Motivation
1518

16-
## Make API simpler
19+
The `radicle-surf` crate aims to provide a safe and easy-to-use API that
20+
supports the features listed in [Introduction]. Based on the existing API,
21+
the main goals of the refactoring are:
1722

18-
The current API has quite a bit accidental complexity that is not inherent with the requirements, especially when we can be git specific and don't care about other VCS systems.
23+
- API review: make API simpler whenever possible.
24+
- Address open issues in the original `radicle-surf` repo as much as possible.
25+
- Not to shy away from being `git` specific. (i.e. not to consider supporting
26+
other VCS systems)
27+
- Hide away `git2` from the API. The use of `git2` should be an implementation
28+
detail.
29+
30+
## API review
31+
32+
The current API has quite a bit accidental complexity that is not inherent with
33+
the requirements, especially when we can be git specific and don't care about
34+
other VCS systems.
1935

2036
### Remove the `Browser`
2137

2238
The type `Browser` is awkward as of today:
2339

24-
- it is not a source of truth of any information. For example, `list_branches` method is just a wrapper of `Repository::list_branches`.
40+
- it is not a source of truth of any information. For example, `list_branches`
41+
method is just a wrapper of `Repository::list_branches`.
2542
- it takes in `History`, but really works at the `Snapshot` level.
2643
- it is mutable but its state does not help much.
2744

28-
Can we just remove `Browser` and implement its functionalities using other types?
45+
Can we just remove `Browser` and implement its functionalities using other
46+
types?
2947

3048
- For iteratoring the history, use `History`.
3149
- For generating `Directory`, use `Repository` directly given a `Rev`.
3250
- For accessing `Branch`, `Tag` or `Commit`, use `Repository`.
3351

34-
## Remove the `Snapshot`
52+
## Remove the `Snapshot` type
53+
54+
A `Snapshot` should be really just a tree (or `Directory`) of a `Commit` in
55+
git. Currently it is a function that returns a `Directory`. Because it is OK
56+
to be git specific, we don't need to have this generic function to create a
57+
snapshot across different VCS systems.
58+
59+
The snapshot function can be easily implement as a method of `RepositoryRef`.
60+
61+
## Simplify `Directory` and remove the `Tree` and `Forest` types
62+
63+
The `Directory` type represents the file system view of a snapshot. Its field
64+
`sub_directories` is defined a `Forest` based on `Tree`. The types are
65+
over-engineered from such a simple concept. We could refactor `Directory` to
66+
use `DirectoryContents` for its items and not to use `Tree` or `Forest` at all.
67+
68+
We also found the `list_directory()` method duplicates with `iter()` method.
69+
Hence `list_directory()` is removed, together with `SystemType` type.
70+
71+
## The new API
72+
73+
With the changes proposed in the previous section, we describe what the new API
74+
would look like and how they meet the requirements.
75+
76+
### Common principles
77+
78+
#### How to identify things that resolve into a commit
79+
80+
In our API, it will be good to have a single way to identify all refs and
81+
objects that resolve into commits. In other words, we try to avoid using
82+
different ways at different places. Currently there are multiple types in the
83+
API for this purpose:
84+
85+
- Commit
86+
- Oid
87+
- Rev
88+
89+
Because `Rev` is the most high level among those and supports refs already,
90+
I think we should use `Rev` in our API as much as possible.
91+
92+
#### How to identify History
93+
94+
TBD
3595

36-
A `Snapshot` should be really just a tree (or `Directory`) of a `Commit` in git. Currently it is a function that returns a `Directory`. Because it is OK to be git specific, we don't need to have this generic function to create a snapshot across different VCS systems.
96+
### Code browsing
3797

38-
The only `snapshot` function defined currently:
98+
The user should be able to browse the files and directories for any given
99+
commits or references. The core API is:
39100

101+
- Create a root Directory:
40102
```Rust
41-
let snapshot = Box::new(|repository: &RepositoryRef<'a>, history: &History| {
42-
let tree = Self::get_tree(repository.repo_ref, history.0.first())?;
43-
Ok(directory::Directory::from_hash_map(tree))
44-
});
103+
imp RepositoryRef {
104+
pub fn snapshot(&self, rev: &Rev) -> Result<Directory, Error>;
105+
}
45106
```
46107

47-
The above function can be easily implement as a method of `Repository`.
108+
- Browse a Directory:
109+
```Rust
110+
impl Directory {
111+
pub fn contents(&self) -> impl Iterator<Item = &DirectoryContents>;
112+
}
113+
```
114+
where `DirectoryContents` supports both files and sub-directories:
115+
```Rust
116+
pub enum DirectoryContents {
117+
/// The `File` variant contains the file's name and the [`File`] itself.
118+
File {
119+
/// The name of the file.
120+
name: Label,
121+
/// The file data.
122+
file: File,
123+
},
124+
/// The `Directory` variant contains a sub-directory to the current one.
125+
Directory(Directory),
126+
}
127+
```
128+
129+
### Diffs
130+
131+
The user would be able to create a diff between any two revisions that resolve
132+
into two commits.
133+
134+
The main change is to use `Rev` instead of `Oid` to identify `from` and `to`.
135+
The core API is:
136+
137+
```Rust
138+
imp RepositoryRef {
139+
pub fn diff(&self, from: &Rev, to: &Rev) -> Result<Diff, Error>;
140+
}
141+
```
142+
143+
To help convert from `Oid` to `Rev`, we provide a helper method:
144+
```Rust
145+
imp RepositoryRef {
146+
/// Returns the Oid of `rev`.
147+
pub fn rev_oid(&self, rev: &Rev) -> Result<Oid, Error>;
148+
}
149+
```
150+
151+
## Error handling
152+
153+
TBD

radicle-surf/examples/diff.rs

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,10 @@ fn main() {
3232
let base_oid = Oid::from_str(&options.base_revision).unwrap();
3333
let now = Instant::now();
3434
let elapsed_nanos = now.elapsed().as_nanos();
35-
let diff = repo.as_ref().diff(base_oid, head_oid).unwrap();
35+
let diff = repo
36+
.as_ref()
37+
.diff(&base_oid.into(), &head_oid.into())
38+
.unwrap();
3639
print_diff_summary(&diff, elapsed_nanos);
3740
}
3841

radicle-surf/src/commit.rs

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -144,12 +144,14 @@ pub struct Commits {
144144
///
145145
/// Will return [`Error`] if the project doesn't exist or the surf interaction
146146
/// fails.
147-
pub fn commit(repo: &RepositoryRef, sha1: Oid) -> Result<Commit, Error> {
147+
pub fn commit(repo: &RepositoryRef, rev: &Rev) -> Result<Commit, Error> {
148+
let sha1 = repo.rev_oid(rev)?;
148149
let commit = repo.get_commit(sha1)?;
149150
let diff = if let Some(parent) = commit.parents.first() {
150-
repo.diff(*parent, sha1)?
151+
let parent_rev = (*parent).into();
152+
repo.diff(&parent_rev, rev)?
151153
} else {
152-
repo.initial_diff(sha1)?
154+
repo.initial_diff(rev)?
153155
};
154156

155157
let mut deletions = 0;

radicle-surf/src/diff.rs

Lines changed: 13 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -295,7 +295,10 @@ impl Diff {
295295
#[allow(clippy::self_named_constructors)]
296296
pub fn diff(left: Directory, right: Directory) -> Self {
297297
let mut diff = Diff::new();
298-
let path = Rc::new(RefCell::new(Path::from_labels(right.current(), &[])));
298+
let path = Rc::new(RefCell::new(Path::from_labels(
299+
right.current().clone(),
300+
&[],
301+
)));
299302
Diff::collect_diff(&left, &right, &path, &mut diff);
300303

301304
// TODO: Some of the deleted files may actually be moved (renamed) to one of the
@@ -315,15 +318,15 @@ impl Diff {
315318
parent_path: &Rc<RefCell<Path>>,
316319
diff: &mut Diff,
317320
) {
318-
let mut old_iter = old.iter();
319-
let mut new_iter = new.iter();
321+
let mut old_iter = old.contents();
322+
let mut new_iter = new.contents();
320323
let mut old_entry_opt = old_iter.next();
321324
let mut new_entry_opt = new_iter.next();
322325

323326
while old_entry_opt.is_some() || new_entry_opt.is_some() {
324327
match (&old_entry_opt, &new_entry_opt) {
325328
(Some(ref old_entry), Some(ref new_entry)) => {
326-
match new_entry.label().cmp(&old_entry.label()) {
329+
match new_entry.label().cmp(old_entry.label()) {
327330
Ordering::Greater => {
328331
diff.add_deleted_files(old_entry, parent_path);
329332
old_entry_opt = old_iter.next();
@@ -414,11 +417,11 @@ impl Diff {
414417
},
415418
}
416419
},
417-
(Some(ref old_entry), None) => {
420+
(Some(old_entry), None) => {
418421
diff.add_deleted_files(old_entry, parent_path);
419422
old_entry_opt = old_iter.next();
420423
},
421-
(None, Some(ref new_entry)) => {
424+
(None, Some(new_entry)) => {
422425
diff.add_created_files(new_entry, parent_path);
423426
new_entry_opt = new_iter.next();
424427
},
@@ -465,15 +468,15 @@ impl Diff {
465468
) where
466469
F: Fn(Path) -> T + Copy,
467470
{
468-
parent_path.borrow_mut().push(dir.current());
469-
for entry in dir.iter() {
471+
parent_path.borrow_mut().push(dir.current().clone());
472+
for entry in dir.contents() {
470473
match entry {
471474
DirectoryContents::Directory(subdir) => {
472-
Diff::collect_files_inner(&subdir, parent_path, mapper, files);
475+
Diff::collect_files_inner(subdir, parent_path, mapper, files);
473476
},
474477
DirectoryContents::File { name, .. } => {
475478
let mut path = parent_path.borrow().clone();
476-
path.push(name);
479+
path.push(name.clone());
477480
files.push(mapper(path));
478481
},
479482
}

0 commit comments

Comments
 (0)