autorebase
This commit is contained in:
parent
b5462a9751
commit
c4b864f38f
137
Cargo.lock
generated
137
Cargo.lock
generated
@ -50,6 +50,9 @@ name = "cc"
|
||||
version = "1.0.54"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7bbb73db36c1246e9034e307d0fba23f9a2e251faa47ade70c1bd252220c8311"
|
||||
dependencies = [
|
||||
"jobserver",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "cfg-if"
|
||||
@ -57,6 +60,23 @@ version = "0.1.10"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "4785bdd1c96b2a846b2bd7cc02e86b6b3dbf14e7e53446c4f54c92a361040822"
|
||||
|
||||
[[package]]
|
||||
name = "console"
|
||||
version = "0.11.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8c0994e656bba7b922d8dd1245db90672ffb701e684e45be58f20719d69abc5a"
|
||||
dependencies = [
|
||||
"encode_unicode",
|
||||
"lazy_static",
|
||||
"libc",
|
||||
"regex",
|
||||
"terminal_size",
|
||||
"termios",
|
||||
"unicode-width",
|
||||
"winapi 0.3.8",
|
||||
"winapi-util",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "core-foundation"
|
||||
version = "0.7.0"
|
||||
@ -73,12 +93,29 @@ version = "0.7.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b3a71ab494c0b5b860bdc8407ae08978052417070c2ced38573a9157ad75b8ac"
|
||||
|
||||
[[package]]
|
||||
name = "dialoguer"
|
||||
version = "0.6.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f4aa86af7b19b40ef9cbef761ed411a49f0afa06b7b6dcd3dfe2f96a3c546138"
|
||||
dependencies = [
|
||||
"console",
|
||||
"lazy_static",
|
||||
"tempfile",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "dtoa"
|
||||
version = "0.4.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "4358a9e11b9a09cf52383b451b49a169e8d797b68aa02301ff586d70d9661ea3"
|
||||
|
||||
[[package]]
|
||||
name = "encode_unicode"
|
||||
version = "0.3.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a357d28ed41a50f9c765dbfe56cbc04a64e53e5fc58ba79fbc34c10ef3df831f"
|
||||
|
||||
[[package]]
|
||||
name = "encoding_rs"
|
||||
version = "0.8.23"
|
||||
@ -241,7 +278,9 @@ dependencies = [
|
||||
name = "gh-stack"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"dialoguer",
|
||||
"futures",
|
||||
"git2",
|
||||
"petgraph",
|
||||
"regex",
|
||||
"reqwest",
|
||||
@ -249,6 +288,21 @@ dependencies = [
|
||||
"tokio",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "git2"
|
||||
version = "0.13.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "11e4b2082980e751c4bf4273e9cbb4a02c655729c8ee8a79f66cad03c8f4d31e"
|
||||
dependencies = [
|
||||
"bitflags",
|
||||
"libc",
|
||||
"libgit2-sys",
|
||||
"log",
|
||||
"openssl-probe",
|
||||
"openssl-sys",
|
||||
"url",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "h2"
|
||||
version = "0.2.5"
|
||||
@ -376,6 +430,15 @@ version = "0.4.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b8b7a7c0c47db5545ed3fef7468ee7bb5b74691498139e4b3f6a20685dc6dd8e"
|
||||
|
||||
[[package]]
|
||||
name = "jobserver"
|
||||
version = "0.1.21"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5c71313ebb9439f74b00d9d2dcec36440beaf57a6aa0623068441dd7cd81a7f2"
|
||||
dependencies = [
|
||||
"libc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "js-sys"
|
||||
version = "0.3.40"
|
||||
@ -407,6 +470,46 @@ version = "0.2.71"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9457b06509d27052635f90d6466700c65095fdf75409b3fbdd903e988b886f49"
|
||||
|
||||
[[package]]
|
||||
name = "libgit2-sys"
|
||||
version = "0.12.7+1.0.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "bcd07968649bcb7b9351ecfde53ca4d27673cccfdf57c84255ec18710f3153e0"
|
||||
dependencies = [
|
||||
"cc",
|
||||
"libc",
|
||||
"libssh2-sys",
|
||||
"libz-sys",
|
||||
"openssl-sys",
|
||||
"pkg-config",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "libssh2-sys"
|
||||
version = "0.2.17"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d45f516b9b19ea6c940b9f36d36734062a153a2b4cc9ef31d82c54bb9780f525"
|
||||
dependencies = [
|
||||
"cc",
|
||||
"libc",
|
||||
"libz-sys",
|
||||
"openssl-sys",
|
||||
"pkg-config",
|
||||
"vcpkg",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "libz-sys"
|
||||
version = "1.0.25"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2eb5e43362e38e2bca2fd5f5134c4d4564a23a5c28e9b95411652021a8675ebe"
|
||||
dependencies = [
|
||||
"cc",
|
||||
"libc",
|
||||
"pkg-config",
|
||||
"vcpkg",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "log"
|
||||
version = "0.4.8"
|
||||
@ -926,6 +1029,25 @@ dependencies = [
|
||||
"winapi 0.3.8",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "terminal_size"
|
||||
version = "0.1.12"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8038f95fc7a6f351163f4b964af631bd26c9e828f7db085f2a84aca56f70d13b"
|
||||
dependencies = [
|
||||
"libc",
|
||||
"winapi 0.3.8",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "termios"
|
||||
version = "0.3.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6f0fcee7b24a25675de40d5bb4de6e41b0df07bc9856295e7e2b3a3600c400c2"
|
||||
dependencies = [
|
||||
"libc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "thread_local"
|
||||
version = "1.0.1"
|
||||
@ -1043,6 +1165,12 @@ dependencies = [
|
||||
"smallvec",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "unicode-width"
|
||||
version = "0.1.7"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "caaa9d531767d1ff2150b9332433f32a24622147e5ebb1f26409d5da67afd479"
|
||||
|
||||
[[package]]
|
||||
name = "unicode-xid"
|
||||
version = "0.2.0"
|
||||
@ -1194,6 +1322,15 @@ version = "0.4.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6"
|
||||
|
||||
[[package]]
|
||||
name = "winapi-util"
|
||||
version = "0.1.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "70ec6ce85bb158151cae5e5c87f95a8e97d2c0c4b001223f33a334e3ce5de178"
|
||||
dependencies = [
|
||||
"winapi 0.3.8",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "winapi-x86_64-pc-windows-gnu"
|
||||
version = "0.4.0"
|
||||
|
@ -19,3 +19,5 @@ serde = { version = "1.0", features = ["derive"] }
|
||||
futures = "0.3.5"
|
||||
petgraph = "0.5"
|
||||
regex = "1"
|
||||
git2 = "0.13"
|
||||
dialoguer = "0.6.2"
|
66
README.md
66
README.md
@ -2,8 +2,6 @@
|
||||
|
||||
- [Usage](#usage)
|
||||
- [Strategy](#strategy)
|
||||
- [Feature Changes](#feature-changes)
|
||||
- [Feature Complete & Merged](#feature-complete--merged)
|
||||
- [Disclaimer](#disclaimer)
|
||||
|
||||
---
|
||||
@ -14,17 +12,16 @@ I use this tool to help managed stacked pull requests on Github, which are notor
|
||||
- https://stackoverflow.com/questions/26619478/are-dependent-pull-requests-in-github-possible
|
||||
- https://gist.github.com/Jlevyd15/66743bab4982838932dda4f13f2bd02a
|
||||
|
||||
This tool assumes that all PRs in a single "stack" all have a unique identifier in their title (I typically use a Jira ticket number for this). It then looks for all PRs containing this containing this identifier and builds a dependency graph in memory. This can technically support a "branched stack" instead of a single chain, but I haven't really tried the latter style. Note that the `gh-stack rebase` command will definitely _not_ work with the branched style.
|
||||
This tool assumes that:
|
||||
|
||||
- All PRs in a single "stack" all have a unique identifier in their title (I typically use a Jira ticket number for this). It then looks for all PRs containing this containing this identifier and builds a dependency graph in memory. This can technically support a "branched stack" instead of a single chain, but I haven't really tried the latter style.
|
||||
- All remote branches that these PRs represent have local branches named identically.
|
||||
|
||||
With this graph built up, the tool can:
|
||||
|
||||
- Add a markdown table to the PR description (idempotently) of each PR in the stack describing _all_ PRs in the stack.
|
||||
- Log a simple list of all PRs in the stack (+ dependencies) to stdout.
|
||||
- Emit a bash script that can update all PRs in the stack.
|
||||
- This generally happens in the event of:
|
||||
- The PR at the base of the stack is merged, leaving all the remaining PRs in a conflicted state.
|
||||
- One of the PRs (not the top of the stack) has a commit added to it, leaving all dependent PRs in a conflicted state.
|
||||
- The script requires two placeholders to be manually specified before execution.
|
||||
- Automatically update the stack + push after making local changes (this handles conflicts as well).
|
||||
|
||||
## Usage
|
||||
|
||||
@ -42,52 +39,27 @@ $ gh-stack github 'stack-identifier' filename.txt
|
||||
# Print a description of the stack to stdout.
|
||||
$ gh-stack log 'stack-identifier'
|
||||
|
||||
# Automatically update the entire stack, both locally and remotely.
|
||||
# WARNING: This operation modifies local branches and force-pushes.
|
||||
$ gh-stack autorebase 'stack-identifier' /path/to/repo
|
||||
|
||||
# Emit a bash script that can update a stack in the case of conflicts.
|
||||
# WARNING: This script could potentially cause destructive behavior.
|
||||
$ gh-stack rebase 'stack-identifier'
|
||||
|
||||
```
|
||||
|
||||
## Strategy
|
||||
|
||||
Here's a quick summary of the strategy that the bash script described above uses to keep the stack up-to-date.
|
||||
This is a quick summary of the strategy the `autorebase` subcommand uses:
|
||||
|
||||
Let's use this stack as an example:
|
||||
|
||||
![](img/initial.png)
|
||||
|
||||
### Feature Changes
|
||||
|
||||
In the first case, let's assume that "feature part 1" had some changes added to it in the form of a commit; this leaves parts 2 & 3 in a conflicted state:
|
||||
|
||||
![](img/feature-1.png)
|
||||
|
||||
The script requires that you pass in a `PREBASE` ref (which is essentially the boundary for the feature part you're operating on - in this case the _parent of the_ last/oldest commit in feature-part-2).
|
||||
The script starts cherry-picking commits at this ref for the first iteration. An initial `TO` ref is also required, which is the point upon which you want to rebase the rest of the stack. In this case, that ref is `remote/feature-part-1`).
|
||||
|
||||
The script executes a single step, we now have this intermediate state:
|
||||
|
||||
![](img/feature-2.png)
|
||||
|
||||
The script completes execution, and we now have this final state with the entire stack updated/recreated:
|
||||
|
||||
![](img/feature-3.png)
|
||||
|
||||
### Feature Complete & Merged
|
||||
|
||||
In the second case, let's assume that "feature part 1" is done and has been merged to `develop`:
|
||||
|
||||
![](/img/complete-1.png)
|
||||
|
||||
This immediately leaves feature parts 2 & 3 in a conflicted state. The script can fix this situation as well.
|
||||
As before, pass a `PREBASE` (in this case _the parent of the_ oldest commit in feature part 2) and an initial `TO` ref to rebase on (in this case `remote/develop`).
|
||||
|
||||
Once the script executes a single step, we're left with:
|
||||
|
||||
![](/img/complete-2.png)
|
||||
|
||||
And once the script is done:
|
||||
|
||||
![](img/complete-3.png)
|
||||
1. Find the `merge_base` between the local branch of the first PR in the stack and the branch it merges into (usually `develop`). This forms the boundary for the initial cherry-pick.
|
||||
2. Check out the commit/ref that the first PR in the stack merges into (usually `develop`). We're going to cherry-pick the entire stack onto this commit.
|
||||
3. Cherry-pick all commits from the first PR (stopping at the cherry-pick boundary calculated in 1.) onto `HEAD`.
|
||||
4. Move the _local_ branch for the first PR onto `HEAD`.
|
||||
5. The _remote_ branch for the first PR becomes the next cherry-pick boundary.
|
||||
6. Repeat steps 3-5 until all PRs have been cherry-picked over.
|
||||
7. Push all refs at once by passing multiple refspecs to a single invocation of `git push -f`.
|
||||
|
||||
## Disclaimer
|
||||
|
||||
@ -95,4 +67,4 @@ Use at your own risk (and make sure your git repository is backed up), especiall
|
||||
|
||||
- This tool works for my specific use case, but has _not_ been extensively tested.
|
||||
- I've been writing Rust for all of 3 weeks at this point.
|
||||
- The script that `gh-stack rebase` emits attempts to force-push when executed.
|
||||
- The `autorebase` command is in an experimental state; there are possibly edge cases I haven't considered.
|
@ -13,7 +13,8 @@ pub struct SearchItem {
|
||||
#[derive(Deserialize, Debug, Clone)]
|
||||
pub struct PullRequestRef {
|
||||
label: String,
|
||||
r#ref: String,
|
||||
#[serde(rename = "ref")]
|
||||
gitref: String,
|
||||
sha: String,
|
||||
}
|
||||
|
||||
@ -39,11 +40,11 @@ pub struct PullRequest {
|
||||
|
||||
impl PullRequest {
|
||||
pub fn head(&self) -> &str {
|
||||
&self.head.label
|
||||
&self.head.gitref
|
||||
}
|
||||
|
||||
pub fn base(&self) -> &str {
|
||||
&self.base.label
|
||||
&self.base.gitref
|
||||
}
|
||||
|
||||
pub fn url(&self) -> &str {
|
||||
|
145
src/git.rs
145
src/git.rs
@ -1,11 +1,28 @@
|
||||
use crate::api::search::PullRequestStatus;
|
||||
use crate::graph::FlatDep;
|
||||
use std::error::Error;
|
||||
use git2::{Cred, ObjectType, Repository, Index, Sort, CherrypickOptions, Remote, Commit, PushOptions, RemoteCallbacks};
|
||||
use git2::build::CheckoutBuilder;
|
||||
use tokio::process::Command;
|
||||
use dialoguer::Input;
|
||||
use std::env;
|
||||
|
||||
fn process_ref(git_ref: &str) -> String {
|
||||
git_ref.replace("heap:", "")
|
||||
fn remote_ref(remote: &str, git_ref: &str) -> String {
|
||||
format!("{}/{}", remote, git_ref)
|
||||
}
|
||||
|
||||
/// For all open pull requests in the graph, generate a series of commands
|
||||
fn loop_until_confirm(prompt: &str) {
|
||||
let prompt = format!("{} Type 'yes' to continue", prompt);
|
||||
loop {
|
||||
let result = Input::<String>::new().with_prompt(&prompt).interact().unwrap();
|
||||
match &result[..] {
|
||||
"yes" => return,
|
||||
_ => continue
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// or 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.
|
||||
@ -40,14 +57,126 @@ pub fn generate_rebase_script(deps: FlatDep) -> String {
|
||||
|
||||
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(&format!("export TO=\"{}\"\n", remote_ref("heap", &to)));
|
||||
out.push_str(&format!("export FROM=\"{}\"\n\n", remote_ref("heap", 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 checkout \"$TO\"\n");
|
||||
out.push_str("git cherry-pick \"$PREBASE\"..\"$FROM\"\n");
|
||||
out.push_str("export PREBASE=\"$(git rev-parse --verify $FROM)\"\n");
|
||||
out.push_str("git push -f heap HEAD:refs/heads/\"$FROM\"\n");
|
||||
}
|
||||
|
||||
out
|
||||
}
|
||||
|
||||
pub async fn perform_rebase(deps: FlatDep, repo: &Repository, remote: &str) -> Result<(), Box<dyn Error>> {
|
||||
let deps = deps
|
||||
.iter()
|
||||
.filter(|(dep, _)| *dep.state() == PullRequestStatus::Open)
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
let (pr, _) = deps[0];
|
||||
|
||||
let base = remote_ref(remote, pr.base());
|
||||
let base = repo.revparse_single(&base).unwrap();
|
||||
let base = base.as_commit().unwrap();
|
||||
|
||||
let head = pr.head();
|
||||
let head = repo.revparse_single(&head).unwrap();
|
||||
let head = head.as_commit().unwrap();
|
||||
|
||||
let mut stop_cherry_pick_at = repo.merge_base(base.id(), head.id()).unwrap();
|
||||
|
||||
println!("Checking out {:?}", base);
|
||||
repo.checkout_tree(&base.as_object(), None).unwrap();
|
||||
repo.set_head_detached(base.id()).unwrap();
|
||||
|
||||
let mut push_refspecs = vec![];
|
||||
|
||||
for (pr, _) in deps {
|
||||
println!("Working on PR: {:?}", pr.head());
|
||||
|
||||
|
||||
let from = repo.revparse_single(&pr.head()).unwrap();
|
||||
let from = from.as_commit().unwrap();
|
||||
|
||||
let mut walk = repo.revwalk().unwrap();
|
||||
walk.set_sorting(Sort::TOPOLOGICAL).unwrap();
|
||||
walk.set_sorting(Sort::REVERSE).unwrap();
|
||||
walk.push(from.id()).unwrap();
|
||||
walk.hide(stop_cherry_pick_at).unwrap();
|
||||
|
||||
// TODO: Simplify by using rebase instead of cherry-pick
|
||||
// TODO: Skip if remote/<branch> is the same SHA as <branch>
|
||||
for from in walk {
|
||||
let from = repo.find_commit(from.unwrap()).unwrap();
|
||||
let to = repo.find_commit(repo.head().unwrap().target().unwrap()).unwrap();
|
||||
|
||||
if from.parent_count() > 1 {
|
||||
panic!("Exiting: I don't know how to deal with merge commits correctly.");
|
||||
}
|
||||
|
||||
let mut cb = CheckoutBuilder::new();
|
||||
cb.allow_conflicts(true);
|
||||
let mut opts = CherrypickOptions::new();
|
||||
opts.checkout_builder(cb);
|
||||
|
||||
println!("Cherry-picking: {:?}", from);
|
||||
repo.cherrypick(&from, Some(&mut opts)).unwrap();
|
||||
|
||||
let mut index = repo.index().unwrap();
|
||||
|
||||
if index.has_conflicts() {
|
||||
let prompt = "Conflicts! Resolve manually and `git add` each one (don't run any `git cherry-pick` commands, though).";
|
||||
loop_until_confirm(prompt);
|
||||
index = repo.index().unwrap();
|
||||
index.read(true).unwrap();
|
||||
}
|
||||
|
||||
let tree = index.write_tree_to(&repo).unwrap();
|
||||
let tree = repo.find_tree(tree).unwrap();
|
||||
|
||||
let signature = repo.signature().unwrap();
|
||||
let commit = repo.commit(None, &signature, &signature, &from.message().unwrap(), &tree, &[&to]).unwrap();
|
||||
let commit = repo.find_commit(commit).unwrap();
|
||||
|
||||
let mut cb = CheckoutBuilder::new();
|
||||
cb.force();
|
||||
repo.checkout_tree(&commit.as_object(), Some(&mut cb)).unwrap();
|
||||
repo.set_head_detached(commit.id()).unwrap();
|
||||
|
||||
// "Complete" the cherry-pick. There is likely a better way to do
|
||||
// this that I haven't found so far.
|
||||
repo.cleanup_state().unwrap();
|
||||
}
|
||||
|
||||
// Update local branch
|
||||
let head = repo.head().unwrap().target().unwrap();
|
||||
let head = repo.find_commit(head).unwrap();
|
||||
repo.branch(pr.head(), &head, true).unwrap();
|
||||
|
||||
// Use remote branch as boundary for next cherry-pick
|
||||
let from = repo.revparse_single(&remote_ref(remote, pr.head())).unwrap();
|
||||
let from = from.as_commit().unwrap();
|
||||
stop_cherry_pick_at = from.id();
|
||||
|
||||
push_refspecs.push(format!("refs/heads/{}:refs/heads/{}", pr.head(), pr.head()));
|
||||
}
|
||||
|
||||
|
||||
let repo_dir = repo.workdir().unwrap().to_str().unwrap();
|
||||
|
||||
// `libgit2` doesn't support refspecs containing raw SHAs, so we shell out
|
||||
// to `git push` instead. https://github.com/libgit2/libgit2/issues/1125
|
||||
let mut command = Command::new("git");
|
||||
command.arg("push").arg("-f").arg(remote);
|
||||
command.args(push_refspecs.as_slice());
|
||||
command.current_dir(repo_dir);
|
||||
|
||||
println!("\n{:?}", push_refspecs);
|
||||
loop_until_confirm("Going to push these refspecs ☝️ ");
|
||||
|
||||
command.spawn()?.await?;
|
||||
|
||||
Ok(())
|
||||
}
|
@ -25,6 +25,7 @@ pub fn build(prs: &[Rc<PullRequest>]) -> Graph<Rc<PullRequest>, usize> {
|
||||
}
|
||||
|
||||
/// Return a flattened list of graph nodes as tuples; each tuple is `(node, node's parent [if exists])`.
|
||||
/// TODO: Panic if this isn't a single flat list of dependencies
|
||||
pub fn log(graph: &Graph<Rc<PullRequest>, usize>) -> FlatDep {
|
||||
let roots: Vec<_> = graph.externals(Direction::Incoming).collect();
|
||||
let mut out = Vec::new();
|
||||
|
12
src/main.rs
12
src/main.rs
@ -1,4 +1,5 @@
|
||||
use std::collections::HashMap;
|
||||
use git2::Repository;
|
||||
use std::env;
|
||||
use std::error::Error;
|
||||
use std::fs;
|
||||
@ -86,6 +87,15 @@ async fn main() -> Result<(), Box<dyn Error>> {
|
||||
println!("{}", script);
|
||||
}
|
||||
|
||||
"autorebase" => {
|
||||
let deps = graph::log(&tree);
|
||||
let repo = Repository::open(prelude.unwrap()).unwrap();
|
||||
// TODO: Make this configurable
|
||||
let remote = repo.find_remote("heap").unwrap();
|
||||
git::perform_rebase(deps, &repo, remote.name().unwrap()).await?;
|
||||
println!("All done!");
|
||||
}
|
||||
|
||||
"log" => {
|
||||
let log = graph::log(&tree);
|
||||
for (pr, maybe_parent) in log {
|
||||
@ -109,6 +119,8 @@ async fn main() -> Result<(), Box<dyn Error>> {
|
||||
- [x] Persist table back to Github
|
||||
- [x] Accept a prelude via STDIN
|
||||
- [x] Log a textual representation of the graph
|
||||
- [x] Automate rebase
|
||||
- [ ] Build status icons
|
||||
- [ ] Panic on non-200s
|
||||
*/
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user