diff --git a/.github/workflows/sync-common.yml b/.github/workflows/sync-common.yml index c3cca72..7b83091 100644 --- a/.github/workflows/sync-common.yml +++ b/.github/workflows/sync-common.yml @@ -110,10 +110,22 @@ jobs: name: sync-common path: . + - name: Determine exclusions + id: exclusions + run: | + case "${{ matrix.full_name }}" in + bootc-dev/bink|bootc-dev/bootc-operator) + echo "args=--exclude .devcontainer/" >> "$GITHUB_OUTPUT" + ;; + *) + echo "args=" >> "$GITHUB_OUTPUT" + ;; + esac + - name: Sync common files to repository run: | chmod +x sync-common - ./sync-common infra repo "${{ github.sha }}" + ./sync-common infra repo "${{ github.sha }}" ${{ steps.exclusions.outputs.args }} - name: Open pull request uses: peter-evans/create-pull-request@v8 diff --git a/scripts/sync-common/Cargo.lock b/scripts/sync-common/Cargo.lock index 09d5ec3..373e87b 100644 --- a/scripts/sync-common/Cargo.lock +++ b/scripts/sync-common/Cargo.lock @@ -2,6 +2,56 @@ # It is not intended for manual editing. version = 4 +[[package]] +name = "anstream" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "824a212faf96e9acacdbd09febd34438f8f711fb84e09a8916013cd7815ca28d" +dependencies = [ + "anstyle", + "anstyle-parse", + "anstyle-query", + "anstyle-wincon", + "colorchoice", + "is_terminal_polyfill", + "utf8parse", +] + +[[package]] +name = "anstyle" +version = "1.0.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "940b3a0ca603d1eade50a4846a2afffd5ef57a9feac2c0e2ec2e14f9ead76000" + +[[package]] +name = "anstyle-parse" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "52ce7f38b242319f7cabaa6813055467063ecdc9d355bbb4ce0c68908cd8130e" +dependencies = [ + "utf8parse", +] + +[[package]] +name = "anstyle-query" +version = "1.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "40c48f72fd53cd289104fc64099abca73db4166ad86ea0b4341abe65af83dadc" +dependencies = [ + "windows-sys", +] + +[[package]] +name = "anstyle-wincon" +version = "3.0.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "291e6a250ff86cd4a820112fb8898808a366d8f9f58ce16d1f538353ad55747d" +dependencies = [ + "anstyle", + "once_cell_polyfill", + "windows-sys", +] + [[package]] name = "anyhow" version = "1.0.103" @@ -20,6 +70,52 @@ version = "1.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801" +[[package]] +name = "clap" +version = "4.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1ddb117e43bbf7dacf0a4190fef4d345b9bad68dfc649cb349e7d17d28428e51" +dependencies = [ + "clap_builder", + "clap_derive", +] + +[[package]] +name = "clap_builder" +version = "4.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "714a53001bf66416adb0e2ef5ac857140e7dc3a0c48fb28b2f10762fc4b5069f" +dependencies = [ + "anstream", + "anstyle", + "clap_lex", + "strsim", +] + +[[package]] +name = "clap_derive" +version = "4.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f2ce8604710f6733aa641a2b3731eaa1e8b3d9973d5e3565da11800813f997a9" +dependencies = [ + "heck", + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "clap_lex" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c8d4a3bb8b1e0c1050499d1815f5ab16d04f0959b233085fb31653fbfc9d98f9" + +[[package]] +name = "colorchoice" +version = "1.0.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1d07550c9036bf2ae0c684c4297d503f838287c83c53686d05370d0e139ae570" + [[package]] name = "errno" version = "0.3.14" @@ -48,6 +144,18 @@ dependencies = [ "wasip2", ] +[[package]] +name = "heck" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" + +[[package]] +name = "is_terminal_polyfill" +version = "1.70.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a6cb138bb79a146c1bd460005623e142ef0181e3d0219cb493e02f7d08a35695" + [[package]] name = "libc" version = "0.2.186" @@ -66,6 +174,30 @@ version = "1.21.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "42f5e15c9953c5e4ccceeb2e7382a716482c34515315f7b03532b8b4e8393d2d" +[[package]] +name = "once_cell_polyfill" +version = "1.70.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "384b8ab6d37215f3c5301a95a4accb5d64aa607f1fcb26a11b5303878451b4fe" + +[[package]] +name = "proc-macro2" +version = "1.0.106" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8fd00f0bb2e90d81d1044c2b32617f68fcb9fa3bb7640c23e9c748e53fb30934" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "quote" +version = "1.0.46" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dfbc457d0c7a0759a614551b11a6409e5951f6c7537be1f1b7682b9ae9230368" +dependencies = [ + "proc-macro2", +] + [[package]] name = "r-efi" version = "5.3.0" @@ -85,11 +217,29 @@ dependencies = [ "windows-sys", ] +[[package]] +name = "strsim" +version = "0.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f" + +[[package]] +name = "syn" +version = "2.0.118" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1b9ae57f904213ebb649ce6895b8a66c66f0203b9319718f69a5612a065b1422" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + [[package]] name = "sync-common" version = "0.1.0" dependencies = [ "anyhow", + "clap", "tempfile", "xshell", ] @@ -107,6 +257,18 @@ dependencies = [ "windows-sys", ] +[[package]] +name = "unicode-ident" +version = "1.0.24" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6e4313cd5fcd3dad5cafa179702e2b244f760991f45397d14d4ebf38247da75" + +[[package]] +name = "utf8parse" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821" + [[package]] name = "wasip2" version = "1.0.1+wasi-0.2.4" diff --git a/scripts/sync-common/Cargo.toml b/scripts/sync-common/Cargo.toml index 99b1c04..e524a3d 100644 --- a/scripts/sync-common/Cargo.toml +++ b/scripts/sync-common/Cargo.toml @@ -5,6 +5,7 @@ edition = "2021" [dependencies] anyhow = "1.0" +clap = { version = "4", features = ["derive"] } xshell = "0.2" [dev-dependencies] diff --git a/scripts/sync-common/src/main.rs b/scripts/sync-common/src/main.rs index 0bf4945..ee7ebad 100644 --- a/scripts/sync-common/src/main.rs +++ b/scripts/sync-common/src/main.rs @@ -1,7 +1,22 @@ use anyhow::{Context, Result}; +use clap::Parser; use std::path::{Path, PathBuf}; use xshell::{cmd, Shell}; +#[derive(Parser)] +#[command(about = "Sync common files from infra to target repositories")] +struct Args { + /// Path to the infra repository + infra_path: PathBuf, + /// Path to the target repository + target_path: PathBuf, + /// Git commit SHA to mark as synced + current_commit: String, + /// Patterns to exclude from syncing (passed to rsync --exclude) + #[arg(long)] + exclude: Vec, +} + const COMMIT_MARKER: &str = ".bootc-dev-infra-commit.txt"; /// Git operations for querying repository history @@ -84,16 +99,25 @@ impl FileOps { } /// Sync directory using rsync - fn sync_directory(sh: &Shell, source: &Path, target: &Path) -> Result<()> { + fn sync_directory(sh: &Shell, source: &Path, target: &Path, excludes: &[String]) -> Result<()> { let source_str = format!("{}/", source.display()); let target_str = target.display().to_string(); - cmd!(sh, "rsync -av {source_str} {target_str}") + let mut rsync_args = vec!["-av".to_string()]; + for pattern in excludes { + rsync_args.push("--exclude".to_string()); + rsync_args.push(pattern.clone()); + } + rsync_args.push(source_str); + rsync_args.push(target_str); + + cmd!(sh, "rsync {rsync_args...}") .run() .context("Failed to sync directory with rsync")?; Ok(()) } + } /// Main syncer that orchestrates the sync process @@ -105,6 +129,7 @@ impl CommonFileSyncer { infra_path: &Path, target_path: &Path, current_commit: &str, + excludes: &[String], ) -> Result { let common_path = infra_path.join("common"); if !common_path.exists() { @@ -120,8 +145,9 @@ impl CommonFileSyncer { &common_path, &prev, current_commit, + excludes, ), - None => Self::sync_initial(target_path, &common_path, current_commit), + None => Self::sync_initial(target_path, &common_path, current_commit, excludes), } } @@ -132,6 +158,7 @@ impl CommonFileSyncer { common_path: &Path, previous_commit: &str, current_commit: &str, + excludes: &[String], ) -> Result { println!("Previous sync: {}", previous_commit); println!("Current commit: {}", current_commit); @@ -160,7 +187,7 @@ impl CommonFileSyncer { } // Sync all current files - FileOps::sync_directory(&sh, common_path, target_path)?; + FileOps::sync_directory(&sh, common_path, target_path, excludes)?; // Update commit marker FileOps::write_commit_marker(target_path, current_commit)?; @@ -173,11 +200,12 @@ impl CommonFileSyncer { target_path: &Path, common_path: &Path, current_commit: &str, + excludes: &[String], ) -> Result { println!("First sync - copying all files"); let sh = Shell::new()?; - FileOps::sync_directory(&sh, common_path, target_path)?; + FileOps::sync_directory(&sh, common_path, target_path, excludes)?; FileOps::write_commit_marker(target_path, current_commit)?; Ok(true) @@ -185,18 +213,14 @@ impl CommonFileSyncer { } fn main() -> Result<()> { - let args: Vec = std::env::args().collect(); - - if args.len() != 4 { - eprintln!("Usage: {} ", args[0]); - std::process::exit(1); - } + let args = Args::parse(); - let infra_path = PathBuf::from(&args[1]); - let target_path = PathBuf::from(&args[2]); - let current_commit = &args[3]; - - CommonFileSyncer::sync(&infra_path, &target_path, current_commit)?; + CommonFileSyncer::sync( + &args.infra_path, + &args.target_path, + &args.current_commit, + &args.exclude, + )?; Ok(()) } @@ -262,6 +286,7 @@ mod tests { infra_dir.path(), target_dir.path(), "abc123", + &[], ); assert!(result.is_err()); @@ -304,6 +329,7 @@ mod tests { infra_dir.path(), target_dir.path(), &commit, + &[], ); assert!(result.is_ok()); @@ -330,7 +356,7 @@ mod tests { let initial_commit = setup_infra_repo(infra_dir.path()); // Initial sync - CommonFileSyncer::sync(infra_dir.path(), target_dir.path(), &initial_commit).unwrap(); + CommonFileSyncer::sync(infra_dir.path(), target_dir.path(), &initial_commit, &[]).unwrap(); // Add a new file to common/ let sh = Shell::new().unwrap(); @@ -348,6 +374,7 @@ mod tests { infra_dir.path(), target_dir.path(), &new_commit, + &[], ); assert!(result.is_ok()); @@ -369,7 +396,7 @@ mod tests { let initial_commit = setup_infra_repo(infra_dir.path()); // Initial sync - CommonFileSyncer::sync(infra_dir.path(), target_dir.path(), &initial_commit).unwrap(); + CommonFileSyncer::sync(infra_dir.path(), target_dir.path(), &initial_commit, &[]).unwrap(); // Verify file2.txt exists assert!(target_dir.path().join("file2.txt").exists()); @@ -389,6 +416,7 @@ mod tests { infra_dir.path(), target_dir.path(), &new_commit, + &[], ); assert!(result.is_ok()); @@ -408,13 +436,14 @@ mod tests { let commit = setup_infra_repo(infra_dir.path()); // Initial sync - CommonFileSyncer::sync(infra_dir.path(), target_dir.path(), &commit).unwrap(); + CommonFileSyncer::sync(infra_dir.path(), target_dir.path(), &commit, &[]).unwrap(); // Sync again with same commit (no changes) let result = CommonFileSyncer::sync( infra_dir.path(), target_dir.path(), &commit, + &[], ); assert!(result.is_ok()); @@ -432,7 +461,7 @@ mod tests { fs::write(target_dir.path().join("repo-specific.txt"), "local content").unwrap(); // Initial sync - CommonFileSyncer::sync(infra_dir.path(), target_dir.path(), &initial_commit).unwrap(); + CommonFileSyncer::sync(infra_dir.path(), target_dir.path(), &initial_commit, &[]).unwrap(); // Verify repo-specific file still exists assert!(target_dir.path().join("repo-specific.txt").exists()); @@ -445,4 +474,30 @@ mod tests { assert!(target_dir.path().join("file1.txt").exists()); assert!(target_dir.path().join("file2.txt").exists()); } + + #[test] + fn test_initial_sync_with_exclusions() { + let infra_dir = TempDir::new().unwrap(); + let target_dir = TempDir::new().unwrap(); + + let commit = setup_infra_repo(infra_dir.path()); + + let excluded = infra_dir.path().join("common/excluded"); + fs::create_dir(&excluded).unwrap(); + fs::write(excluded.join("skip.txt"), "should be excluded").unwrap(); + + let sh = Shell::new().unwrap(); + let _d = sh.push_dir(infra_dir.path()); + cmd!(sh, "git add .").run().unwrap(); + cmd!(sh, "git commit -m 'Add excluded dir'").run().unwrap(); + let commit = cmd!(sh, "git rev-parse HEAD").read().unwrap().trim().to_string(); + + let excludes = vec!["excluded/".to_string()]; + CommonFileSyncer::sync(infra_dir.path(), target_dir.path(), &commit, &excludes).unwrap(); + + assert!(!target_dir.path().join("excluded").exists()); + assert!(target_dir.path().join("file1.txt").exists()); + assert!(target_dir.path().join("file2.txt").exists()); + } + }