1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
#![doc = include_str!("../README.md")]

use std::{net::TcpStream, io::Write};
use lmfu::{json::{JsonFile, Path as JsonPath}, ArcStr};
pub use coolssh::{create_ed25519_keypair, dump_ed25519_pk_openssh, Error as SshError};

mod objectstore;
mod repository;
mod directory;
mod protocol;
mod packfile;
mod clone;
mod push;

pub use {
    repository::Repository, directory::{Mode, EntryType, FileType},
    clone::Reference, objectstore::Hash,
};

/// object store, directories, packfiles, git protocol
pub mod internals {
    pub(crate) use super::{
        TcpStream, Write, Remote, Result, Error, Repository,
        EntryType, FileType, Mode, Hash,
    };
    pub use {
        super::objectstore::{
            ObjectStore, Object, ObjectType, TreeIter, CommitParentsIter,
            CommitField, get_commit_field, get_commit_field_hash,
        },
        super::directory::{Directory, Path},
        super::protocol::{PacketLine, GitProtocol},
        super::packfile::{
            PackfileReader, PackfileObject, PackfileSender,
            dump_packfile_header, dump_packfile_object,
        },
    };
}

/// SSH & Remote Repository Settings
#[derive(Debug)]
pub struct Remote {
    /// `github.com:22`
    pub host: ArcStr,
    /// `git`
    pub username: ArcStr,
    /// `Username/Repository.git`
    pub path: ArcStr,
    /// Must be registered at the remote
    pub keypair: ArcStr,
}

impl Remote {
    pub fn new(
        host: ArcStr,
        username: ArcStr,
        path: ArcStr,
        keypair: ArcStr,
    ) -> Remote {
        Self {
            host,
            username,
            path,
            keypair,
        }
    }

    /// Reads remote access configuration from a [`JsonFile`]
    ///
    /// At `path`, the json file is expected to contain an
    /// object with the following keys:
    /// - `host`: SSH host (example: `github.com:22`)
    /// - `username`: SSH username (usually `git`)
    /// - `path`: path to the git repository
    /// - `keypair_hex`: 128-characters long hex-encoded key pair
    pub fn parse(json: &JsonFile, path: &JsonPath) -> core::result::Result<Self, &'static str> {
        let get = |prop, msg| json.get(&path.clone().i_str(prop)).as_string().ok_or(msg).cloned();
        let username = get("username", "Invalid username in json remote config")?;
        let keypair = get("keypair_hex", "Invalid keypair in json remote config")?;
        let host = get("host", "Invalid host in json remote config")?;
        let path = get("path", "Invalid path in json remote config")?;

        Ok(Self {
            host,
            username,
            path,
            keypair,
        })
    }
}

/// Errors that can occur during repository manipulation
#[derive(Copy, Clone, Debug)]
pub enum Error {
    SshError(SshError),
    DirtyWorkspace,
    InvalidObject,
    PathError,
    MissingObject,
    NoSuchReference,
    GitProtocolError,
    InvalidPackfile,
    MustForcePush,
    UnsupportedByRemote,
}

impl From<SshError> for Error {
    fn from(ssh_error: SshError) -> Self {
        Self::SshError(ssh_error)
    }
}

/// `Result<T, Error>`
type Result<T> = core::result::Result<T, Error>;