diff --git a/Cargo.lock b/Cargo.lock index ce042b4..97ce7e0 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -94,12 +94,6 @@ dependencies = [ "rustc-demangle", ] -[[package]] -name = "base64" -version = "0.11.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b41b7ea54a0c9d92199de89e20e58d49f02f8e699814ef3fdf266f6f748d15c7" - [[package]] name = "base64" version = "0.12.3" @@ -143,6 +137,9 @@ name = "cc" version = "1.0.59" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "66120af515773fb005778dc07c261bd201ec8ce50bd6e7144c927753fe013381" +dependencies = [ + "jobserver", +] [[package]] name = "cfg-if" @@ -225,12 +222,12 @@ checksum = "b3a71ab494c0b5b860bdc8407ae08978052417070c2ced38573a9157ad75b8ac" [[package]] name = "crossbeam-channel" -version = "0.4.3" +version = "0.4.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "09ee0cc8804d5393478d743b035099520087a5186f3b93fa58cec08fa62407b6" +checksum = "b153fe7cbef478c567df0f972e02e6d736db11affe43dfc9c56a9374d1adfb87" dependencies = [ - "cfg-if", "crossbeam-utils", + "maybe-uninit", ] [[package]] @@ -385,6 +382,7 @@ dependencies = [ "console", "expanduser", "fs_extra", + "git2", "globset", "graphql_client", "indicatif", @@ -397,6 +395,21 @@ dependencies = [ "walkdir", ] +[[package]] +name = "git2" +version = "0.13.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e094214efbc7fdbbdee952147e493b00e99a4e52817492277e98967ae918165" +dependencies = [ + "bitflags", + "libc", + "libgit2-sys", + "log", + "openssl-probe", + "openssl-sys", + "url", +] + [[package]] name = "globset" version = "0.4.5" @@ -519,6 +532,15 @@ version = "0.4.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "dc6f3ad7b9d11a0c00842ff8de1b60ee58661048eb8049ed33c73594f359d7e6" +[[package]] +name = "jobserver" +version = "0.1.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c71313ebb9439f74b00d9d2dcec36440beaf57a6aa0623068441dd7cd81a7f2" +dependencies = [ + "libc", +] + [[package]] name = "lazy_static" version = "1.4.0" @@ -531,6 +553,46 @@ version = "0.2.76" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "755456fae044e6fa1ebbbd1b3e902ae19e73097ed4ed87bb79934a867c007bc3" +[[package]] +name = "libgit2-sys" +version = "0.12.13+1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "069eea34f76ec15f2822ccf78fe0cdb8c9016764d0a12865278585a74dbdeae5" +dependencies = [ + "cc", + "libc", + "libssh2-sys", + "libz-sys", + "openssl-sys", + "pkg-config", +] + +[[package]] +name = "libssh2-sys" +version = "0.2.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ca46220853ba1c512fc82826d0834d87b06bcd3c2a42241b7de72f3d2fe17056" +dependencies = [ + "cc", + "libc", + "libz-sys", + "openssl-sys", + "pkg-config", + "vcpkg", +] + +[[package]] +name = "libz-sys" +version = "1.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "602113192b08db8f38796c4e85c39e960c145965140e918018bcde1952429655" +dependencies = [ + "cc", + "libc", + "pkg-config", + "vcpkg", +] + [[package]] name = "log" version = "0.4.11" @@ -569,9 +631,9 @@ dependencies = [ [[package]] name = "miniz_oxide" -version = "0.4.0" +version = "0.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "be0f75932c1f6cfae3c04000e40114adf955636e19040f9c0a2c380702aa1c7f" +checksum = "4d7559a8a40d0f97e1edea3220f698f78b1c5ab67532e49f68fde3910323b722" dependencies = [ "adler", ] @@ -693,9 +755,9 @@ dependencies = [ [[package]] name = "proc-macro2" -version = "1.0.19" +version = "1.0.21" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "04f5f085b5d71e2188cb8271e5da0161ad52c3f227a661a3c135fdf28e258b12" +checksum = "36e28516df94f3dd551a587da5357459d9b36d945a7c37c3557928c1c2ff2a2c" dependencies = [ "unicode-xid", ] @@ -802,9 +864,9 @@ checksum = "41cc0f7e4d5d4544e8861606a285bb08d3e70712ccc7d2b84d7c0ccfaf4b05ce" [[package]] name = "redox_users" -version = "0.3.4" +version = "0.3.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "09b23093265f8d200fa7b4c2c76297f47e681c655f6f1285a8780d6a022f7431" +checksum = "de0737333e7a9502c789a36d7c7fa6092a49895d4faa31ca5df163857ded2e9d" dependencies = [ "getrandom", "redox_syscall", @@ -840,11 +902,11 @@ dependencies = [ [[package]] name = "rust-argon2" -version = "0.7.0" +version = "0.8.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2bc8af4bda8e1ff4932523b94d3dd20ee30a87232323eda55903ffd71d2fb017" +checksum = "9dab61250775933275e84053ac235621dfb739556d5c54a2f2e9313b7cf43a19" dependencies = [ - "base64 0.11.0", + "base64", "blake2b_simd", "constant_time_eq", "crossbeam-utils", @@ -973,9 +1035,9 @@ dependencies = [ [[package]] name = "syn" -version = "1.0.38" +version = "1.0.40" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e69abc24912995b3038597a7a593be5053eb0fb44f3cc5beec0deb421790c1f4" +checksum = "963f7d3cc59b59b9325165add223142bbf1df27655d07789f109896d353d8350" dependencies = [ "proc-macro2", "quote", @@ -1111,7 +1173,7 @@ version = "1.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b770aa61edaa144d3af86a8b0ccbb1bf8ca9dd0c1ac2a17081f35943aae6eb82" dependencies = [ - "base64 0.12.3", + "base64", "chunked_transfer", "lazy_static", "native-tls", diff --git a/Cargo.toml b/Cargo.toml index f666c16..88eb8ef 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -28,6 +28,7 @@ atomic-counter = "1.0.1" ureq = { version = "1.4.0", features = ["json", "native-tls"], default_features = false } serde_json = "1.0.57" globset = "0.4.5" +git2 = "0.13.11" [target."cfg(unix)".dependencies] expanduser = "1.2.1" diff --git a/README.md b/README.md index 6bc39a4..9eac52a 100644 --- a/README.md +++ b/README.md @@ -61,25 +61,26 @@ USAGE: git-workspace --workspace FLAGS: - -h, --help + -h, --help Prints help information - -V, --version + -V, --version Prints version information OPTIONS: - -w, --workspace - [env: GIT_WORKSPACE=...] + -w, --workspace + [env: GIT_WORKSPACE=/Users/tom/PycharmProjects/] SUBCOMMANDS: - add Add a provider to the configuration - fetch Fetch new commits for all repositories in the workspace - help Prints this message or the help of the given subcommand(s) - list List all repositories in the workspace - run Run a git command in all repositories - update Update the workspace, removing and adding any repositories as needed + add Add a provider to the configuration + fetch Fetch new commits for all repositories in the workspace + help Prints this message or the help of the given subcommand(s) + list List all repositories in the workspace + run Run a git command in all repositories + switch-and-pull Pull new commits on the primary branch for all repositories in the workspace + update Update the workspace, removing and adding any repositories as needed ``` ## Define your workspace diff --git a/src/main.rs b/src/main.rs index 3454384..cccb53a 100644 --- a/src/main.rs +++ b/src/main.rs @@ -61,6 +61,11 @@ enum Command { #[structopt(short = "t", long = "threads", default_value = "8")] threads: usize, }, + /// Pull new commits on the primary branch for all repositories in the workspace + SwitchAndPull { + #[structopt(short = "t", long = "threads", default_value = "8")] + threads: usize, + }, /// List all repositories in the workspace /// /// This command will output the names of all known repositories in the workspace. @@ -161,6 +166,7 @@ fn handle_main(args: Args) -> anyhow::Result<()> { command, args, } => execute_cmd(&workspace_path, threads, command, args)?, + Command::SwitchAndPull { threads } => pull_all_repositories(&workspace_path, threads)?, }; Ok(()) } @@ -216,6 +222,34 @@ fn update(workspace: &PathBuf, threads: usize) -> anyhow::Result<()> { Ok(()) } +fn pull_all_repositories(workspace: &PathBuf, threads: usize) -> anyhow::Result<()> { + let lockfile = Lockfile::new(workspace.join("workspace-lock.toml")); + let repositories = lockfile.read().with_context(|| "Error reading lockfile")?; + + println!( + "Switching to the primary branch and pulling {} repositories", + repositories.len() + ); + + map_repositories(&repositories, threads, |r, progress_bar| { + r.switch_to_primary_branch(&workspace)?; + let pull_args = match (&r.upstream, &r.branch) { + // This fucking sucks, but it's because my abstractions suck ass. + // I need to learn how to fix this. + (Some(_), Some(branch)) => vec![ + "pull".to_string(), + "upstream".to_string(), + branch.to_string(), + ], + _ => vec!["pull".to_string()], + }; + r.execute_cmd(&workspace, &progress_bar, "git", &pull_args)?; + Ok(()) + })?; + + Ok(()) +} + /// Execute a command on all our repositories fn execute_cmd( workspace: &PathBuf, @@ -410,7 +444,6 @@ where eprintln!("{}:", repo.name()); error .chain() - .skip(1) .for_each(|cause| eprintln!("because: {}", cause)); } } diff --git a/src/repository.rs b/src/repository.rs index 37d0200..e158350 100644 --- a/src/repository.rs +++ b/src/repository.rs @@ -1,5 +1,7 @@ use anyhow::{anyhow, Context}; use console::{strip_ansi_codes, truncate_str}; +use git2::build::CheckoutBuilder; +use git2::{Repository as Git2Repository, StatusOptions}; use indicatif::ProgressBar; use serde::{Deserialize, Serialize}; use std::io::{BufRead, BufReader}; @@ -11,8 +13,8 @@ use std::process::{Command, Stdio}; pub struct Repository { path: String, url: String, - upstream: Option, - branch: Option, + pub upstream: Option, + pub branch: Option, } impl Repository { @@ -38,6 +40,7 @@ impl Repository { upstream, } } + pub fn set_upstream(&self, root: &PathBuf) -> anyhow::Result<()> { let upstream = match &self.upstream { Some(upstream) => upstream, @@ -137,6 +140,26 @@ impl Repository { Ok(()) } + pub fn switch_to_primary_branch(&self, root: &PathBuf) -> anyhow::Result<()> { + let branch = match &self.branch { + None => return Ok(()), + Some(b) => b, + }; + let repo = Git2Repository::init(root.join(&self.name()))?; + let status = repo.statuses(Some(&mut StatusOptions::default()))?; + if !status.is_empty() { + return Err(anyhow!( + "Repository is dirty, cannot switch to branch {}", + branch + )); + } + repo.set_head(&format!("refs/heads/{}", branch).to_string()) + .with_context(|| format!("Cannot find branch {}", branch))?; + repo.checkout_head(Some(CheckoutBuilder::default().safe().force())) + .with_context(|| format!("Error checking out branch {}", branch))?; + Ok(()) + } + pub fn clone(&self, root: &PathBuf, progress_bar: &ProgressBar) -> anyhow::Result<()> { let mut command = Command::new("git");