3 Commits
v0.6 ... v0.7

Author SHA1 Message Date
568e5ece49 Add case-insensitive duplicate detection for repos
All checks were successful
Cargo Build & Test / Rust project - latest (1.90) (push) Successful in 5m25s
```
│  Agent powering down. Goodbye!
│
│  Interaction Summary
│  Session ID:                 4fe7dbe6-ab78-49d0-9073-f4ed5ee0afb3
│  Tool Calls:                 21 ( ✓ 20 x 1 )
│  Success Rate:               95.2%
│  User Agreement:             95.2% (21 reviewed)
│  Code Changes:               +73 -2
│
│  Performance
│  Wall Time:                  17m 8s
│  Agent Active:               4m 23s
│    » API Time:               2m 35s (59.2%)
│    » Tool Time:              1m 47s (40.8%)
│
│
│  Model Usage                 Reqs   Input Tokens   Cache Reads  Output Tokens
│  ────────────────────────────────────────────────────────────────────────────
│  gemini-2.5-flash-lite          4          6,360             0            261
│  gemini-3-pro-preview          22         80,208       264,075          4,529
│
│  Savings Highlight: 264,075 (75.3%) of input tokens were served from the cache, reducing costs.
```
2026-01-10 14:40:40 -05:00
4c2086e2b4 Add --version flag showing git tag and sha
```
│  Agent powering down. Goodbye!
│
│  Interaction Summary
│  Session ID:                 66751fe9-fcad-4221-a90a-34d84d303807
│  Tool Calls:                 15 ( ✓ 15 x 0 )
│  Success Rate:               100.0%
│  User Agreement:             100.0% (15 reviewed)
│  Code Changes:               +81 -0
│
│  Performance
│  Wall Time:                  9m 51s
│  Agent Active:               2m 50s
│    » API Time:               1m 40s (58.6%)
│    » Tool Time:              1m 10s (41.4%)
│
│
│  Model Usage                 Reqs   Input Tokens   Cache Reads  Output Tokens
│  ────────────────────────────────────────────────────────────────────────────
│  gemini-2.5-flash-lite          2          2,635             0            199
│  gemini-3-pro-preview          15         54,232       163,544          1,828
│
│  Savings Highlight: 163,544 (74.2%) of input tokens were served from the cache, reducing costs.
```
2026-01-10 14:22:49 -05:00
f13906d762 Add a GEMINI.md for vibe coding 2026-01-10 14:11:46 -05:00
5 changed files with 143 additions and 0 deletions

11
GEMINI.md Normal file
View File

@@ -0,0 +1,11 @@
# Project: Tool to help handle mirroring of projects to Gitea
## General Instructions
Look at the `vibe_coding_log` directory (especially the `README.md` file) where you will track conversations for audit/archival purposes.
Any changes you do should be as minimal as possible to the underlying code (to make code review easier). Also, follow coding styles that already exist and do not deviate from them.
Always run `cargo check` and `cargo test` and `cargo clippy` to maintain general project quality.
When creating commits, look at past commits and try to follow that style (so no smileys).

42
build.rs Normal file
View File

@@ -0,0 +1,42 @@
use std::process::Command;
fn main() {
// Only re-run if .git state changes
println!("cargo:rerun-if-changed=.git/HEAD");
println!("cargo:rerun-if-changed=.git/refs/tags");
let output = Command::new("git")
.args(["describe", "--tags", "--exact-match"])
.output();
let version = if let Ok(output) = output {
if output.status.success() {
// Exact tag match
String::from_utf8(output.stdout).unwrap().trim().to_string()
} else {
// Not an exact match, construct version string
let tag_output = Command::new("git")
.args(["describe", "--tags", "--abbrev=0"])
.output()
.expect("Failed to execute git describe");
let sha_output = Command::new("git")
.args(["rev-parse", "--short", "HEAD"])
.output()
.expect("Failed to execute git rev-parse");
if tag_output.status.success() && sha_output.status.success() {
let tag = String::from_utf8(tag_output.stdout).unwrap().trim().to_string();
let sha = String::from_utf8(sha_output.stdout).unwrap().trim().to_string();
format!("{}-g{}", tag, sha)
} else {
// Fallback if git fails or no tags
"unknown".to_string()
}
}
} else {
"unknown".to_string()
};
println!("cargo:rustc-env=GIT_VERSION={}", version);
}

View File

