Add verify-canfetch flag and git2 integration for repo accessibility checks
``` │ Agent powering down. Goodbye! │ │ Interaction Summary │ Session ID: 9010bd84-1fb3-489d-ae48-89d5e7570b34 │ Tool Calls: 26 ( ✓ 25 x 1 ) │ Success Rate: 96.2% │ User Agreement: 96.2% (26 reviewed) │ Code Changes: +116 -23 │ │ Performance │ Wall Time: 59m 38s │ Agent Active: 9m 53s │ » API Time: 4m 22s (44.1%) │ » Tool Time: 5m 31s (55.9%) │ │ │ Model Usage Reqs Input Tokens Cache Reads Output Tokens │ ──────────────────────────────────────────────────────────────────────────── │ gemini-2.5-flash-lite 6 10,569 0 596 │ gemini-3-pro-preview 23 114,081 377,312 10,537 │ gemini-3-flash-preview 7 88,472 83,196 436 │ │ Savings Highlight: 460,508 (68.4%) of input tokens were served from the cache, reducing costs. ```
This commit is contained in:
88
Cargo.lock
generated
88
Cargo.lock
generated
@@ -360,11 +360,27 @@ dependencies = [
|
||||
"wasm-bindgen",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "git2"
|
||||
version = "0.19.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b903b73e45dc0c6c596f2d37eccece7c1c8bb6e4407b001096387c63d0d93724"
|
||||
dependencies = [
|
||||
"bitflags",
|
||||
"libc",
|
||||
"libgit2-sys",
|
||||
"log",
|
||||
"openssl-probe 0.1.6",
|
||||
"openssl-sys",
|
||||
"url",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "gitea_mirror"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"clap",
|
||||
"git2",
|
||||
"reqwest",
|
||||
"serde",
|
||||
"serde_json",
|
||||
@@ -702,6 +718,46 @@ version = "0.2.179"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c5a2d376baa530d1238d133232d15e239abad80d05838b4b59354e5268af431f"
|
||||
|
||||
[[package]]
|
||||
name = "libgit2-sys"
|
||||
version = "0.17.0+1.8.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "10472326a8a6477c3c20a64547b0059e4b0d086869eee31e6d7da728a8eb7224"
|
||||
dependencies = [
|
||||
"cc",
|
||||
"libc",
|
||||
"libssh2-sys",
|
||||
"libz-sys",
|
||||
"openssl-sys",
|
||||
"pkg-config",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "libssh2-sys"
|
||||
version = "0.3.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "220e4f05ad4a218192533b300327f5150e809b54c4ec83b5a1d91833601811b9"
|
||||
dependencies = [
|
||||
"cc",
|
||||
"libc",
|
||||
"libz-sys",
|
||||
"openssl-sys",
|
||||
"pkg-config",
|
||||
"vcpkg",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "libz-sys"
|
||||
version = "1.1.23"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "15d118bbf3771060e7311cc7bb0545b01d08a8b4a7de949198dec1fa0ca1c0f7"
|
||||
dependencies = [
|
||||
"cc",
|
||||
"libc",
|
||||
"pkg-config",
|
||||
"vcpkg",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "litemap"
|
||||
version = "0.8.1"
|
||||
@@ -773,12 +829,30 @@ version = "1.70.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "384b8ab6d37215f3c5301a95a4accb5d64aa607f1fcb26a11b5303878451b4fe"
|
||||
|
||||
[[package]]
|
||||
name = "openssl-probe"
|
||||
version = "0.1.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d05e27ee213611ffe7d6348b942e8f942b37114c00cc03cec254295a4a17852e"
|
||||
|
||||
[[package]]
|
||||
name = "openssl-probe"
|
||||
version = "0.2.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9f50d9b3dabb09ecd771ad0aa242ca6894994c130308ca3d7684634df8037391"
|
||||
|
||||
[[package]]
|
||||
name = "openssl-sys"
|
||||
version = "0.9.111"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "82cab2d520aa75e3c58898289429321eb788c3106963d0dc886ec7a5f4adc321"
|
||||
dependencies = [
|
||||
"cc",
|
||||
"libc",
|
||||
"pkg-config",
|
||||
"vcpkg",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "parking_lot"
|
||||
version = "0.12.5"
|
||||
@@ -820,6 +894,12 @@ version = "0.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184"
|
||||
|
||||
[[package]]
|
||||
name = "pkg-config"
|
||||
version = "0.3.32"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7edddbd0b52d732b21ad9a5fab5c704c14cd949e5e9a1ec5929a24fded1b904c"
|
||||
|
||||
[[package]]
|
||||
name = "potential_utf"
|
||||
version = "0.1.4"
|
||||
@@ -1037,7 +1117,7 @@ version = "0.8.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "612460d5f7bea540c490b2b6395d8e34a953e52b491accd6c86c8164c5932a63"
|
||||
dependencies = [
|
||||
"openssl-probe",
|
||||
"openssl-probe 0.2.0",
|
||||
"rustls-pki-types",
|
||||
"schannel",
|
||||
"security-framework",
|
||||
@@ -1646,6 +1726,12 @@ version = "0.1.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ba73ea9cf16a25df0c8caa16c51acb937d5712a8429db78a3ee29d5dcacd3a65"
|
||||
|
||||
[[package]]
|
||||
name = "vcpkg"
|
||||
version = "0.2.15"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "accd4ea62f7bb7a82fe23066fb0957d48ef677f6eeb8215f372f52e48bb32426"
|
||||
|
||||
[[package]]
|
||||
name = "walkdir"
|
||||
version = "2.5.0"
|
||||
|
||||
@@ -12,3 +12,4 @@ serde_json = "1.0"
|
||||
toml = "0.9"
|
||||
tracing = "0.1"
|
||||
tracing-subscriber = "0.3"
|
||||
git2 = { version = "0.19", features = ["vendored-libgit2"] }
|
||||
|
||||
73
src/main.rs
73
src/main.rs
@@ -5,6 +5,7 @@ use std::fs;
|
||||
use std::io::{self, Write};
|
||||
use std::path::{Path, PathBuf};
|
||||
use tracing::{Level, error, info, instrument, warn};
|
||||
use git2::{Cred, Direction, Remote, RemoteCallbacks};
|
||||
|
||||
#[derive(Parser, Debug)]
|
||||
#[command(name = "gitea-mirror")]
|
||||
@@ -30,6 +31,10 @@ struct Args {
|
||||
/// Do not delete repositories from Gitea.
|
||||
#[clap(long, default_value_t = false)]
|
||||
no_delete: bool,
|
||||
|
||||
/// Verify if repositories are fetchable (checks connectivity and presence of commits).
|
||||
#[clap(long, default_value_t = false)]
|
||||
verify_canfetch: bool,
|
||||
}
|
||||
|
||||
#[derive(Deserialize, Debug, Clone)]
|
||||
@@ -154,6 +159,26 @@ async fn main() -> Result<(), Box<dyn std::error::Error>> {
|
||||
.await?;
|
||||
let existing_set: HashSet<String> = existing_repos.into_iter().collect();
|
||||
|
||||
// --- Verify Existing Repos if requested ---
|
||||
if args.verify_canfetch {
|
||||
info!("Verifying accessibility of existing repositories...");
|
||||
let mut verification_failed = false;
|
||||
for name in &existing_set {
|
||||
let repo_url = format!("{}/{}/{}.git", config.gitea_url, owner_name, name);
|
||||
// Use the API key for auth if needed
|
||||
match verify_repo_accessible(&repo_url, &owner_name, Some(&final_api_key)) {
|
||||
Ok(_) => info!("Verified [OK]: {}", name),
|
||||
Err(e) => {
|
||||
error!("Verified [FAIL]: {} - {}", name, e);
|
||||
verification_failed = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
if verification_failed {
|
||||
return Err("Verification of existing repositories failed. Please investigate the errors above.".into());
|
||||
}
|
||||
}
|
||||
|
||||
// 4. Calculate Diff
|
||||
let mut to_add: Vec<(String, String)> = desired_repos
|
||||
.iter()
|
||||
@@ -237,6 +262,7 @@ async fn main() -> Result<(), Box<dyn std::error::Error>> {
|
||||
}
|
||||
|
||||
// 7. Execute
|
||||
let mut migration_verification_failed = false;
|
||||
// Additions
|
||||
for (name, url) in to_add {
|
||||
info!("Migrating {}...", name);
|
||||
@@ -250,7 +276,20 @@ async fn main() -> Result<(), Box<dyn std::error::Error>> {
|
||||
};
|
||||
|
||||
match create_migration(&http_client, &config.gitea_url, &final_api_key, &payload).await {
|
||||
Ok(_) => info!("Successfully migrated {}", name),
|
||||
Ok(_) => {
|
||||
info!("Successfully migrated {}", name);
|
||||
// Verify after migration if requested
|
||||
if args.verify_canfetch {
|
||||
let repo_url = format!("{}/{}/{}.git", config.gitea_url, owner_name, name);
|
||||
match verify_repo_accessible(&repo_url, &owner_name, Some(&final_api_key)) {
|
||||
Ok(_) => info!("Verified [OK] (Post-Migration): {}", name),
|
||||
Err(e) => {
|
||||
error!("Verified [FAIL] (Post-Migration): {} - {}", name, e);
|
||||
migration_verification_failed = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
Err(e) => error!("Failed to migrate {}: {}", name, e),
|
||||
}
|
||||
}
|
||||
@@ -276,12 +315,44 @@ async fn main() -> Result<(), Box<dyn std::error::Error>> {
|
||||
info!("Skipping deletions due to --no-delete flag.");
|
||||
}
|
||||
|
||||
if migration_verification_failed {
|
||||
return Err("Verification of migrated repositories failed. Please investigate the errors above.".into());
|
||||
}
|
||||
|
||||
info!("Process completed.");
|
||||
Ok(())
|
||||
}
|
||||
|
||||
// --- Helpers ---
|
||||
|
||||
fn verify_repo_accessible(url: &str, username: &str, api_key: Option<&str>) -> Result<(), String> {
|
||||
let mut callbacks = RemoteCallbacks::new();
|
||||
if let Some(key) = api_key {
|
||||
let key = key.to_string();
|
||||
let user = username.to_string();
|
||||
callbacks.credentials(move |_url, _username_from_url, _allowed_types| {
|
||||
Cred::userpass_plaintext(&user, &key)
|
||||
});
|
||||
}
|
||||
|
||||
// Create a detached remote (no local repo needed)
|
||||
let mut remote = Remote::create_detached(url).map_err(|e| format!("Invalid Remote URL: {}", e))?;
|
||||
|
||||
// Attempt to connect and fetch list of refs
|
||||
// connect_auth handles authentication if needed
|
||||
remote.connect_auth(Direction::Fetch, Some(callbacks), None)
|
||||
.map_err(|e| format!("Connection/Auth failed: {}", e))?;
|
||||
|
||||
// List refs to ensure it's a valid git repo and accessible
|
||||
let list = remote.list().map_err(|e| format!("Failed to list refs: {}", e))?;
|
||||
|
||||
if list.is_empty() {
|
||||
return Err("Repository is empty (no refs found)".to_string());
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[instrument(skip(path))]
|
||||
fn load_config(path: &Path) -> Result<Config, Box<dyn std::error::Error>> {
|
||||
let content = fs::read_to_string(path)?;
|
||||
|
||||
21
vibe_coding_log/session_2026_01_14_verify_canfetch.md
Normal file
21
vibe_coding_log/session_2026_01_14_verify_canfetch.md
Normal file
@@ -0,0 +1,21 @@
|
||||
# Session 2026-01-14: Verify CanFetch Flag
|
||||
|
||||
**Date**: 2026-01-14
|
||||
**Model**: Gemini Pro (CLI Agent)
|
||||
**Goal**: Verify migration error detection and add a `--verify_canfetch` flag to check repository accessibility (fetching) without using the `git` executable.
|
||||
|
||||
## Plan
|
||||
1. Add `git2` dependency to `Cargo.toml`.
|
||||
2. Add `--verify_canfetch` flag to `Args`.
|
||||
3. Implement a helper function `verify_repo_accessible(url: &str)` using `git2`.
|
||||
4. Integrate this check into the "Existing Repos" discovery phase and the "Post-Migration" phase.
|
||||
5. Refine error handling to exit on verification failures.
|
||||
|
||||
## Outcome
|
||||
- Added `git2` v0.19 to `Cargo.toml`.
|
||||
- Implemented `verify_repo_accessible` using `git2::Remote::create_detached` and `connect_auth`.
|
||||
- Added `--verify_canfetch` CLI flag.
|
||||
- The tool now optionally verifies that existing repositories and newly migrated ones are reachable and non-empty (contain refs).
|
||||
- Implemented strict error handling for verifications:
|
||||
- If "Existing Repos" verification fails for *any* repo, the tool exits with an error *before* calculating/printing the execution plan.
|
||||
- If "Post-Migration" verification fails for any repo, the tool continues to attempt other migrations but exits with an error at the very end to indicate failure.
|
||||
Reference in New Issue
Block a user