emit a script than can rebase an entire (non-branched) stack
This commit is contained in:
parent
7ef8b43102
commit
cf9e18afa4
@ -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
|
||||
}
|
||||
|
53
src/git.rs
Normal file
53
src/git.rs
Normal file
@ -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::<Vec<_>>();
|
||||
|
||||
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=\"<enter a marker to stop the initial cherry-pick at>\"\n");
|
||||
|
||||
for (from, to) in deps {
|
||||
let to = if let Some(pr) = to {
|
||||
pr.head().to_string()
|
||||
} else {
|
||||
String::from("<enter a ref to rebase the stack on; usually `develop`>")
|
||||
};
|
||||
|
||||
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
|
||||
}
|
@ -6,6 +6,8 @@ use std::collections::HashMap;
|
||||
|
||||
use crate::api::search::PullRequest;
|
||||
|
||||
pub type FlatDep = Vec<(Rc<PullRequest>, Option<Rc<PullRequest>>)>;
|
||||
|
||||
pub fn build(prs: &Vec<Rc<PullRequest>>) -> Graph<Rc<PullRequest>, usize> {
|
||||
let mut tree = Graph::<Rc<PullRequest>, usize>::new();
|
||||
let heads = prs.iter().map(|pr| pr.head());
|
||||
@ -23,7 +25,7 @@ pub fn build(prs: &Vec<Rc<PullRequest>>) -> Graph<Rc<PullRequest>, usize> {
|
||||
}
|
||||
|
||||
/// Return a flattened list of graph nodes as tuples; each tuple is `(node, node's parent [if exists])`.
|
||||
pub fn log(graph: &Graph<Rc<PullRequest>, usize>) -> Vec<(Rc<PullRequest>, Option<Rc<PullRequest>>)> {
|
||||
pub fn log(graph: &Graph<Rc<PullRequest>, usize>) -> FlatDep {
|
||||
let roots: Vec<_> = graph.externals(Direction::Incoming).collect();
|
||||
let mut out = Vec::new();
|
||||
|
||||
|
@ -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
|
||||
|
16
src/main.rs
16
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<dyn Error>> {
|
||||
let args: Vec<String> = env::args().collect();
|
||||
|
||||
if args.len() > 4 {
|
||||
println!("usage: gh-stack <command=save|log> <pattern> <prelude_filename?>");
|
||||
println!("usage: gh-stack <command=save|log|rebase> <pattern> <prelude_filename?>");
|
||||
process::exit(1);
|
||||
}
|
||||
|
||||
@ -67,11 +67,20 @@ async fn main() -> Result<(), Box<dyn Error>> {
|
||||
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<dyn Error>> {
|
||||
_ => { panic!("Invalid command!") }
|
||||
};
|
||||
|
||||
|
||||
println!("Done!");
|
||||
|
||||
Ok(())
|
||||
/*
|
||||
# TODO
|
||||
|
Loading…
Reference in New Issue
Block a user