Remove octocrab.

Setup initialization of local git repository.
pull/3/head
Joshua Potter 2022-01-01 12:10:57 -05:00
parent d4183f2b52
commit 2c95c43124
8 changed files with 146 additions and 129 deletions

View File

@ -14,7 +14,6 @@ ansi_term = "0.12.1"
clap = { version = "3.0.0-rc.9", features = ["derive"] } clap = { version = "3.0.0-rc.9", features = ["derive"] }
git2 = "0.13.25" git2 = "0.13.25"
notify = "4.0.16" notify = "4.0.16"
octocrab = "0.15"
regex = "1.5.4" regex = "1.5.4"
serde = "1.0" serde = "1.0"
serde_derive = "1.0.132" serde_derive = "1.0.132"

View File

@ -1,5 +1,5 @@
--- ---
local: "" local: $HOME/.homesync
remote: "https://github.com/jrpotter/home-config.git" remote: "https://github.com/jrpotter/home-config.git"
packages: packages:
homesync: homesync:

View File

@ -1,12 +1,13 @@
use super::path; use super::{path, path::ResPathBuf};
use super::path::ResPathBuf;
use ansi_term::Colour::{Green, Yellow}; use ansi_term::Colour::{Green, Yellow};
use serde_derive::{Deserialize, Serialize}; use serde_derive::{Deserialize, Serialize};
use std::collections::BTreeMap; use std::{
use std::env::VarError; collections::BTreeMap,
use std::io::Write; env::VarError,
use std::path::{Path, PathBuf}; error, fmt, fs, io,
use std::{error, fmt, fs, io}; io::Write,
path::{Path, PathBuf},
};
use url::{ParseError, Url}; use url::{ParseError, Url};
// ======================================== // ========================================
@ -210,7 +211,6 @@ pub fn write(path: &ResPathBuf, loaded: Option<Config>) -> Result<PathConfig> {
}, },
); );
generated.write()?; generated.write()?;
println!("\nFinished writing configuration file.");
Ok(generated) Ok(generated)
} }

View File

