Skip to content

Commit f4f7407

Browse files
committed
include advanced git checkout as a core feature
1 parent 4fd6499 commit f4f7407

File tree

15 files changed

+379
-7
lines changed

15 files changed

+379
-7
lines changed

examples/circleci_node_simple_split/.cigen/workflows/test/jobs/build.yml renamed to examples/circleci_node_simple_split/.cigen/workflows/main/jobs/build.yml

File renamed without changes.

examples/circleci_node_simple_split/.cigen/workflows/test/jobs/lint.yml renamed to examples/circleci_node_simple_split/.cigen/workflows/main/jobs/lint.yml

File renamed without changes.

examples/circleci_node_simple_split/.cigen/workflows/test/jobs/test.yml renamed to examples/circleci_node_simple_split/.cigen/workflows/main/jobs/test.yml

File renamed without changes.

src/graph/dependency_graph.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -197,6 +197,7 @@ mod tests {
197197
services: None,
198198
packages: None,
199199
steps: None,
200+
checkout: None,
200201
}
201202
}
202203

src/models/config.rs

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -71,6 +71,9 @@ pub struct Config {
7171

7272
#[serde(skip_serializing_if = "Option::is_none")]
7373
pub workflows: Option<HashMap<String, WorkflowConfig>>,
74+
75+
#[serde(skip_serializing_if = "Option::is_none")]
76+
pub checkout: Option<CheckoutConfig>,
7477
}
7578

7679
#[derive(Debug, Clone, Serialize, Deserialize)]
@@ -210,6 +213,7 @@ impl Default for Config {
210213
docker_images: None,
211214
package_managers: None,
212215
workflows: None,
216+
checkout: None,
213217
}
214218
}
215219
}
@@ -299,10 +303,38 @@ fn default_parse_version() -> bool {
299303
true
300304
}
301305

306+
fn default_shallow_checkout() -> bool {
307+
true // Default to shallow checkout for 99% of use cases
308+
}
309+
302310
#[derive(Debug, Clone, Serialize, Deserialize)]
303311
pub struct WorkflowConfig {
304312
#[serde(skip_serializing_if = "Option::is_none")]
305313
pub jobs: Option<HashMap<String, super::job::Job>>,
314+
315+
#[serde(skip_serializing_if = "Option::is_none")]
316+
pub checkout: Option<CheckoutConfig>,
317+
}
318+
319+
#[derive(Debug, Clone, Serialize, Deserialize)]
320+
pub struct CheckoutConfig {
321+
#[serde(default = "default_shallow_checkout")]
322+
pub shallow: bool,
323+
324+
#[serde(skip_serializing_if = "Option::is_none")]
325+
pub clone_options: Option<String>,
326+
327+
#[serde(skip_serializing_if = "Option::is_none")]
328+
pub fetch_options: Option<String>,
329+
330+
#[serde(skip_serializing_if = "Option::is_none")]
331+
pub tag_fetch_options: Option<String>,
332+
333+
#[serde(skip_serializing_if = "Option::is_none")]
334+
pub keyscan: Option<HashMap<String, bool>>,
335+
336+
#[serde(skip_serializing_if = "Option::is_none")]
337+
pub path: Option<String>,
306338
}
307339

308340
#[derive(Debug, Clone, Serialize, Deserialize)]

src/models/job.rs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -58,6 +58,9 @@ pub struct Job {
5858

5959
#[serde(skip_serializing_if = "Option::is_none")]
6060
pub steps: Option<Vec<Step>>,
61+
62+
#[serde(skip_serializing_if = "Option::is_none")]
63+
pub checkout: Option<super::config::CheckoutConfig>,
6164
}
6265

6366
/// Intermediate parsing structure for cache definitions that handles multiple YAML formats

