Skip to content

Commit 88a4867

Browse files
committed
feat: add tag format and default repo to config
1 parent ce17d7b commit 88a4867

4 files changed

Lines changed: 80 additions & 44 deletions

File tree

src/cmd/build.rs

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -8,10 +8,10 @@ use tokio::{fs, task::JoinSet};
88
use crate::{
99
builder::{BuildError, MetaBuild},
1010
config::Config,
11+
git,
1112
image::Image,
1213
progress,
1314
registry::{self, PushError, Registry},
14-
tag::{self, TagError},
1515
};
1616

1717
pub mod output {
@@ -41,9 +41,9 @@ pub enum WriteError {
4141

4242
#[derive(Debug, Diagnostic, thiserror::Error)]
4343
pub enum Error {
44-
#[error("failed to tag")]
44+
#[error(transparent)]
4545
#[diagnostic(transparent)]
46-
Tag(#[from] TagError),
46+
Git(#[from] git::GitError),
4747
#[error("failed to build")]
4848
#[diagnostic(transparent)]
4949
Build(#[from] BuildError),
@@ -80,15 +80,15 @@ pub async fn run(
8080
repo: Option<String>,
8181
output_file: Option<&Path>,
8282
) -> Result<(), Error> {
83-
let tag = tag::resolve().await?;
8483
let root = progress::tree();
8584
let handle = progress::setup_line_renderer(&root);
8685
let insecure_registries = mem::take(&mut config.insecure_registries);
8786

87+
let (tag, default_repo) = (config.tag_format.clone(), config.default_repo.take());
8888
let builder = MetaBuild::new(config);
8989
let output = builder.build(root.add_child("build"), &platform).await?;
9090

91-
match repo {
91+
match repo.or(default_repo) {
9292
Some(repo) => {
9393
let mut progress = root.add_child("push");
9494
progress.init(Some(output.artifacts.len()), None);

src/config.rs

Lines changed: 42 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -8,14 +8,21 @@ use miette::Diagnostic;
88
use serde::Deserialize;
99
use serde_yml::{Mapping, Value};
1010

11+
use crate::git;
12+
13+
const DEFAULT_TAG_FORMAT: &str = "${gitTag:$gitShortCommit}${gitDirty:}";
14+
1115
#[derive(Debug, Deserialize, Clone)]
1216
#[serde(rename_all = "camelCase")]
1317
pub struct Config {
14-
#[serde(default)]
15-
pub insecure_registries: Vec<String>,
1618
pub build: HashMap<String, Build>,
1719
#[serde(default)]
1820
pub deploy: HashMap<String, Release>,
21+
#[serde(default)]
22+
pub insecure_registries: Vec<String>,
23+
pub default_repo: Option<String>,
24+
#[serde(default)]
25+
pub tag_format: String,
1926
}
2027

2128
#[derive(Clone, Debug, Deserialize)]
@@ -91,6 +98,8 @@ pub enum ConfigError {
9198
Subst(#[from] subst::Error),
9299
#[error("failed to deserialize")]
93100
Yaml(#[from] serde_yml::Error),
101+
#[error("failed to parse git status")]
102+
Git(#[from] git::GitError),
94103
#[error("profile '{0}' does not exist")]
95104
Profile(String),
96105
}
@@ -116,25 +125,45 @@ fn template(vars: &HashMap<String, String>, config: Value) -> Result<Value, subs
116125
}
117126
}
118127

128+
fn extract_git_vars(state: git::State) -> HashMap<String, String> {
129+
let mut vars = HashMap::new();
130+
131+
vars.insert("gitShortCommit".to_string(), state.commit[0..6].to_string());
132+
vars.insert("gitCommit".to_string(), state.commit);
133+
if let Some(tag) = state.tag {
134+
vars.insert("gitTag".to_string(), tag);
135+
}
136+
if state.dirty {
137+
vars.insert("gitDirty".to_string(), "-dirty".to_string());
138+
}
139+
140+
vars
141+
}
142+
119143
pub async fn load_from_path(
120144
profile: Option<&str>,
121145
path: impl AsRef<Path>,
122146
) -> Result<Config, ConfigError> {
147+
let mut vars = extract_git_vars(git::state().await?);
123148
let data = tokio::fs::read(path).await?;
124149
let mut config = serde_yml::from_slice::<Value>(&data)?;
125150

126151
if let Some(profile) = profile {
127-
match config
128-
.get_mut("profiles")
129-
.and_then(|profiles| profiles.get_mut(profile))
130-
{
131-
Some(profile) => {
132-
let profile = serde_yml::from_value::<Profile>(mem::take(profile))?;
133-
config = template(&profile.vars, config)?;
134-
}
135-
None => return Err(ConfigError::Profile(profile.to_string())),
136-
};
152+
let profile = serde_yml::from_value::<Profile>(mem::take(
153+
config
154+
.get_mut("profiles")
155+
.and_then(|profiles| profiles.get_mut(profile))
156+
.ok_or_else(|| ConfigError::Profile(profile.to_string()))?,
157+
))?;
158+
159+
vars.extend(profile.vars);
160+
}
161+
162+
let mut config = serde_yml::from_value::<Config>(template(&vars, config)?)?;
163+
164+
if config.tag_format.is_empty() {
165+
config.tag_format = subst::substitute(DEFAULT_TAG_FORMAT, &vars)?;
137166
}
138167

139-
Ok(serde_yml::from_value(config)?)
168+
Ok(config)
140169
}

src/tag.rs renamed to src/git.rs

Lines changed: 23 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -1,32 +1,16 @@
11
use std::convert::Infallible;
22

3-
use gix::{Head, Repository, refs::Category};
3+
use gix::{Repository, refs::Category};
44
use miette::Diagnostic;
55

66
#[derive(Debug, Diagnostic, thiserror::Error)]
7-
pub enum TagError {
7+
pub enum GitError {
88
#[error("failed to open git repository")]
99
Open(#[from] gix::open::Error),
1010
#[error("failed to resolve HEAD reference")]
1111
FindRef(#[from] gix::reference::find::existing::Error),
1212
#[error("failed to retrieve dirty status")]
1313
Dirty(#[from] gix::status::is_dirty::Error),
14-
#[error("unable to find tag")]
15-
NotFound,
16-
}
17-
18-
fn parse_name(head: &mut Head<'_>) -> Result<String, TagError> {
19-
if let Some(ref_name) = head.referent_name() {
20-
if let Some((Category::Tag, name)) = ref_name.category_and_short_name() {
21-
return Ok(name.to_string());
22-
}
23-
}
24-
25-
if let Ok(commit) = head.peel_to_commit_in_place() {
26-
return Ok(commit.id.to_hex_with_len(6).to_string());
27-
}
28-
29-
Err(TagError::NotFound)
3014
}
3115

3216
// Copied from gix but takes untracked files into account
@@ -62,14 +46,30 @@ fn is_dirty(repo: &Repository) -> Result<bool, gix::status::is_dirty::Error> {
6246
.is_some())
6347
}
6448

65-
pub async fn resolve() -> Result<String, TagError> {
49+
#[derive(Default)]
50+
pub struct State {
51+
pub dirty: bool,
52+
pub tag: Option<String>,
53+
pub commit: String,
54+
}
55+
56+
pub async fn state() -> Result<State, GitError> {
6657
let repo = gix::open(".")?;
6758
let mut head = repo.head()?;
68-
let name = parse_name(&mut head)?;
59+
let mut state = State {
60+
dirty: is_dirty(&repo)?,
61+
..State::default()
62+
};
6963

70-
if is_dirty(&repo)? {
71-
return Ok(format!("{name}-dirty"));
64+
if let Some(ref_name) = head.referent_name() {
65+
if let Some((Category::Tag, name)) = ref_name.category_and_short_name() {
66+
state.tag = Some(name.to_string());
67+
}
68+
}
69+
70+
if let Ok(commit) = head.peel_to_commit_in_place() {
71+
state.commit = commit.id.to_hex().to_string();
7272
}
7373

74-
Ok(name)
74+
Ok(state)
7575
}

src/main.rs

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -11,10 +11,10 @@ mod cmd;
1111
mod config;
1212
mod deploy;
1313
mod exec;
14+
mod git;
1415
mod image;
1516
mod progress;
1617
mod registry;
17-
mod tag;
1818

1919
#[derive(Parser)]
2020
struct Opts {
@@ -64,7 +64,7 @@ enum Cmd {
6464
Run {
6565
/// OCI registry to use
6666
#[arg(short, long)]
67-
repo: String,
67+
repo: Option<String>,
6868

6969
/// Platform selector (e.g. linux/amd64)
7070
#[arg(long)]
@@ -117,6 +117,8 @@ enum AppError {
117117
SetCurrentDir(std::io::Error),
118118
#[error("failed to create temp file")]
119119
TempFile(#[from] async_tempfile::Error),
120+
#[error("no repository specified, either set in config or pass via --repo")]
121+
RepoRequired,
120122
}
121123

122124
impl From<cmd::build::Error> for AppError {
@@ -144,6 +146,7 @@ async fn run(opts: Opts) -> Result<(), AppError> {
144146
platform,
145147
} => {
146148
let config = config::load_from_path(profile.as_deref(), config_path).await?;
149+
147150
cmd::build::run(
148151
config,
149152
platform.unwrap_or(detected_platform),
@@ -167,10 +170,14 @@ async fn run(opts: Opts) -> Result<(), AppError> {
167170
let dest = TempFile::new().await?;
168171
let config = config::load_from_path(profile.as_deref(), config_path).await?;
169172

173+
if repo.is_none() && config.default_repo.is_none() {
174+
return Err(AppError::RepoRequired);
175+
}
176+
170177
cmd::build::run(
171178
config.clone(),
172179
platform.unwrap_or(detected_platform),
173-
Some(repo),
180+
repo,
174181
Some(dest.file_path()),
175182
)
176183
.await?;

0 commit comments

Comments
 (0)