use crate::cli::output::Output;
use crate::core::repo_manifest::XmlManifest;
use std::path::Path;
pub fn run_manifest_import(path: &str, output_path: Option<&str>) -> anyhow::Result<()> {
let xml_path = Path::new(path);
if !xml_path.exists() {
anyhow::bail!("XML manifest not found: {}", path);
}
Output::header("Importing git-repo manifest...");
println!();
let xml_manifest = XmlManifest::parse_file(xml_path)?;
let result = xml_manifest.to_manifest()?;
Output::info(&format!(
"{} total projects, {} Gerrit (skipped), {} non-Gerrit (imported)",
result.total_projects, result.gerrit_skipped, result.non_gerrit_imported
));
for (platform, count) in &result.platform_counts {
Output::info(&format!(" {}: {} repos", platform, count));
}
let yaml = serde_yaml::to_string(&result.manifest)?;
let dest = output_path.unwrap_or("gripspace.yml");
std::fs::write(dest, &yaml)?;
println!();
Output::success(&format!("Written: {}", dest));
Ok(())
}
pub fn run_manifest_sync(workspace_root: &Path) -> anyhow::Result<()> {
let repo_dir = workspace_root.join(".repo");
let xml_path = repo_dir.join("manifest.xml");
if !xml_path.exists() {
anyhow::bail!("No .repo/manifest.xml found. Are you in a repo-managed workspace?");
}
Output::header("Syncing manifest from .repo/...");
println!();
let xml_manifest = XmlManifest::parse_file(&xml_path)?;
let result = xml_manifest.to_manifest()?;
Output::info(&format!(
"{} total projects, {} Gerrit (skipped), {} non-Gerrit (imported)",
result.total_projects, result.gerrit_skipped, result.non_gerrit_imported
));
let yaml = serde_yaml::to_string(&result.manifest)?;
let manifests_dir = repo_dir.join("manifests");
let yaml_path = manifests_dir.join("gripspace.yml");
std::fs::write(&yaml_path, &yaml)?;
std::fs::write(manifests_dir.join("manifest.yaml"), &yaml)?;
println!();
Output::success(&format!("Updated: {}", yaml_path.display()));
Ok(())
}
pub fn run_manifest_schema(format: &str) -> anyhow::Result<()> {
let schema = include_str!("../../../docs/manifest-schema.yaml");
match format {
"yaml" => {
println!("{}", schema);
}
"json" => {
let value: serde_yaml::Value = serde_yaml::from_str(schema)?;
let json = serde_json::to_string_pretty(&value)?;
println!("{}", json);
}
"markdown" | "md" => {
print_schema_markdown();
}
_ => {
anyhow::bail!("Unknown format: {}. Use yaml, json, or markdown.", format);
}
}
Ok(())
}
fn print_schema_markdown() {
println!(
r#"# gitgrip Manifest Schema (v2)
## Overview
The workspace file (`gripspace.yml`) defines a multi-repository workspace configuration.
It is typically located at `.gitgrip/spaces/main/gripspace.yml`.
## Top-Level Fields
| Field | Type | Required | Description |
|-------|------|----------|-------------|
| `version` | integer | Yes | Schema version (currently `2`) |
| `remotes` | object | No | Named remotes with base fetch URLs |
| `gripspaces` | array | No | Gripspace includes for manifest inheritance |
| `manifest` | object | No | Self-tracking manifest repo config |
| `repos` | object | Yes | Repository definitions |
| `settings` | object | No | Global workspace settings |
| `workspace` | object | No | Scripts, hooks, and environment |
## Remotes
Named remotes with base fetch URLs. Repos can reference a remote by name
instead of specifying a full URL. The repo name + `.git` is auto-appended.
| Field | Type | Required | Description |
|-------|------|----------|-------------|
| `fetch` | string | Yes | Base fetch URL (e.g. `git@github.com:org/`) |
## Gripspace Includes
Include repos, scripts, env, hooks, and linkfiles from other gripspace repos.
| Field | Type | Required | Description |
|-------|------|----------|-------------|
| `url` | string | Yes | Git URL for the gripspace repository |
| `rev` | string | No | Branch, tag, or commit to pin (default: remote HEAD) |
### Composefile (in manifest section)
Generate files by concatenating parts from gripspaces and local manifest.
| Field | Type | Required | Description |
|-------|------|----------|-------------|
| `dest` | string | Yes | Destination path relative to workspace root |
| `separator` | string | No | Separator between parts (default: `\n\n`) |
| `parts` | array | Yes | Ordered list of content sources |
| `parts[].src` | string | Yes | Source file path |
| `parts[].gripspace` | string | No | Gripspace name (omit for local manifest) |
## Repository Configuration
Each repository under `repos` supports:
| Field | Type | Default | Description |
|-------|------|---------|-------------|
| `url` | string | - | Git URL (SSH or HTTPS). Required unless `remote` is set. |
| `remote` | string | - | Reference to a top-level remote (derives URL from remote base + repo name) |
| `path` | string | - | Local path relative to workspace |
| `revision` | string | `main` | Default revision/branch to clone (inherits from settings) |
| `target` | string | revision | PR base branch name (inherits from settings, then revision) |
| `sync_remote` | string | `origin` | Remote for fetch/rebase (inherits from settings) |
| `push_remote` | string | `origin` | Remote for push (inherits from settings) |
| `groups` | array | `[]` | Groups for selective operations |
| `reference` | boolean | `false` | Read-only reference repo |
| `clone_strategy` | string | inherit | `clone` or `worktree` (inherits from settings) |
| `copyfile` | array | - | Files to copy to workspace |
| `linkfile` | array | - | Symlinks to create |
| `platform` | object | auto | Platform type and base URL |
| `agent` | object | - | Agent context (AI tool metadata) |
| `agent.description` | string | - | What this repo does |
| `agent.language` | string | - | Primary language |
| `agent.build` | string | - | Build command |
| `agent.test` | string | - | Test command |
| `agent.lint` | string | - | Lint command |
| `agent.format` | string | - | Format command |
### Resolution Chains
- **`revision`**: repo > settings > `"main"`
- **`target`**: repo > settings > revision
- **`sync_remote`**: repo > settings > `"origin"`
- **`push_remote`**: repo > settings > `"origin"`
- **`url`**: explicit `url`, or `remotes[repo.remote].fetch + name + ".git"`
## Settings
| Field | Type | Default | Description |
|-------|------|---------|-------------|
| `pr_prefix` | string | `[cross-repo]` | Prefix for PR titles |
| `merge_strategy` | string | `all-or-nothing` | `all-or-nothing` or `independent` |
| `revision` | string | `main` | Default revision for all repos |
| `target` | string | revision | PR base branch for all repos |
| `sync_remote` | string | `origin` | Default remote for fetch/rebase |
| `push_remote` | string | `origin` | Default remote for push |
| `clone_strategy` | string | `clone` | Default clone strategy: `clone` or `worktree` |
## Platform Types
- `github` - GitHub.com or GitHub Enterprise
- `gitlab` - GitLab.com or self-hosted
- `azure-devops` - Azure DevOps or Azure DevOps Server
- `bitbucket` - Bitbucket Cloud or Server
## Workspace Agent Config
| Field | Type | Description |
|-------|------|-------------|
| `agent.description` | string | Workspace description for agents |
| `agent.conventions` | array | Coding conventions to follow |
| `agent.workflows` | object | Named workflow descriptions |
## Example
```yaml
version: 2
remotes:
upstream:
fetch: git@github.com:org/
gripspaces:
- url: https://github.com/org/base-gripspace.git
rev: main
manifest:
url: git@github.com:org/manifest.git
revision: main
composefile:
- dest: CLAUDE.md
parts:
- gripspace: base-gripspace
src: CODI.md
- src: PRIVATE_DOCS.md
repos:
frontend:
url: git@github.com:me/frontend.git
path: ./frontend
sync_remote: upstream
groups: [core, web]
agent:
description: "React web application"
language: typescript
build: "pnpm build"
test: "pnpm test"
lint: "pnpm lint"
backend:
remote: upstream
path: ./backend
revision: master
target: staging
groups: [core, api]
agent:
description: "Rust API server"
language: rust
build: "cargo build"
test: "cargo test"
lint: "cargo clippy"
format: "cargo fmt"
settings:
pr_prefix: "[multi-repo]"
merge_strategy: all-or-nothing
revision: main
target: develop
workspace:
agent:
description: "Multi-repo web application"
conventions:
- "Use conventional commits"
- "All PRs require review"
workflows:
deploy: "gr pr merge && ./scripts/deploy.sh"
scripts:
build:
command: "npm run build"
```
## v1 Backward Compatibility
v1 manifests are auto-migrated when parsed:
- `default_branch` is accepted as an alias for `revision`
- `target` containing "/" (e.g. `upstream/develop`) is split into `target=develop`, `sync_remote=upstream`
- Version is upgraded to 2 internally
"#
);
}