Skip to content

Commit

Permalink
Merge pull request #10 from daniel7grant/feature/repository-command
Browse files Browse the repository at this point in the history
Add repository creation and forking commands
  • Loading branch information
daniel7grant committed May 29, 2023
2 parents 1aec797 + 8909e11 commit 4974c4f
Show file tree
Hide file tree
Showing 28 changed files with 2,155 additions and 93 deletions.
1 change: 1 addition & 0 deletions .github/workflows/main.yml
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ jobs:
uses: actions-rs/cargo@v1
with:
command: clippy
args: -- -Dwarnings
- name: Setup configuration files
run: |
git config --global user.email "gr-test@d7gr.net"
Expand Down
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@

- Allow type to be defined at login
- Add Gitea integration
- Allow repository creation and forking with repo subcommand
- Add hidden repository delete command to repo

## [0.1.5] - 2023-03-22

Expand Down
148 changes: 147 additions & 1 deletion src/cmd/args.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
use clap::{ArgAction, Command, CommandFactory, Parser, Subcommand, ValueEnum};
use clap_complete::{generate, Generator, Shell};
use gr_bin::formatters::formatter::FormatterType;
use gr_bin::vcs::common::RepositoryVisibility;
use std::io;
use std::process;

Expand Down Expand Up @@ -44,6 +45,27 @@ impl From<OutputType> for FormatterType {
}
}

#[derive(Copy, Clone, Debug, PartialEq, Eq, PartialOrd, Ord, ValueEnum, Default)]
pub enum Visibility {
/// Repo can be visible by anyone (default)
#[default]
Public,
/// Repo can only be visible by you
Private,
/// Repo can only be visible by logged in users (GitLab only)
Internal,
}

impl From<Visibility> for RepositoryVisibility {
fn from(val: Visibility) -> Self {
match val {
Visibility::Public => RepositoryVisibility::Public,
Visibility::Private => RepositoryVisibility::Private,
Visibility::Internal => RepositoryVisibility::Internal,
}
}
}

#[derive(Debug, Subcommand)]
#[command(after_help = "Examples:
Expand Down Expand Up @@ -168,6 +190,127 @@ $ gr pr decline")]
Close {},
}

#[derive(Debug, Subcommand)]
#[command(after_help = "Examples:
Create new repository:
$ gr repo new new-repo
Fork a repository:
$ gr repo fork https://github.com/daniel7grant/gr
Get information about the current repository:
$ gr repo get
")]
pub enum RepoCommands {
#[command(alias = "create")]
#[command(after_help = "Examples:
Create new repository (and push the current dir if it has a git repository):
$ gr repo new new-repo
Create new repository for a specific remote:
$ gr repo new new-repo --host github.com
Create new repo and clone it immediately:
$ gr repo new new-repo --clone
Create new repo and clone somewhere else:
$ gr repo new new-repo --clone --dir path/to/another
Create new repository and initialize with (all options):
$ gr repo new new-repo --init --default-branch develop --gitignore Rust --license MIT
")]
/// Create new repository
New {
/// The name of the new repository, can be either: a full URL (e.g. "https://github.com/user/gr.git"), an organization and repo name, (e.g. "user/gr") or a repo name (will be created under user) e.g. "gr".
repository: String,
/// The host of the server (e.g. "github.com")
#[arg(long)]
host: Option<String>,
/// Whether to clone the new repository
#[arg(long)]
clone: bool,
/// The description of the new repo
#[arg(short, long)]
description: Option<String>,
/// The visibility of the new repo
#[arg(long, default_value = "private")]
visibility: Visibility,
/// Whether to initialize the repository with a README (GitHub, GitLab and Gitea only)
#[arg(long)]
init: bool,
/// The default branch to initialize with (GitLab and Gitea only)
#[arg(long)]
default_branch: Option<String>,
/// The gitignore to initialize with (GitHub and Gitea only)
#[arg(long)]
gitignore: Option<String>,
/// The license to initialize with (GitHub and Gitea only)
#[arg(long)]
license: Option<String>,
/// Whether to open the new repository in the browser
#[arg(long)]
open: bool,
},
#[command(after_help = "Examples:
Fork an existing repository:
$ gr repo fork https://github.com/daniel7grant/gr
Fork an existing repository to a different name:
$ gr repo fork https://github.com/daniel7grant/gr gr_forked
Fork an existing repository to a different organization:
$ gr repo fork https://github.com/daniel7grant/gr organization/gr
")]
/// Fork existing repository
Fork {
/// The source repository to fork from
source: String,
/// The target name, e.g. "name" or "org/name" (by default the same name to the current user)
repository: Option<String>,
/// Whether to clone the forked repository
#[arg(long)]
clone: bool,
},
#[command(after_help = "Examples:
Get the repository information in the current directory:
$ gr repo get
Open repository in the browser:
$ gr repo get --open
")]
/// Get the open repository
Get {
/// Open the repository in the browser
#[arg(long)]
open: bool,
},
#[command(after_help = "Examples:
Open repository in the browser:
$ gr repo open
")]
/// Open the repository in the browser
Open {},
#[command(
hide = true,
after_help = "Examples:
Delete the current repository:
$ gr repo delete
"
)]
/// Delete the current repository
Delete {
/// Delete the repository FOREVER without interaction
#[arg(long = "yes-delete-permanently")]
force: bool,
},
}

