Skip to content

Commit 3baa3e8

Browse files
committed
Add patchable init
1 parent f21136d commit 3baa3e8

File tree

3 files changed

+100
-13
lines changed

3 files changed

+100
-13
lines changed

rust/patchable/README.md

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,3 +37,11 @@ It currently recognizes the following keys:
3737

3838
- `upstream` - the URL of the upstream repository (such as `https://github.com/apache/druid.git`)
3939
- `base` - the commit hash of the upstream base commit (such as `7cffb81a8e124d5f218f9af16ad685acf5e9c67c`)
40+
41+
### Template
42+
43+
Instead of creating this manually, run `patchable init`:
44+
45+
```toml
46+
cargo patchable init druid 28.0.0 --upstream=https://github.com/apache/druid.git --base=druid-28.0.0
47+
```

rust/patchable/src/main.rs

Lines changed: 76 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -5,20 +5,29 @@ mod repo;
55
mod utils;
66

77
use core::str;
8-
use std::path::{Path, PathBuf};
8+
use std::{
9+
fs::File,
10+
io::Write,
11+
path::{Path, PathBuf},
12+
};
913

1014
use git2::Repository;
11-
use serde::Deserialize;
15+
use serde::{Deserialize, Serialize};
1216
use snafu::{OptionExt, ResultExt as _, Snafu};
1317
use tracing_subscriber::{layer::SubscriberExt as _, util::SubscriberInitExt as _};
1418

1519
#[derive(clap::Parser)]
1620
struct ProductVersion {
21+
/// The product name slug (such as druid)
1722
product: String,
23+
24+
/// The product version (such as 28.0.0)
25+
///
26+
/// Should not contain a v prefix.
1827
version: String,
1928
}
2029

21-
#[derive(Deserialize)]
30+
#[derive(Deserialize, Serialize)]
2231
struct ProductVersionConfig {
2332
upstream: String,
2433
base: String,
@@ -101,6 +110,22 @@ enum Cmd {
101110
#[clap(flatten)]
102111
pv: ProductVersion,
103112
},
113+
114+
/// Creates a patchable.toml for a given product version
115+
Init {
116+
#[clap(flatten)]
117+
pv: ProductVersion,
118+
119+
/// The upstream URL (such as https://github.com/apache/druid.git)
120+
#[clap(long)]
121+
upstream: String,
122+
123+
/// The upstream commit-ish (such as druid-28.0.0) that the patch series applies to
124+
///
125+
/// Refs (such as tags and branches) will be resolved to commit IDs.
126+
#[clap(long)]
127+
base: String,
128+
},
104129
}
105130

106131
#[derive(Debug, Snafu)]
@@ -118,6 +143,18 @@ pub enum Error {
118143
source: toml::de::Error,
119144
path: PathBuf,
120145
},
146+
#[snafu(display("failed to serialize config"))]
147+
SerializeConfig { source: toml::ser::Error },
148+
#[snafu(display("failed to create patch dir at {path:?}"))]
149+
CreatePatchDir {
150+
source: std::io::Error,
151+
path: PathBuf,
152+
},
153+
#[snafu(display("failed to save config to {path:?}"))]
154+
SaveConfig {
155+
source: std::io::Error,
156+
path: PathBuf,
157+
},
121158

