From 3f5c5026909af023c8a3885a4a3d97c669302c1b Mon Sep 17 00:00:00 2001 From: Mel Date: Fri, 1 Oct 2021 00:34:16 +0200 Subject: Global repository cursor --- src/actions/create.rs | 12 ++++-- src/actions/shift.rs | 67 ++++++++++++++++-------------- src/actions/update.rs | 94 ++++++++++++++++++++++++++++++------------ src/files.rs | 20 +++++++-- src/history.rs | 112 ++++++++++++++++++++++++++++++++++++-------------- 5 files changed, 210 insertions(+), 95 deletions(-) (limited to 'src') diff --git a/src/actions/create.rs b/src/actions/create.rs index f00e95d..472cbe3 100644 --- a/src/actions/create.rs +++ b/src/actions/create.rs @@ -1,8 +1,8 @@ -use std::fs; +use std::fs::{self, File}; use anyhow::Result; -use crate::{actions::update, files::Locations}; +use crate::{actions::update, files::Locations, history::RepositoryHistory}; use super::ActionOptions; @@ -13,8 +13,12 @@ pub fn create(command_options: ActionOptions) -> Result<()> { fs::remove_dir_all(locations.ka_path.as_path())?; } - fs::create_dir(locations.ka_path)?; - fs::create_dir(locations.ka_files_path)?; + fs::create_dir(&locations.ka_path)?; + fs::create_dir(&locations.ka_files_path)?; + + let mut index_file = File::create(locations.get_repository_index())?; + let empty_history = RepositoryHistory::default(); + empty_history.write_to_file(&mut index_file)?; update(command_options)?; diff --git a/src/actions/shift.rs b/src/actions/shift.rs index 6880c20..0fbc233 100644 --- a/src/actions/shift.rs +++ b/src/actions/shift.rs @@ -1,56 +1,63 @@ use std::{ - fs, + fs::{self, OpenOptions}, io::{Seek, Write}, - path::Path, }; use anyhow::Result; use crate::{ - files::{Locations, FileState}, - history::FileHistory, + files::{FileState, Locations}, + history::{FileHistory, RepositoryHistory}, }; use super::ActionOptions; -pub fn shift(command_options: ActionOptions, path: &str, new_cursor: usize) -> Result<()> { +pub fn shift(command_options: ActionOptions, new_cursor: usize) -> Result<()> { let locations = Locations::from(&command_options); - match FileState::from_working(&locations, Path::new(path))? { - FileState::Tracked(tracked) => { - let mut history_file = tracked.load_history_file()?; + let repository_index_path = locations.get_repository_index(); + let mut repository_index_file = OpenOptions::new().write(true).open(repository_index_path)?; + let mut repository_history = RepositoryHistory::from_file(&mut repository_index_file)?; - let mut file_history = FileHistory::from_file(&mut history_file)?; - file_history.set_cursor(new_cursor); + repository_history.cursor = new_cursor; + repository_history.write_to_file(&mut repository_index_file)?; - if file_history.file_is_deleted() { - fs::remove_file(path)?; - } else { - let new_content = file_history.get_content(); - let mut working_file = tracked.create_working_file()?; + // TOOD: Only shift files which would be affected by cursor change - working_file.rewind()?; - working_file.set_len(0)?; + for state in locations.get_repository_files()? { + match state { + FileState::Tracked(tracked) => { + let mut history_file = tracked.load_history_file()?; - working_file.write_all(&new_content)?; - } + let file_history = FileHistory::from_file(&mut history_file)?; - file_history.write_to_file(&mut history_file)?; - } - FileState::Deleted(deleted) => { - let mut history_file = deleted.load_history_file()?; + if file_history.is_file_deleted(new_cursor) { + fs::remove_file(tracked.working_path)?; + } else { + let new_content = file_history.get_content(new_cursor); + let mut working_file = tracked.create_working_file()?; + + working_file.rewind()?; + working_file.set_len(0)?; + + working_file.write_all(&new_content)?; + } + } + FileState::Deleted(deleted) => { + let mut history_file = deleted.load_history_file()?; - let mut file_history = FileHistory::from_file(&mut history_file)?; - file_history.set_cursor(new_cursor); + let file_history = FileHistory::from_file(&mut history_file)?; - if !file_history.file_is_deleted() { - let mut new_working_file = deleted.create_working_file(&locations)?; - let new_content = file_history.get_content(); + if !file_history.is_file_deleted(new_cursor) { + let mut new_working_file = deleted.create_working_file(&locations)?; + let new_content = file_history.get_content(new_cursor); - new_working_file.write_all(&new_content)?; + new_working_file.write_all(&new_content)?; + } } + // TODO: What do we do with untracked files on a shift? Delete them? + _ => (), } - FileState::Untracked(_) => panic!("File is not tracked with Ka."), } Ok(()) diff --git a/src/actions/update.rs b/src/actions/update.rs index aa9c3a4..a9bc0d2 100644 --- a/src/actions/update.rs +++ b/src/actions/update.rs @@ -1,40 +1,78 @@ -use std::io::Read; +use std::{ + fs::{File, OpenOptions}, + io::Read, + time::SystemTime, +}; use anyhow::{Context, Result}; use crate::{ - files::{Locations, FileState}, - history::{FileChange, FileHistory}, diff::ContentChange, + files::{FileState, Locations}, + history::{FileChange, FileChangeVariant, FileHistory, RepositoryChange, RepositoryHistory}, }; use super::ActionOptions; pub fn update(command_options: ActionOptions) -> Result<()> { + let timestamp = SystemTime::now() + .duration_since(SystemTime::UNIX_EPOCH)? + .as_secs(); + let locations = Locations::from(&command_options); + let repository_index_path = locations.get_repository_index(); + let mut repository_index_file = OpenOptions::new() + .read(true) + .write(true) + .open(repository_index_path)?; + let mut repository_history = RepositoryHistory::from_file(&mut repository_index_file)?; + let entries = locations .get_repository_files() .context("Could not traverse files.")?; - for element in entries { - update_file(element, &locations)?; + let mut affected_files = Vec::new(); + + for state in entries { + let changed_file = get_new_history_for_file(repository_history.cursor, &state, &locations)?; + if let Some((mut history_file, new_file_history)) = changed_file { + new_file_history.write_to_file(&mut history_file)?; + affected_files.push(state.get_working_path(&locations)?); + } + } + + if affected_files.len() > 0 { + repository_history.add_change(RepositoryChange { + affected_files, + timestamp, + }); + repository_history.cursor += 1; + + repository_history.write_to_file(&mut repository_index_file)?; } Ok(()) } -fn update_file(file_state: FileState, locations: &Locations) -> Result<()> { - let new_state = match file_state { +fn get_new_history_for_file( + cursor: usize, + file_state: &FileState, + locations: &Locations, +) -> Result> { + match file_state { FileState::Deleted(deleted) => { let mut history_file = deleted.load_history_file()?; let file_history = FileHistory::from_file(&mut history_file)?; - if !file_history.file_is_deleted() { + if !file_history.is_file_deleted(cursor) { let mut new_history = file_history; - new_history.add_change(FileChange::Deleted); - Some((history_file, new_history)) + new_history.add_change(FileChange { + change_index: cursor + 1, + variant: FileChangeVariant::Deleted, + }); + Ok(Some((history_file, new_history))) } else { - None + Ok(None) } } FileState::Untracked(untracked) => { @@ -43,15 +81,21 @@ fn update_file(file_state: FileState, locations: &Locations) -> Result<()> { let mut file_content = Vec::new(); file.read_to_end(&mut file_content)?; - let change = FileChange::Updated(vec![ContentChange::Inserted { - at: 0, - new_content: file_content, - }]); + let change = FileChange { + change_index: cursor + 1, + variant: FileChangeVariant::Updated(vec![ContentChange::Inserted { + at: 0, + new_content: file_content, + }]), + }; let mut new_history = FileHistory::default(); new_history.add_change(change); - Some((untracked.create_history_file(locations)?, new_history)) + Ok(Some(( + untracked.create_history_file(locations)?, + new_history, + ))) } FileState::Tracked(tracked) => { let mut history_file = tracked.load_history_file()?; @@ -62,21 +106,19 @@ fn update_file(file_state: FileState, locations: &Locations) -> Result<()> { let mut new_content = Vec::new(); working_file.read_to_end(&mut new_content)?; - let old_content = file_history.get_content(); + let old_content = file_history.get_content(cursor); let changes = ContentChange::diff(&old_content, &new_content); + // TODO: Check if file was changed. + let mut new_history = file_history; - new_history.add_change(FileChange::Updated(changes)); - new_history.set_cursor(new_history.cursor() + 1); + new_history.add_change(FileChange { + change_index: cursor + 1, + variant: FileChangeVariant::Updated(changes), + }); - Some((history_file, new_history)) + Ok(Some((history_file, new_history))) } - }; - - if let Some((mut history_file, new_file_history)) = new_state { - new_file_history.write_to_file(&mut history_file)?; } - - Ok(()) } diff --git a/src/files.rs b/src/files.rs index 41dbad1..a2b1db4 100644 --- a/src/files.rs +++ b/src/files.rs @@ -15,6 +15,10 @@ pub struct Locations { } impl Locations { + pub fn get_repository_index(&self) -> PathBuf { + return self.ka_path.join("index") + } + pub fn get_repository_files(&self) -> Result, Error> { let working_entries = fs::read_dir(&self.repository_path) .context("Failed reading working file entries.")? @@ -124,10 +128,18 @@ impl FileState { }) }) } + + pub fn get_working_path(&self, locations: &Locations) -> Result { + match self { + FileState::Deleted(deleted) => locations.working_from_history(&deleted.history_path), + FileState::Untracked(untracked) => Ok(untracked.path.clone()), + FileState::Tracked(tracked) => Ok(tracked.working_path.clone()), + } + } } pub struct FileDeleted { - history_path: PathBuf, + pub history_path: PathBuf, } impl FileDeleted { @@ -146,7 +158,7 @@ impl FileDeleted { } pub struct FileUntracked { - path: PathBuf, + pub path: PathBuf, } impl FileUntracked { @@ -168,8 +180,8 @@ impl FileUntracked { } pub struct FileTracked { - history_path: PathBuf, - working_path: PathBuf, + pub history_path: PathBuf, + pub working_path: PathBuf, } impl FileTracked { diff --git a/src/history.rs b/src/history.rs index 3f5e43f..c474143 100644 --- a/src/history.rs +++ b/src/history.rs @@ -1,6 +1,7 @@ use std::{ fs::File, io::{Read, Seek, Write}, + path::PathBuf, }; use serde::{Deserialize, Serialize}; @@ -9,9 +10,52 @@ use anyhow::{Context, Result}; use crate::diff::ContentChange; +#[derive(Serialize, Deserialize, Debug)] +pub struct RepositoryHistory { + pub cursor: usize, + changes: Vec, +} + +impl RepositoryHistory { + pub fn from_file(file: &mut File) -> Result { + let mut buffer = Vec::new(); + file.read_to_end(&mut buffer) + .context("Failed reading repository history.")?; + + let repository_history = serde_json::from_slice::(&buffer); + repository_history.context("Corrupted repository history.") + } + + pub fn write_to_file(&self, file: &mut File) -> anyhow::Result<()> { + let encoded: Vec = serde_json::to_vec(self)?; + file.rewind()?; + file.set_len(0)?; + file.write_all(encoded.as_ref())?; + Ok(()) + } + + pub fn add_change(&mut self, change: RepositoryChange) { + self.changes.push(change); + } +} + +impl Default for RepositoryHistory { + fn default() -> Self { + Self { + cursor: 0, + changes: Vec::new(), + } + } +} + +#[derive(Serialize, Deserialize, Debug)] +pub struct RepositoryChange { + pub affected_files: Vec, + pub timestamp: u64, +} + #[derive(Serialize, Deserialize, Debug)] pub struct FileHistory { - cursor: usize, changes: Vec, } @@ -33,24 +77,30 @@ impl FileHistory { Ok(()) } - pub fn cursor(&self) -> usize { - self.cursor - } - - pub fn file_is_deleted(&self) -> bool { - match self.changes.last() { - Some(change) => match change { - FileChange::Deleted => true, - FileChange::Updated(_) => false, + pub fn is_file_deleted(&self, at_cursor: usize) -> bool { + match self + .changes + .iter() + .take_while(|c| c.change_index <= at_cursor) + .last() + { + Some(change) => match change.variant { + FileChangeVariant::Deleted => true, + FileChangeVariant::Updated(_) => false, }, None => false, } } - pub fn get_content(&self) -> Vec { + pub fn get_content(&self, at_cursor: usize) -> Vec { let mut buffer = Vec::new(); - for file_change in self.changes.iter().take(self.cursor + 1) { - if let FileChange::Updated(ref updated) = file_change { + + for file_change in self + .changes + .iter() + .take_while(|change| change.change_index <= at_cursor) + { + if let FileChangeVariant::Updated(ref updated) = file_change.variant { for change in updated.iter() { change.apply(&mut buffer) } @@ -61,18 +111,6 @@ impl FileHistory { buffer } - pub fn set_cursor(&mut self, new_cursor: usize) { - if new_cursor < self.changes.len() { - self.cursor = new_cursor; - } else { - panic!( - "Out-of-bounds cursor for file history: {}, can be at most {}", - new_cursor, - self.changes.len() - 1 - ); - } - } - pub fn add_change(&mut self, change: FileChange) { self.changes.push(change); } @@ -81,14 +119,19 @@ impl FileHistory { impl Default for FileHistory { fn default() -> Self { Self { - cursor: 0, changes: Vec::new(), } } } #[derive(Serialize, Deserialize, Debug)] -pub enum FileChange { +pub struct FileChange { + pub change_index: usize, + pub variant: FileChangeVariant, +} + +#[derive(Serialize, Deserialize, Debug)] +pub enum FileChangeVariant { Updated(Vec), Deleted, } @@ -107,19 +150,26 @@ mod tests { ]; let mut history = FileHistory::default(); - history.add_change(FileChange::Updated(Vec::new())); + + history.add_change(FileChange { + change_index: 0, + variant: FileChangeVariant::Updated(Vec::new()), + }); for old_index in 0..stages.len() - 1 { let old = stages[old_index].as_bytes(); let new = stages[old_index + 1].as_bytes(); let stage_difference = ContentChange::diff(old, new); - history.add_change(FileChange::Updated(stage_difference)); + + history.add_change(FileChange { + change_index: old_index + 1, + variant: FileChangeVariant::Updated(stage_difference), + }); } for index in 0..stages.len() { - history.set_cursor(index); - assert_eq!(stages[index].as_bytes(), history.get_content()); + assert_eq!(stages[index].as_bytes(), history.get_content(index)); } } } -- cgit 1.4.1