1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
use anyhow::Result;
use clap::Parser;
use std::fs;
use std::path::PathBuf;
pub mod blob;
pub mod branch;
mod diagnose;
mod merge;
mod migrate;
pub mod net;
mod signing;
mod squash;
#[derive(Parser)]
pub enum PileCommand {
/// Operations on branches stored in a pile file.
Branch {
#[command(subcommand)]
cmd: branch::Command,
},
/// Operations on blobs stored in a pile file.
Blob {
#[command(subcommand)]
cmd: blob::Command,
},
/// Merge source branch heads into a target branch.
Merge {
/// Path to the pile file to modify
pile: PathBuf,
/// Target branch id (hex)
target: String,
/// Source branch id(s) (hex)
#[arg(num_args = 1..)]
sources: Vec<String>,
/// Optional signing key path. The file should contain a 64-char hex seed.
#[arg(long)]
signing_key: Option<PathBuf>,
},
/// Create a new empty pile file.
///
/// This is mainly a cross-platform convenience; a plain `touch` on
/// Unix-like systems achieves the same result.
Create {
/// Path to the pile file to create
path: PathBuf,
},
/// Diagnostic helpers for inspecting and repairing piles.
Diagnose {
#[command(subcommand)]
cmd: diagnose::Command,
},
/// Migrate legacy pile metadata to the current schemas.
Migrate {
/// Path to the pile file to modify
pile: PathBuf,
#[command(subcommand)]
cmd: migrate::Command,
},
/// Distributed pile sync over iroh (p2p QUIC connections).
Net {
#[command(subcommand)]
cmd: net::Command,
},
/// Squash all branch histories into single commits in a new pile.
///
/// For each branch, the full accumulated content and metadata are
/// checked out and written as a single commit. Only blobs reachable
/// from the squashed content are copied. The result is a minimal
/// pile with clean commit timestamps and no orphaned data.
Squash {
/// Source pile file
source: PathBuf,
/// Destination pile file (will be created)
dest: PathBuf,
/// Only include these branches (by name or hex ID). If omitted, all branches are included.
#[arg(long)]
include: Vec<String>,
/// Exclude these branches (by name or hex ID).
#[arg(long)]
exclude: Vec<String>,
/// Optional signing key path
#[arg(long)]
signing_key: Option<PathBuf>,
},
}
pub fn run(cmd: PileCommand) -> Result<()> {
match cmd {
PileCommand::Branch { cmd } => branch::run(cmd),
PileCommand::Blob { cmd } => blob::run(cmd),
PileCommand::Merge {
pile,
target,
sources,
signing_key,
} => merge::run(pile, target, sources, signing_key),
PileCommand::Create { path } => {
use triblespace_core::repo::pile::Pile;
if let Some(parent) = path.parent() {
fs::create_dir_all(parent)?;
}
// Pile::open no longer auto-creates files (v0.32.1), so we
// explicitly touch the path first. Fine if the file already
// exists — fs::File::create truncates empty-or-not, and
// piles are append-only so an empty file is the initial
// state.
fs::File::create(&path)?;
let pile: Pile = Pile::open(&path)?;
// Explicit close makes the empty pile durable and avoids Drop warnings.
pile.close().map_err(|e| anyhow::anyhow!("{e:?}"))?;
Ok(())
}
PileCommand::Net { cmd } => net::run(cmd),
PileCommand::Diagnose { cmd } => diagnose::run(cmd),
PileCommand::Migrate { pile, cmd } => migrate::run(pile, cmd),
PileCommand::Squash {
source,
dest,
include,
exclude,
signing_key,
} => squash::run(source, dest, signing_key, include, exclude),
}
}