Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
49 changes: 34 additions & 15 deletions src/ops/branch.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,10 @@ use crate::{
},
item_data::{ItemData, RefKind},
menu::arg::Arg,
picker::PickerState,
term::Term,
};

use std::{process::Command, rc::Rc};

pub(crate) fn init_args() -> Vec<Arg> {
Expand All @@ -21,17 +23,16 @@ pub(crate) struct Checkout;
impl OpTrait for Checkout {
fn get_action(&self, _target: &ItemData) -> Option<Action> {
Some(Rc::new(move |app: &mut App, term: &mut Term| {
let rev = app.prompt(
term,
&PromptParams {
prompt: "Checkout",
create_default_value: Box::new(selected_rev),
..Default::default()
},
)?;

checkout(app, term, &rev)?;
Ok(())
let picker = PickerState::for_branches("Checkout", &app.state.repo, selected_rev(app))?;
match app.picker(term, picker)? {
Some(data) => checkout(app, term, data.display()),
None => {
// TODO: necessary to make sure parent menu closes, shouldn't this be
// handled by .picker, like .prompt does?
app.close_menu();
Ok(())
}
}
}))
}

Expand All @@ -53,15 +54,28 @@ pub(crate) struct CheckoutNewBranch;
impl OpTrait for CheckoutNewBranch {
fn get_action(&self, _target: &ItemData) -> Option<Action> {
Some(Rc::new(|app: &mut App, term: &mut Term| {
let start_point_picker = PickerState::for_branches(
"Create branch starting at",
&app.state.repo,
Some(get_current_branch_name(&app.state.repo)?),
)?;

let Some(starting_point) = app.picker(term, start_point_picker)? else {
// TODO: necessary to make sure parent menu closes, shouldn't this be
// handled by .picker, like .prompt does?
app.close_menu();
return Ok(());
};

let branch_name = app.prompt(
term,
&PromptParams {
prompt: "Create and checkout branch:",
prompt: "Create and checkout branch",
..Default::default()
},
)?;

checkout_new_branch_prompt_update(app, term, &branch_name)?;
checkout_new_branch_prompt_update(app, term, &branch_name, starting_point.display())?;
Ok(())
}))
}
Expand All @@ -71,9 +85,14 @@ impl OpTrait for CheckoutNewBranch {
}
}

fn checkout_new_branch_prompt_update(app: &mut App, term: &mut Term, branch_name: &str) -> Res<()> {
fn checkout_new_branch_prompt_update(
app: &mut App,
term: &mut Term,
branch_name: &str,
starting_point: &str,
) -> Res<()> {
let mut cmd = Command::new("git");
cmd.args(["checkout", "-b", branch_name]);
cmd.args(["checkout", "-b", branch_name, starting_point]);

app.close_menu();
app.run_cmd(term, &[], cmd)?;
Expand Down
54 changes: 54 additions & 0 deletions src/picker.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,8 @@
use crate::error::Error;
use fuzzy_matcher::FuzzyMatcher;
use fuzzy_matcher::skim::SkimMatcherV2;
use git2::Repository;
use itertools::Itertools;
use std::borrow::Cow;
use tui_prompts::State as _;
use tui_prompts::TextState;
Expand Down Expand Up @@ -106,6 +109,57 @@ impl PickerState {
state
}

// Creates a new picker displaying the given branches sorted from local to remote.
//
// The picker also allows to pick a custom revision, i.e. a commit hash or tag.
//
// If a default revision is provided, it is at the top and selected by default.
//
// The current branch and any invalid branches are excluded.
pub fn for_branches(
prompt: impl Into<Cow<'static, str>>,
repo: &Repository,
default_revision: Option<String>,
) -> Result<Self, Error> {
// Collect and sort all branches (local and remote) excluding the current branch &
// default_value, if there's any.
let mut branches: Vec<PickerItem> = repo
.branches(None)
.map_err(Error::ListGitReferences)?
.filter_map(Result::ok)
.filter_map(|(branch, _)| {
if branch.is_head() {
return None;
}

let name = branch.name().ok()??;

// The default revision will be added to the top below,
// so filter it out.
if let Some(ref rev) = default_revision
&& rev == name
{
return None;
}

// Remote is only used for sorting
Some((branch.get().is_remote(), name.to_string()))
})
.sorted()
.map(|(_remote, branch_name)| {
PickerItem::new(branch_name.clone(), PickerData::Revision(branch_name))
})
.collect();

// Add the default revision to the top, so it's selected by default and
// can be accepted by <enter> without any extra steps.
if let Some(rev) = default_revision {
branches.insert(0, PickerItem::new(rev.clone(), PickerData::Revision(rev)));
}

Ok(Self::new(prompt, branches, true))
}

/// Get current input pattern
pub fn pattern(&self) -> &str {
self.input_state.value()
Expand Down
27 changes: 26 additions & 1 deletion src/tests/branch.rs
Original file line number Diff line number Diff line change
Expand Up @@ -23,9 +23,34 @@ fn switch_branch_input() {
snapshot!(setup(setup_clone!()), "Ybbmerged<enter>");
}

#[test]
fn switch_branch_picker() {
snapshot!(setup(setup_clone!()), "bb");
}

#[test]
fn switch_branch_selected_revision_picker() {
snapshot!(setup(setup_clone!()), "Yjjbb");
}

#[test]
fn checkout_new_branch_starting_point_picker() {
snapshot!(setup(setup_clone!()), "bc");
}

#[test]
fn checkout_new_branch_starting_point_picker_from_selected_rev() {
snapshot!(setup(setup_clone!()), "Yjjbc");
}

#[test]
fn checkout_new_branch_name_prompt() {
snapshot!(setup(setup_clone!()), "bc<enter>");
}

#[test]
fn checkout_new_branch() {
snapshot!(setup(setup_clone!()), "bcnew<enter>");
snapshot!(setup(setup_clone!()), "bc<enter>new<enter>");
}

#[test]
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,6 @@ expression: ctx.redact_buffer()
|
|
────────────────────────────────────────────────────────────────────────────────|
$ git checkout -b new |
$ git checkout -b new main |
Switched to a new branch 'new' |
styles_hash: 2afc72138214b087
styles_hash: 51d00da50a627344
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
---
source: src/tests/branch.rs
expression: ctx.redact_buffer()
---
▌On branch main |
▌Your branch is up to date with 'origin/main'. |
|
Recent commits |
b66a0bf main merged origin/main add initial-file |
|
|
|
|
|
|
|
|
|
|
|
|
|
────────────────────────────────────────────────────────────────────────────────|
? Create and checkout branch: › █ |
styles_hash: 5475e2883a3b1774
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
---
source: src/tests/branch.rs
expression: ctx.redact_buffer()
---
On branch main |
Your branch is up to date with 'origin/main'. |
|
Recent commits |
b66a0bf main merged origin/main add initial-file |
|
|
|
────────────────────────────────────────────────────────────────────────────────|
5/5 Create branch starting at › █ |
▌main |
merged |
unmerged |
origin/HEAD |
origin/main |
|
|
|
|
|
styles_hash: 106946388e06558f
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
---
source: src/tests/branch.rs
expression: ctx.redact_buffer()
---
Branches |
* main |
merged |
unmerged |
|
Remote origin |
origin/HEAD |
origin/main |
────────────────────────────────────────────────────────────────────────────────|
5/5 Create branch starting at › █ |
▌main |
merged |
unmerged |
origin/HEAD |
origin/main |
|
|
|
|
|
styles_hash: e48ad585ed1f40a5
25 changes: 25 additions & 0 deletions src/tests/snapshots/gitu__tests__branch__switch_branch_picker.snap
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
---
source: src/tests/branch.rs
expression: ctx.redact_buffer()
---
On branch main |
Your branch is up to date with 'origin/main'. |
|
Recent commits |
b66a0bf main merged origin/main add initial-file |
|
|
|
────────────────────────────────────────────────────────────────────────────────|
4/4 Checkout › █ |
▌merged |
unmerged |
origin/HEAD |
origin/main |
|
|
|
|
|
|
styles_hash: a64f5b0f5c87b3e5
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
---
source: src/tests/branch.rs
expression: ctx.redact_buffer()
---
Branches |
* main |
merged |
unmerged |
|
Remote origin |
origin/HEAD |
origin/main |
────────────────────────────────────────────────────────────────────────────────|
4/4 Checkout › █ |
▌merged |
unmerged |
origin/HEAD |
origin/main |
|
|
|
|
|
|
styles_hash: 2cac6f3d25b0271c
Original file line number Diff line number Diff line change
Expand Up @@ -2,14 +2,18 @@
source: src/tests/editor.rs
expression: ctx.redact_buffer()
---
On branch main |
Your branch is up to date with 'origin/main'. |
On branch main |
Your branch is up to date with 'origin/main'. |
|
Recent commits |
b66a0bf main origin/main add initial-file |
|
|
|
────────────────────────────────────────────────────────────────────────────────|
2/2 Checkout › █ |
▌origin/HEAD |
origin/main |
|
|
|
Expand All @@ -18,8 +22,4 @@ expression: ctx.redact_buffer()
|
|
|
|
|
────────────────────────────────────────────────────────────────────────────────|
? Checkout: › █ |
styles_hash: 43da189f53b3be3d
styles_hash: 49e995be810c0237