Skip to content

Commit

Permalink
Provide porcelain output for scripts.
Browse files Browse the repository at this point in the history
Options:
* local branches
* remote branches
* JSON output.

\foriequal0#138
  • Loading branch information
Christoph Siedentop authored and siedentop committed Nov 16, 2020
1 parent 42a0874 commit d5c5ed8
Show file tree
Hide file tree
Showing 9 changed files with 321 additions and 11 deletions.
47 changes: 43 additions & 4 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 2 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,8 @@ man = { version = "0.3.0", optional = true }
rson_rs = { version = "0.2.1", optional = true }
regex = { version = "1.4.2", optional = true }
indicatif = { version = "0.15.0" , features = ["rayon"]}
serde = { version = "1.0.117", features = ["derive"] }
serde_json = "1.0.59"

[dev-dependencies]
tempfile = "3.1.0"
39 changes: 39 additions & 0 deletions src/args.rs
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,10 @@ pub struct Args {
#[clap(long, hidden(true))]
pub update: bool,

/// Output for scripting. Options are "json" for full structured output or "local" or "remote" for a list of branches to be deleted.
#[clap(long)]
pub porcelain: Option<PorcelainFormat>,

/// Prevents too frequent updates. Seconds between updates in seconds. 0 to disable.
/// [default: 5] [config: trim.updateInterval]
#[clap(long)]
Expand Down Expand Up @@ -127,6 +131,41 @@ fn exclusive_bool(
}
}

/// Configuration of --porcelain format.
#[derive(Debug)]
pub enum PorcelainFormat {
/// List of to-be-deleted local branches
LocalBranches,
/// List of remote branches to be deleted.
RemoteBranches,
/// Full structured JSON output
JSON,
}

impl FromStr for PorcelainFormat {
type Err = PorcelainFormatParseError;

fn from_str(s: &str) -> Result<Self, Self::Err> {
match s.trim() {
"" => Err(PorcelainFormatParseError {
message: "Porcelain format is empty".to_owned(),
}),
"json" => Ok(PorcelainFormat::JSON),
"local" | "l" => Ok(PorcelainFormat::LocalBranches),
"remote" | "r" => Ok(PorcelainFormat::RemoteBranches),
unknown => Err(PorcelainFormatParseError {
message: format!("Unknown porcelain format: {}", unknown),
}),
}
}
}

#[derive(Error, Debug)]
#[error("{message}")]
pub struct PorcelainFormatParseError {
message: String,
}

#[derive(Hash, Eq, PartialEq, Clone, Debug)]
pub enum Scope {
All,
Expand Down
7 changes: 4 additions & 3 deletions src/branch.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ use std::convert::TryFrom;
use anyhow::{Context, Result};
use git2::{Branch, Config, Direction, Reference, Repository};
use log::*;
use serde::Serialize;
use thiserror::Error;

use crate::config;
Expand All @@ -12,7 +13,7 @@ pub trait Refname {
fn refname(&self) -> &str;
}

#[derive(Eq, PartialEq, Ord, PartialOrd, Debug, Hash, Clone)]
#[derive(Eq, PartialEq, Ord, PartialOrd, Debug, Hash, Clone, Serialize)]
pub struct LocalBranch {
pub refname: String,
}
Expand Down Expand Up @@ -83,7 +84,7 @@ impl<'repo> TryFrom<&git2::Reference<'repo>> for LocalBranch {
}
}

#[derive(Eq, PartialEq, Ord, PartialOrd, Debug, Hash, Clone)]
#[derive(Eq, PartialEq, Ord, PartialOrd, Debug, Hash, Clone, Serialize)]
pub struct RemoteTrackingBranch {
pub refname: String,
}
Expand Down Expand Up @@ -202,7 +203,7 @@ pub enum RemoteTrackingBranchStatus {
None,
}

