summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--.gitignore1
-rw-r--r--Cargo.lock69
-rw-r--r--Cargo.toml8
-rw-r--r--rustfmt.toml2
-rw-r--r--src/lib.rs132
-rw-r--r--src/workarea.rs74
6 files changed, 286 insertions, 0 deletions
diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..ea8c4bf
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1 @@
+/target
diff --git a/Cargo.lock b/Cargo.lock
new file mode 100644
index 0000000..c537729
--- /dev/null
+++ b/Cargo.lock
@@ -0,0 +1,69 @@
+# This file is automatically @generated by Cargo.
+# It is not intended for manual editing.
+version = 4
+
+[[package]]
+name = "arrayvec"
+version = "0.7.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7c02d123df017efcdfbd739ef81735b36c5ba83ec3c59c80a9d7ecc718f92e50"
+
+[[package]]
+name = "fast-glob"
+version = "1.0.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3d26eec0ae9682c457cb0f85de67ad417b716ae852736a5d94c2ad6e92a997c9"
+dependencies = [
+ "arrayvec",
+]
+
+[[package]]
+name = "pj"
+version = "0.1.0"
+dependencies = [
+ "fast-glob",
+ "walkdir",
+]
+
+[[package]]
+name = "same-file"
+version = "1.0.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "93fc1dc3aaa9bfed95e02e6eadabb4baf7e3078b0bd1b4d7b6b0b68378900502"
+dependencies = [
+ "winapi-util",
+]
+
+[[package]]
+name = "walkdir"
+version = "2.5.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "29790946404f91d9c5d06f9874efddea1dc06c5efe94541a7d6863108e3a5e4b"
+dependencies = [
+ "same-file",
+ "winapi-util",
+]
+
+[[package]]
+name = "winapi-util"
+version = "0.1.11"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c2a7b1c03c876122aa43f3020e6c3c3ee5c05081c9a00739faf7503aeba10d22"
+dependencies = [
+ "windows-sys",
+]
+
+[[package]]
+name = "windows-link"
+version = "0.2.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "45e46c0661abb7180e7b9c281db115305d49ca1709ab8242adf09666d2173c65"
+
+[[package]]
+name = "windows-sys"
+version = "0.61.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "6f109e41dd4a3c848907eb83d5a42ea98b3769495597450cf6d153507b166f0f"
+dependencies = [
+ "windows-link",
+]
diff --git a/Cargo.toml b/Cargo.toml
new file mode 100644
index 0000000..1b4b7ce
--- /dev/null
+++ b/Cargo.toml
@@ -0,0 +1,8 @@
+[package]
+name = "pj"
+version = "0.1.0"
+edition = "2024"
+
+[dependencies]
+walkdir = "2"
+fast-glob = "1" \ No newline at end of file
diff --git a/rustfmt.toml b/rustfmt.toml
new file mode 100644
index 0000000..3efb9c3
--- /dev/null
+++ b/rustfmt.toml
@@ -0,0 +1,2 @@
+hard_tabs = true
+newline_style = "Unix" \ No newline at end of file
diff --git a/src/lib.rs b/src/lib.rs
new file mode 100644
index 0000000..7f6b86f
--- /dev/null
+++ b/src/lib.rs
@@ -0,0 +1,132 @@
+#![warn(clippy::pedantic)]
+#![warn(clippy::nursery)]
+
+use std::collections::HashMap;
+use std::fs::{File, Metadata};
+use std::path::{Path, PathBuf};
+use std::time::Instant;
+
+mod workarea;
+
+struct ContributorId(String);
+struct ChannelId(String);
+struct PatchId(String);
+struct FileId(String);
+struct SpanId(String);
+
+struct Remote {
+ url: String,
+ name: String,
+}
+
+struct Contributor {
+ id: ContributorId,
+ emails: Vec<String>,
+ nickname: String,
+ realname: Option<String>,
+ public_keys: Vec<String>,
+}
+
+struct Channel {
+ id: ChannelId,
+ name: String,
+ remotes: Vec<Remote>,
+ main_remote: Option<Remote>,
+ patches: Vec<PatchId>,
+}
+
+struct Patch {
+ id: PatchId,
+ authors: Vec<ContributorId>,
+ recorder: ContributorId,
+ metadata: HashMap<String, String>,
+ added_spans: Vec<SpanId>,
+ deleted_spans: Vec<SpanId>,
+ added_files: Vec<FileId>,
+ deleted_files: Vec<FileId>,
+}
+
+struct FileInfo {
+ id: FileId,
+ inode: Option<u64>,
+ spans: Vec<SpanId>,
+ added_by: Vec<PatchId>,
+ renamed_by: Vec<(PatchId, PathBuf)>,
+ deleted_by: Vec<PatchId>,
+}
+
+struct Span {
+ id: SpanId,
+ file: FileId,
+ after: Vec<SpanId>,
+ before: Vec<SpanId>,
+ contents: Vec<u8>,
+ added_by: Vec<PatchId>,
+ deleted_by: Vec<PatchId>,
+}
+
+type DiffAlgorithm = fn(File, File) -> Diff;
+
+struct Diff(Vec<DiffSpan>);
+
+struct DiffSpan {
+ left: Vec<u8>,
+ right: Vec<u8>,
+}
+
+struct Log {
+ entries: Vec<LogEntry>,
+}
+
+enum LogEntry {
+ CreatePatch(PatchId),
+}
+
+type RevertAlgorithm = fn(Patch) -> Patch;
+
+trait StagingArea {
+ fn list_files() -> std::io::Result<Metadata>;
+ fn open_file(path: &Path) -> std::io::Result<File>;
+ fn file_metadata(path: &Path) -> std::io::Result<Metadata>;
+ fn is_file_changed(path: &Path, since: Instant) -> std::io::Result<bool>;
+}
+
+trait Repository {
+ fn archive(&self) -> Vec<u8>;
+
+ fn remotes(&self) -> Vec<Remote>;
+ fn main_remote(&self) -> Option<Remote>;
+ fn set_main_remote(&mut self, remote: Option<Remote>);
+ fn add_remote(&mut self, remote: Remote);
+ fn delete_remote(&mut self, remote: Remote);
+ fn push(&self, remote: Remote);
+ fn pull(&mut self, remote: Remote);
+
+ fn all_contributors(&self) -> Vec<ContributorId>;
+ fn contributor(&self, id: ContributorId) -> Contributor;
+ fn credit(&self, filename: &Path, byte: usize) -> Option<ContributorId>;
+
+ fn all_channels(&self) -> Vec<ChannelId>;
+ fn active_channel(&self) -> ChannelId;
+ fn channel(&self, id: ChannelId) -> Channel;
+ fn create_channel(&mut self, channel: Channel);
+ fn change_channel(&mut self, id: ChannelId) -> Channel;
+ fn rename_channel(&mut self, id: ChannelId, name: &str) -> Channel;
+ fn delete_channel(&mut self, id: ChannelId);
+ fn add_patches_to_channel(&mut self, channel: ChannelId, patches: &[PatchId]) -> Channel;
+ fn add_channel_to_channel(&mut self, channel: ChannelId, plus: ChannelId) -> Channel;
+
+ fn all_patches(&self) -> Vec<PatchId>;
+ fn active_patches(&self) -> Vec<PatchId>;
+ fn patch(&self, id: PatchId) -> Option<Patch>;
+ fn create_patch(&mut self, patch: Patch);
+ fn delete_patch(&mut self, id: PatchId);
+ fn set_active_patches(&mut self, ids: &[PatchId]);
+ fn combine_patches(&mut self, patches: &[PatchId]) -> Patch;
+
+ fn active_files(&self) -> Vec<FileId>;
+ fn file(&self, id: FileId) -> Option<FileInfo>;
+ fn write_file_from_patch(&self, id: FileId, patch: PatchId) -> Vec<u8>;
+
+ fn span(&self, id: SpanId) -> Option<Span>;
+}
diff --git a/src/workarea.rs b/src/workarea.rs
new file mode 100644
index 0000000..d5d2e13
--- /dev/null
+++ b/src/workarea.rs
@@ -0,0 +1,74 @@
+use std::fs::File;
+use std::io::{BufRead, BufReader};
+use std::ops::Deref;
+use std::path::{Path, PathBuf};
+use std::sync::Arc;
+use std::time::{Instant, SystemTime};
+
+use walkdir::WalkDir;
+
+pub struct IgnoreFile {
+ globs: Arc<[Arc<str>]>,
+}
+
+pub struct WorkFileMetadata {
+ name: Arc<Path>,
+ last_modified: Instant,
+}
+
+fn equal_to_path(a: impl AsRef<Path>, b: impl AsRef<Path>) -> bool {
+ a.as_ref() == b.as_ref()
+}
+pub fn included_files(
+ root: impl AsRef<Path>,
+ ignored_files: Option<&IgnoreFile>,
+ since: Option<SystemTime>,
+) -> std::io::Result<Vec<PathBuf>> {
+ let mut files = Vec::new();
+
+ let walker = WalkDir::new(root).into_iter().filter_entry(|entry| {
+ !equal_to_path(entry.path(), ".pj")
+ && !equal_to_path(entry.path(), ".ignore")
+ && entry
+ .metadata()
+ .ok()
+ .is_some_and(|metadata| metadata.is_file())
+ && ignored_files
+ .is_none_or(|file| !file.should_ignore(entry.path().to_string_lossy().deref()))
+ && since
+ .zip(entry.metadata().ok())
+ .and_then(|(since, metadata)| {
+ metadata.modified().ok().map(|modified| (since, modified))
+ })
+ .is_none_or(|(since, modified)| since < modified)
+ });
+
+ for entry in walker {
+ files.push(entry?.into_path());
+ }
+
+ Ok(files)
+}
+
+impl IgnoreFile {
+ fn open(root: impl AsRef<Path>) -> std::io::Result<Self> {
+ let mut globs = Vec::new();
+ globs.push(String::from(".pj/"));
+
+ let ignore_file = Path::join(root.as_ref(), ".ignore");
+ let ignore_file = BufReader::new(File::open(ignore_file)?);
+ for line in ignore_file.lines() {
+ globs.push(line?);
+ }
+
+ Ok(Self {
+ globs: globs.iter().map(|s| s.deref().into()).collect(),
+ })
+ }
+
+ fn should_ignore(&self, path: &str) -> bool {
+ self.globs
+ .iter()
+ .any(|glob| fast_glob::glob_match(&**glob, path))
+ }
+}