Skip to content

Commit

Permalink
✨ Add verbose option and rename option
Browse files Browse the repository at this point in the history
  • Loading branch information
sjquant committed Sep 1, 2024
1 parent ceefc1e commit 4d1feea
Show file tree
Hide file tree
Showing 4 changed files with 114 additions and 39 deletions.
13 changes: 9 additions & 4 deletions src/cli.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
use clap::{Args, Parser, Subcommand};

use crate::versioning::VersionPart;
use crate::versioning::Increment;

#[derive(Parser)]
#[clap(author, version, about, long_about = None)]
Expand Down Expand Up @@ -28,16 +28,19 @@ pub enum Commands {
#[derive(Args, Debug)]
pub struct NextVersionArgs {
#[clap(
help = "major, minor or patch",
help = "Specify the version part to increment: major, minor, or patch",
long,
arg_enum,
default_value = "patch"
default_value = "patch",
short = 'i'
)]
pub version_part: VersionPart,
pub increment: Increment,
#[clap(help = "Get next version based on pattern", long, short = 'p')]
pub pattern: Option<String>,
#[clap(help = "Tag current commit as next version", long, short = 't', action)]
pub tag: bool,
#[clap(help = "Verbose output", long, short = 'V', action)]
pub verbose: bool,
}

#[derive(Args, Debug)]
Expand All @@ -46,4 +49,6 @@ pub struct LastVersionArgs {
pub pattern: Option<String>,
#[clap(help = "Check out to last version", long, short = 'c', action)]
pub checkout: bool,
#[clap(help = "Verbose output", long, short = 'V', action)]
pub verbose: bool,
}
83 changes: 70 additions & 13 deletions src/gitutils.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,17 @@ use std::path::Path;

use git2::{Branch, DescribeFormatOptions, DescribeOptions, Repository};

#[derive(Default, Clone)]
pub struct CommandOptions {
pub verbose: bool,
}

fn print_verbose(message: &str, verbose: bool) {
if verbose {
println!("{}", message);
}
}

pub fn get_repo(path: &Path) -> Repository {
let repo = Repository::open(path).expect("Repository not found");
repo
Expand Down Expand Up @@ -38,7 +49,14 @@ pub fn commit(repo: &Repository, message: &str) -> Result<git2::Oid, git2::Error
)
}

