Skip to content

Commit

Permalink
Merge pull request #116 from mang0kitty/feature/switch-command
Browse files Browse the repository at this point in the history
feat: add support for git switch command
  • Loading branch information
notheotherben authored Oct 4, 2020
2 parents 1a246a7 + b124d27 commit 3764d1a
Show file tree
Hide file tree
Showing 7 changed files with 363 additions and 4 deletions.
7 changes: 3 additions & 4 deletions src/commands/branch.rs
Original file line number Diff line number Diff line change
Expand Up @@ -118,9 +118,8 @@ mod tests {

let args: ArgMatches = cmd.app().get_matches_from(vec!["branch", "feature/test"]);

match cmd.run(&core, &args).await {
Ok(_) => panic!("This command should not have succeeded"),
_ => {}
}
cmd.run(&core, &args)
.await
.expect_err("this command should not have succeeded");
}
}
2 changes: 2 additions & 0 deletions src/commands/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ mod open;
mod scratch;
mod services;
mod shell_init;
mod switch;
mod update;

pub trait Command: Send + Sync {
Expand Down Expand Up @@ -61,5 +62,6 @@ pub fn commands<C: Core>() -> Vec<Arc<dyn CommandRunnable<C>>> {
Arc::new(services::ServicesCommand {}),
Arc::new(shell_init::ShellInitCommand {}),
Arc::new(update::UpdateCommand {}),
Arc::new(switch::SwitchCommand {}),
]
}
189 changes: 189 additions & 0 deletions src/commands/switch.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,189 @@
use super::super::errors;
use super::*;
use crate::core::Target;
use crate::git;
use crate::tasks::*;
use clap::{App, Arg};

pub struct SwitchCommand {}

impl Command for SwitchCommand {
fn name(&self) -> String {
String::from("switch")
}

fn app<'a>(&self) -> App<'a> {
App::new(self.name().as_str())
.version("1.0")
.alias("s")
.about("switches to the specified branch.")
.long_about(
"This command switches to the specified branch within the current repository.",
)
.arg(
Arg::new("branch")
.about("The name of the branch to switch to.")
.index(1),
)
.arg(
Arg::new("create")
.short('c')
.long("create")
.about("creates a new branch before switching to it."),
)
}
}

#[async_trait]
impl<C: Core> CommandRunnable<C> for SwitchCommand {
async fn run(&self, core: &C, matches: &ArgMatches) -> Result<i32, errors::Error> {
let repo = core.resolver().get_current_repo()?;

match matches.value_of("branch") {
Some(branch) => {
let task = tasks::GitSwitch {
branch: branch.to_string(),
create_if_missing: matches.is_present("create"),
};

task.apply_repo(core, &repo).await?;
}
None => {
let branches = git::git_branches(&repo.get_path()).await?;
for branch in branches {
println!("{}", branch);
}
}
};

Ok(0)
}

async fn complete(&self, core: &C, completer: &Completer, _matches: &ArgMatches) {
if let Ok(repo) = core.resolver().get_current_repo() {
completer.offer("--create");
if let Ok(branches) = git::git_branches(&repo.get_path()).await {
completer.offer_many(branches);
}
}
}
}

#[cfg(test)]
mod tests {

use super::*;
use crate::core::*;
use tempfile::tempdir;

#[tokio::test]
async fn switch_branch_exists() {
let cmd: SwitchCommand = SwitchCommand {};

let temp = tempdir().unwrap();
let repo: Repo = core::Repo::new(
"github.com/sierrasoftworks/test-git-switch-command",
temp.path().join("repo").into(),
);

let core = core::CoreBuilder::default()
.with_config(&core::Config::for_dev_directory(temp.path()))
.with_mock_resolver(|r| r.set_repo(repo.clone()))
.build();

sequence!(
// Run a `git init` to setup the repo
tasks::GitInit {},
// Create the branch we want to switch to
tasks::GitCheckout {
branch: "feature/test".into(),
},
tasks::WriteFile {
path: "README.md".into(),
content: "This is an example README file.",
},
tasks::GitAdd {
paths: vec!["README.md"],
},
tasks::GitCommit {
message: "Add README.md",
paths: vec!["README.md"],
},
tasks::GitCheckout {
branch: "main".into(),
}
)
.apply_repo(&core, &repo)
.await
.unwrap();

assert!(repo.valid(), "the repository should exist and be valid");

let args: ArgMatches = cmd.app().get_matches_from(vec!["switch", "feature/test"]);
cmd.run(&core, &args).await.unwrap();

assert!(repo.valid(), "the repository should still be valid");
assert_eq!(
git::git_current_branch(&repo.get_path()).await.unwrap(),
"feature/test"
);
}

#[tokio::test]
async fn switch_branch_not_exists() {
let cmd: SwitchCommand = SwitchCommand {};

let temp = tempdir().unwrap();
let repo: Repo = core::Repo::new(
"github.com/sierrasoftworks/test-git-switch-command",
temp.path().join("repo").into(),
);

let core = core::CoreBuilder::default()
.with_config(&core::Config::for_dev_directory(temp.path()))
.with_mock_resolver(|r| r.set_repo(repo.clone()))
.build();

// Run a `git init` to setup the repo
tasks::GitInit {}.apply_repo(&core, &repo).await.unwrap();

assert!(repo.valid(), "the repository should exist and be valid");

let args: ArgMatches = cmd.app().get_matches_from(vec!["switch", "feature/test"]);
cmd.run(&core, &args)
.await
.expect_err("this command should not have succeeded");
}

#[tokio::test]
async fn switch_create_branch() {
let cmd: SwitchCommand = SwitchCommand {};

let temp = tempdir().unwrap();
let repo: Repo = core::Repo::new(
"github.com/sierrasoftworks/test-git-switch-command",
temp.path().join("repo").into(),
);

let core = core::CoreBuilder::default()
.with_config(&core::Config::for_dev_directory(temp.path()))
.with_mock_resolver(|r| r.set_repo(repo.clone()))
.build();

// Run a `git init` to setup the repo
tasks::GitInit {}.apply_repo(&core, &repo).await.unwrap();

assert!(repo.valid(), "the repository should exist and be valid");

let args: ArgMatches = cmd
.app()
.get_matches_from(vec!["switch", "-c", "feature/test"]);
cmd.run(&core, &args).await.unwrap();

assert!(repo.valid(), "the repository should still be valid");
assert_eq!(
git::git_current_branch(&repo.get_path()).await.unwrap(),
"feature/test"
);
}
}
2 changes: 2 additions & 0 deletions src/git/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ mod commit;
mod init;
mod refs;
mod remote;
mod switch;

