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"] }
git2 = "0.13.25"
notify = "4.0.16"
octocrab = "0.15"
regex = "1.5.4"
serde = "1.0"
serde_derive = "1.0.132"

View File

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

View File

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

View File

@ -1,15 +1,13 @@
use super::config;
use super::config::PathConfig;
use super::path;
use super::path::ResPathBuf;
use super::{config, config::PathConfig, path, path::ResPathBuf};
use notify::{DebouncedEvent, RecommendedWatcher, RecursiveMode, Watcher};
use std::collections::HashSet;
use std::error::Error;
use std::path::PathBuf;
use std::sync::mpsc::channel;
use std::sync::mpsc::{Receiver, Sender, TryRecvError};
use std::thread;
use std::time::Duration;
use std::{
collections::HashSet,
error::Error,
path::PathBuf,
sync::mpsc::{channel, Receiver, Sender, TryRecvError},
thread,
time::Duration,
};
// TODO(jrpotter): Add logging.
// 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::path;
use super::path::ResPathBuf;
use std::env::VarError;
use std::path::{Path, PathBuf};
use std::{error, fmt, fs, io, result};
use super::{config::PathConfig, path};
use git2::Repository;
use std::{env::VarError, error, fmt, fs, io, path::PathBuf, result};
// ========================================
// Error
@ -13,10 +10,18 @@ pub type Result<T> = result::Result<T, Error>;
#[derive(Debug)]
pub enum Error {
GitError(git2::Error),
IOError(io::Error),
NotHomesyncRepo,
VarError(VarError),
}
impl From<git2::Error> for Error {
fn from(err: git2::Error) -> Error {
Error::GitError(err)
}
}
impl From<io::Error> for Error {
fn from(err: io::Error) -> Error {
Error::IOError(err)
@ -41,7 +46,12 @@ impl From<path::Error> for Error {
impl fmt::Display for Error {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
match self {
Error::GitError(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),
}
}
@ -49,91 +59,94 @@ impl fmt::Display 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
// ========================================
// 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.
/// 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
/// the presence of a sentinel file `.homesync`). Otherwise we raise an error.
///
/// If there is no local repository but a remote is available, we clone it.
/// Otherwise we create a new, empty repository.
///
/// NOTE! This does not perform any syncing between local and remote. That
/// should be done as a specific command line request.
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!("")
/// NOTE! This does not perform any syncing between local and remote. In fact,
/// this method does not perform any validation on the remote.
pub fn init(config: &PathConfig) -> Result<git2::Repository> {
// Permit the use of environment variables within the local configuration
// path (e.g. `$HOME`). Unlike with resolution, we want to fail if the
// environment variable is not defined.
let expanded = match config.1.local.to_str() {
Some(s) => s,
None => Err(io::Error::new(
io::ErrorKind::InvalidInput,
"Could not local path to a UTF-8 encoded string.",
))?,
};
let expanded = path::expand_env(expanded)?;
// Attempt to open the local path as a git repository if possible. The
// `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 path::ResPathBuf;
use std::error::Error;
use std::io;
use std::{error::Error, io};
pub fn run_add(_config: PathConfig) -> Result<(), config::Error> {
// 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.",
)))?;
}
match config::load(&candidates) {
let config = match config::load(&candidates) {
// Check if we already have a local config somewhere. If so, reprompt
// the same configuration options and override the values present in the
// current YAML file.
Ok(loaded) => {
config::write(&loaded.0, Some(loaded.1))?;
Ok(())
}
Ok(loaded) => config::write(&loaded.0, Some(loaded.1))?,
// 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
// 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.
// Make directories if necessary.
Err(config::Error::MissingConfig) if !candidates.is_empty() => {
config::write(&candidates[0], None)?;
Ok(())
config::write(&candidates[0], None)?
}
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> {

View File

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

View File

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