Skip to content

Commit bc179c5

Browse files
authored
Merge pull request #156 from blairconrad/rebase-args
Take rebase options from command-line
2 parents 504d600 + 55e422b commit bc179c5

File tree

6 files changed

+150
-12
lines changed

6 files changed

+150
-12
lines changed

Cargo.lock

Lines changed: 7 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,4 +35,5 @@ memchr = "2.3"
3535
anyhow = "1.0"
3636

3737
[dev-dependencies]
38+
current_dir = "0.1.0"
3839
tempfile = "3.1"

Documentation/git-absorb.adoc

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -44,7 +44,8 @@ FLAGS
4444

4545
-r::
4646
--and-rebase::
47-
Run rebase if successful
47+
Run rebase if successful.
48+
See also the REBASE_OPTIONS below.
4849

4950
-n::
5051
--dry-run::
@@ -62,7 +63,7 @@ FLAGS
6263

6364
-f::
6465
--force::
65-
Skip all safety checks as if all --force-* flags were givenj
66+
Skip all safety checks as if all --force-* flags were given.
6667
See those flags to understand the full effect of supplying --force.
6768

6869
-w::
@@ -93,6 +94,11 @@ OPTIONS
9394
Generate completions
9495
[possible values: bash, fish, nushell, zsh, powershell, elvish]
9596

97+
-- <REBASE_OPTIONS>::
98+
Options to pass to git rebase after generating commits.
99+
Must be the last arguments and the `--` must be present.
100+
Only valid when `--and-rebase` is used.
101+
96102
USAGE
97103
-----
98104

src/lib.rs

Lines changed: 102 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ pub struct Config<'a> {
1515
pub force_detach: bool,
1616
pub base: Option<&'a str>,
1717
pub and_rebase: bool,
18+
pub rebase_options: &'a Vec<&'a str>,
1819
pub whole_file: bool,
1920
pub one_fixup_per_commit: bool,
2021
}
@@ -27,6 +28,12 @@ pub fn run(logger: &slog::Logger, config: &Config) -> Result<()> {
2728
}
2829