#[derive(Eq, PartialEq, Ord, PartialOrd, Clone, Hash, Debug)]
#[derive(Eq, PartialEq, Ord, PartialOrd, Clone, Hash, Debug, Serialize)]
pub struct RemoteBranch {
pub remote: String,
pub refname: String,
Expand Down
7 changes: 5 additions & 2 deletions src/core.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ use anyhow::{Context, Result};
use git2::{BranchType, Config, Repository};
use log::*;
use rayon::prelude::*;
use serde::Serialize;

use crate::args::DeleteFilter;
use crate::branch::{
Expand All @@ -19,12 +20,14 @@ use crate::{config, BaseSpec, Git};
use indicatif::ParallelProgressIterator;
use rayon::iter::ParallelIterator;

#[derive(Serialize)]
pub struct TrimPlan {
pub skipped: HashMap<String, SkipSuggestion>,
pub to_delete: HashSet<ClassifiedBranch>,
pub preserved: Vec<Preserved>,
}

#[derive(Serialize)]
pub struct Preserved {
pub branch: ClassifiedBranch,
pub reason: String,
Expand Down Expand Up @@ -392,7 +395,7 @@ impl TrimPlan {
}
}

#[derive(Clone, Eq, PartialEq)]
#[derive(Clone, Eq, PartialEq, Serialize)]
pub enum SkipSuggestion {
Tracking,
TrackingRemote(String),
Expand Down Expand Up @@ -436,7 +439,7 @@ fn get_protect_pattern<'a, B: Refname>(
Ok(None)
}

#[derive(Hash, Eq, PartialEq, Debug, Clone)]
#[derive(Hash, Eq, PartialEq, Debug, Clone, Serialize)]
pub enum ClassifiedBranch {
MergedLocal(LocalBranch),
Stray(LocalBranch),
Expand Down
1 change: 1 addition & 0 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ mod branch;
pub mod config;
mod core;
mod merge_tracker;
pub mod porcelain_outputs;
mod simple_glob;
mod subprocess;
mod util;
Expand Down
21 changes: 19 additions & 2 deletions src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,9 @@ use dialoguer::Confirm;
use git2::{BranchType, Repository};
use log::*;

use git_trim::args::Args;
use git_trim::args::{Args, PorcelainFormat};
use git_trim::config::{self, get, Config, ConfigValue};
use git_trim::porcelain_outputs::{print_json, print_local, print_remote};
use git_trim::{
delete_local_branches, delete_remote_branches, get_trim_plan, ls_remote_head, remote_update,
ClassifiedBranch, ForceSendSync, Git, LocalBranch, PlanParam, RemoteHead, RemoteTrackingBranch,
Expand Down Expand Up @@ -58,7 +59,23 @@ fn main(args: Args) -> Result<()> {
},
)?;

print_summary(&plan, &git.repo)?;
match args.porcelain {
None => {
print_summary(&plan, &git.repo)?;
}
Some(PorcelainFormat::LocalBranches) => {
print_local(&plan, &git.repo, &mut std::io::stdout())?;
return Ok(());
}
Some(PorcelainFormat::RemoteBranches) => {
print_remote(&plan, &git.repo, &mut std::io::stdout())?;
return Ok(());
}
Some(PorcelainFormat::JSON) => {
print_json(&plan, &git.repo, &mut std::io::stdout())?;
return Ok(());
}
}

let locals = plan.locals_to_delete();
let remotes = plan.remotes_to_delete(&git.repo)?;
Expand Down
53 changes: 53 additions & 0 deletions src/porcelain_outputs.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
use anyhow::Result;
use git2::Repository;

use crate::TrimPlan;

/// Prints all locally to-be-deleted branches.
pub fn print_local(
plan: &TrimPlan,
_repo: &Repository,
mut writer: impl std::io::Write,
) -> Result<()> {
let mut merged_locals = Vec::new();
for branch in &plan.to_delete {
if let Some(local) = branch.local() {
merged_locals.push(local.short_name().to_owned());
}
}

merged_locals.sort();
for branch in merged_locals {
writeln!(writer, "{}", branch)?;
}

Ok(())
}

/// Print all remotely to-be-deleted branches in the form "<remote>/<branch_name>"
pub fn print_remote(
plan: &TrimPlan,
repo: &Repository,
mut writer: impl std::io::Write,
) -> Result<()> {
let mut merged_remotes = Vec::new();
for branch in &plan.to_delete {
if let Some(remote) = branch.remote(repo)? {
merged_remotes.push(remote);
}
}

merged_remotes.sort();
for branch in merged_remotes {
let branch_name = &branch.refname["/refs/heads".len()..];
writeln!(writer, "{}/{}", branch.remote, branch_name)?;
}

Ok(())
}

pub fn print_json(plan: &TrimPlan, _repo: &Repository, writer: impl std::io::Write) -> Result<()> {
serde_json::to_writer(writer, &plan)?;

Ok(())
}
Loading

0 comments on commit d5c5ed8

Please sign in to comment.