WIP: Add github integration

This commit is contained in:
2022-01-23 16:33:10 +01:00
parent 7e673200c8
commit 936e2bdba8
7 changed files with 969 additions and 102 deletions

363
Cargo.lock generated
View File

@@ -11,6 +11,17 @@ dependencies = [
"memchr", "memchr",
] ]
[[package]]
name = "async-channel"
version = "1.6.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2114d64672151c0c5eaa5e131ec84a74f06e1e559830dabba01ca30605d66319"
dependencies = [
"concurrent-queue",
"event-listener",
"futures-core",
]
[[package]] [[package]]
name = "atty" name = "atty"
version = "0.2.14" version = "0.2.14"
@@ -34,6 +45,24 @@ version = "1.3.2"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a"
[[package]]
name = "bytes"
version = "1.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c4872d67bab6358e59559027aa3b9157c53d9358c51423c17554809a8858e0f8"
[[package]]
name = "cache-padded"
version = "1.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c1db59621ec70f09c5e9b597b220c7a2b43611f4710dc03ceb8748637775692c"
[[package]]
name = "castaway"
version = "0.1.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a2698f953def977c68f935bb0dfa959375ad4638570e969e2f1e9f433cbf1af6"
[[package]] [[package]]
name = "cc" name = "cc"
version = "1.0.72" version = "1.0.72"
@@ -91,6 +120,15 @@ dependencies = [
"unicode-width", "unicode-width",
] ]
[[package]]
name = "concurrent-queue"
version = "1.2.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "30ed07550be01594c6026cff2a1d7fe9c8f683caa798e12b68694ac9e88286a3"
dependencies = [
"cache-padded",
]
[[package]] [[package]]
name = "console" name = "console"
version = "0.15.0" version = "0.15.0"
@@ -106,6 +144,16 @@ dependencies = [
"winapi", "winapi",
] ]
[[package]]
name = "crossbeam-utils"
version = "0.8.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cfcae03edb34f947e64acdb1c33ec169824e20657e9ecb61cef6c8c74dcb8120"
dependencies = [
"cfg-if",
"lazy_static",
]
[[package]] [[package]]
name = "crossterm" name = "crossterm"
version = "0.22.1" version = "0.22.1"
@@ -131,6 +179,37 @@ dependencies = [
"winapi", "winapi",
] ]
[[package]]
name = "curl"
version = "0.4.42"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7de97b894edd5b5bcceef8b78d7da9b75b1d2f2f9a910569d0bde3dd31d84939"
dependencies = [
"curl-sys",
"libc",
"openssl-probe",
"openssl-sys",
"schannel",
"socket2",
"winapi",
]
[[package]]
name = "curl-sys"
version = "0.4.52+curl-7.81.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "14b8c2d1023ea5fded5b7b892e4b8e95f70038a421126a056761a84246a28971"
dependencies = [
"cc",
"libc",
"libnghttp2-sys",
"libz-sys",
"openssl-sys",
"pkg-config",
"vcpkg",
"winapi",
]
[[package]] [[package]]
name = "dirs-next" name = "dirs-next"
version = "2.0.0" version = "2.0.0"
@@ -158,6 +237,36 @@ version = "0.3.6"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a357d28ed41a50f9c765dbfe56cbc04a64e53e5fc58ba79fbc34c10ef3df831f" checksum = "a357d28ed41a50f9c765dbfe56cbc04a64e53e5fc58ba79fbc34c10ef3df831f"
[[package]]
name = "encoding_rs"
version = "0.8.30"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7896dc8abb250ffdda33912550faa54c88ec8b998dec0b2c55ab224921ce11df"
dependencies = [
"cfg-if",
]
[[package]]
name = "event-listener"
version = "2.5.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "77f3309417938f28bf8228fcff79a4a37103981e3e186d2ccd19c74b38f4eb71"
[[package]]
name = "fastrand"
version = "1.7.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c3fcf0cee53519c866c09b5de1f6c56ff9d647101f81c1964fa632e148896cdf"
dependencies = [
"instant",
]
[[package]]
name = "fnv"
version = "1.0.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1"
[[package]] [[package]]
name = "form_urlencoded" name = "form_urlencoded"
version = "1.0.1" version = "1.0.1"
@@ -174,6 +283,33 @@ version = "0.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a06f77d526c1a601b7c4cdd98f54b5eaabffc14d5f2f0296febdc7f357c6d3ba" checksum = "a06f77d526c1a601b7c4cdd98f54b5eaabffc14d5f2f0296febdc7f357c6d3ba"
[[package]]
name = "futures-core"
version = "0.3.19"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d0c8ff0461b82559810cdccfde3215c3f373807f5e5232b71479bff7bb2583d7"
[[package]]
name = "futures-io"
version = "0.3.19"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b1f9d34af5a1aac6fb380f735fe510746c38067c5bf16c7fd250280503c971b2"
[[package]]
name = "futures-lite"
version = "1.12.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7694489acd39452c77daa48516b894c153f192c3578d5a839b62c58099fcbf48"
dependencies = [
"fastrand",
"futures-core",
"futures-io",
"memchr",
"parking",
"pin-project-lite",
"waker-fn",
]
[[package]] [[package]]
name = "getrandom" name = "getrandom"
version = "0.2.4" version = "0.2.4"
@@ -193,8 +329,11 @@ dependencies = [
"comfy-table", "comfy-table",
"console", "console",
"git2", "git2",
"isahc",
"parse_link_header",
"regex", "regex",
"serde", "serde",
"serde_json",
"serde_yaml", "serde_yaml",
"shellexpand", "shellexpand",
"tempdir", "tempdir",
@@ -246,6 +385,17 @@ dependencies = [
"libc", "libc",
] ]
[[package]]
name = "http"
version = "0.2.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "31f4c6746584866f0feabcc69893c5b51beef3831656a968ed7ae254cdc4fd03"
dependencies = [
"bytes",
"fnv",
"itoa",
]
[[package]] [[package]]
name = "idna" name = "idna"
version = "0.2.3" version = "0.2.3"
@@ -276,6 +426,41 @@ dependencies = [
"cfg-if", "cfg-if",
] ]
[[package]]
name = "isahc"
version = "1.6.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d140e84730d325378912ede32d7cd53ef1542725503b3353e5ec8113c7c6f588"
dependencies = [
"async-channel",
"castaway",
"crossbeam-utils",
"curl",
"curl-sys",
"encoding_rs",
"event-listener",
"futures-lite",
"http",
"log",
"mime",
"once_cell",
"polling",
"serde",
"serde_json",
"slab",
"sluice",
"tracing",
"tracing-futures",
"url",
"waker-fn",
]
[[package]]
name = "itoa"
version = "1.0.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1aab8fc367588b89dcee83ab0fd66b72b50b72fa1904d7095045ace2b0c81c35"
[[package]] [[package]]
name = "jobserver" name = "jobserver"
version = "0.1.24" version = "0.1.24"
@@ -311,6 +496,16 @@ dependencies = [
"pkg-config", "pkg-config",
] ]
[[package]]
name = "libnghttp2-sys"
version = "0.1.7+1.45.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "57ed28aba195b38d5ff02b9170cbff627e336a20925e43b4945390401c5dc93f"
dependencies = [
"cc",
"libc",
]
[[package]] [[package]]
name = "libssh2-sys" name = "libssh2-sys"
version = "0.2.23" version = "0.2.23"
@@ -373,6 +568,12 @@ version = "2.4.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "308cc39be01b73d0d18f82a0e7b2a3df85245f84af96fdddc5d202d27e47b86a" checksum = "308cc39be01b73d0d18f82a0e7b2a3df85245f84af96fdddc5d202d27e47b86a"
[[package]]
name = "mime"
version = "0.3.16"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2a60c7ce501c71e03a9c9c0d35b861413ae925bd979cc7a4e30d060069aaac8d"
[[package]] [[package]]
name = "mio" name = "mio"
version = "0.7.14" version = "0.7.14"
@@ -438,6 +639,12 @@ dependencies = [
"memchr", "memchr",
] ]
[[package]]
name = "parking"
version = "2.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "427c3892f9e783d91cc128285287e70a59e206ca452770ece88a76f7a3eddd72"
[[package]] [[package]]
name = "parking_lot" name = "parking_lot"
version = "0.11.2" version = "0.11.2"
@@ -463,18 +670,68 @@ dependencies = [
"winapi", "winapi",
] ]
[[package]]
name = "parse_link_header"
version = "0.3.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "40728c9c01de984c45f49385ab054fdc31cd3322658a6934347887e72cb48df9"
dependencies = [
"http",
"lazy_static",
"regex",
]
[[package]] [[package]]
name = "percent-encoding" name = "percent-encoding"
version = "2.1.0" version = "2.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d4fd5641d01c8f18a23da7b6fe29298ff4b55afcccdf78973b24cf3175fee32e" checksum = "d4fd5641d01c8f18a23da7b6fe29298ff4b55afcccdf78973b24cf3175fee32e"
[[package]]
name = "pin-project"
version = "1.0.10"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "58ad3879ad3baf4e44784bc6a718a8698867bb991f8ce24d1bcbe2cfb4c3a75e"
dependencies = [
"pin-project-internal",
]
[[package]]
name = "pin-project-internal"
version = "1.0.10"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "744b6f092ba29c3650faf274db506afd39944f48420f6c86b17cfe0ee1cb36bb"
dependencies = [
"proc-macro2",
"quote",
"syn",
]
[[package]]
name = "pin-project-lite"
version = "0.2.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e280fbe77cc62c91527259e9442153f4688736748d24660126286329742b4c6c"
[[package]] [[package]]
name = "pkg-config" name = "pkg-config"
version = "0.3.24" version = "0.3.24"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "58893f751c9b0412871a09abd62ecd2a00298c6c83befa223ef98c52aef40cbe" checksum = "58893f751c9b0412871a09abd62ecd2a00298c6c83befa223ef98c52aef40cbe"
[[package]]
name = "polling"
version = "2.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "685404d509889fade3e86fe3a5803bca2ec09b0c0778d5ada6ec8bf7a8de5259"
dependencies = [
"cfg-if",
"libc",
"log",
"wepoll-ffi",
"winapi",
]
[[package]] [[package]]
name = "proc-macro-error" name = "proc-macro-error"
version = "1.0.4" version = "1.0.4"
@@ -605,6 +862,16 @@ version = "1.0.9"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "73b4b750c782965c211b42f022f59af1fbceabdd026623714f104152f1ec149f" checksum = "73b4b750c782965c211b42f022f59af1fbceabdd026623714f104152f1ec149f"
[[package]]
name = "schannel"
version = "0.1.19"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8f05ba609c234e60bee0d547fe94a4c7e9da733d1c962cf6e59efa4cd9c8bc75"
dependencies = [
"lazy_static",
"winapi",
]
[[package]] [[package]]
name = "scopeguard" name = "scopeguard"
version = "1.1.0" version = "1.1.0"
@@ -631,6 +898,17 @@ dependencies = [
"syn", "syn",
] ]
[[package]]
name = "serde_json"
version = "1.0.78"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d23c1ba4cf0efd44be32017709280b32d1cea5c3f1275c3b6d9e8bc54f758085"
dependencies = [
"itoa",
"ryu",
"serde",
]
[[package]] [[package]]
name = "serde_yaml" name = "serde_yaml"
version = "0.8.23" version = "0.8.23"
@@ -682,12 +960,39 @@ dependencies = [
"libc", "libc",
] ]
[[package]]
name = "slab"
version = "0.4.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9def91fd1e018fe007022791f865d0ccc9b3a0d5001e01aabb8b40e46000afb5"
[[package]]
name = "sluice"
version = "0.5.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6d7400c0eff44aa2fcb5e31a5f24ba9716ed90138769e4977a2ba6014ae63eb5"
dependencies = [
"async-channel",
"futures-core",
"futures-io",
]
[[package]] [[package]]
name = "smallvec" name = "smallvec"
version = "1.8.0" version = "1.8.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f2dd574626839106c320a323308629dcb1acfc96e32a8cba364ddc61ac23ee83" checksum = "f2dd574626839106c320a323308629dcb1acfc96e32a8cba364ddc61ac23ee83"
[[package]]
name = "socket2"
version = "0.4.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0f82496b90c36d70af5fcd482edaa2e0bd16fade569de1330405fecbbdac736b"
dependencies = [
"libc",
"winapi",
]
[[package]] [[package]]
name = "strsim" name = "strsim"
version = "0.10.0" version = "0.10.0"
@@ -782,6 +1087,49 @@ dependencies = [
"serde", "serde",
] ]
[[package]]
name = "tracing"
version = "0.1.29"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "375a639232caf30edfc78e8d89b2d4c375515393e7af7e16f01cd96917fb2105"
dependencies = [
"cfg-if",
"log",
"pin-project-lite",
"tracing-attributes",
"tracing-core",
]
[[package]]
name = "tracing-attributes"
version = "0.1.18"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f4f480b8f81512e825f337ad51e94c1eb5d3bbdf2b363dcd01e2b19a9ffe3f8e"
dependencies = [
"proc-macro2",
"quote",
"syn",
]
[[package]]
name = "tracing-core"
version = "0.1.21"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1f4ed65637b8390770814083d20756f87bfa2c21bf2f110babdc5438351746e4"
dependencies = [
"lazy_static",
]
[[package]]
name = "tracing-futures"
version = "0.2.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "97d095ae15e245a057c8e8451bab9b3ee1e1f68e9ba2b4fbc18d0ac5237835f2"
dependencies = [
"pin-project",
"tracing",
]
[[package]] [[package]]
name = "unicode-bidi" name = "unicode-bidi"
version = "0.3.7" version = "0.3.7"
@@ -839,12 +1187,27 @@ version = "0.9.4"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f" checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f"
[[package]]
name = "waker-fn"
version = "1.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9d5b2c62b4012a3e1eca5a7e077d13b3bf498c4073e33ccd58626607748ceeca"
[[package]] [[package]]
name = "wasi" name = "wasi"
version = "0.10.2+wasi-snapshot-preview1" version = "0.10.2+wasi-snapshot-preview1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fd6fbd9a79829dd1ad0cc20627bf1ed606756a7f77edff7b66b7064f9cb327c6" checksum = "fd6fbd9a79829dd1ad0cc20627bf1ed606756a7f77edff7b66b7064f9cb327c6"
[[package]]
name = "wepoll-ffi"
version = "0.1.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d743fdedc5c64377b5fc2bc036b01c7fd642205a0d96356034ae3404d49eb7fb"
dependencies = [
"cc",
]
[[package]] [[package]]
name = "winapi" name = "winapi"
version = "0.3.9" version = "0.3.9"