122159
#[snafu(display("failed to find images repository"))]
123160
FindImagesRepo { source: git2::Error },
@@ -208,7 +245,7 @@ fn main() -> Result<()> {
208245
.context(OpenProductRepoForCheckoutSnafu)?;
209246

210247
let base_commit =
211-
repo::ensure_commit_exists_or_fetch(&product_repo, &config.base, &config.upstream)
248+
repo::resolve_commitish_or_fetch(&product_repo, &config.base, &config.upstream)
212249
.context(FetchBaseCommitSnafu)?;
213250
let patched_commit = patch::apply_patches(&product_repo, &ctx.patch_dir(), base_commit)
214251
.context(ApplyPatchesSnafu)?;
@@ -228,6 +265,7 @@ fn main() -> Result<()> {
228265
"worktree is ready!"
229266
);
230267
}
268+
231269
Cmd::Export { pv } => {
232270
let ctx = ProductVersionContext {
233271
pv,
@@ -288,6 +326,40 @@ fn main() -> Result<()> {
288326
"worktree is exported!"
289327
);
290328
}
329+
330+
Cmd::Init { pv, upstream, base } => {
331+
let ctx = ProductVersionContext {
332+
pv,
333+
images_repo_root,
334+
};
335+
336+
let product_repo_root = ctx.repo();
337+
let product_repo = tracing::info_span!(
338+
"finding product repository",
339+
product.repository = ?product_repo_root,
340+
)
341+
.in_scope(|| repo::ensure_bare_repo(&product_repo_root))
342+
.context(OpenProductRepoForCheckoutSnafu)?;
343+
344+
tracing::info!(?base, "resolving base commit-ish");
345+
let base_commit = repo::resolve_commitish_or_fetch(&product_repo, &base, &upstream)
346+
.context(FetchBaseCommitSnafu)?;
347+
tracing::info!(?base, base.commit = ?base_commit, "resolved base commit");
348+
349+
let config = ProductVersionConfig {
350+
upstream,
351+
base: base.to_string(),
352+
};
353+
let config_path = ctx.config_path();
354+
if let Some(config_dir) = config_path.parent() {
355+
std::fs::create_dir_all(config_dir)
356+
.context(CreatePatchDirSnafu { path: config_dir })?;
357+
}
358+
let config_toml = toml::to_string_pretty(&config).context(SerializeConfigSnafu)?;
359+
File::create_new(&config_path)
360+
.and_then(|mut f| f.write_all(config_toml.as_bytes()))
361+
.context(SaveConfigSnafu { path: config_path })?;
362+
}
291363
}
292364

293365
Ok(())

rust/patchable/src/repo.rs

Lines changed: 16 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -57,7 +57,7 @@ pub enum Error {
5757
target: error::CommitRef,
5858
},
5959

60-
#[snafu(display("failed to find {commit} in {repo}"))]
60+
#[snafu(display("failed to find commit {commit} in {repo}"))]
6161
FindCommit {
6262
source: git2::Error,
6363
repo: error::RepoPath,
@@ -105,14 +105,16 @@ pub fn ensure_bare_repo(path: &Path) -> Result<Repository> {
105105
}
106106
}
107107

108-
/// Fetch `commit` from `upstream_url`, if it doesn't already exist.
108+
/// Try to resolve `commitish` locally. If it doesn't exist, try to fetch it from `upstream_url`.
109+
///
110+
/// Returns the resolved commit ID.
109111
#[tracing::instrument(skip(repo))]
110-
pub fn ensure_commit_exists_or_fetch(
112+
pub fn resolve_commitish_or_fetch(
111113
repo: &Repository,
112-
commit: &str,
114+
commitish: &str,
113115
upstream_url: &str,
114116
) -> Result<Oid> {
115-
let commit = match repo.revparse_single(commit) {
117+
let commit = match repo.revparse_single(commitish) {
116118
Ok(commit_obj) => {
117119
tracing::info!("base commit exists, reusing");
118120
Ok(commit_obj)
@@ -128,9 +130,10 @@ pub fn ensure_commit_exists_or_fetch(
128130
url: upstream_url,
129131
})?
130132
.fetch(
131-
&[commit],
133+
&[commitish],
132134
Some(
133135
FetchOptions::new()
136+
.update_fetchhead(true)
134137
// TODO: could be 1, CLI option maybe?
135138
.depth(0),
136139
),
@@ -139,14 +142,18 @@ pub fn ensure_commit_exists_or_fetch(
139142
.with_context(|_| FetchSnafu {
140143
repo,
141144
url: upstream_url,
142-
refs: vec![commit.to_string()],
145+
refs: vec![commitish.to_string()],
143146
})?;
144147
tracing::info!("fetched base commit");
145-
repo.revparse_single(commit)
148+
// FETCH_HEAD is written by Remote::fetch to be the last reference fetched
149+
repo.revparse_single("FETCH_HEAD")
146150
}
147151
Err(err) => Err(err),
148152
}
149-
.context(FindCommitSnafu { repo, commit })?;
153+
.context(FindCommitSnafu {
154+
repo,
155+
commit: commitish,
156+
})?;
150157
Ok(commit
151158
.peel_to_commit()
152159
.context(FindCommitSnafu { repo, commit })?

0 commit comments

Comments
 (0)