Add rough notes on syncing up a git repository.

pull/3/head
Joshua Potter 2021-12-31 22:49:34 -05:00
parent 17e2bf7a69
commit 223dfaf8a0
5 changed files with 179 additions and 27 deletions

View File

@ -1,13 +1,14 @@
use super::config::PathConfig; use super::config::PathConfig;
use super::path::ResPathBuf; use super::{config, git, path};
use super::{config, path};
use ansi_term::Colour::{Green, Yellow}; use ansi_term::Colour::{Green, Yellow};
use std::env::VarError; use std::env::VarError;
use std::io::Write; use std::io::Write;
use std::path::PathBuf; use std::path::PathBuf;
use std::{error, fmt, fs, io}; use std::{error, fmt, io};
use url::{ParseError, Url}; use url::{ParseError, Url};
// TODO(jrpotter): Use curses to make this module behave nicer.
// ======================================== // ========================================
// Error // Error
// ======================================== // ========================================
@ -28,6 +29,15 @@ impl From<config::Error> for Error {
} }
} }
impl From<git::Error> for Error {
fn from(err: git::Error) -> Error {
match err {
git::Error::IOError(e) => Error::IOError(e),
git::Error::VarError(e) => Error::VarError(e),
}
}
}
impl From<io::Error> for Error { impl From<io::Error> for Error {
fn from(err: io::Error) -> Error { fn from(err: io::Error) -> Error {
Error::IOError(err) Error::IOError(err)
@ -72,9 +82,7 @@ impl error::Error for Error {}
// Prompts // Prompts
// ======================================== // ========================================
// TODO(jrpotter): Use curses to make this module behave nicer. fn prompt_local(config: &PathConfig) -> Result<PathBuf> {
fn prompt_local(config: &PathConfig) -> Result<ResPathBuf> {
print!( print!(
"Local git repository <{}> (enter to continue): ", "Local git repository <{}> (enter to continue): ",
Yellow.paint( Yellow.paint(
@ -86,16 +94,9 @@ fn prompt_local(config: &PathConfig) -> Result<ResPathBuf> {
) )
); );
io::stdout().flush()?; io::stdout().flush()?;
let mut local = String::new(); let mut local = String::new();
io::stdin().read_line(&mut local)?; io::stdin().read_line(&mut local)?;
let expanded = PathBuf::from(path::expand_env(&local.trim())?); Ok(PathBuf::from(path::expand_env(&local.trim())?))
// We need to generate the directory beforehand to verify the path is
// actually valid. Worst case this leaves empty directories scattered in
// various locations after repeated initialization.
fs::create_dir_all(&expanded)?;
// Hard resolution should succeed now that the above directory was created.
Ok(path::resolve(&expanded)?)
} }
fn prompt_remote(config: &PathConfig) -> Result<Url> { fn prompt_remote(config: &PathConfig) -> Result<Url> {
@ -118,8 +119,12 @@ pub fn write_config(mut pending: PathConfig) -> Result<()> {
"Generating config at {}...\n", "Generating config at {}...\n",
Green.paint(pending.0.unresolved().display().to_string()) Green.paint(pending.0.unresolved().display().to_string())
); );
pending.1.local = Some(prompt_local(&pending)?); let local = prompt_local(&pending)?;
pending.1.remote = prompt_remote(&pending)?; let remote = prompt_remote(&pending)?;
// Try to initialize the local respository if we can.
let resolved = git::init(&local, &pending)?;
pending.1.local = Some(resolved);
pending.1.remote = remote;
pending.write()?; pending.write()?;
println!("\nFinished writing configuration file."); println!("\nFinished writing configuration file.");
Ok(()) Ok(())

View File

@ -13,6 +13,7 @@ use std::time::Duration;
// TODO(jrpotter): Add logging. // TODO(jrpotter): Add logging.
// TODO(jrpotter): Add pid file to only allow one daemon at a time. // TODO(jrpotter): Add pid file to only allow one daemon at a time.
// TODO(jrpotter): Sync files to local git repository.
// ======================================== // ========================================
// Polling // Polling

View File

@ -1,4 +1,145 @@
use super::config::PathConfig; use super::config::PathConfig;
use super::path;
use super::path::ResPathBuf;
use std::env::VarError;
use std::path::{Path, PathBuf};
use std::{error, fmt, fs, io, result};
// ========================================
// Error
// ========================================
pub type Result<T> = result::Result<T, Error>;
#[derive(Debug)]
pub enum Error {
IOError(io::Error),
VarError(VarError),
}
impl From<io::Error> for Error {
fn from(err: io::Error) -> Error {
Error::IOError(err)
}
}
impl From<VarError> for Error {
fn from(err: VarError) -> Error {
Error::VarError(err)
}
}
impl From<path::Error> for Error {
fn from(err: path::Error) -> Error {
match err {
path::Error::IOError(e) => Error::IOError(e),
path::Error::VarError(e) => Error::VarError(e),
}
}
}
impl fmt::Display for Error {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
match self {
Error::IOError(e) => write!(f, "{}", e),
Error::VarError(e) => write!(f, "{}", e),
}
}
}
impl error::Error for Error {}
// ========================================
// Validation
// ========================================
fn validate_is_file(path: &Path) -> Result<()> {
let metadata = fs::metadata(path)?;
if !metadata.is_file() {
// TODO(jrpotter): Use `IsADirectory` when stable.
Err(io::Error::new(
io::ErrorKind::Other,
format!("'{}' is not a file.", path.display()),
))?;
}
Ok(())
}
fn validate_is_dir(path: &Path) -> Result<()> {
let metadata = fs::metadata(path)?;
if !metadata.is_dir() {
// TODO(jrpotter): Use `NotADirectory` when stable.
Err(io::Error::new(
io::ErrorKind::Other,
format!("'{}' is not a directory.", path.display()),
))?;
}
Ok(())
}
pub fn validate_local(path: &Path) -> Result<()> {
let resolved = path::resolve(path)?;
validate_is_dir(resolved.as_ref())?;
let mut local: PathBuf = resolved.into();
local.push(".git");
path::resolve(&local).map_err(|_| {
io::Error::new(
io::ErrorKind::NotFound,
format!(
"Local directory '{}' is not a git repository.",
path.display()
),
)
})?;
validate_is_dir(local.as_ref())?;
local.pop();
local.push(".homesync");
path::resolve(&local).map_err(|_| {
io::Error::new(
io::ErrorKind::NotFound,
format!(
"Sentinel file '.homesync' missing from local repository '{}'.",
path.display()
),
)
})?;
validate_is_file(local.as_ref())?;
// TODO(jrpotter): Verify git repository is pointing to remote.
Ok(())
}
// ========================================
// Repository
// ========================================
fn _setup_repo(path: &Path) -> Result<()> {
match path.parent() {
Some(p) => fs::create_dir_all(p)?,
None => (),
};
let mut repo_dir = path.to_path_buf();
repo_dir.push(".homesync");
match path::soft_resolve(&repo_dir) {
// The path already exists. Verify we are working with a git respository
// with sentinel value.
Ok(Some(resolved)) => {
validate_local(resolved.as_ref())?;
}
// Path does not exist yet. If a remote path exists, we should clone it.
// Otherwise boot up a local repsoitory.
Ok(None) => {}
Err(e) => Err(e)?,
}
Ok(())
}
// ========================================
// Initialization
// ========================================
/// Sets up a local github repository all configuration files will be synced to. /// Sets up a local github repository all configuration files will be synced to.
/// We attempt to clone the remote repository in favor of building our own. /// We attempt to clone the remote repository in favor of building our own.
@ -11,6 +152,12 @@ use super::config::PathConfig;
/// ///
/// NOTE! This does not perform any syncing between local and remote. That /// NOTE! This does not perform any syncing between local and remote. That
/// should be done as a specific command line request. /// should be done as a specific command line request.
pub async fn init(_config: &PathConfig) { pub fn init(_path: &Path, _config: &PathConfig) -> Result<ResPathBuf> {
// TODO(jrpotter): Fill this out. // let repository = match Repository::clone(url, "/path/to/a/repo") {
// Ok(repo) => repo,
// Err(e) => panic!("failed to clone: {}", e),
// };
// Hard resolution should succeed now that the above directory was created.
// Ok(path::resolve(&expanded)?);
panic!("")
} }

View File

@ -60,6 +60,9 @@ fn dispatch(matches: clap::ArgMatches) -> Result<(), Box<dyn Error>> {
// used, even if one of higher priority is eventually defined. // used, even if one of higher priority is eventually defined.
subcommand => { subcommand => {
let config = homesync::config::load(&candidates)?; let config = homesync::config::load(&candidates)?;
if let Some(local) = &config.1.local {
homesync::git::validate_local(local.as_ref())?;
}
match subcommand { match subcommand {
Some(("add", _)) => Ok(homesync::run_add(config)?), Some(("add", _)) => Ok(homesync::run_add(config)?),
Some(("daemon", matches)) => { Some(("daemon", matches)) => {

View File

@ -3,14 +3,10 @@ use serde::de;
use serde::de::{Unexpected, Visitor}; use serde::de::{Unexpected, Visitor};
use serde::{Deserialize, Deserializer, Serialize, Serializer}; use serde::{Deserialize, Deserializer, Serialize, Serializer};
use std::env::VarError; use std::env::VarError;
use std::error;
use std::ffi::OsString; use std::ffi::OsString;
use std::fmt;
use std::hash::{Hash, Hasher}; use std::hash::{Hash, Hasher};
use std::path::{Component, Path, PathBuf}; use std::path::{Component, Path, PathBuf};
use std::result; use std::{env, error, fmt, io, result, str};
use std::str;
use std::{env, io};
// ======================================== // ========================================
// Error // Error
@ -87,14 +83,14 @@ impl From<ResPathBuf> for PathBuf {
} }
} }
impl AsRef<Path> for ResPathBuf { impl AsRef<PathBuf> for ResPathBuf {
fn as_ref(&self) -> &Path { fn as_ref(&self) -> &PathBuf {
&self.inner &self.inner
} }
} }
impl AsRef<PathBuf> for ResPathBuf { impl AsRef<Path> for ResPathBuf {
fn as_ref(&self) -> &PathBuf { fn as_ref(&self) -> &Path {
&self.inner &self.inner
} }
} }