View File

@@ -65,5 +65,15 @@ version = "=5.0.0"
[dependencies.serde_yaml] [dependencies.serde_yaml]
version = "=0.8.23" version = "=0.8.23"
[dependencies.serde_json]
version = "=1.0.78"
[dependencies.isahc]
version = "=1.6.0"
features = ["json"]
[dependencies.parse_link_header]
version = "=0.3.2"
[dev-dependencies.tempdir] [dev-dependencies.tempdir]
version = "=0.3.7" version = "=0.3.7"

View File

@@ -31,20 +31,94 @@ pub struct Repos {
#[derive(Parser)] #[derive(Parser)]
pub enum ReposAction { pub enum ReposAction {
#[clap( #[clap(subcommand)]
visible_alias = "run", Sync(SyncAction),
about = "Synchronize the repositories to the configured values" #[clap(subcommand)]
)] Find(FindAction),
Sync(Sync),
#[clap(about = "Generate a repository configuration from an existing file tree")]
Find(Find),
#[clap(about = "Show status of configured repositories")] #[clap(about = "Show status of configured repositories")]
Status(OptionalConfig), Status(OptionalConfig),
} }
#[derive(Parser)]
#[clap(about = "Sync local repositories with a configured list")]
pub enum SyncAction {
#[clap(
visible_alias = "run",
about = "Synchronize the repositories to the configured values"
)]
Config(Config),
#[clap(about = "Synchronize the repositories from a remote provider")]
Remote(Remote),
}
#[derive(Parser)]
#[clap(about = "Generate a repository configuration from existing repositories")]
pub enum FindAction {
#[clap(about = "Find local repositories")]
Local(FindLocalArgs),
#[clap(about = "Find repositories on remote provider")]
Remote(FindRemoteArgs),
}
#[derive(Parser)]
pub struct FindLocalArgs {
#[clap(help = "The path to search through")]
pub path: String,
#[clap(
arg_enum,
short,
long,
help = "Format to produce",
default_value_t = ConfigFormat::Toml,
)]
pub format: ConfigFormat,
}
#[derive(Parser)] #[derive(Parser)]
#[clap()] #[clap()]
pub struct Sync { pub struct FindRemoteArgs {
#[clap(arg_enum, short, long, help = "Remote provider to use")]
pub provider: RemoteProvider,
#[clap(
multiple_occurrences = true,
name = "user",
long,
help = "Users to get repositories from"
)]
pub users: Vec<String>,
#[clap(
multiple_occurrences = true,
name = "group",
long,
help = "Groups to get repositories from"
)]
pub groups: Vec<String>,
#[clap(long, help = "Get repositories that belong to the requesting user")]
pub owner: bool,
#[clap(long, help = "Command to get API token")]
pub token_command: String,
#[clap(
arg_enum,
short,
long,
help = "Format to produce",
default_value_t = ConfigFormat::Toml,
)]
pub format: ConfigFormat,
#[clap(long, help = "Root of the repo tree to produce")]
pub root: String,
}
#[derive(Parser)]
#[clap()]
pub struct Config {
#[clap( #[clap(
short, short,
long, long,
@@ -54,6 +128,40 @@ pub struct Sync {
pub config: String, pub config: String,
} }
#[derive(clap::ArgEnum, Clone)]
pub enum RemoteProvider {
Github,
}
#[derive(Parser)]
#[clap()]
pub struct Remote {
#[clap(arg_enum, short, long, help = "Remote provider to use")]
pub provider: RemoteProvider,
#[clap(
multiple_occurrences = true,
name = "user",
long,
help = "Users to get repositories from"
)]
pub users: Vec<String>,
#[clap(
multiple_occurrences = true,
name = "group",
long,
help = "Groups to get repositories from"
)]
pub groups: Vec<String>,
#[clap(long, help = "Get repositories that belong to the requesting user")]
pub owner: bool,
#[clap(long, help = "Command to get API token")]
pub token_command: String,
}
#[derive(Parser)] #[derive(Parser)]
#[clap()] #[clap()]
pub struct OptionalConfig { pub struct OptionalConfig {
@@ -67,21 +175,6 @@ pub enum ConfigFormat {
Toml, Toml,
} }
#[derive(Parser)]
pub struct Find {
#[clap(help = "The path to search through")]
pub path: String,
#[clap(
arg_enum,
short,
long,
help = "Format to produce",
default_value_t = ConfigFormat::Toml,
)]
pub format: ConfigFormat,
}
#[derive(Parser)] #[derive(Parser)]
pub struct Worktree { pub struct Worktree {
#[clap(subcommand, name = "action")] #[clap(subcommand, name = "action")]

View File

@@ -5,6 +5,7 @@ mod cmd;
use grm::config; use grm::config;
use grm::output::*; use grm::output::*;
use grm::provider::Provider;
use grm::repo; use grm::repo;
fn main() { fn main() {
@@ -12,8 +13,9 @@ fn main() {
match opts.subcmd { match opts.subcmd {
cmd::SubCommand::Repos(repos) => match repos.action { cmd::SubCommand::Repos(repos) => match repos.action {
cmd::ReposAction::Sync(sync) => { cmd::ReposAction::Sync(sync) => match sync {
let config = match config::read_config(&sync.config) { cmd::SyncAction::Config(args) => {
let config = match config::read_config(&args.config) {
Ok(config) => config, Ok(config) => config,
Err(error) => { Err(error) => {
print_error(&error); print_error(&error);
@@ -32,6 +34,67 @@ fn main() {
} }
} }
} }
cmd::SyncAction::Remote(args) => {
let users = if args.users.is_empty() {
None
} else {
Some(args.users)
};
let groups = if args.groups.is_empty() {
None
} else {
Some(args.groups)
};
let token_process = std::process::Command::new("/usr/bin/env")
.arg("sh")
.arg("-c")
.arg(args.token_command)
.output();
let token: String = match token_process {
Err(error) => {
print_error(&format!("Failed to run token-command: {}", error));
process::exit(1);
}
Ok(output) => {
let stderr = String::from_utf8(output.stderr).unwrap();
let stdout = String::from_utf8(output.stdout).unwrap();
if !output.status.success() {
if !stderr.is_empty() {
print_error(&format!("Token command failed: {}", stderr));
} else {
print_error("Token command failed.");
}
}
if !stderr.is_empty() {
print_error(&format!("Token command produced stderr: {}", stderr));
}
if stdout.is_empty() {
print_error("Token command did not produce output");
}
let token = stdout.split('\n').next().unwrap();
token.to_string()
}
};
let filter = grm::provider::Filter::new(users, groups, args.owner);
let github = grm::provider::Github::new(filter, token);
match github.get_repos() {
Ok(repos) => println!("{:?}", repos),
Err(error) => {
print_error(&format!("Error: {}", error));
process::exit(1);
}
}
}
},
cmd::ReposAction::Status(args) => match &args.config { cmd::ReposAction::Status(args) => match &args.config {
Some(config_path) => { Some(config_path) => {
let config = match config::read_config(config_path) { let config = match config::read_config(config_path) {
@@ -79,8 +142,9 @@ fn main() {
} }
} }
}, },
cmd::ReposAction::Find(find) => { cmd::ReposAction::Find(find) => match find {
let path = Path::new(&find.path); cmd::FindAction::Local(args) => {
let path = Path::new(&args.path);
if !path.exists() { if !path.exists() {
print_error(&format!("Path \"{}\" does not exist", path.display())); print_error(&format!("Path \"{}\" does not exist", path.display()));
process::exit(1); process::exit(1);
@@ -119,7 +183,7 @@ fn main() {
} else { } else {
let config = trees.to_config(); let config = trees.to_config();
match find.format { match args.format {
cmd::ConfigFormat::Toml => { cmd::ConfigFormat::Toml => {
let toml = match config.as_toml() { let toml = match config.as_toml() {
Ok(toml) => toml, Ok(toml) => toml,
@@ -152,6 +216,113 @@ fn main() {
print_warning(&warning); print_warning(&warning);
} }
} }
cmd::FindAction::Remote(args) => {
let users = if args.users.is_empty() {
None
} else {
Some(args.users)
};
let groups = if args.groups.is_empty() {
None
} else {
Some(args.groups)
};
let token_process = std::process::Command::new("/usr/bin/env")
.arg("sh")
.arg("-c")
.arg(args.token_command)
.output();
let token: String = match token_process {
Err(error) => {
print_error(&format!("Failed to run token-command: {}", error));
process::exit(1);
}
Ok(output) => {
let stderr = String::from_utf8(output.stderr).unwrap();
let stdout = String::from_utf8(output.stdout).unwrap();
if !output.status.success() {
if !stderr.is_empty() {
print_error(&format!("Token command failed: {}", stderr));
} else {
print_error("Token command failed.");
}
}
if !stderr.is_empty() {
print_error(&format!("Token command produced stderr: {}", stderr));
}
if stdout.is_empty() {
print_error("Token command did not produce output");
}
let token = stdout.split('\n').next().unwrap();
token.to_string()
}
};
let filter = grm::provider::Filter::new(users, groups, args.owner);
let github = grm::provider::Github::new(filter, token);
match github.get_repos() {
Ok(repos) => {
let mut trees: Vec<config::Tree> = vec![];
for (namespace, repolist) in repos {
let tree = config::Tree {
root: Path::new(&args.root)
.join(namespace)
.display()
.to_string(),
repos: Some(repolist),
};
trees.push(tree);
}
let config = config::Config {
trees: config::Trees::from_vec(trees),
};
match args.format {
cmd::ConfigFormat::Toml => {
let toml = match config.as_toml() {
Ok(toml) => toml,
Err(error) => {
print_error(&format!(
"Failed converting config to TOML: {}",
&error
));
process::exit(1);
}
};
print!("{}", toml);
}
cmd::ConfigFormat::Yaml => {
let yaml = match config.as_yaml() {
Ok(yaml) => yaml,
Err(error) => {
print_error(&format!(
"Failed converting config to YAML: {}",
&error
));
process::exit(1);
}
};
print!("{}", yaml);
}
}
}
Err(error) => {
print_error(&format!("Error: {}", error));
process::exit(1);
}
}
}
},
}, },
cmd::SubCommand::Worktree(args) => { cmd::SubCommand::Worktree(args) => {
let cwd = std::env::current_dir().unwrap_or_else(|error| { let cwd = std::env::current_dir().unwrap_or_else(|error| {

View File

@@ -6,13 +6,14 @@ use std::process;
pub mod config; pub mod config;
pub mod output; pub mod output;
pub mod provider;
pub mod repo; pub mod repo;
pub mod table; pub mod table;
use config::{Config, Tree}; use config::{Config, Tree};
use output::*; use output::*;
use repo::{clone_repo, detect_remote_type, Remote, RepoConfig}; use repo::{clone_repo, detect_remote_type, Remote, RemoteType, RepoConfig};
pub use repo::{RemoteTrackingStatus, Repo, RepoErrorKind, WorktreeRemoveFailureReason}; pub use repo::{RemoteTrackingStatus, Repo, RepoErrorKind, WorktreeRemoveFailureReason};

199
src/provider/github.rs Normal file
View File

@@ -0,0 +1,199 @@
use std::collections::HashMap;
use isahc::prelude::*;
use serde::Deserialize;
use crate::{Remote, RemoteType, RepoConfig};
use super::Filter;
use super::Provider;
use super::SecretToken;
#[derive(Deserialize)]
#[serde(untagged)]
enum GithubUserProjectResponse {
Success(Vec<GithubProject>),
Failure(GithubFailureResponse),
}
#[derive(Deserialize)]
struct GithubProject {
pub name: String,
pub full_name: String,
pub clone_url: String,
pub ssh_url: String,
pub private: bool,
}
impl GithubProject {
fn into_repo_config(self) -> RepoConfig {
RepoConfig {
name: self.name,
worktree_setup: false,
remotes: Some(vec![Remote {
name: String::from("github"),
url: match self.private {
true => self.ssh_url,
false => self.clone_url,
},
remote_type: match self.private {
true => RemoteType::Ssh,
false => RemoteType::Https,
},
}]),
}
}
}
#[derive(Deserialize)]
struct GithubFailureResponse {
pub message: String,
}
pub struct Github {
filter: Filter,
secret_token: SecretToken,
}
impl Github {
fn get_repo_list_from_uri(
uri: &str,
secret_token: &SecretToken,
) -> Result<Vec<(String, GithubProject)>, String> {
let mut repos: Vec<(String, GithubProject)> = vec![];
let client = isahc::HttpClient::new().map_err(|error| error.to_string())?;
let request = isahc::Request::builder()
.uri(uri)
.header("accept", " application/vnd.github.v3+json")
.header("authorization", format!("token {}", secret_token))
.body(())
.map_err(|error| error.to_string())?;
let mut response = client.send(request).map_err(|error| error.to_string())?;
let success = response.status().is_success();
{
let response: GithubUserProjectResponse = response
.json()
.map_err(|error| format!("Failed deserializing response: {}", error))?;
if !success {
match response {
GithubUserProjectResponse::Failure(error) => return Err(error.message),
_ => return Err(String::from("Unknown response error")),
}
}
match response {
GithubUserProjectResponse::Failure(error) => {
return Err(format!(
"Received error response but no error code: {}",
error.message
))
}
GithubUserProjectResponse::Success(repo_list) => {
for repo in repo_list {
let (namespace, _name) = repo
.full_name
.rsplit_once('/')
.unwrap_or(("", &repo.full_name));
repos.push((namespace.to_string(), repo));
}
}
}
}
let headers = response.headers();
if let Some(link_header) = headers.get("link") {
let link_header = link_header.to_str().map_err(|error| error.to_string())?;
let link_header =
parse_link_header::parse(link_header).map_err(|error| error.to_string())?;
let next_page = link_header.get(&Some(String::from("next")));
if let Some(page) = next_page {
let following_repos = Github::get_repo_list_from_uri(&page.raw_uri, secret_token)?;
repos.extend(following_repos);
}
}
Ok(repos)
}
}
impl Provider for Github {
fn new(filter: Filter, secret_token: SecretToken) -> Self {
Github {
filter,
secret_token,
}
}
fn get_repos(&self) -> Result<HashMap<String, Vec<RepoConfig>>, String> {
let mut namespaces: HashMap<String, HashMap<String, RepoConfig>> = HashMap::new();
let mut register = |namespace: String, repo: GithubProject| {
let name = repo.name.clone();
let repo_config = repo.into_repo_config();
match namespaces.get_mut(&namespace) {
Some(ns) => match ns.get_mut(&name) {
Some(_entry) => {}
None => {
ns.insert(name, repo_config);
}
},
None => {
let mut ns = HashMap::new();
ns.insert(name, repo_config);
namespaces.insert(namespace, ns);
}
}
};
if let Some(users) = &self.filter.users {
for user in users {
let repos = Github::get_repo_list_from_uri(
&format!("https://api.github.com/users/{}/repos", user),
&self.secret_token,
)?;
for (namespace, repo) in repos {
register(namespace, repo);
}
}
}
if let Some(groups) = &self.filter.groups {
for group in groups {
let repos = Github::get_repo_list_from_uri(
&format!("https://api.github.com/orgs/{}/repos", group),
&self.secret_token,
)?;
for (namespace, repo) in repos {
register(namespace, repo);
}
}
}
if self.filter.owner {
let repos = Github::get_repo_list_from_uri(
"https://api.github.com/user/repos?affiliation=owner",
&self.secret_token,
)?;
for (namespace, repo) in repos {
register(namespace, repo);
}
}
let mut ret: HashMap<String, Vec<RepoConfig>> = HashMap::new();
for (namespace, repos) in namespaces {
ret.insert(namespace, repos.into_values().collect());
}
Ok(ret)
}
}

30
src/provider/mod.rs Normal file
View File

@@ -0,0 +1,30 @@
pub mod github;
pub use github::Github;
use super::RepoConfig;
use std::collections::HashMap;
pub struct Filter {
users: Option<Vec<String>>,
groups: Option<Vec<String>>,
owner: bool,
}
type SecretToken = String;
impl Filter {
pub fn new(users: Option<Vec<String>>, groups: Option<Vec<String>>, owner: bool) -> Self {
Filter {
users,
groups,
owner,
}
}
}
pub trait Provider {
fn new(filter: Filter, secret_token: SecretToken) -> Self;
fn get_repos(&self) -> Result<HashMap<String, Vec<RepoConfig>>, String>;
}