pub fn checkout_branch(repo: &Repository, name: &str, force: bool) -> Result<(), git2::Error> {
pub fn checkout_branch(
repo: &Repository,
name: &str,
force: bool,
options: Option<&CommandOptions>,
) -> Result<(), git2::Error> {
let default = CommandOptions::default();
let opts = options.unwrap_or(&default);
let branch = repo.find_branch(name, git2::BranchType::Local);
if force && branch.is_err() {
let commit = repo.head().unwrap().peel_to_commit().unwrap();
Expand All @@ -50,16 +68,22 @@ pub fn checkout_branch(repo: &Repository, name: &str, force: bool) -> Result<(),
repo.checkout_tree(&object, None)
.expect("Failed to checkout");
repo.set_head(reference.unwrap().name().unwrap())?;
println!("Switched to branch '{}'", name);
print_verbose(&format!("Switched to branch '{}'", name), opts.verbose);
Result::Ok(())
}

pub fn checkout_tag(repo: &Repository, tag: &str) -> Result<(), git2::Error> {
pub fn checkout_tag(
repo: &Repository,
tag: &str,
options: Option<&CommandOptions>,
) -> Result<(), git2::Error> {
let default = CommandOptions::default();
let opts = options.unwrap_or(&default);
let (object, reference) = repo.revparse_ext(tag).expect("Tag not found");
repo.checkout_tree(&object, None)
.expect("Failed to checkout");
repo.set_head(reference.unwrap().name().unwrap())?;
println!("Switched to tag '{}'", tag);
print_verbose(&format!("Switched to tag '{}'", tag), opts.verbose);
Result::Ok(())
}

Expand All @@ -72,11 +96,22 @@ pub fn get_last_tag_name(repo: &Repository) -> Result<String, git2::Error> {
Ok(describe.format(Some(DescribeFormatOptions::new().abbreviated_size(0)))?)
}

pub fn fetch_all(remote: &mut git2::Remote) -> Result<(), git2::Error> {
println!("Fetching all branches and tags from remote...");
pub fn fetch_all(
remote: &mut git2::Remote,
options: Option<&CommandOptions>,
) -> Result<(), git2::Error> {
let default = CommandOptions::default();
let opts = options.unwrap_or(&default);
print_verbose(
&format!("Fetching all branches and tags from remote..."),
opts.verbose,
);
let mut fo = fetch_options();
remote.fetch(&["refs/heads/*:refs/heads/*"], Some(&mut fo), None)?;
println!("Successfully fetched all branches and tags from remote.");
print_verbose(
&format!("Successfully fetched all branches and tags from remote."),
opts.verbose,
);
Result::Ok(())
}

Expand All @@ -88,29 +123,51 @@ fn fetch_options() -> git2::FetchOptions<'static> {
fo
}

pub fn push_tag(remote: &mut git2::Remote, tag: &str) -> Result<(), git2::Error> {
println!("Pushing tag '{}' to remote...", tag);
// Update the push_tag function to accept options
pub fn push_tag(
remote: &mut git2::Remote,
tag: &str,
options: Option<&CommandOptions>,
) -> Result<(), git2::Error> {
let default = CommandOptions::default();
let opts = options.unwrap_or(&default);
print_verbose(&format!("Pushing tag '{}' to remote...", tag), opts.verbose);
let mut po = push_options();
let ref_spec = format!("refs/tags/{}:refs/tags/{}", tag, tag);
remote.push(&[&ref_spec], Some(&mut po))?;
println!("Successfully pushed tag '{}' to remote.", tag);
print_verbose(
&format!("Successfully pushed tag '{}' to remote.", tag),
opts.verbose,
);
Result::Ok(())
}

pub fn push_branch(remote: &mut git2::Remote, branch: &mut Branch) -> Result<(), git2::Error> {
pub fn push_branch(
remote: &mut git2::Remote,
branch: &mut Branch,
options: Option<&CommandOptions>,
) -> Result<(), git2::Error> {
let default = CommandOptions::default();
let opts = options.unwrap_or(&default);
let branch_name = branch
.name()
.unwrap()
.expect("Failed to get branch name")
.to_string();
let remote_name = remote.name().unwrap();
let upstream_name = format!("{}/{}", remote_name, branch_name.as_str());
println!("Pushing branch '{}' to remote...", branch_name);
print_verbose(
&format!("Pushing branch '{}' to remote...", branch_name),
opts.verbose,
);
let mut po = push_options();
let refspec = format!("refs/heads/{}:refs/heads/{}", branch_name, branch_name);
remote.push(&[&refspec], Some(&mut po))?;
branch.set_upstream(Some(&upstream_name))?;
println!("Successfully pushed branch '{}' to remote.", branch_name);
print_verbose(
&format!("Successfully pushed branch '{}' to remote.", branch_name),
opts.verbose,
);
Result::Ok(())
}

Expand Down
33 changes: 23 additions & 10 deletions src/service.rs
Original file line number Diff line number Diff line change
@@ -1,21 +1,26 @@
use std::path::Path;

use crate::cli::{LastVersionArgs, NextVersionArgs};
use crate::gitutils;
use crate::gitutils::{self, CommandOptions};
use crate::versioning::Versioner;

pub fn last_version(path: &Path, args: &LastVersionArgs) -> Option<String> {
let repo = gitutils::get_repo(path);
let mut remote = gitutils::get_remote(&repo, "origin");
gitutils::fetch_all(&mut remote).expect("Failed to fetch from remote");
let opts = CommandOptions {
verbose: args.verbose,
};

gitutils::fetch_all(&mut remote, Some(&opts)).expect("Failed to fetch from remote");
let pattern = args
.pattern
.clone()
.unwrap_or("v{major}.{minor}.{patch}".to_string());
let versioner = get_versioner(&repo, pattern);
if let Some(version) = versioner.last_version() {
if args.checkout {
gitutils::checkout_tag(&repo, &version.tag).expect("Failed to checkout tag");
gitutils::checkout_tag(&repo, &version.tag, Some(&opts))
.expect("Failed to checkout tag");
}
println!("{}", version.tag);
Some(version.tag)
Expand All @@ -28,14 +33,17 @@ pub fn last_version(path: &Path, args: &LastVersionArgs) -> Option<String> {
pub fn next_version(path: &Path, args: &NextVersionArgs) -> Option<String> {
let repo = gitutils::get_repo(path);
let mut remote = gitutils::get_remote(&repo, "origin");
gitutils::fetch_all(&mut remote).expect("Failed to fetch from remote");
let opts = CommandOptions {
verbose: args.verbose,
};
gitutils::fetch_all(&mut remote, Some(&opts)).expect("Failed to fetch from remote");
let pattern = args
.pattern
.clone()
.unwrap_or("v{major}.{minor}.{patch}".to_string());
let versioner = get_versioner(&repo, pattern);

if let Some(version) = versioner.next_version(args.version_part.clone()) {
if let Some(version) = versioner.next_version(args.increment.clone()) {
if args.tag {
let head = repo.head().unwrap();
let head_id = head.target().unwrap();
Expand Down Expand Up @@ -64,7 +72,7 @@ fn get_versioner(repo: &git2::Repository, pattern: String) -> Versioner {
mod tests {
use super::*;
use crate::cli::LastVersionArgs;
use crate::versioning::VersionPart;
use crate::versioning::Increment;
use crate::{gitutils, testutils};

#[test]
Expand Down Expand Up @@ -93,6 +101,7 @@ mod tests {
let args = LastVersionArgs {
pattern: Some("flopha@{major}.{minor}.{patch}".to_string()),
checkout: false,
verbose: false,
};

let result = last_version(td.path(), &args);
Expand All @@ -116,6 +125,7 @@ mod tests {
let args = LastVersionArgs {
pattern: Some("flopha@{major}.{minor}.{patch}".to_string()),
checkout: false,
verbose: false,
};
let result = last_version(td.path(), &args);

Expand Down Expand Up @@ -145,6 +155,7 @@ mod tests {
let args = LastVersionArgs {
pattern: Some("flopha@{major}.{minor}.{patch}".to_string()),
checkout: true,
verbose: false,
};
last_version(td.path(), &args);

Expand Down Expand Up @@ -174,14 +185,15 @@ mod tests {
for tag in tags {
create_new_remote_tag(&repo, &mut remote, tag, false);
}
gitutils::checkout_tag(&repo, "[email protected]").unwrap();
gitutils::checkout_tag(&repo, "[email protected]", None).unwrap();
gitutils::commit(&repo, "New commit").unwrap();

// When
let args = NextVersionArgs {
pattern: Some("flopha@{major}.{minor}.{patch}".to_string()),
version_part: VersionPart::Patch,
increment: Increment::Patch,
tag: false,
verbose: false,
};
let result = next_version(td.path(), &args);

Expand All @@ -205,14 +217,15 @@ mod tests {
for tag in tags {
create_new_remote_tag(&repo, &mut remote, tag, false);
}
gitutils::checkout_tag(&repo, "[email protected]").unwrap();
gitutils::checkout_tag(&repo, "[email protected]", None).unwrap();
gitutils::commit(&repo, "New commit").unwrap();

// When
let args = NextVersionArgs {
pattern: Some("flopha@{major}.{minor}.{patch}".to_string()),
version_part: VersionPart::Patch,
increment: Increment::Patch,
tag: true,
verbose: false,
};
next_version(td.path(), &args);

Expand Down
24 changes: 12 additions & 12 deletions src/versioning.rs
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ impl Version {
}

#[derive(Debug, Clone, ArgEnum)]
pub enum VersionPart {
pub enum Increment {
Major,
Minor,
Patch,
Expand Down Expand Up @@ -75,25 +75,25 @@ impl Versioner {
}
}

pub fn next_version(&self, version_part: VersionPart) -> Option<Version> {
pub fn next_version(&self, increment: Increment) -> Option<Version> {
let last_version = self.last_version()?;

let major;
let minor;
let patch;

match version_part {
VersionPart::Major => {
match increment {
Increment::Major => {
major = last_version.major.expect("Can't find {major}") + 1;
minor = 0;
patch = 0;
}
VersionPart::Minor => {
Increment::Minor => {
major = last_version.major.expect("Can't find {major}");
minor = last_version.minor.expect("Can't find {minor}") + 1;
patch = 0;
}
VersionPart::Patch => {
Increment::Patch => {
major = last_version.major.expect("Can't find {major}");
minor = last_version.minor.expect("Can't find {minor}");
patch = last_version.patch.expect("Can't find {patch}") + 1;
Expand Down Expand Up @@ -244,7 +244,7 @@ mod tests {
"z4.0.0".to_string(),
];
let versioner = Versioner::new(tags.clone(), "v{major}.{minor}.{patch}".to_string());
let next_version = versioner.next_version(VersionPart::Major);
let next_version = versioner.next_version(Increment::Major);
assert_eq!(
next_version,
Some(Version::new(
Expand All @@ -255,7 +255,7 @@ mod tests {
))
);

let next_version = versioner.next_version(VersionPart::Minor);
let next_version = versioner.next_version(Increment::Minor);
assert_eq!(
next_version,
Some(Version::new(
Expand All @@ -266,7 +266,7 @@ mod tests {
))
);

let next_version = versioner.next_version(VersionPart::Patch);
let next_version = versioner.next_version(Increment::Patch);
assert_eq!(
next_version,
Some(Version::new(
Expand All @@ -284,18 +284,18 @@ mod tests {
vec!["v1.0.0".to_string(), "v1.0.1".to_string()],
"no-{major}.{minor}.{patch}".to_string(),
);
let next_version = versioner.next_version(VersionPart::Major);
let next_version = versioner.next_version(Increment::Major);
assert_eq!(next_version, None);
}

#[test]
fn test_next_version_panic_when_no_version_part_in_pattern() {
fn test_next_version_panic_when_no_increment_in_pattern() {
let versioner = Versioner::new(
vec!["v1.0.0".to_string(), "v1.0.1".to_string()],
"v1.{minor}.{patch}".to_string(),
);
assert!(std::panic::catch_unwind(|| {
versioner.next_version(VersionPart::Major);
versioner.next_version(Increment::Major);
})
.is_err());
}
Expand Down

0 comments on commit 4d1feea

Please sign in to comment.