2930
fn run_with_repo(logger: &slog::Logger, config: &Config, repo: &git2::Repository) -> Result<()> {
31+
if !config.rebase_options.is_empty() && !config.and_rebase {
32+
return Err(anyhow!(
33+
"REBASE_OPTIONS were specified without --and-rebase flag"
34+
));
35+
}
36+
3037
let config = config::unify(&config, repo);
3138
let stack = stack::working_stack(
3239
repo,
@@ -367,6 +374,10 @@ fn run_with_repo(logger: &slog::Logger, config: &Config, repo: &git2::Repository
367374
let mut command = Command::new("git");
368375
command.args(["rebase", "--interactive", "--autosquash", "--autostash"]);
369376

377+
for arg in config.rebase_options {
378+
command.arg(arg);
379+
}
380+
370381
if number_of_parents == 0 {
371382
command.arg("--root");
372383
} else {
@@ -480,6 +491,7 @@ fn index_stats(repo: &git2::Repository) -> Result<git2::DiffStats> {
480491

481492
#[cfg(test)]
482493
mod tests {
494+
use git2::message_trailers_strs;
483495
use std::path::PathBuf;
484496

485497
use super::*;
@@ -525,7 +537,7 @@ mod tests {
525537
fn foreign_author() {
526538
let ctx = repo_utils::prepare_and_stage();
527539

528-
repo_utils::become_new_author(&ctx.repo);
540+
repo_utils::become_author(&ctx.repo, "nobody2", "nobody2@example.com");
529541

530542
// run 'git-absorb'
531543
let drain = slog::Discard;
@@ -543,7 +555,7 @@ mod tests {
543555
fn foreign_author_with_force_author_flag() {
544556
let ctx = repo_utils::prepare_and_stage();
545557

546-
repo_utils::become_new_author(&ctx.repo);
558+
repo_utils::become_author(&ctx.repo, "nobody2", "nobody2@example.com");
547559

548560
// run 'git-absorb'
549561
let drain = slog::Discard;
@@ -565,7 +577,7 @@ mod tests {
565577
fn foreign_author_with_force_author_config() {
566578
let ctx = repo_utils::prepare_and_stage();
567579

568-
repo_utils::become_new_author(&ctx.repo);
580+
repo_utils::become_author(&ctx.repo, "nobody2", "nobody2@example.com");
569581

570582
repo_utils::set_config_flag(&ctx.repo, "absorb.forceAuthor");
571583

@@ -663,6 +675,92 @@ mod tests {
663675
assert!(nothing_left_in_index(&ctx.repo).unwrap());
664676
}
665677

678+
#[test]
679+
fn and_rebase_flag() {
680+
let ctx = repo_utils::prepare_and_stage();
681+
repo_utils::set_config_option(&ctx.repo, "core.editor", "true");
682+
683+
// run 'git-absorb'
684+
let drain = slog::Discard;
685+
let logger = slog::Logger::root(drain, o!());
686+
let config = Config {
687+
and_rebase: true,
688+
..DEFAULT_CONFIG
689+
};
690+
repo_utils::run_in_repo(&ctx, || run_with_repo(&logger, &config, &ctx.repo)).unwrap();
691+
692+
let mut revwalk = ctx.repo.revwalk().unwrap();
693+
revwalk.push_head().unwrap();
694+
695+
assert_eq!(revwalk.count(), 1);
696+
assert!(nothing_left_in_index(&ctx.repo).unwrap());
697+
}
698+
699+
#[test]
700+
fn and_rebase_flag_with_rebase_options() {
701+
let ctx = repo_utils::prepare_and_stage();
702+
repo_utils::set_config_option(&ctx.repo, "core.editor", "true");
703+
704+
// run 'git-absorb'
705+
let drain = slog::Discard;
706+
let logger = slog::Logger::root(drain, o!());
707+
let config = Config {
708+
and_rebase: true,
709+
rebase_options: &vec!["--signoff"],
710+
..DEFAULT_CONFIG
711+
};
712+
repo_utils::run_in_repo(&ctx, || run_with_repo(&logger, &config, &ctx.repo)).unwrap();
713+
714+
let mut revwalk = ctx.repo.revwalk().unwrap();
715+
revwalk.push_head().unwrap();
716+
assert_eq!(revwalk.count(), 1);
717+
718+
let trailers = message_trailers_strs(
719+
ctx.repo
720+
.head()
721+
.unwrap()
722+
.peel_to_commit()
723+
.unwrap()
724+
.message()
725+
.unwrap(),
726+
)
727+
.unwrap();
728+
assert_eq!(
729+
trailers
730+
.iter()
731+
.filter(|trailer| trailer.0 == "Signed-off-by")
732+
.count(),
733+
1
734+
);
735+
736+
assert!(nothing_left_in_index(&ctx.repo).unwrap());
737+
}
738+
739+
#[test]
740+
fn rebase_options_without_and_rebase_flag() {
741+
let ctx = repo_utils::prepare_and_stage();
742+
743+
// run 'git-absorb'
744+
let drain = slog::Discard;
745+
let logger = slog::Logger::root(drain, o!());
746+
let config = Config {
747+
rebase_options: &vec!["--some-option"],
748+
..DEFAULT_CONFIG
749+
};
750+
let result = run_with_repo(&logger, &config, &ctx.repo);
751+
752+
assert_eq!(
753+
result.err().unwrap().to_string(),
754+
"REBASE_OPTIONS were specified without --and-rebase flag"
755+
);
756+
757+
let mut revwalk = ctx.repo.revwalk().unwrap();
758+
revwalk.push_head().unwrap();
759+
assert_eq!(revwalk.count(), 1);
760+
let is_something_in_index = !nothing_left_in_index(&ctx.repo).unwrap();
761+
assert!(is_something_in_index);
762+
}
763+
666764
fn autostage_common(ctx: &repo_utils::Context, file_path: &PathBuf) -> (PathBuf, PathBuf) {
667765
// 1 modification w/o staging
668766
let path = ctx.join(&file_path);
@@ -788,6 +886,7 @@ mod tests {
788886
force_detach: false,
789887
base: None,
790888
and_rebase: false,
889+
rebase_options: &Vec::new(),
791890
whole_file: false,
792891
one_fixup_per_commit: false,
793892
};

src/main.rs

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,9 @@ struct Cli {
3232
/// Run rebase if successful
3333
#[clap(long, short = 'r')]
3434
and_rebase: bool,
35+
/// Extra arguments to pass to git rebase. Only valid if --and-rebase is set
36+
#[clap(last = true)]
37+
rebase_options: Vec<String>,
3538
/// Generate completions
3639
#[clap(long, value_name = "SHELL", value_parser = ["bash", "fish", "nushell", "zsh", "powershell", "elvish"])]
3740
gen_completions: Option<String>,
@@ -52,6 +55,7 @@ fn main() {
5255
force,
5356
verbose,
5457
and_rebase,
58+
rebase_options,
5559
gen_completions,
5660
whole_file,
5761
one_fixup_per_commit,
@@ -93,6 +97,7 @@ fn main() {
9397
));
9498
}
9599

100+
let rebase_options: Vec<&str> = rebase_options.iter().map(AsRef::as_ref).collect();
96101
if let Err(e) = git_absorb::run(
97102
&logger,
98103
&git_absorb::Config {
@@ -101,6 +106,7 @@ fn main() {
101106
force_detach: force_detach || force,
102107
base: base.as_deref(),
103108
and_rebase,
109+
rebase_options: &rebase_options,
104110
whole_file,
105111
one_fixup_per_commit,
106112
},

src/tests/repo_utils.rs

Lines changed: 26 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,6 @@
11
#[cfg(test)]
2+
use anyhow::Result;
3+
use current_dir::Cwd;
24
use std::path::{Path, PathBuf};
35
pub struct Context {
46
pub repo: git2::Repository,
@@ -15,6 +17,7 @@ impl Context {
1517
pub fn prepare_repo() -> (Context, PathBuf) {
1618
let dir = tempfile::tempdir().unwrap();
1719
let repo = git2::Repository::init(dir.path()).unwrap();
20+
become_author(&repo, "nobody", "nobody@example.com");
1821

1922
let path = PathBuf::from("test-file.txt");
2023
std::fs::write(
@@ -32,10 +35,7 @@ lines
3235
// make the borrow-checker happy by introducing a new scope
3336
{
3437
let tree = add(&repo, &path);
35-
let signature = repo
36-
.signature()
37-
.or_else(|_| git2::Signature::now("nobody", "nobody@example.com"))
38-
.unwrap();
38+
let signature = repo.signature().unwrap();
3939
repo.commit(
4040
Some("HEAD"),
4141
&signature,
@@ -76,10 +76,29 @@ pub fn prepare_and_stage() -> Context {
7676
ctx
7777
}
7878

79-
pub fn become_new_author(repo: &git2::Repository) {
79+
/// Set the named repository config option to value.
80+
pub fn set_config_option(repo: &git2::Repository, name: &str, value: &str) {
81+
repo.config().unwrap().set_str(name, value).unwrap();
82+
}
83+
84+
/// Run a function while in the working directory of the repository.
85+
///
86+
/// Can be used to ensure that at most one test changes the working
87+
/// directory at a time, preventing clashes.
88+
pub fn run_in_repo<F>(ctx: &Context, f: F) -> Result<()>
89+
where
90+
F: FnOnce() -> Result<()>,
91+
{
92+
let mut locked_cwd = Cwd::mutex().lock().unwrap();
93+
locked_cwd.set(ctx.dir.path()).unwrap();
94+
f()
95+
}
96+
97+
/// Become a new author - set the user.name and user.email config options.
98+
pub fn become_author(repo: &git2::Repository, name: &str, email: &str) {
8099
let mut config = repo.config().unwrap();
81-
config.set_str("user.name", "nobody2").unwrap();
82-
config.set_str("user.email", "nobody2@example.com").unwrap();
100+
config.set_str("user.name", name).unwrap();
101+
config.set_str("user.email", email).unwrap();
83102
}
84103

85104
/// Detach HEAD from the current branch.

0 commit comments

Comments
 (0)