pub use add::git_add;
pub use branch::{git_branches, git_current_branch};
Expand All @@ -17,3 +18,4 @@ pub use commit::git_commit;
pub use init::git_init;
pub use refs::{git_rev_parse, git_update_ref};
pub use remote::{git_remote_add, git_remote_list, git_remote_set_url};
pub use switch::git_switch;
28 changes: 28 additions & 0 deletions src/git/switch.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
use tokio::process::Command;

use super::git_cmd;
use crate::errors;
use std::path;

pub async fn git_switch(repo: &path::Path, name: &str, create: bool) -> Result<(), errors::Error> {
if create {
git_cmd(
Command::new("git")
.current_dir(repo)
.arg("switch")
.arg("-c")
.arg(name),
)
.await?;
} else {
git_cmd(
Command::new("git")
.current_dir(repo)
.arg("switch")
.arg(name),
)
.await?;
}

Ok(())
}
137 changes: 137 additions & 0 deletions src/tasks/git_switch.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,137 @@
use super::*;
use crate::{core::Target, git};

pub struct GitSwitch {
pub branch: String,
pub create_if_missing: bool,
}

#[async_trait::async_trait]
impl<C: Core> Task<C> for GitSwitch {
async fn apply_repo(&self, _core: &C, repo: &core::Repo) -> Result<(), core::Error> {
git::git_switch(&repo.get_path(), &self.branch, self.create_if_missing).await
}

async fn apply_scratchpad(
&self,
_core: &C,
_scratch: &core::Scratchpad,
) -> Result<(), core::Error> {
Ok(())
}
}

#[cfg(test)]
mod tests {
use crate::core::{Config, Repo};

use super::*;
use crate::tasks::GitInit;
use tempfile::tempdir;

#[tokio::test]
async fn test_repo() {
let temp = tempdir().unwrap();
let repo = core::Repo::new(
"github.com/sierrasoftworks/test-git-switch",
temp.path().join("repo").into(),
);

let core = core::CoreBuilder::default()
.with_config(&Config::for_dev_directory(temp.path()))
.with_mock_output()
.with_mock_resolver(|r| {
r.set_repo(Repo::new(
"example.com/test/cmd-branch",
temp.path().to_path_buf(),
))
})
.build();

sequence![
GitInit {},
GitCheckout {
branch: "main".into(),
},
GitSwitch {
branch: "test".into(),
create_if_missing: true,
}
]
.apply_repo(&core, &repo)
.await
.unwrap();
assert!(repo.valid());

assert_eq!(
git::git_current_branch(&repo.get_path()).await.unwrap(),
"test"
);
}

#[tokio::test]
async fn test_repo_no_create() {
let temp = tempdir().unwrap();
let repo = core::Repo::new(
"github.com/sierrasoftworks/test-git-switch",
temp.path().join("repo").into(),
);

let core = core::CoreBuilder::default()
.with_config(&Config::for_dev_directory(temp.path()))
.with_mock_output()
.with_mock_resolver(|r| {
r.set_repo(Repo::new(
"example.com/test/cmd-branch",
temp.path().to_path_buf(),
))
})
.build();

sequence![
GitInit {},
GitCheckout {
branch: "main".into(),
},
GitSwitch {
branch: "test".into(),
create_if_missing: false,
}
]
.apply_repo(&core, &repo)
.await
.expect_err("this command should fail");
assert!(repo.valid());

assert_eq!(
git::git_current_branch(&repo.get_path()).await.unwrap(),
"main"
);
}

#[tokio::test]
async fn test_scratch() {
let temp = tempdir().unwrap();
let scratch = core::Scratchpad::new("2019w15", temp.path().join("scratch").into());

let core = core::CoreBuilder::default()
.with_config(&Config::for_dev_directory(temp.path()))
.with_mock_output()
.with_mock_resolver(|r| {
r.set_repo(Repo::new(
"example.com/test/cmd-branch",
temp.path().to_path_buf(),
))
})
.build();

let task = GitSwitch {
branch: "test".into(),
create_if_missing: true,
};

task.apply_scratchpad(&core, &scratch).await.unwrap();
assert_eq!(scratch.get_path().join(".git").exists(), false);
assert_eq!(scratch.exists(), false);
}
}
Loading

0 comments on commit 3764d1a

Please sign in to comment.