Skip to content

Commit

Permalink
Merge pull request #2 from FauxFaux/feat/all-branches
Browse files Browse the repository at this point in the history
feat: --all-branches
  • Loading branch information
Timmmm authored Jun 16, 2021
2 parents 9d5fa4d + 8762d22 commit 3ead237
Show file tree
Hide file tree
Showing 14 changed files with 150 additions and 47 deletions.
6 changes: 4 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,9 @@

<img align="right" src="logo.svg">

Autorebase automatically rebases all of your feature branches onto `master`. If conflicts are found it will rebase to the last commit that doesn't cause conflicts. Currently it will rebase all branches that don't have an upstream. You don't need to switch to any branch, the only limitation is that a branch that is checked out and not clean will not be rebased (though I may add that in future).
Autorebase automatically rebases all of your feature branches onto `master`. If conflicts are found it will rebase to the last commit that doesn't cause conflicts.
By default, branches with an upstream are excluded.
You don't need to switch to any branch, the only limitation is that a branch that is checked out and not clean will not be rebased (though I may add that in future).

Here is a demo. Before autorebase we have a number of old feature branches.

Expand Down Expand Up @@ -32,7 +34,7 @@ Just run `autorebase` in your repo. This will perform the following actions

1. Update `master`, by pulling it with `--ff-only` unless you have it checked out with pending changes.
2. Create a temporary work tree inside `.git/autorebase` (this is currently never deleted but you can do it manually with `git worktree remove autorebase_worktree`).
3. Get the list of branches that have no upstream, and aren't checked out with pending changes.
3. Get the list of branches that have no upstream (except with `--all-branches`), and aren't checked out with pending changes.
4. For each branch:
1. Try to rebase it onto `master`.
2. If that fails due to conflicts, abort and try to rebase it as far as possible. There are two strategies for this (see below).
Expand Down
26 changes: 11 additions & 15 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@ pub fn autorebase(
repo_path: &Path,
onto_branch: &str,
slow_conflict_detection: bool,
include_all_branches: bool,
) -> Result<()> {
// Check the git version. `git switch` was introduced in 2.23.
if git_version(repo_path)?.as_slice() < &[2, 23] {
Expand Down Expand Up @@ -83,7 +84,7 @@ pub fn autorebase(
for branch in all_branches.iter() {
if branch.branch == onto_branch {
eprintln!(" - {} (target branch)", branch.branch.blue().bold());
} else if branch.upstream.is_some() {
} else if !include_all_branches && branch.upstream.is_some() {
eprintln!(
" - {} (skipping because it has an upstream)",
branch.branch.bold()
Expand All @@ -103,7 +104,7 @@ pub fn autorebase(
.iter()
.filter(|branch| {
branch.branch != onto_branch
&& branch.upstream.is_none()
&& (include_all_branches || branch.upstream.is_none())
&& !matches!(&branch.worktree, Some(worktree) if !worktree.clean)
})
.collect();
Expand Down Expand Up @@ -403,6 +404,10 @@ fn get_branches(repo_path: &Path) -> Result<Vec<BranchInfo>> {
worktree,
})
})
.filter(|branch| match branch {
Ok(b) if b.branch == TEMPORARY_BRANCH_NAME => false,
_ => true,
})
.collect::<Result<_, _>>()?;
Ok(branches)
}
Expand Down Expand Up @@ -471,6 +476,8 @@ fn attempt_rebase(repo_path: &Path, worktree_path: &Path, onto: &str) -> Result<
Ok(RebaseResult::Conflict)
}

const TEMPORARY_BRANCH_NAME: &'static str = "autorebase_tmp_safe_to_delete";

/// Create a temporary branch at master (`onto`), then try to rebase it ont
/// `branch`. Count how many commits were rebased successfully, and
/// return that number. Then abort the rebase, and delete the branch.
Expand All @@ -485,12 +492,7 @@ fn count_nonconflicting_commits_via_rebase(
// Create a temporary branch at master. If it already exists (e.g. because
// a previous command failed) just reset it to here.
git(
&[
"switch",
"--force-create",
"autorebase_tmp_safe_to_delete",
onto,
],
&["switch", "--force-create", TEMPORARY_BRANCH_NAME, onto],
worktree_path,
)?;

Expand Down Expand Up @@ -518,12 +520,7 @@ fn count_nonconflicting_commits_via_rebase(
git(&["switch", "--detach", onto], worktree_path)?;

git(
&[
"branch",
"--delete",
"--force",
"autorebase_tmp_safe_to_delete",
],
&["branch", "--delete", "--force", TEMPORARY_BRANCH_NAME],
worktree_path,
)?;

Expand Down Expand Up @@ -605,7 +602,6 @@ fn get_current_branch_or_commit(worktree_path: &Path) -> Result<BranchOrCommit>
return Ok(BranchOrCommit::Branch(branch));
}


let commit = get_commit_hash(worktree_path, "HEAD")?;
Ok(BranchOrCommit::Commit(commit))
}
Expand Down
10 changes: 9 additions & 1 deletion src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,9 @@ struct CliOptions {
/// target branch directly
#[argh(switch)]
slow: bool,
/// include branches which have an upstream, the default is to exclude these
#[argh(switch)]
all_branches: bool,
}

fn main() -> Result<()> {
Expand All @@ -35,7 +38,12 @@ fn run() -> Result<()> {
// Find the repo dir in the same way git does.
let repo_path = get_repo_path()?;

autorebase(&repo_path, &options.onto, options.slow)?;
autorebase(
&repo_path,
&options.onto,
options.slow,
options.all_branches,
)?;

Ok(())
}
81 changes: 81 additions & 0 deletions tests/tests/all_branches.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
use std::collections::BTreeMap;

use autorebase::autorebase;

use crate::{commit_graph, utils::*};

fn with_include(include_all_branches: bool) -> BTreeMap<String, CommitGraphNode> {
git_fixed_dates();

let root = commit("First")
.write("a.txt", "hello")
.child(commit("Second").write("a.txt", "world").branch("master"))
.child(
commit("Third")
.write("b.txt", "foo")
.branch_with_upstream("other_main", "master"),
)
.child(commit("wip").write("c.txt", "foo").branch("wip"));

let repo = build_repo(&root, Some("master"));

let repo_dir = repo.path();

print_git_log_graph(&repo_dir);

autorebase(repo_dir, "master", false, include_all_branches).expect("error autorebasing");

print_git_log_graph(&repo_dir);

get_repo_graph(&repo_dir).expect("error getting repo graph")
}

#[test]
fn skips_branches() {
let graph = with_include(false);

let expected_graph = commit_graph!(
"6781625b397d4f2eeb6da4b1fea570052683629f": CommitGraphNode {
parents: ["d3591307bd5590f14ae24d03ab41121ab94e2a90"],
refs: {"other_main"},
},
"a6de41485a5af44adc18b599a63840c367043e39": CommitGraphNode {
parents: ["d3591307bd5590f14ae24d03ab41121ab94e2a90"],
refs: {"master"},
},
"d3591307bd5590f14ae24d03ab41121ab94e2a90": CommitGraphNode {
parents: [],
refs: {""},
},
"f7aad7ec74984d4cd89090e572de921d5f9d1fc4": CommitGraphNode {
parents: ["a6de41485a5af44adc18b599a63840c367043e39"],
refs: {"wip"}
}
);
assert_eq!(graph, expected_graph);
}

#[test]
fn includes_branches() {
let graph = with_include(true);

let expected_graph = commit_graph!(
"089f39ba0066fd2380da7dbe5201ec4b13f01b4a": CommitGraphNode {
parents: ["a6de41485a5af44adc18b599a63840c367043e39"],
refs: {"other_main"}
},
"a6de41485a5af44adc18b599a63840c367043e39": CommitGraphNode {
parents: ["d3591307bd5590f14ae24d03ab41121ab94e2a90"],
refs: {"master"}
},
"d3591307bd5590f14ae24d03ab41121ab94e2a90": CommitGraphNode {
parents: [],
refs: {""}
},
"f7aad7ec74984d4cd89090e572de921d5f9d1fc4": CommitGraphNode {
parents: ["a6de41485a5af44adc18b599a63840c367043e39"],
refs: {"wip"}
}
);
assert_eq!(graph, expected_graph);
}
4 changes: 2 additions & 2 deletions tests/tests/basic.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
use autorebase::autorebase;
use crate::{commit_graph, utils::*};
use autorebase::autorebase;

// Test building a repo using `build_repo`.
#[test]
Expand Down Expand Up @@ -63,7 +63,7 @@ fn basic_autorebase(slow_conflict_detection: bool) {

print_git_log_graph(&repo_dir);

autorebase(repo_dir, "master", slow_conflict_detection).expect("error autorebasing");
autorebase(repo_dir, "master", slow_conflict_detection, false).expect("error autorebasing");

print_git_log_graph(&repo_dir);

Expand Down
4 changes: 2 additions & 2 deletions tests/tests/basic_conflict.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
use autorebase::autorebase;
use crate::{commit_graph, utils::*};
use autorebase::autorebase;

// Single branch that cannot be rebased all the way to `master` commit due to conflicts.
#[test]
Expand Down Expand Up @@ -34,7 +34,7 @@ fn conflict(slow_conflict_detection: bool) {

print_git_log_graph(&repo_dir);

autorebase(repo_dir, "master", slow_conflict_detection).expect("error autorebasing");
autorebase(repo_dir, "master", slow_conflict_detection, false).expect("error autorebasing");

print_git_log_graph(&repo_dir);

Expand Down
6 changes: 3 additions & 3 deletions tests/tests/checked_out.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
use crate::{commit_graph, utils::*};
use autorebase::autorebase;
use std::fs;
use crate::{commit_graph, utils::*};

// Check we can rebase with the current checked out branch.
#[test]
Expand All @@ -27,7 +27,7 @@ fn checkedout_clean(slow_conflict_detection: bool) {

print_git_log_graph(&repo_dir);

autorebase(repo_dir, "master", slow_conflict_detection).expect("error autorebasing");
autorebase(repo_dir, "master", slow_conflict_detection, false).expect("error autorebasing");

print_git_log_graph(&repo_dir);

Expand Down Expand Up @@ -88,7 +88,7 @@ fn checkedout_dirty(slow_conflict_detection: bool) {

print_git_log_graph(&repo_dir);

autorebase(repo_dir, "master", slow_conflict_detection).expect("error autorebasing");
autorebase(repo_dir, "master", slow_conflict_detection, false).expect("error autorebasing");

print_git_log_graph(&repo_dir);

Expand Down
9 changes: 4 additions & 5 deletions tests/tests/conflict_resume.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
use crate::{commit_graph, utils::*};
use autorebase::autorebase;
use git_commands::git;
use std::fs;
use crate::{commit_graph, utils::*};

// Single branch that cannot be rebased all the way to `master` commit due to conflicts,
// However we then change master so there's no conflict, but when we run `autorebase`
Expand Down Expand Up @@ -40,7 +40,7 @@ fn conflict_resume(slow_conflict_detection: bool) {

print_git_log_graph(&repo_dir);

autorebase(repo_dir, "master", slow_conflict_detection).expect("error autorebasing");
autorebase(repo_dir, "master", slow_conflict_detection, false).expect("error autorebasing");

print_git_log_graph(&repo_dir);

Expand Down Expand Up @@ -98,7 +98,7 @@ fn conflict_resume(slow_conflict_detection: bool) {

print_git_log_graph(&repo_dir);

autorebase(repo_dir, "master", true).expect("error autorebasing");
autorebase(repo_dir, "master", true, false).expect("error autorebasing");

print_git_log_graph(&repo_dir);

Expand Down Expand Up @@ -163,12 +163,11 @@ fn conflict_resume(slow_conflict_detection: bool) {
// Check out master again so `wip` can be autorebased.
git(&["checkout", "master"], repo_dir).expect("error checking out master");


// Ok if we run `autorebase` is should succesfully rebase to master.

print_git_log_graph(&repo_dir);

autorebase(repo_dir, "master", true).expect("error autorebasing");
autorebase(repo_dir, "master", true, false).expect("error autorebasing");

print_git_log_graph(&repo_dir);

Expand Down
3 changes: 2 additions & 1 deletion tests/tests/mod.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
mod basic_conflict;
mod all_branches;
mod basic;
mod basic_conflict;
mod checked_out;
mod conflict_resume;
mod multiple_branches;
Expand Down
4 changes: 2 additions & 2 deletions tests/tests/multiple_branches.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
use autorebase::autorebase;
use crate::{commit_graph, utils::*};
use autorebase::autorebase;

// Basic test but there is more than one branch that needs to be rebased.
#[test]
Expand Down Expand Up @@ -27,7 +27,7 @@ fn multiple_branches(slow_conflict_detection: bool) {

print_git_log_graph(&repo_dir);

autorebase(repo_dir, "master", slow_conflict_detection).expect("error autorebasing");
autorebase(repo_dir, "master", slow_conflict_detection, false).expect("error autorebasing");

print_git_log_graph(&repo_dir);

Expand Down
4 changes: 2 additions & 2 deletions tests/tests/multiple_refs_on_branch.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
use autorebase::autorebase;
use crate::{commit_graph, utils::*};
use autorebase::autorebase;

// Basic test but there are multiple chained refs on the branch.
#[test]
Expand Down Expand Up @@ -31,7 +31,7 @@ fn multiple_branches(slow_conflict_detection: bool) {

print_git_log_graph(&repo_dir);

autorebase(repo_dir, "master", slow_conflict_detection).expect("error autorebasing");
autorebase(repo_dir, "master", slow_conflict_detection, false).expect("error autorebasing");

print_git_log_graph(&repo_dir);

Expand Down
5 changes: 2 additions & 3 deletions tests/tests/random.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
use autorebase::autorebase;
use crate::utils::*;
use autorebase::autorebase;

// Test randomly generated repos.
#[test]
Expand All @@ -23,7 +23,7 @@ fn random_test(slow_conflict_detection: bool) {

print_git_log_graph(&repo_dir);

autorebase(repo_dir, "master", slow_conflict_detection).expect("error autorebasing");
autorebase(repo_dir, "master", slow_conflict_detection, false).expect("error autorebasing");

print_git_log_graph(&repo_dir);

Expand All @@ -41,7 +41,6 @@ fn random_test_many_fast() {
}

fn random_test_many(slow_conflict_detection: bool) {

// This takes about 0.5 seconds per iteration.
for _ in 0..10 {
random_test(slow_conflict_detection);
Expand Down
Loading

0 comments on commit 3ead237

Please sign in to comment.