@ -1,15 +1,13 @@
use super::config; use super::{config, config::PathConfig, path, path::ResPathBuf};
use super::config::PathConfig;
use super::path;
use super::path::ResPathBuf;
use notify::{DebouncedEvent, RecommendedWatcher, RecursiveMode, Watcher}; use notify::{DebouncedEvent, RecommendedWatcher, RecursiveMode, Watcher};
use std::collections::HashSet; use std::{
use std::error::Error; collections::HashSet,
use std::path::PathBuf; error::Error,
use std::sync::mpsc::channel; path::PathBuf,
use std::sync::mpsc::{Receiver, Sender, TryRecvError}; sync::mpsc::{channel, Receiver, Sender, TryRecvError},
use std::thread; thread,
use std::time::Duration; 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.

View File

@ -1,9 +1,6 @@
use super::config::PathConfig; use super::{config::PathConfig, path};
use super::path; use git2::Repository;
use super::path::ResPathBuf; use std::{env::VarError, error, fmt, fs, io, path::PathBuf, result};
use std::env::VarError;
use std::path::{Path, PathBuf};
use std::{error, fmt, fs, io, result};
// ======================================== // ========================================
// Error // Error
@ -13,10 +10,18 @@ pub type Result<T> = result::Result<T, Error>;
#[derive(Debug)] #[derive(Debug)]
pub enum Error { pub enum Error {
GitError(git2::Error),
IOError(io::Error), IOError(io::Error),
NotHomesyncRepo,
VarError(VarError), VarError(VarError),
} }
impl From<git2::Error> for Error {
fn from(err: git2::Error) -> Error {
Error::GitError(err)
}
}
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)
@ -41,7 +46,12 @@ impl From<path::Error> for Error {
impl fmt::Display for Error { impl fmt::Display for Error {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
match self { match self {
Error::GitError(e) => write!(f, "{}", e),
Error::IOError(e) => write!(f, "{}", e), Error::IOError(e) => write!(f, "{}", e),
Error::NotHomesyncRepo => write!(
f,
"Local repository is not managed by `homesync`. Missing `.homesync` sentinel file."
),
Error::VarError(e) => write!(f, "{}", e), Error::VarError(e) => write!(f, "{}", e),
} }
} }
@ -49,91 +59,94 @@ impl fmt::Display for Error {
impl error::Error for Error {} impl error::Error for Error {}
// ========================================
// Validation
// ========================================
pub fn validate_local(path: &Path) -> Result<()> {
let resolved = path::resolve(path)?;
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()
),
)
})?;
path::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()
),
)
})?;
path::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 // Initialization
// ======================================== // ========================================
// All git error codes.
// TODO(jrpotter): Remove these once done needing to reference them.
// git2::ErrorCode::GenericError => panic!("generic"),
// git2::ErrorCode::NotFound => panic!("not_found"),
// git2::ErrorCode::Exists => panic!("exists"),
// git2::ErrorCode::Ambiguous => panic!("ambiguous"),
// git2::ErrorCode::BufSize => panic!("buf_size"),
// git2::ErrorCode::User => panic!("user"),
// git2::ErrorCode::BareRepo => panic!("bare_repo"),
// git2::ErrorCode::UnbornBranch => panic!("unborn_branch"),
// git2::ErrorCode::Unmerged => panic!("unmerged"),
// git2::ErrorCode::NotFastForward => panic!("not_fast_forward"),
// git2::ErrorCode::InvalidSpec => panic!("invalid_spec"),
// git2::ErrorCode::Conflict => panic!("conflict"),
// git2::ErrorCode::Locked => panic!("locked"),
// git2::ErrorCode::Modified => panic!("modified"),
// git2::ErrorCode::Auth => panic!("auth"),
// git2::ErrorCode::Certificate => panic!("certificate"),
// git2::ErrorCode::Applied => panic!("applied"),
// git2::ErrorCode::Peel => panic!("peel"),
// git2::ErrorCode::Eof => panic!("eof"),
// git2::ErrorCode::Invalid => panic!("invalid"),
// git2::ErrorCode::Uncommitted => panic!("uncommitted"),
// git2::ErrorCode::Directory => panic!("directory"),
// git2::ErrorCode::MergeConflict => panic!("merge_conflict"),
// git2::ErrorCode::HashsumMismatch => panic!("hashsum_mismatch"),
// git2::ErrorCode::IndexDirty => panic!("index_dirty"),
// git2::ErrorCode::ApplyFail => panic!("apply_fail"),
/// 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. /// If there does not exist a local repository at the requested location, we
/// attempt to make it.
/// ///
/// If a remote repository exists, we verify its managed by homesync (based on /// NOTE! This does not perform any syncing between local and remote. In fact,
/// the presence of a sentinel file `.homesync`). Otherwise we raise an error. /// this method does not perform any validation on the remote.
/// pub fn init(config: &PathConfig) -> Result<git2::Repository> {
/// If there is no local repository but a remote is available, we clone it. // Permit the use of environment variables within the local configuration
/// Otherwise we create a new, empty repository. // path (e.g. `$HOME`). Unlike with resolution, we want to fail if the
/// // environment variable is not defined.
/// NOTE! This does not perform any syncing between local and remote. That let expanded = match config.1.local.to_str() {
/// should be done as a specific command line request. Some(s) => s,
pub fn init(_path: &Path, _config: &PathConfig) -> Result<ResPathBuf> { None => Err(io::Error::new(
// let repository = match Repository::clone(url, "/path/to/a/repo") { io::ErrorKind::InvalidInput,
// Ok(repo) => repo, "Could not local path to a UTF-8 encoded string.",
// Err(e) => panic!("failed to clone: {}", e), ))?,
// }; };
// Hard resolution should succeed now that the above directory was created. let expanded = path::expand_env(expanded)?;
// Ok(path::resolve(&expanded)?); // Attempt to open the local path as a git repository if possible. The
panic!("") // `NotFound` error is thrown if:
//
// - the directory does not exist.
// - the directory is not git-initialized (i.e. has a valid `.git`
// subfolder).
// - the directory does not have appropriate permissions.
let local = match Repository::open(&expanded) {
Ok(repo) => Some(repo),
Err(e) => match e.code() {
git2::ErrorCode::NotFound => None,
_ => Err(e)?,
},
};
// Setup a sentinel file in the given repository. This is used for both
// ensuring any remote repositories are already managed by homesync and for
// storing any persisted configurations.
let mut sentinel = PathBuf::from(&expanded);
sentinel.push(".homesync");
match local {
Some(repo) => {
// Verify the given repository has a homesync sentinel file.
match path::validate_is_file(&sentinel) {
Ok(_) => (),
Err(_) => Err(Error::NotHomesyncRepo)?,
};
Ok(repo)
}
// If no local repository exists, we choose to just always initialize a
// new one instead of cloning from remote. Cloning has a separate set of
// issues that we need to resolve anyways (e.g. setting remote, pulling,
// managing possible merge conflicts, etc.).
None => {
println!("Creating new homesync repository.");
let repo = Repository::init(&expanded)?;
fs::File::create(sentinel)?;
Ok(repo)
}
}
} }

View File

@ -5,8 +5,7 @@ pub mod path;
use config::PathConfig; use config::PathConfig;
use path::ResPathBuf; use path::ResPathBuf;
use std::error::Error; use std::{error::Error, io};
use std::io;
pub fn run_add(_config: PathConfig) -> Result<(), config::Error> { pub fn run_add(_config: PathConfig) -> Result<(), config::Error> {
// TODO(jrpotter): Show $EDITOR that allows writing specific package. // TODO(jrpotter): Show $EDITOR that allows writing specific package.
@ -26,14 +25,11 @@ pub fn run_init(candidates: Vec<ResPathBuf>) -> Result<(), Box<dyn Error>> {
"No suitable config file found.", "No suitable config file found.",
)))?; )))?;
} }
match config::load(&candidates) { let config = match config::load(&candidates) {
// Check if we already have a local config somewhere. If so, reprompt // Check if we already have a local config somewhere. If so, reprompt
// the same configuration options and override the values present in the // the same configuration options and override the values present in the
// current YAML file. // current YAML file.
Ok(loaded) => { Ok(loaded) => config::write(&loaded.0, Some(loaded.1))?,
config::write(&loaded.0, Some(loaded.1))?;
Ok(())
}
// Otherwise create a new config file at the given location. We always // Otherwise create a new config file at the given location. We always
// assume we want to write to the first file in our priority list. If // assume we want to write to the first file in our priority list. If
// not, the user should specify which config they want to write using // not, the user should specify which config they want to write using
@ -41,11 +37,16 @@ pub fn run_init(candidates: Vec<ResPathBuf>) -> Result<(), Box<dyn Error>> {
// TODO(jrpotter): Verify I have permission to write at specified path. // TODO(jrpotter): Verify I have permission to write at specified path.
// Make directories if necessary. // Make directories if necessary.
Err(config::Error::MissingConfig) if !candidates.is_empty() => { Err(config::Error::MissingConfig) if !candidates.is_empty() => {
config::write(&candidates[0], None)?; config::write(&candidates[0], None)?
Ok(())
} }
Err(e) => Err(e)?, Err(e) => Err(e)?,
} };
// Verify (or create) our local and remote git repositories. The internal
// git library we chose to use employs async/await so let's wrap around a
// channel.
git::init(&config)?;
println!("Finished initialization.");
Ok(())
} }
pub fn run_list(config: PathConfig) -> Result<(), config::Error> { pub fn run_list(config: PathConfig) -> Result<(), config::Error> {

View File

@ -1,8 +1,6 @@
use clap::{App, AppSettings, Arg}; use clap::{App, AppSettings, Arg};
use homesync::path::ResPathBuf; use homesync::path::ResPathBuf;
use std::error::Error; use std::{error::Error, io, path::PathBuf};
use std::io;
use std::path::PathBuf;
fn main() { fn main() {
let matches = App::new("homesync") let matches = App::new("homesync")

View File

@ -1,12 +1,20 @@
use regex::Regex; use regex::Regex;
use serde::de; use serde::{
use serde::de::{Unexpected, Visitor}; de,
use serde::{Deserialize, Deserializer, Serialize, Serializer}; de::{Unexpected, Visitor},
use std::env::VarError; Deserialize, Deserializer, Serialize, Serializer,
use std::ffi::OsString; };
use std::hash::{Hash, Hasher}; use std::{
use std::path::{Component, Path, PathBuf}; env,
use std::{env, error, fmt, fs, io, result, str}; env::VarError,
error,
ffi::OsString,
fmt, fs,
hash::{Hash, Hasher},
io,
path::{Component, Path, PathBuf},
result, str,
};
// ======================================== // ========================================
// Error // Error