#[derive(Debug, Subcommand)]
pub enum Commands {
#[command(after_help = "Examples:
Expand All @@ -194,9 +337,12 @@ $ gr login git.example.org --type gitlab")]
#[arg(long)]
token: Option<String>,
},
/// Interact with pull requests
/// Open, list and merge pull requests
#[command(subcommand)]
Pr(PrCommands),
/// Fork or create repositories
#[command(subcommand)]
Repo(RepoCommands),
/// Generate tab completion to shell
Completion { shell: Shell },
}
Expand Down
5 changes: 4 additions & 1 deletion src/cmd/config.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,10 +5,12 @@ use serde::{Deserialize, Serialize};
use std::{collections::HashMap, fs::read_to_string, fs::write};
use tracing::{info, instrument, trace};

#[derive(Debug, Deserialize, Serialize)]
#[derive(Debug, Deserialize, Serialize, Default)]
pub struct RepositoryConfig {
pub auth: Option<String>,
pub default_branch: Option<String>,
#[serde(default)]
pub fork: bool,
}

#[derive(Debug, Deserialize, Serialize)]
Expand Down Expand Up @@ -97,6 +99,7 @@ impl Configuration {
auth: r.and_then(|r| r.auth.clone()).unwrap_or(v.auth.clone()),
default_branch: r.and_then(|r| r.default_branch.clone()),
vcs_type: v.vcs_type.clone(),
fork: r.map(|r| r.fork).unwrap_or_default(),
}
})
}
Expand Down
2 changes: 1 addition & 1 deletion src/cmd/login/login.rs
Original file line number Diff line number Diff line change
Expand Up @@ -89,7 +89,7 @@ pub fn login(args: Cli, mut conf: Configuration) -> Result<()> {
})
.or_insert(RepositoryConfig {
auth: Some(token),
default_branch: None,
..Default::default()
});
});
}
Expand Down
1 change: 1 addition & 0 deletions src/cmd/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,3 +2,4 @@ pub mod args;
pub mod config;
pub mod login;
pub mod pr;
pub mod repo;
2 changes: 1 addition & 1 deletion src/cmd/pr/create.rs
Original file line number Diff line number Diff line change
Expand Up @@ -147,8 +147,8 @@ pub fn create(args: Cli, mut conf: Configuration) -> Result<()> {
.entry(repo)
.and_modify(|repo| repo.default_branch = Some(pr.target.clone()))
.or_insert(RepositoryConfig {
auth: None,
default_branch: Some(pr.target.clone()),
..Default::default()
});
});

Expand Down
82 changes: 82 additions & 0 deletions src/cmd/repo/delete.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
use std::io::{self, Write};

use crate::cmd::{
args::{Cli, Commands, RepoCommands},
config::Configuration,
};
use colored::Colorize;
use eyre::{eyre, Context, ContextCompat, Result};
use gr_bin::vcs::common::init_vcs;
use gr_bin::{git::git::LocalRepository, vcs::common::VersionControlSettings};
use tracing::instrument;

#[instrument(skip_all, fields(command = ?args.command))]
pub fn delete(args: Cli, conf: Configuration) -> Result<()> {
let Cli {
command,
branch,
dir,
auth,
output,
verbose: _,
} = args;
if let Commands::Repo(RepoCommands::Delete { force }) = command {
let repository = LocalRepository::init(dir)?;
let (hostname, repo, ..) = repository.get_parsed_remote(branch)?;

// Find settings or use the auth command
let settings = conf.find_settings(&hostname, &repo);
let settings = if let Some(auth) = auth {
VersionControlSettings {
auth,
..settings.unwrap_or_default()
}
} else {
settings.wrap_err(eyre!(
"Authentication not found for {} in {}.",
&hostname,
&repo
))?
};

let vcs = init_vcs(hostname, repo, settings)?;

let repo = vcs.get_repository()?;
repo.print(false, output.into());

if !force {
println!(
"You are about to {} this repository. {}",
"delete".red().bold(),
"This action cannot be undone!".bold()
);
let mut stdout = io::stdout();
let stdin = io::stdin();
write!(
stdout,
"Please enter the repository name if you are absolutely sure: "
)?;
stdout.flush()?;
let mut entered_name = String::new();
stdin
.read_line(&mut entered_name)
.wrap_err("Reading the repository name failed.")?;
entered_name = entered_name.trim().to_string();
if entered_name != repo.full_name {
return Err(eyre!(
"You cannot delete {}! You entered: {}.",
repo.full_name,
entered_name
));
}
}

vcs.delete_repository()?;

println!("Repository {} deleted.", repo.full_name);

Ok(())
} else {
Err(eyre!("Invalid command!"))
}
}
Loading

0 comments on commit 4974c4f

Please sign in to comment.