@@ -8,6 +8,7 @@ use tracing::{Level, error, info, instrument, warn};
#[derive(Parser, Debug)] #[derive(Parser, Debug)]
#[command(name = "gitea-mirror")] #[command(name = "gitea-mirror")]
#[command(version = env!("GIT_VERSION"))]
#[command(about = "Syncs Git repositories to Gitea based on a TOML config.")] #[command(about = "Syncs Git repositories to Gitea based on a TOML config.")]
struct Args { struct Args {
/// Path to the TOML configuration file. /// Path to the TOML configuration file.
@@ -91,6 +92,8 @@ async fn main() -> Result<(), Box<dyn std::error::Error>> {
// 2. Build 'Desired' State (Map<RepoName, CloneUrl>) // 2. Build 'Desired' State (Map<RepoName, CloneUrl>)
info!("Resolving desired state from configuration..."); info!("Resolving desired state from configuration...");
let mut desired_repos: HashMap<String, String> = HashMap::new(); let mut desired_repos: HashMap<String, String> = HashMap::new();
let mut seen_names: HashSet<String> = HashSet::new();
let mut has_error = false;
// 2a. Static Repos // 2a. Static Repos
if let Some(repos) = &config.repos { if let Some(repos) = &config.repos {
@@ -100,6 +103,18 @@ async fn main() -> Result<(), Box<dyn std::error::Error>> {
.as_deref() .as_deref()
.or_else(|| extract_repo_name(&r.url)) .or_else(|| extract_repo_name(&r.url))
.ok_or_else(|| format!("Invalid URL: {}", r.url))?; .ok_or_else(|| format!("Invalid URL: {}", r.url))?;
let name_lower = name.to_lowercase();
if seen_names.contains(&name_lower) {
warn!(
"Duplicate repository name detected (case-insensitive): '{}'. URL: {}",
name, r.url
);
has_error = true;
continue;
}
seen_names.insert(name_lower);
desired_repos.insert(name.to_string(), r.url.clone()); desired_repos.insert(name.to_string(), r.url.clone());
} }
} }
@@ -112,12 +127,26 @@ async fn main() -> Result<(), Box<dyn std::error::Error>> {
fetch_external_org_repos(&http_client, &org.url, org.api_key.as_deref()).await?; fetch_external_org_repos(&http_client, &org.url, org.api_key.as_deref()).await?;
for url in urls { for url in urls {
if let Some(name) = extract_repo_name(&url) { if let Some(name) = extract_repo_name(&url) {
let name_lower = name.to_lowercase();
if seen_names.contains(&name_lower) {
warn!(
"Duplicate repository name detected (case-insensitive) from organization import: '{}'. URL: {}",
name, url
);
has_error = true;
continue;
}
seen_names.insert(name_lower);
desired_repos.insert(name.to_string(), url); desired_repos.insert(name.to_string(), url);
} }
} }
} }
} }
if has_error {
return Err("Duplicate repository names detected. Please fix the configuration.".into());
}
// 3. Build 'Current' State (Set<RepoName>) // 3. Build 'Current' State (Set<RepoName>)
info!("Fetching existing repositories from Gitea ({})", owner_name); info!("Fetching existing repositories from Gitea ({})", owner_name);
let existing_repos = let existing_repos =

View File

@@ -0,0 +1,23 @@
# Session 2026-01-10: Duplicate Repository Detection
* **Date**: 2026-01-10
* **Model**: Gemini 2.5 Flash / Gemini 3 Pro Preview
* **Goal**: Implement case-insensitive duplication detection for repository names in the configuration.
* **Outcome**: Added logic to detect duplicate repository names (case-insensitive) from both static configuration and organization imports. The tool now logs warnings for all detected duplicates and then exits with a fatal error if any duplicates were found.
## Details
1. **Duplicate Detection**:
* Modified `src/main.rs` to maintain a `HashSet` of lowercased repository names.
* Checks both the `repos` list and `organizations` imports.
* If a duplicate is found, a `WARN` log is emitted with details (name and URL).
* A `has_error` flag is set to true.
2. **Error Handling**:
* After processing all sources, if `has_error` is true, the program returns a fatal error: "Duplicate repository names detected. Please fix the configuration."
* This ensures the user sees all conflicts before the program exits.
## Testing
* Created a `duplicate_repro.toml` with conflicting names (e.g., `ProjectA` vs `projecta`).
* Verified that `cargo run -- --config duplicate_repro.toml --dry-run` correctly outputted warnings for each duplicate and then exited with a non-zero status code and the expected error message.

View File

@@ -0,0 +1,38 @@
# Session Log: Add Version Flag
**Date**: Saturday, January 10, 2026
**Model**: Gemini (CLI Agent)
**Goal**: Add a `--version` flag that displays the git tag and short SHA.
**Outcome**: Implemented dynamic versioning using `build.rs` and `clap`.
---
## Transcript Summary
### 1. Version Flag Implementation
**User**: Requested a `--version` flag that output the latest git tag. If the current commit is not exactly on a tag, it should append the short SHA (e.g., `v0.6-gd8a9fc`).
**Agent Action**:
1. Analyzed `Cargo.toml` and `src/main.rs`.
2. Created a `build.rs` script to handle the logic:
* Tries `git describe --tags --exact-match`.
* If that fails, tries `git describe --tags --abbrev=0` (latest tag) combined with `git rev-parse --short HEAD` (current SHA).
* Sets the `GIT_VERSION` environment variable.
3. Modified `src/main.rs`:
* Updated the `clap` `Args` struct to use `#[command(version = env!("GIT_VERSION"))]`.
4. Verified the output using `cargo run -- --version`.
### 2. Verification
**User**: Implicitly relied on agent verification.
**Agent Action**:
1. Ran `cargo run -- --version` -> Output: `gitea-mirror v0.6-gf13906d` (correct format).
2. Created a temporary tag `v0.7.0-test`, verified output was `gitea-mirror v0.7.0-test`, then deleted the tag.
3. Ran `cargo clippy` (clean).
4. Committed changes including the new `build.rs` and updated `src/main.rs`.
## Final State
The application now supports `gitea-mirror --version` which dynamically reports the git version at build time.