src/models/mod.rs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -6,9 +6,9 @@ pub mod package_managers;
66
// Re-export commonly used types
77
pub use command::Command;
88
pub use config::{
9-
Cache, CacheBackend, CacheBackendConfig, CacheDefinition, Config, DefaultCacheConfig,
10-
DockerAuth, DockerConfig, DockerImageConfig, OutputConfig, ParameterConfig, PathOrDetect,
11-
Service, VersionSource, WorkflowConfig,
9+
Cache, CacheBackend, CacheBackendConfig, CacheDefinition, CheckoutConfig, Config,
10+
DefaultCacheConfig, DockerAuth, DockerConfig, DockerImageConfig, OutputConfig, ParameterConfig,
11+
PathOrDetect, Service, VersionSource, WorkflowConfig,
1212
};
1313
pub use job::Job;
1414
pub use package_managers::{

src/packages/deduplicator.rs

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -89,6 +89,7 @@ impl PackageDeduplicator {
8989
cache: None,
9090
restore_cache: None,
9191
services: None,
92+
checkout: None,
9293
};
9394

9495
// Add installation steps (checkout will be added automatically by CircleCI provider)
@@ -202,6 +203,7 @@ mod tests {
202203
cache: None,
203204
restore_cache: None,
204205
services: None,
206+
checkout: None,
205207
},
206208
);
207209

@@ -219,6 +221,7 @@ mod tests {
219221
cache: None,
220222
restore_cache: None,
221223
services: None,
224+
checkout: None,
222225
},
223226
);
224227

@@ -236,6 +239,7 @@ mod tests {
236239
cache: None,
237240
restore_cache: None,
238241
services: None,
242+
checkout: None,
239243
},
240244
);
241245

@@ -321,6 +325,7 @@ mod tests {
321325
cache: None,
322326
restore_cache: None,
323327
services: None,
328+
checkout: None,
324329
},
325330
)]);
326331

src/providers/circleci/config.rs

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -111,6 +111,14 @@ pub enum CircleCIParameter {
111111
#[serde(skip_serializing_if = "Option::is_none")]
112112
description: Option<String>,
113113
},
114+
Object {
115+
#[serde(rename = "type")]
116+
param_type: String,
117+
#[serde(skip_serializing_if = "Option::is_none")]
118+
default: Option<serde_json::Value>,
119+
#[serde(skip_serializing_if = "Option::is_none")]
120+
description: Option<String>,
121+
},
114122
}
115123

