Add rough notes on syncing up a git repository.
parent
17e2bf7a69
commit
223dfaf8a0
37
src/cli.rs
37
src/cli.rs
|
@ -1,13 +1,14 @@
|
|||
use super::config::PathConfig;
|
||||
use super::path::ResPathBuf;
|
||||
use super::{config, path};
|
||||
use super::{config, git, path};
|
||||
use ansi_term::Colour::{Green, Yellow};
|
||||
use std::env::VarError;
|
||||
use std::io::Write;
|
||||
use std::path::PathBuf;
|
||||
use std::{error, fmt, fs, io};
|
||||
use std::{error, fmt, io};
|
||||
use url::{ParseError, Url};
|
||||
|
||||
// TODO(jrpotter): Use curses to make this module behave nicer.
|
||||
|
||||
// ========================================
|
||||
// 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 {
|
||||
fn from(err: io::Error) -> Error {
|
||||
Error::IOError(err)
|
||||
|
@ -72,9 +82,7 @@ impl error::Error for Error {}
|
|||
// Prompts
|
||||
// ========================================
|
||||
|
||||
// TODO(jrpotter): Use curses to make this module behave nicer.
|
||||
|
||||
fn prompt_local(config: &PathConfig) -> Result<ResPathBuf> {
|
||||
fn prompt_local(config: &PathConfig) -> Result<PathBuf> {
|
||||
print!(
|
||||
"Local git repository <{}> (enter to continue): ",
|
||||
Yellow.paint(
|
||||
|
@ -86,16 +94,9 @@ fn prompt_local(config: &PathConfig) -> Result<ResPathBuf> {
|
|||
)
|
||||
);
|
||||
io::stdout().flush()?;
|
||||
|
||||
let mut local = String::new();
|
||||
io::stdin().read_line(&mut local)?;
|
||||
let expanded = 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)?)
|
||||
Ok(PathBuf::from(path::expand_env(&local.trim())?))
|
||||
}
|
||||
|
||||
fn prompt_remote(config: &PathConfig) -> Result<Url> {
|
||||
|
@ -118,8 +119,12 @@ pub fn write_config(mut pending: PathConfig) -> Result<()> {
|
|||
"Generating config at {}...\n",
|
||||
Green.paint(pending.0.unresolved().display().to_string())
|
||||
);
|
||||
pending.1.local = Some(prompt_local(&pending)?);
|
||||
pending.1.remote = prompt_remote(&pending)?;
|
||||
let local = prompt_local(&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()?;
|
||||
println!("\nFinished writing configuration file.");
|
||||
Ok(())
|
||||
|
|
|
@ -13,6 +13,7 @@ use std::time::Duration;
|
|||
|
||||
// TODO(jrpotter): Add logging.
|
||||
// TODO(jrpotter): Add pid file to only allow one daemon at a time.
|
||||
// TODO(jrpotter): Sync files to local git repository.
|
||||
|
||||
// ========================================
|
||||
// Polling
|
||||
|
|
151
src/git.rs
151
src/git.rs
|
@ -1,4 +1,145 @@
|
|||
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.
|
||||
/// 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
|
||||
/// should be done as a specific command line request.
|
||||
pub async fn init(_config: &PathConfig) {
|
||||
// TODO(jrpotter): Fill this out.
|
||||
pub fn init(_path: &Path, _config: &PathConfig) -> Result<ResPathBuf> {
|
||||
// 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!("")
|
||||
}
|
||||
|
|
|
@ -60,6 +60,9 @@ fn dispatch(matches: clap::ArgMatches) -> Result<(), Box<dyn Error>> {
|
|||
// used, even if one of higher priority is eventually defined.
|
||||
subcommand => {
|
||||
let config = homesync::config::load(&candidates)?;
|
||||
if let Some(local) = &config.1.local {
|
||||
homesync::git::validate_local(local.as_ref())?;
|
||||
}
|
||||
match subcommand {
|
||||
Some(("add", _)) => Ok(homesync::run_add(config)?),
|
||||
Some(("daemon", matches)) => {
|
||||
|
|
14
src/path.rs
14
src/path.rs
|
@ -3,14 +3,10 @@ use serde::de;
|
|||
use serde::de::{Unexpected, Visitor};
|
||||
use serde::{Deserialize, Deserializer, Serialize, Serializer};
|
||||
use std::env::VarError;
|
||||
use std::error;
|
||||
use std::ffi::OsString;
|
||||
use std::fmt;
|
||||
use std::hash::{Hash, Hasher};
|
||||
use std::path::{Component, Path, PathBuf};
|
||||
use std::result;
|
||||
use std::str;
|
||||
use std::{env, io};
|
||||
use std::{env, error, fmt, io, result, str};
|
||||
|
||||
// ========================================
|
||||
// Error
|
||||
|
@ -87,14 +83,14 @@ impl From<ResPathBuf> for PathBuf {
|
|||
}
|
||||
}
|
||||
|
||||
impl AsRef<Path> for ResPathBuf {
|
||||
fn as_ref(&self) -> &Path {
|
||||
impl AsRef<PathBuf> for ResPathBuf {
|
||||
fn as_ref(&self) -> &PathBuf {
|
||||
&self.inner
|
||||
}
|
||||
}
|
||||
|
||||
impl AsRef<PathBuf> for ResPathBuf {
|
||||
fn as_ref(&self) -> &PathBuf {
|
||||
impl AsRef<Path> for ResPathBuf {
|
||||
fn as_ref(&self) -> &Path {
|
||||
&self.inner
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue