FossilRepo

Delete _old_fossilrepo/, implement sync CLI commands Removed: - _old_fossilrepo/ directory (373 lines) — all code fully superseded: - server/config.py → Django Constance settings - server/manager.py → ctl/main.py repo commands + fossil/cli.py - sync/mirror.py → fossil/sync_models.py + fossil/tasks.py - sync/mappings.py → fossil/sync_models.SyncLog - cli/main.py → ctl/main.py Implemented: - `fossilrepo-ctl sync run <repo>` — runs Git sync for all configured mirrors - `fossilrepo-ctl sync status [repo]` — Rich table showing mirror status - Both use real models/tasks instead of NotImplementedError

lmata 2026-04-07 02:57 trunk
Commit 89b91b106927708e7ca8735be051cfa7e91b27a0513e4a4e2af128c53eb0456a
D _old_fossilrepo/__init__.py
-1
--- a/_old_fossilrepo/__init__.py
+++ b/_old_fossilrepo/__init__.py
@@ -1 +0,0 @@
1
-__version__ = "0.1.0"
--- a/_old_fossilrepo/__init__.py
+++ b/_old_fossilrepo/__init__.py
@@ -1 +0,0 @@
1 __version__ = "0.1.0"
--- a/_old_fossilrepo/__init__.py
+++ b/_old_fossilrepo/__init__.py
@@ -1 +0,0 @@
 
D _old_fossilrepo/cli/__init__.py

No diff available

D _old_fossilrepo/cli/main.py
-104
--- a/_old_fossilrepo/cli/main.py
+++ b/_old_fossilrepo/cli/main.py
@@ -1,104 +0,0 @@
1
-"""fossilrepo CLI — manage Fossil servers, repos, and Git sync."""
2
-
3
-import click
4
-from rich.console import Console
5
-
6
-console = Console()
7
-
8
-
9
-@click.group()
10
-@click.version_option(package_name="fossilrepo")
11
-def cli() -> None:
12
- """fossilrepo — self-hosted Fossil SCM infrastructure."""
13
-
14
-
15
-# ---------------------------------------------------------------------------
16
-# Server commands
17
-# ---------------------------------------------------------------------------
18
-
19
-
20
-@cli.group()
21
-def server() -> None:
22
- """Manage the Fossil server."""
23
-
24
-
25
-@server.command()
26
-def start() -> None:
27
- """Start the Fossil server (Docker + Caddy + Litestream)."""
28
- console.print("[bold]Starting Fossil server...[/bold]")
29
- raise NotImplementedError
30
-
31
-
32
-@server.command()
33
-def stop() -> None:
34
- """Stop the Fossil server."""
35
- console.print("[bold]Stopping Fossil server...[/bold]")
36
- raise NotImplementedError
37
-
38
-
39
-@server.command()
40
-def status() -> None:
41
- """Show Fossil server status."""
42
- console.print("[bold]Server status:[/bold]")
43
- raise NotImplementedError
44
-
45
-
46
-# ---------------------------------------------------------------------------
47
-# Repo commands
48
-# ---------------------------------------------------------------------------
49
-
50
-
51
-@cli.group()
52
-def repo() -> None:
53
- """Manage Fossil repositories."""
54
-
55
-
56
-@repo.command()
57
-@click.argument("name")
58
-def create(name: str) -> None:
59
- """Create a new Fossil repository."""
60
- console.print(f"[bold]Creating repo:[/bold] {name}")
61
- raise NotImplementedError
62
-
63
-
64
-@repo.command(name="list")
65
-def list_repos() -> None:
66
- """List all Fossil repositories."""
67
- raise NotImplementedError
68
-
69
-
70
-@repo.command()
71
-@click.argument("name")
72
-def delete(name: str) -> None:
73
- """Delete a Fossil repository."""
74
- console.print(f"[bold]Deleting repo:[/bold] {name}")
75
- raise NotImplementedError
76
-
77
-
78
-# ---------------------------------------------------------------------------
79
-# Sync commands
80
-# ---------------------------------------------------------------------------
81
-
82
-
83
-@cli.group()
84
-def sync() -> None:
85
- """Sync Fossil repos to GitHub/GitLab."""
86
-
87
-
88
-@sync.command()
89
-@click.argument("repo_name")
90
-@click.option("--remote", required=True, help="Git remote URL to sync to.")
91
-@click.option("--tickets/--no-tickets", default=False, help="Sync tickets as issues.")
92
-@click.option("--wiki/--no-wiki", default=False, help="Sync wiki pages.")
93
-def run(repo_name: str, remote: str, tickets: bool, wiki: bool) -> None:
94
- """Run a sync from a Fossil repo to a Git remote."""
95
- console.print(f"[bold]Syncing[/bold] {repo_name} -> {remote}")
96
- raise NotImplementedError
97
-
98
-
99
-@sync.command()
100
-@click.argument("repo_name")
101
-def status(repo_name: str) -> None: # noqa: F811
102
- """Show sync status for a repository."""
103
- console.print(f"[bold]Sync status for:[/bold] {repo_name}")
104
- raise NotImplementedError
--- a/_old_fossilrepo/cli/main.py
+++ b/_old_fossilrepo/cli/main.py
@@ -1,104 +0,0 @@
1 """fossilrepo CLI — manage Fossil servers, repos, and Git sync."""
2
3 import click
4 from rich.console import Console
5
6 console = Console()
7
8
9 @click.group()
10 @click.version_option(package_name="fossilrepo")
11 def cli() -> None:
12 """fossilrepo — self-hosted Fossil SCM infrastructure."""
13
14
15 # ---------------------------------------------------------------------------
16 # Server commands
17 # ---------------------------------------------------------------------------
18
19
20 @cli.group()
21 def server() -> None:
22 """Manage the Fossil server."""
23
24
25 @server.command()
26 def start() -> None:
27 """Start the Fossil server (Docker + Caddy + Litestream)."""
28 console.print("[bold]Starting Fossil server...[/bold]")
29 raise NotImplementedError
30
31
32 @server.command()
33 def stop() -> None:
34 """Stop the Fossil server."""
35 console.print("[bold]Stopping Fossil server...[/bold]")
36 raise NotImplementedError
37
38
39 @server.command()
40 def status() -> None:
41 """Show Fossil server status."""
42 console.print("[bold]Server status:[/bold]")
43 raise NotImplementedError
44
45
46 # ---------------------------------------------------------------------------
47 # Repo commands
48 # ---------------------------------------------------------------------------
49
50
51 @cli.group()
52 def repo() -> None:
53 """Manage Fossil repositories."""
54
55
56 @repo.command()
57 @click.argument("name")
58 def create(name: str) -> None:
59 """Create a new Fossil repository."""
60 console.print(f"[bold]Creating repo:[/bold] {name}")
61 raise NotImplementedError
62
63
64 @repo.command(name="list")
65 def list_repos() -> None:
66 """List all Fossil repositories."""
67 raise NotImplementedError
68
69
70 @repo.command()
71 @click.argument("name")
72 def delete(name: str) -> None:
73 """Delete a Fossil repository."""
74 console.print(f"[bold]Deleting repo:[/bold] {name}")
75 raise NotImplementedError
76
77
78 # ---------------------------------------------------------------------------
79 # Sync commands
80 # ---------------------------------------------------------------------------
81
82
83 @cli.group()
84 def sync() -> None:
85 """Sync Fossil repos to GitHub/GitLab."""
86
87
88 @sync.command()
89 @click.argument("repo_name")
90 @click.option("--remote", required=True, help="Git remote URL to sync to.")
91 @click.option("--tickets/--no-tickets", default=False, help="Sync tickets as issues.")
92 @click.option("--wiki/--no-wiki", default=False, help="Sync wiki pages.")
93 def run(repo_name: str, remote: str, tickets: bool, wiki: bool) -> None:
94 """Run a sync from a Fossil repo to a Git remote."""
95 console.print(f"[bold]Syncing[/bold] {repo_name} -> {remote}")
96 raise NotImplementedError
97
98
99 @sync.command()
100 @click.argument("repo_name")
101 def status(repo_name: str) -> None: # noqa: F811
102 """Show sync status for a repository."""
103 console.print(f"[bold]Sync status for:[/bold] {repo_name}")
104 raise NotImplementedError
--- a/_old_fossilrepo/cli/main.py
+++ b/_old_fossilrepo/cli/main.py
@@ -1,104 +0,0 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
D _old_fossilrepo/server/__init__.py

No diff available

D _old_fossilrepo/server/config.py
-66
--- a/_old_fossilrepo/server/config.py
+++ b/_old_fossilrepo/server/config.py
@@ -1,66 +0,0 @@
1
-"""Server configuration for Fossil repository hosting."""
2
-
3
-from pathlib import Path
4
-
5
-from pydantic import Field
6
-from pydantic_settings import BaseSettings
7
-
8
-
9
-class ServerConfig(BaseSettings):
10
- """Configuration for the Fossil server infrastructure.
11
-
12
- Values are loaded from environment variables prefixed with FOSSILREPO_.
13
- For example, FOSSILREPO_DATA_DIR sets data_dir.
14
- """
15
-
16
- model_config = {"env_prefix": "FOSSILREPO_"}
17
-
18
- data_dir: Path = Field(
19
- default=Path("/data/repos"),
20
- description="Directory where .fossil repository files are stored.",
21
- )
22
-
23
- caddy_domain: str = Field(
24
- default="localhost",
25
- description="Base domain for subdomain routing (e.g., fossilrepos.io).",
26
- )
27
-
28
- caddy_config_path: Path = Field(
29
- default=Path("/etc/caddy/Caddyfile"),
30
- description="Path to the Caddy configuration file.",
31
- )
32
-
33
- fossil_port: int = Field(
34
- default=8080,
35
- description="Port the fossil server listens on.",
36
- )
37
-
38
- s3_bucket: str = Field(
39
- default="",
40
- description="S3 bucket for Litestream replication.",
41
- )
42
-
43
- s3_endpoint: str = Field(
44
- default="",
45
- description="S3-compatible endpoint URL (for MinIO, R2, etc.).",
46
- )
47
-
48
- s3_access_key_id: str = Field(
49
- default="",
50
- description="AWS access key ID for S3 replication.",
51
- )
52
-
53
- s3_secret_access_key: str = Field(
54
- default="",
55
- description="AWS secret access key for S3 replication.",
56
- )
57
-
58
- s3_region: str = Field(
59
- default="us-east-1",
60
- description="AWS region for S3 bucket.",
61
- )
62
-
63
- litestream_config_path: Path = Field(
64
- default=Path("/etc/litestream.yml"),
65
- description="Path to the Litestream configuration file.",
66
- )
--- a/_old_fossilrepo/server/config.py
+++ b/_old_fossilrepo/server/config.py
@@ -1,66 +0,0 @@
1 """Server configuration for Fossil repository hosting."""
2
3 from pathlib import Path
4
5 from pydantic import Field
6 from pydantic_settings import BaseSettings
7
8
9 class ServerConfig(BaseSettings):
10 """Configuration for the Fossil server infrastructure.
11
12 Values are loaded from environment variables prefixed with FOSSILREPO_.
13 For example, FOSSILREPO_DATA_DIR sets data_dir.
14 """
15
16 model_config = {"env_prefix": "FOSSILREPO_"}
17
18 data_dir: Path = Field(
19 default=Path("/data/repos"),
20 description="Directory where .fossil repository files are stored.",
21 )
22
23 caddy_domain: str = Field(
24 default="localhost",
25 description="Base domain for subdomain routing (e.g., fossilrepos.io).",
26 )
27
28 caddy_config_path: Path = Field(
29 default=Path("/etc/caddy/Caddyfile"),
30 description="Path to the Caddy configuration file.",
31 )
32
33 fossil_port: int = Field(
34 default=8080,
35 description="Port the fossil server listens on.",
36 )
37
38 s3_bucket: str = Field(
39 default="",
40 description="S3 bucket for Litestream replication.",
41 )
42
43 s3_endpoint: str = Field(
44 default="",
45 description="S3-compatible endpoint URL (for MinIO, R2, etc.).",
46 )
47
48 s3_access_key_id: str = Field(
49 default="",
50 description="AWS access key ID for S3 replication.",
51 )
52
53 s3_secret_access_key: str = Field(
54 default="",
55 description="AWS secret access key for S3 replication.",
56 )
57
58 s3_region: str = Field(
59 default="us-east-1",
60 description="AWS region for S3 bucket.",
61 )
62
63 litestream_config_path: Path = Field(
64 default=Path("/etc/litestream.yml"),
65 description="Path to the Litestream configuration file.",
66 )
--- a/_old_fossilrepo/server/config.py
+++ b/_old_fossilrepo/server/config.py
@@ -1,66 +0,0 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
D _old_fossilrepo/server/manager.py
-77
--- a/_old_fossilrepo/server/manager.py
+++ b/_old_fossilrepo/server/manager.py
@@ -1,77 +0,0 @@
1
-"""Fossil repository management — create, delete, list, inspect repos."""
2
-
3
-from pathlib import Path
4
-
5
-from fossilrepo.server.config import ServerConfig
6
-
7
-
8
-class RepoInfo:
9
- """Information about a single Fossil repository."""
10
-
11
- def __init__(self, name: str, path: Path, size_bytes: int) -> None:
12
- self.name = name
13
- self.path = path
14
- self.size_bytes = size_bytes
15
-
16
-
17
-class FossilRepoManager:
18
- """Manages Fossil repositories on the server.
19
-
20
- Handles repo lifecycle: creation via `fossil init`, deletion (soft — moves
21
- to trash), listing, and metadata inspection. Coordinates with Litestream
22
- for S3 replication of new repos.
23
- """
24
-
25
- def __init__(self, config: ServerConfig | None = None) -> None:
26
- self.config = config or ServerConfig()
27
-
28
- def create_repo(self, name: str) -> RepoInfo:
29
- """Create a new Fossil repository.
30
-
31
- Runs `fossil init` to create the .fossil file in the data directory,
32
- registers the repo with Caddy for subdomain routing, and ensures
33
- Litestream picks up the new file for replication.
34
-
35
- Args:
36
- name: Repository name. Used as the subdomain and filename.
37
-
38
- Returns:
39
- RepoInfo for the newly created repository.
40
- """
41
- raise NotImplementedError
42
-
43
- def delete_repo(self, name: str) -> None:
44
- """Soft-delete a Fossil repository.
45
-
46
- Moves the .fossil file to a trash directory rather than deleting it.
47
- Removes the Caddy subdomain route. Litestream retains the S3 replica.
48
-
49
- Args:
50
- name: Repository name to delete.
51
- """
52
- raise NotImplementedError
53
-
54
- def list_repos(self) -> list[RepoInfo]:
55
- """List all active Fossil repositories.
56
-
57
- Scans the data directory for .fossil files and returns metadata
58
- for each.
59
-
60
- Returns:
61
- List of RepoInfo objects for all active repositories.
62
- """
63
- raise NotImplementedError
64
-
65
- def get_repo_info(self, name: str) -> RepoInfo:
66
- """Get detailed information about a specific repository.
67
-
68
- Args:
69
- name: Repository name to inspect.
70
-
71
- Returns:
72
- RepoInfo with metadata about the repository.
73
-
74
- Raises:
75
- FileNotFoundError: If the repository does not exist.
76
- """
77
- raise NotImplementedError
--- a/_old_fossilrepo/server/manager.py
+++ b/_old_fossilrepo/server/manager.py
@@ -1,77 +0,0 @@
1 """Fossil repository management — create, delete, list, inspect repos."""
2
3 from pathlib import Path
4
5 from fossilrepo.server.config import ServerConfig
6
7
8 class RepoInfo:
9 """Information about a single Fossil repository."""
10
11 def __init__(self, name: str, path: Path, size_bytes: int) -> None:
12 self.name = name
13 self.path = path
14 self.size_bytes = size_bytes
15
16
17 class FossilRepoManager:
18 """Manages Fossil repositories on the server.
19
20 Handles repo lifecycle: creation via `fossil init`, deletion (soft — moves
21 to trash), listing, and metadata inspection. Coordinates with Litestream
22 for S3 replication of new repos.
23 """
24
25 def __init__(self, config: ServerConfig | None = None) -> None:
26 self.config = config or ServerConfig()
27
28 def create_repo(self, name: str) -> RepoInfo:
29 """Create a new Fossil repository.
30
31 Runs `fossil init` to create the .fossil file in the data directory,
32 registers the repo with Caddy for subdomain routing, and ensures
33 Litestream picks up the new file for replication.
34
35 Args:
36 name: Repository name. Used as the subdomain and filename.
37
38 Returns:
39 RepoInfo for the newly created repository.
40 """
41 raise NotImplementedError
42
43 def delete_repo(self, name: str) -> None:
44 """Soft-delete a Fossil repository.
45
46 Moves the .fossil file to a trash directory rather than deleting it.
47 Removes the Caddy subdomain route. Litestream retains the S3 replica.
48
49 Args:
50 name: Repository name to delete.
51 """
52 raise NotImplementedError
53
54 def list_repos(self) -> list[RepoInfo]:
55 """List all active Fossil repositories.
56
57 Scans the data directory for .fossil files and returns metadata
58 for each.
59
60 Returns:
61 List of RepoInfo objects for all active repositories.
62 """
63 raise NotImplementedError
64
65 def get_repo_info(self, name: str) -> RepoInfo:
66 """Get detailed information about a specific repository.
67
68 Args:
69 name: Repository name to inspect.
70
71 Returns:
72 RepoInfo with metadata about the repository.
73
74 Raises:
75 FileNotFoundError: If the repository does not exist.
76 """
77 raise NotImplementedError
--- a/_old_fossilrepo/server/manager.py
+++ b/_old_fossilrepo/server/manager.py
@@ -1,77 +0,0 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
D _old_fossilrepo/sync/__init__.py

No diff available

D _old_fossilrepo/sync/mappings.py
-39
--- a/_old_fossilrepo/sync/mappings.py
+++ b/_old_fossilrepo/sync/mappings.py
@@ -1,39 +0,0 @@
1
-"""Data models for Fossil-to-Git sync mappings."""
2
-
3
-from datetime import datetime
4
-
5
-from pydantic import BaseModel, Field
6
-
7
-
8
-class CommitMapping(BaseModel):
9
- """Maps a Fossil checkin to a Git commit."""
10
-
11
- fossil_hash: str = Field(description="Fossil checkin hash (SHA1).")
12
- git_sha: str = Field(description="Corresponding Git commit SHA.")
13
- timestamp: datetime = Field(description="Commit timestamp.")
14
- message: str = Field(description="Commit message.")
15
- author: str = Field(description="Author name.")
16
-
17
-
18
-class TicketMapping(BaseModel):
19
- """Maps a Fossil ticket to a GitHub/GitLab issue."""
20
-
21
- fossil_ticket_id: str = Field(description="Fossil ticket UUID.")
22
- remote_issue_number: int = Field(description="GitHub/GitLab issue number.")
23
- remote_issue_url: str = Field(description="URL to the remote issue.")
24
- title: str = Field(description="Ticket/issue title.")
25
- status: str = Field(description="Current status (open, closed, etc.).")
26
- last_synced: datetime = Field(description="Timestamp of last sync.")
27
-
28
-
29
-class WikiMapping(BaseModel):
30
- """Maps a Fossil wiki page to a remote doc/wiki page."""
31
-
32
- fossil_page_name: str = Field(description="Fossil wiki page name.")
33
- remote_path: str = Field(
34
- description="Path in the remote repo (e.g., docs/page.md) or wiki URL."
35
- )
36
- last_synced: datetime = Field(description="Timestamp of last sync.")
37
- content_hash: str = Field(
38
- description="Hash of the content at last sync, for change detection."
39
- )
--- a/_old_fossilrepo/sync/mappings.py
+++ b/_old_fossilrepo/sync/mappings.py
@@ -1,39 +0,0 @@
1 """Data models for Fossil-to-Git sync mappings."""
2
3 from datetime import datetime
4
5 from pydantic import BaseModel, Field
6
7
8 class CommitMapping(BaseModel):
9 """Maps a Fossil checkin to a Git commit."""
10
11 fossil_hash: str = Field(description="Fossil checkin hash (SHA1).")
12 git_sha: str = Field(description="Corresponding Git commit SHA.")
13 timestamp: datetime = Field(description="Commit timestamp.")
14 message: str = Field(description="Commit message.")
15 author: str = Field(description="Author name.")
16
17
18 class TicketMapping(BaseModel):
19 """Maps a Fossil ticket to a GitHub/GitLab issue."""
20
21 fossil_ticket_id: str = Field(description="Fossil ticket UUID.")
22 remote_issue_number: int = Field(description="GitHub/GitLab issue number.")
23 remote_issue_url: str = Field(description="URL to the remote issue.")
24 title: str = Field(description="Ticket/issue title.")
25 status: str = Field(description="Current status (open, closed, etc.).")
26 last_synced: datetime = Field(description="Timestamp of last sync.")
27
28
29 class WikiMapping(BaseModel):
30 """Maps a Fossil wiki page to a remote doc/wiki page."""
31
32 fossil_page_name: str = Field(description="Fossil wiki page name.")
33 remote_path: str = Field(
34 description="Path in the remote repo (e.g., docs/page.md) or wiki URL."
35 )
36 last_synced: datetime = Field(description="Timestamp of last sync.")
37 content_hash: str = Field(
38 description="Hash of the content at last sync, for change detection."
39 )
--- a/_old_fossilrepo/sync/mappings.py
+++ b/_old_fossilrepo/sync/mappings.py
@@ -1,39 +0,0 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
D _old_fossilrepo/sync/mirror.py
-86
--- a/_old_fossilrepo/sync/mirror.py
+++ b/_old_fossilrepo/sync/mirror.py
@@ -1,86 +0,0 @@
1
-"""Fossil-to-Git mirror — sync commits, tickets, and wiki to GitHub/GitLab."""
2
-
3
-from pathlib import Path
4
-
5
-from fossilrepo.sync.mappings import CommitMapping, TicketMapping, WikiMapping
6
-
7
-
8
-class FossilMirror:
9
- """Mirrors a Fossil repository to a Git remote (GitHub or GitLab).
10
-
11
- Fossil is the source of truth. The Git remote is a downstream mirror
12
- for ecosystem visibility. Syncs commits, optionally maps tickets to
13
- issues and wiki pages to docs.
14
- """
15
-
16
- def __init__(self, fossil_path: Path, remote_url: str) -> None:
17
- self.fossil_path = fossil_path
18
- self.remote_url = remote_url
19
-
20
- def sync_to_github(
21
- self,
22
- *,
23
- include_tickets: bool = False,
24
- include_wiki: bool = False,
25
- ) -> None:
26
- """Run a full sync to a GitHub repository.
27
-
28
- Exports Fossil commits to Git format and pushes to the GitHub remote.
29
- Optionally syncs tickets as GitHub Issues and wiki as repo docs.
30
-
31
- Args:
32
- include_tickets: If True, map Fossil tickets to GitHub Issues.
33
- include_wiki: If True, export Fossil wiki pages to repo docs.
34
- """
35
- raise NotImplementedError
36
-
37
- def sync_to_gitlab(
38
- self,
39
- *,
40
- include_tickets: bool = False,
41
- include_wiki: bool = False,
42
- ) -> None:
43
- """Run a full sync to a GitLab repository.
44
-
45
- Exports Fossil commits to Git format and pushes to the GitLab remote.
46
- Optionally syncs tickets as GitLab Issues and wiki pages.
47
-
48
- Args:
49
- include_tickets: If True, map Fossil tickets to GitLab Issues.
50
- include_wiki: If True, export Fossil wiki pages to GitLab wiki.
51
- """
52
- raise NotImplementedError
53
-
54
- def sync_commits(self) -> list[CommitMapping]:
55
- """Sync Fossil commits to the Git remote.
56
-
57
- Exports the Fossil timeline as Git commits and pushes to the
58
- configured remote. Returns a mapping of Fossil checkin hashes
59
- to Git commit SHAs.
60
-
61
- Returns:
62
- List of CommitMapping objects for each synced commit.
63
- """
64
- raise NotImplementedError
65
-
66
- def sync_tickets(self) -> list[TicketMapping]:
67
- """Sync Fossil tickets to the remote issue tracker.
68
-
69
- Maps Fossil ticket fields to GitHub/GitLab issue fields. Creates
70
- new issues for new tickets, updates existing ones.
71
-
72
- Returns:
73
- List of TicketMapping objects for each synced ticket.
74
- """
75
- raise NotImplementedError
76
-
77
- def sync_wiki(self) -> list[WikiMapping]:
78
- """Sync Fossil wiki pages to the remote.
79
-
80
- Exports Fossil wiki pages as Markdown files. For GitHub, these go
81
- into a docs/ directory. For GitLab, they go to the project wiki.
82
-
83
- Returns:
84
- List of WikiMapping objects for each synced page.
85
- """
86
- raise NotImplementedError
--- a/_old_fossilrepo/sync/mirror.py
+++ b/_old_fossilrepo/sync/mirror.py
@@ -1,86 +0,0 @@
1 """Fossil-to-Git mirror — sync commits, tickets, and wiki to GitHub/GitLab."""
2
3 from pathlib import Path
4
5 from fossilrepo.sync.mappings import CommitMapping, TicketMapping, WikiMapping
6
7
8 class FossilMirror:
9 """Mirrors a Fossil repository to a Git remote (GitHub or GitLab).
10
11 Fossil is the source of truth. The Git remote is a downstream mirror
12 for ecosystem visibility. Syncs commits, optionally maps tickets to
13 issues and wiki pages to docs.
14 """
15
16 def __init__(self, fossil_path: Path, remote_url: str) -> None:
17 self.fossil_path = fossil_path
18 self.remote_url = remote_url
19
20 def sync_to_github(
21 self,
22 *,
23 include_tickets: bool = False,
24 include_wiki: bool = False,
25 ) -> None:
26 """Run a full sync to a GitHub repository.
27
28 Exports Fossil commits to Git format and pushes to the GitHub remote.
29 Optionally syncs tickets as GitHub Issues and wiki as repo docs.
30
31 Args:
32 include_tickets: If True, map Fossil tickets to GitHub Issues.
33 include_wiki: If True, export Fossil wiki pages to repo docs.
34 """
35 raise NotImplementedError
36
37 def sync_to_gitlab(
38 self,
39 *,
40 include_tickets: bool = False,
41 include_wiki: bool = False,
42 ) -> None:
43 """Run a full sync to a GitLab repository.
44
45 Exports Fossil commits to Git format and pushes to the GitLab remote.
46 Optionally syncs tickets as GitLab Issues and wiki pages.
47
48 Args:
49 include_tickets: If True, map Fossil tickets to GitLab Issues.
50 include_wiki: If True, export Fossil wiki pages to GitLab wiki.
51 """
52 raise NotImplementedError
53
54 def sync_commits(self) -> list[CommitMapping]:
55 """Sync Fossil commits to the Git remote.
56
57 Exports the Fossil timeline as Git commits and pushes to the
58 configured remote. Returns a mapping of Fossil checkin hashes
59 to Git commit SHAs.
60
61 Returns:
62 List of CommitMapping objects for each synced commit.
63 """
64 raise NotImplementedError
65
66 def sync_tickets(self) -> list[TicketMapping]:
67 """Sync Fossil tickets to the remote issue tracker.
68
69 Maps Fossil ticket fields to GitHub/GitLab issue fields. Creates
70 new issues for new tickets, updates existing ones.
71
72 Returns:
73 List of TicketMapping objects for each synced ticket.
74 """
75 raise NotImplementedError
76
77 def sync_wiki(self) -> list[WikiMapping]:
78 """Sync Fossil wiki pages to the remote.
79
80 Exports Fossil wiki pages as Markdown files. For GitHub, these go
81 into a docs/ directory. For GitLab, they go to the project wiki.
82
83 Returns:
84 List of WikiMapping objects for each synced page.
85 """
86 raise NotImplementedError
--- a/_old_fossilrepo/sync/mirror.py
+++ b/_old_fossilrepo/sync/mirror.py
@@ -1,86 +0,0 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
+63 -12
--- ctl/main.py
+++ ctl/main.py
@@ -206,25 +206,76 @@
206206
"""Sync Fossil repos to GitHub/GitLab."""
207207
208208
209209
@sync.command(name="run")
210210
@click.argument("repo_name")
211
-@click.option("--remote", required=True, help="Git remote URL.")
212
-@click.option("--tickets/--no-tickets", default=False, help="Sync tickets as issues.")
213
-@click.option("--wiki/--no-wiki", default=False, help="Sync wiki pages.")
214
-def sync_run(repo_name: str, remote: str, tickets: bool, wiki: bool) -> None:
215
- """Run a sync from a Fossil repo to a Git remote."""
216
- console.print(f"[bold]Syncing[/bold] {repo_name} -> {remote}")
217
- raise NotImplementedError("Sync not yet implemented")
211
+@click.option("--mirror-id", type=int, help="Specific Git mirror ID to sync.")
212
+def sync_run(repo_name: str, mirror_id: int | None = None) -> None:
213
+ """Run Git sync for a Fossil repository."""
214
+ import django
215
+
216
+ django.setup()
217
+ from fossil.models import FossilRepository
218
+ from fossil.sync_models import GitMirror
219
+ from fossil.tasks import run_git_sync
220
+
221
+ repo = FossilRepository.objects.filter(filename=f"{repo_name}.fossil").first()
222
+ if not repo:
223
+ console.print(f"[red]Repo not found: {repo_name}.fossil[/red]")
224
+ return
225
+
226
+ mirrors = GitMirror.objects.filter(repository=repo, deleted_at__isnull=True).exclude(sync_mode="disabled")
227
+ if mirror_id:
228
+ mirrors = mirrors.filter(pk=mirror_id)
229
+
230
+ if not mirrors.exists():
231
+ console.print("[yellow]No Git mirrors configured for this repo.[/yellow]")
232
+ return
233
+
234
+ for mirror in mirrors:
235
+ console.print(f"[bold]Syncing[/bold] {repo.filename} → {mirror.git_remote_url}")
236
+ run_git_sync(mirror.pk)
237
+ mirror.refresh_from_db()
238
+ if mirror.last_sync_status == "success":
239
+ console.print(f" [green]Success[/green] — {mirror.last_sync_message[:100]}")
240
+ else:
241
+ console.print(f" [red]Failed[/red] — {mirror.last_sync_message[:100]}")
218242
219243
220244
@sync.command(name="status")
221
-@click.argument("repo_name")
222
-def sync_status(repo_name: str) -> None:
223
- """Show sync status for a repository."""
224
- console.print(f"[bold]Sync status for:[/bold] {repo_name}")
225
- raise NotImplementedError("Sync status not yet implemented")
245
+@click.argument("repo_name", required=False)
246
+def sync_status(repo_name: str | None = None) -> None:
247
+ """Show sync status for repositories."""
248
+ import django
249
+
250
+ django.setup()
251
+ from rich.table import Table
252
+
253
+ from fossil.sync_models import GitMirror
254
+
255
+ mirrors = GitMirror.objects.filter(deleted_at__isnull=True)
256
+ if repo_name:
257
+ mirrors = mirrors.filter(repository__filename=f"{repo_name}.fossil")
258
+
259
+ table = Table(title="Git Mirror Status")
260
+ table.add_column("Repo", style="cyan")
261
+ table.add_column("Remote")
262
+ table.add_column("Mode")
263
+ table.add_column("Status")
264
+ table.add_column("Last Sync")
265
+ table.add_column("Syncs", justify="right")
266
+ for m in mirrors:
267
+ status_style = "green" if m.last_sync_status == "success" else "red" if m.last_sync_status == "failed" else "yellow"
268
+ table.add_row(
269
+ m.repository.filename,
270
+ m.git_remote_url[:40],
271
+ m.get_sync_mode_display(),
272
+ f"[{status_style}]{m.last_sync_status or 'never'}[/{status_style}]",
273
+ str(m.last_sync_at.strftime("%Y-%m-%d %H:%M") if m.last_sync_at else "—"),
274
+ str(m.total_syncs),
275
+ )
276
+ console.print(table)
226277
227278
228279
# ---------------------------------------------------------------------------
229280
# Backup commands
230281
# ---------------------------------------------------------------------------
231282
--- ctl/main.py
+++ ctl/main.py
@@ -206,25 +206,76 @@
206 """Sync Fossil repos to GitHub/GitLab."""
207
208
209 @sync.command(name="run")
210 @click.argument("repo_name")
211 @click.option("--remote", required=True, help="Git remote URL.")
212 @click.option("--tickets/--no-tickets", default=False, help="Sync tickets as issues.")
213 @click.option("--wiki/--no-wiki", default=False, help="Sync wiki pages.")
214 def sync_run(repo_name: str, remote: str, tickets: bool, wiki: bool) -> None:
215 """Run a sync from a Fossil repo to a Git remote."""
216 console.print(f"[bold]Syncing[/bold] {repo_name} -> {remote}")
217 raise NotImplementedError("Sync not yet implemented")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
218
219
220 @sync.command(name="status")
221 @click.argument("repo_name")
222 def sync_status(repo_name: str) -> None:
223 """Show sync status for a repository."""
224 console.print(f"[bold]Sync status for:[/bold] {repo_name}")
225 raise NotImplementedError("Sync status not yet implemented")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
226
227
228 # ---------------------------------------------------------------------------
229 # Backup commands
230 # ---------------------------------------------------------------------------
231
--- ctl/main.py
+++ ctl/main.py
@@ -206,25 +206,76 @@
206 """Sync Fossil repos to GitHub/GitLab."""
207
208
209 @sync.command(name="run")
210 @click.argument("repo_name")
211 @click.option("--mirror-id", type=int, help="Specific Git mirror ID to sync.")
212 def sync_run(repo_name: str, mirror_id: int | None = None) -> None:
213 """Run Git sync for a Fossil repository."""
214 import django
215
216 django.setup()
217 from fossil.models import FossilRepository
218 from fossil.sync_models import GitMirror
219 from fossil.tasks import run_git_sync
220
221 repo = FossilRepository.objects.filter(filename=f"{repo_name}.fossil").first()
222 if not repo:
223 console.print(f"[red]Repo not found: {repo_name}.fossil[/red]")
224 return
225
226 mirrors = GitMirror.objects.filter(repository=repo, deleted_at__isnull=True).exclude(sync_mode="disabled")
227 if mirror_id:
228 mirrors = mirrors.filter(pk=mirror_id)
229
230 if not mirrors.exists():
231 console.print("[yellow]No Git mirrors configured for this repo.[/yellow]")
232 return
233
234 for mirror in mirrors:
235 console.print(f"[bold]Syncing[/bold] {repo.filename} → {mirror.git_remote_url}")
236 run_git_sync(mirror.pk)
237 mirror.refresh_from_db()
238 if mirror.last_sync_status == "success":
239 console.print(f" [green]Success[/green] — {mirror.last_sync_message[:100]}")
240 else:
241 console.print(f" [red]Failed[/red] — {mirror.last_sync_message[:100]}")
242
243
244 @sync.command(name="status")
245 @click.argument("repo_name", required=False)
246 def sync_status(repo_name: str | None = None) -> None:
247 """Show sync status for repositories."""
248 import django
249
250 django.setup()
251 from rich.table import Table
252
253 from fossil.sync_models import GitMirror
254
255 mirrors = GitMirror.objects.filter(deleted_at__isnull=True)
256 if repo_name:
257 mirrors = mirrors.filter(repository__filename=f"{repo_name}.fossil")
258
259 table = Table(title="Git Mirror Status")
260 table.add_column("Repo", style="cyan")
261 table.add_column("Remote")
262 table.add_column("Mode")
263 table.add_column("Status")
264 table.add_column("Last Sync")
265 table.add_column("Syncs", justify="right")
266 for m in mirrors:
267 status_style = "green" if m.last_sync_status == "success" else "red" if m.last_sync_status == "failed" else "yellow"
268 table.add_row(
269 m.repository.filename,
270 m.git_remote_url[:40],
271 m.get_sync_mode_display(),
272 f"[{status_style}]{m.last_sync_status or 'never'}[/{status_style}]",
273 str(m.last_sync_at.strftime("%Y-%m-%d %H:%M") if m.last_sync_at else "—"),
274 str(m.total_syncs),
275 )
276 console.print(table)
277
278
279 # ---------------------------------------------------------------------------
280 # Backup commands
281 # ---------------------------------------------------------------------------
282

Keyboard Shortcuts

Open search /
Next entry (timeline) j
Previous entry (timeline) k
Open focused entry Enter
Show this help ?
Toggle theme Top nav button