116124
#[derive(Debug, Clone, Serialize, Deserialize)]
@@ -180,7 +188,10 @@ impl CircleCIStep {
180188

181189
fn detect_step_type(value: &serde_yaml::Value) -> Option<String> {
182190
match value {
183-
serde_yaml::Value::String(_) => Some("run".to_string()),
191+
serde_yaml::Value::String(s) => {
192+
// Simple strings can be commands like 'checkout', 'shallow_checkout', etc.
193+
Some(s.clone())
194+
}
184195
serde_yaml::Value::Mapping(map) => {
185196
if map.len() == 1
186197
&& let Some((key, _)) = map.iter().next()

src/providers/circleci/generator.rs

Lines changed: 117 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -355,8 +355,8 @@ impl CircleCIGenerator {
355355

356356
circleci_job.docker = Some(docker_images);
357357

358-
// Add checkout step as the first step (standard CircleCI practice)
359-
let checkout_step = serde_yaml::Value::String("checkout".to_string());
358+
// Add checkout step based on configuration hierarchy
359+
let checkout_step = self.resolve_checkout_step(config, None, job)?;
360360
circleci_job.steps.push(CircleCIStep::new(checkout_step));
361361

362362
// Add skip logic if job has source_files defined (job-status cache)
@@ -1182,4 +1182,119 @@ echo "$(date): Job completed successfully" > "/tmp/cigen_skip_cache/job_${{JOB_H
11821182

11831183
Ok(())
11841184
}
1185+
1186+
/// Resolve checkout configuration based on hierarchy (job > workflow > global > default)
1187+
fn resolve_checkout_step(
1188+
&self,
1189+
config: &Config,
1190+
workflow_config: Option<&crate::models::config::WorkflowConfig>,
1191+
job: &Job,
1192+
) -> Result<serde_yaml::Value> {
1193+
use crate::models::config::CheckoutConfig;
1194+
1195+
// Resolve checkout config with hierarchy: job > workflow > global > default
1196+
let checkout_config = job
1197+
.checkout
1198+
.as_ref()
1199+
.or_else(|| workflow_config?.checkout.as_ref())
1200+
.or(config.checkout.as_ref())
1201+
.cloned()
1202+
.unwrap_or(CheckoutConfig {
1203+
shallow: true, // Default to shallow checkout
1204+
clone_options: None,
1205+
fetch_options: None,
1206+
tag_fetch_options: None,
1207+
keyscan: None,
1208+
path: None,
1209+
});
1210+
1211+
if checkout_config.shallow {
1212+
// Use our vendored shallow checkout command
1213+
let mut shallow_checkout = serde_yaml::Mapping::new();
1214+
1215+
// Add parameters if specified
1216+
if let Some(clone_options) = &checkout_config.clone_options {
1217+
shallow_checkout.insert(
1218+
serde_yaml::Value::String("clone_options".to_string()),
1219+
serde_yaml::Value::String(clone_options.clone()),
1220+
);
1221+
}
1222+
if let Some(fetch_options) = &checkout_config.fetch_options {
1223+
shallow_checkout.insert(
1224+
serde_yaml::Value::String("fetch_options".to_string()),
1225+
serde_yaml::Value::String(fetch_options.clone()),
1226+
);
1227+
}
1228+
if let Some(tag_fetch_options) = &checkout_config.tag_fetch_options {
1229+
shallow_checkout.insert(
1230+
serde_yaml::Value::String("tag_fetch_options".to_string()),
1231+
serde_yaml::Value::String(tag_fetch_options.clone()),
1232+
);
1233+
}
1234+
if let Some(keyscan) = &checkout_config.keyscan {
1235+
if keyscan.get("github").unwrap_or(&false) == &true {
1236+
shallow_checkout.insert(
1237+
serde_yaml::Value::String("keyscan_github".to_string()),
1238+
serde_yaml::Value::Bool(true),
1239+
);
1240+
}
1241+
if keyscan.get("gitlab").unwrap_or(&false) == &true {
1242+
shallow_checkout.insert(
1243+
serde_yaml::Value::String("keyscan_gitlab".to_string()),
1244+
serde_yaml::Value::Bool(true),
1245+
);
1246+
}
1247+
if keyscan.get("bitbucket").unwrap_or(&false) == &true {
1248+
shallow_checkout.insert(
1249+
serde_yaml::Value::String("keyscan_bitbucket".to_string()),
1250+
serde_yaml::Value::Bool(true),
1251+
);
1252+
}
1253+
}
1254+
if let Some(path) = &checkout_config.path {
1255+
shallow_checkout.insert(
1256+
serde_yaml::Value::String("path".to_string()),
1257+
serde_yaml::Value::String(path.clone()),
1258+
);
1259+
}
1260+
1261+
if shallow_checkout.is_empty() {
1262+
// Simple command with no parameters
1263+
Ok(serde_yaml::Value::String("shallow_checkout".to_string()))
1264+
} else {
1265+
// Command with parameters
1266+
let mut shallow_step = serde_yaml::Mapping::new();
1267+
shallow_step.insert(
1268+
serde_yaml::Value::String("shallow_checkout".to_string()),
1269+
serde_yaml::Value::Mapping(shallow_checkout),
1270+
);
1271+
Ok(serde_yaml::Value::Mapping(shallow_step))
1272+
}
1273+
} else {
1274+
// Use standard CircleCI checkout
1275+
let mut checkout_step = serde_yaml::Mapping::new();
1276+
let mut checkout_params = serde_yaml::Mapping::new();
1277+
1278+
if let Some(path) = &checkout_config.path {
1279+
checkout_params.insert(
1280+
serde_yaml::Value::String("path".to_string()),
1281+
serde_yaml::Value::String(path.clone()),
1282+
);
1283+
}
1284+
1285+
if checkout_params.is_empty() {
1286+
checkout_step.insert(
1287+
serde_yaml::Value::String("checkout".to_string()),
1288+
serde_yaml::Value::Null,
1289+
);
1290+
} else {
1291+
checkout_step.insert(
1292+
serde_yaml::Value::String("checkout".to_string()),
1293+
serde_yaml::Value::Mapping(checkout_params),
1294+
);
1295+
}
1296+
1297+
Ok(serde_yaml::Value::Mapping(checkout_step))
1298+
}
1299+
}
11851300
}

0 commit comments

Comments
 (0)