From cf9e18afa4b4a09091cbe74b2e7f539ec6a9b2ce Mon Sep 17 00:00:00 2001 From: Timothy Andrew Date: Thu, 4 Jun 2020 18:13:57 +0530 Subject: [PATCH] emit a script than can rebase an entire (non-branched) stack --- src/api/search.rs | 6 +++++- src/git.rs | 53 +++++++++++++++++++++++++++++++++++++++++++++++ src/graph.rs | 4 +++- src/lib.rs | 1 + src/main.rs | 16 +++++++++----- 5 files changed, 73 insertions(+), 7 deletions(-) create mode 100644 src/git.rs diff --git a/src/api/search.rs b/src/api/search.rs index 6832f3c..4599948 100644 --- a/src/api/search.rs +++ b/src/api/search.rs @@ -17,7 +17,7 @@ pub struct PullRequestRef { sha: String, } -#[derive(Deserialize, Debug, Clone)] +#[derive(Deserialize, Debug, Clone, PartialEq)] pub enum PullRequestStatus { #[serde(rename = "open")] Open, @@ -61,6 +61,10 @@ impl PullRequest { } } + pub fn state(&self) -> &PullRequestStatus { + &self.state + } + pub fn body(&self) -> &str { &self.body } diff --git a/src/git.rs b/src/git.rs new file mode 100644 index 0000000..b0dc70e --- /dev/null +++ b/src/git.rs @@ -0,0 +1,53 @@ +use regex::Regex; +use crate::graph::FlatDep; +use crate::api::search::PullRequestStatus; + +fn process_ref(git_ref: &str) -> String { + let re = Regex::new("heap:").unwrap(); + re.replace_all(git_ref, "").into_owned() +} + +/// For all open pull requests in the graph, generate a series of commands +/// (force-pushes) that will rebase the entire stack. The "PREBASE" variable +/// is a base for the first branch in the stack (essentially a "stop cherry-picking +/// here" marker), and is required because of our squash-merge workflow. +/// TODO: Move this directly into Rust. +pub fn generate_rebase_script(deps: FlatDep) -> String { + + let deps = deps.iter().filter(|(dep, _)| { + *dep.state() == PullRequestStatus::Open + }).collect::>(); + + let mut out = String::new(); + + out.push_str("#!/usr/bin/env bash\n\n"); + out.push_str("set -euo pipefail\n"); + out.push_str("set -o xtrace\n\n"); + + out.push_str("# ------ THIS SCRIPT ASSUMES YOUR PR STACK IS A SINGLE CHAIN WITHOUT BRANCHING ----- #\n\n"); + out.push_str("# It starts at the base of the stack, cherry-picking onto the new base and force-pushing as it goes.\n"); + out.push_str("# We can't tell where the initial cherry-pick should stop (mainly because of our squash merge workflow),\n"); + out.push_str("# so that initial stopping point for the first PR needs to be specified manually.\n\n"); + + out.push_str("export PREBASE=\"\"\n"); + + for (from, to) in deps { + let to = if let Some(pr) = to { + pr.head().to_string() + } else { + String::from("") + }; + + out.push_str("\n# -------------- #\n\n"); + + out.push_str(&format!("export TO=\"{}\"\n", process_ref(&to))); + out.push_str(&format!("export FROM=\"{}\"\n\n", process_ref(from.head()))); + + out.push_str("git checkout heap/\"$TO\"\n"); + out.push_str("git cherry-pick \"$PREBASE\"..heap/\"$FROM\"\n"); + out.push_str("export PREBASE=\"$(git rev-parse --verify heap/$FROM)\"\n"); + out.push_str("git push -f heap HEAD:refs/heads/\"$FROM\"\n"); + } + + out +} \ No newline at end of file diff --git a/src/graph.rs b/src/graph.rs index bac5bbc..02ee08a 100644 --- a/src/graph.rs +++ b/src/graph.rs @@ -6,6 +6,8 @@ use std::collections::HashMap; use crate::api::search::PullRequest; +pub type FlatDep = Vec<(Rc, Option>)>; + pub fn build(prs: &Vec>) -> Graph, usize> { let mut tree = Graph::, usize>::new(); let heads = prs.iter().map(|pr| pr.head()); @@ -23,7 +25,7 @@ pub fn build(prs: &Vec>) -> Graph, usize> { } /// Return a flattened list of graph nodes as tuples; each tuple is `(node, node's parent [if exists])`. -pub fn log(graph: &Graph, usize>) -> Vec<(Rc, Option>)> { +pub fn log(graph: &Graph, usize>) -> FlatDep { let roots: Vec<_> = graph.externals(Direction::Incoming).collect(); let mut out = Vec::new(); diff --git a/src/lib.rs b/src/lib.rs index 24cf1cf..312f289 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -2,6 +2,7 @@ pub mod api; pub mod graph; pub mod markdown; pub mod persist; +pub mod git; pub struct Credentials { // Personal access token diff --git a/src/main.rs b/src/main.rs index 989b67b..7138e7f 100644 --- a/src/main.rs +++ b/src/main.rs @@ -7,7 +7,7 @@ use std::rc::Rc; use std::fs; use gh_stack::Credentials; -use gh_stack::{api, graph, markdown, persist}; +use gh_stack::{api, graph, markdown, persist, git}; pub fn read_cli_input(message: &str) -> String { print!("{}", message); @@ -36,7 +36,7 @@ async fn main() -> Result<(), Box> { let args: Vec = env::args().collect(); if args.len() > 4 { - println!("usage: gh-stack "); + println!("usage: gh-stack "); process::exit(1); } @@ -67,11 +67,20 @@ async fn main() -> Result<(), Box> { println!("{}: {}", pr.number(), pr.title()); } + let response = read_cli_input("Going to update these PRs ☝️ (y/n): "); match &response[..] { "y" => persist::persist(&prs, &output, &credentials).await?, _ => std::process::exit(1), } + + println!("Done!"); + } + + "rebase" => { + let deps = graph::log(&tree); + let script = git::generate_rebase_script(deps); + println!("{}", script); } "log" => { @@ -87,9 +96,6 @@ async fn main() -> Result<(), Box> { _ => { panic!("Invalid command!") } }; - - println!("Done!"); - Ok(()) /* # TODO