Skip to main content

buntrail_core/contracts/
git.rs

1use super::*;
2#[derive(serde::Serialize, serde::Deserialize, Clone, Debug, PartialEq, Eq)]
3pub struct WorktreeIdentity {
4    pub target: TargetIdentity,
5    pub canonical_path: TargetPath,
6    pub common_git_directory_identity: Vec<u8>,
7}
8#[derive(serde::Serialize, serde::Deserialize, Clone, Debug, PartialEq, Eq)]
9pub enum GitHead {
10    Branch { name: Vec<u8>, oid: String },
11    Detached(String),
12    Unborn(Vec<u8>),
13}
14#[derive(serde::Serialize, serde::Deserialize, Clone, Debug, PartialEq, Eq)]
15pub struct GitExpectedState {
16    pub head: GitHead,
17    pub index_fingerprint: [u8; 32],
18    pub worktree_fingerprint: [u8; 32],
19}
20#[derive(serde::Serialize, serde::Deserialize, Clone, Debug, PartialEq, Eq)]
21pub struct GitEntry {
22    pub path: TargetPath,
23    pub previous_path: Option<TargetPath>,
24    pub status: String,
25}
26#[derive(serde::Serialize, serde::Deserialize, Clone, Debug, PartialEq, Eq)]
27pub struct GitSnapshot {
28    pub worktree: WorktreeIdentity,
29    pub state: GitExpectedState,
30    pub staged: Vec<GitEntry>,
31    pub unstaged: Vec<GitEntry>,
32    pub untracked: Vec<GitEntry>,
33    pub conflicts: Vec<GitEntry>,
34}
35#[derive(serde::Serialize, serde::Deserialize, Clone, Debug, PartialEq, Eq)]
36pub enum DiffSource {
37    Index,
38    Worktree,
39    Commit(String),
40}
41#[derive(serde::Serialize, serde::Deserialize, Clone, Debug, PartialEq, Eq)]
42pub struct DiffRequest {
43    pub worktree: WorktreeIdentity,
44    pub path: TargetPath,
45    pub source: DiffSource,
46}
47#[derive(serde::Serialize, serde::Deserialize, Clone, Debug, PartialEq, Eq)]
48pub enum DiffContent {
49    Text,
50    Binary,
51    Unavailable(String),
52}
53#[derive(serde::Serialize, serde::Deserialize, Clone, Debug, PartialEq, Eq)]
54pub enum DiffChange {
55    Modified,
56    Added,
57    Deleted,
58    Renamed,
59    Conflicted,
60}
61#[derive(serde::Serialize, serde::Deserialize, Clone, Debug, PartialEq, Eq)]
62pub struct DiffSnapshot {
63    pub source: DiffSource,
64    pub content: DiffContent,
65    pub change: DiffChange,
66    pub previous_path: Option<TargetPath>,
67    pub expected: GitExpectedState,
68    pub bytes: Vec<u8>,
69}
70#[derive(serde::Serialize, serde::Deserialize, Clone, Debug, PartialEq, Eq)]
71pub struct HistoryFilter {
72    pub authored_since: Option<i64>,
73    pub text: String,
74}
75#[derive(serde::Serialize, serde::Deserialize, Clone, Debug, PartialEq, Eq)]
76pub struct HistoryQuery {
77    pub worktree: WorktreeIdentity,
78    pub ref_filter: Vec<Vec<u8>>,
79    pub cursor: Option<Vec<u8>>,
80    #[serde(default, skip_serializing_if = "Option::is_none")]
81    pub filter: Option<HistoryFilter>,
82}
83#[derive(serde::Serialize, serde::Deserialize, Clone, Debug, PartialEq, Eq)]
84pub struct GraphCommit {
85    pub lane: u32,
86    pub parent_lanes: Vec<u32>,
87    pub author: String,
88    pub authored_at: String,
89    pub message: String,
90    pub oid: String,
91    pub parents: Vec<String>,
92    pub decorations: Vec<Vec<u8>>,
93    pub subject: String,
94}
95#[derive(serde::Serialize, serde::Deserialize, Clone, Debug, PartialEq, Eq)]
96pub struct GraphPage {
97    pub commits: Vec<GraphCommit>,
98    pub continuation_edges: Vec<(String, String)>,
99    pub next_cursor: Option<Vec<u8>>,
100}
101#[derive(serde::Serialize, serde::Deserialize, Clone, Debug, PartialEq, Eq)]
102pub enum GitMutation {
103    Repository(RepositoryAction),
104    Stage(Vec<TargetPath>),
105    Unstage(Vec<TargetPath>),
106    Commit { message: String },
107    Checkout { reference: Vec<u8> },
108    Fetch { remote: Vec<u8> },
109    Push { remote: Vec<u8>, reference: Vec<u8> },
110    PullFastForward { remote: Vec<u8>, reference: Vec<u8> },
111}
112#[derive(serde::Serialize, serde::Deserialize, Clone, Debug, PartialEq, Eq)]
113pub struct GitMutationRequest {
114    pub operation: OperationID,
115    pub worktree: WorktreeIdentity,
116    pub expected: GitExpectedState,
117    pub command: GitMutation,
118}
119
120#[derive(Clone, Debug, PartialEq, Eq)]
121pub struct GitSaveApproval {
122    pub operation: OperationID,
123    pub request_digest: [u8; 32],
124    pub actor: WorkspaceLease,
125}
126
127#[derive(serde::Serialize, serde::Deserialize, Clone, Debug, PartialEq, Eq)]
128pub enum GitNetworkAction {
129    Fetch,
130    Push,
131    PullFastForward,
132}
133#[derive(serde::Serialize, serde::Deserialize, Clone, Debug, PartialEq, Eq)]
134pub struct GitNetworkIntent {
135    pub action: GitNetworkAction,
136    pub remote: Vec<u8>,
137    pub reference: Vec<u8>,
138    /// Digest of the single endpoint resolved before execution; never persist credential URLs.
139    pub endpoint_fingerprint: Option<[u8; 32]>,
140    /// Only an absent target remote.push mapping qualifies this observer's source-named ref.
141    #[serde(default)]
142    pub push_mapping_unconfigured: bool,
143}
144#[derive(serde::Serialize, serde::Deserialize, Clone, Debug, PartialEq, Eq)]
145pub struct GitNetworkObservation {
146    /// Present only when the selected full ref exists at the observed endpoint.
147    pub remote_oid: Option<String>,
148    pub remote_refs_fingerprint: [u8; 32],
149    pub local_refs_fingerprint: [u8; 32],
150    pub fetch_head_fingerprint: [u8; 32],
151    /// Validated newline-separated OIDs only; FETCH_HEAD descriptions may contain credentials.
152    pub fetch_head_oids: Vec<u8>,
153}
154
155#[derive(Clone, Debug, PartialEq, Eq)]
156pub enum WorktreeChangePhase {
157    Resolving,
158    NeedsSave,
159    Saving,
160    Running,
161    Refreshing,
162}
163#[derive(Clone, Debug)]
164pub struct WorktreeChangeState {
165    pub phase: WorktreeChangePhase,
166    pub documents: Vec<DocumentID>,
167    pub message: String,
168}
169
170/// Fixed read-only queries accepted by native consumers; no caller-supplied Git flags.
171#[derive(serde::Serialize, serde::Deserialize, Clone, Copy, Debug, PartialEq, Eq)]
172pub enum GitRead {
173    Upstream,
174    StatusShort,
175    StatusPorcelain,
176    Head,
177    HeadSymbolic,
178    Inside,
179    Refs,
180    Stashes,
181    Remotes,
182    Diff,
183    StagedDiff,
184    Log,
185    Graph,
186}
187#[derive(serde::Serialize, serde::Deserialize, Clone, Debug, PartialEq, Eq)]
188pub struct GitReadOutput {
189    pub stdout: Vec<u8>,
190    pub stderr: Vec<u8>,
191    pub success: bool,
192}
193
194impl GitRead {
195    pub fn from_args(args: &[&str]) -> Option<GitRead> {
196        Some(match args {
197            ["status", "--short", "--branch"] => GitRead::StatusShort,
198            ["status", "--porcelain=v1", "-z", "--untracked-files=all"] => GitRead::StatusPorcelain,
199            ["rev-parse", "--abbrev-ref", "HEAD"] => GitRead::Head,
200            ["symbolic-ref", "--quiet", "--short", "HEAD"] => GitRead::HeadSymbolic,
201            ["rev-parse", "--is-inside-work-tree"] => GitRead::Inside,
202            [
203                "for-each-ref",
204                "--format=%(refname)%00%(HEAD)%00%(symref)",
205                "refs/heads",
206                "refs/remotes",
207                "refs/tags",
208            ] => GitRead::Refs,
209            ["stash", "list", "--format=%gd: %s"] => GitRead::Stashes,
210            ["remote", "-v"] => GitRead::Remotes,
211            ["diff", "--no-ext-diff", "--"] => GitRead::Diff,
212            ["diff", "--cached", "--no-ext-diff", "--"] => GitRead::StagedDiff,
213            ["log", "-30", "--oneline", "--decorate"] => GitRead::Log,
214            ["log", "--graph", "--oneline", "--decorate", "--all", "-100"] => GitRead::Graph,
215            _ => return None,
216        })
217    }
218}
219
220#[derive(serde::Serialize, serde::Deserialize, Clone, Debug, PartialEq, Eq)]
221pub struct GitRef {
222    pub name: Vec<u8>,
223    pub oid: String,
224    pub peeled_oid: Option<String>,
225    pub current: bool,
226}
227#[derive(serde::Serialize, serde::Deserialize, Clone, Debug, PartialEq, Eq)]
228pub struct GitStash {
229    pub selector: Vec<u8>,
230    pub oid: String,
231    pub subject: String,
232}
233#[derive(serde::Serialize, serde::Deserialize, Clone, Debug, PartialEq, Eq)]
234pub struct GitWorktree {
235    pub path: TargetPath,
236    pub branch: Option<Vec<u8>>,
237}
238#[derive(serde::Serialize, serde::Deserialize, Clone, Debug, PartialEq, Eq)]
239pub struct GitMergeState {
240    pub heads: Vec<String>,
241    pub session: Option<OperationID>,
242    pub message: String,
243    pub conflicts: Vec<TargetPath>,
244    pub error: Option<String>,
245}
246#[derive(serde::Serialize, serde::Deserialize, Clone, Debug, PartialEq, Eq)]
247pub struct GitRepositorySnapshot {
248    pub worktree: WorktreeIdentity,
249    pub state: GitExpectedState,
250    pub refs: Vec<GitRef>,
251    pub changes: Vec<GitEntry>,
252    pub stashes: Vec<GitStash>,
253    pub remotes: Vec<Vec<u8>>,
254    pub worktrees: Vec<GitWorktree>,
255    pub merge: Option<GitMergeState>,
256}
257
258#[derive(serde::Serialize, serde::Deserialize, Clone, Debug, PartialEq, Eq)]
259pub struct CommitQuery {
260    pub worktree: WorktreeIdentity,
261    pub oid: String,
262    pub cursor: Option<u64>,
263}
264#[derive(serde::Serialize, serde::Deserialize, Clone, Debug, PartialEq, Eq)]
265pub struct GitCommitFile {
266    pub path: TargetPath,
267    pub previous_path: Option<TargetPath>,
268    pub change: DiffChange,
269}
270#[derive(serde::Serialize, serde::Deserialize, Clone, Debug, PartialEq, Eq)]
271pub struct CommitPage {
272    pub oid: String,
273    pub parents: Vec<String>,
274    pub author: String,
275    pub email: String,
276    pub authored_at: String,
277    pub message: String,
278    pub files: Vec<GitCommitFile>,
279    pub next_cursor: Option<u64>,
280}
281
282#[derive(serde::Serialize, serde::Deserialize, Clone, Debug, PartialEq, Eq)]
283#[serde(deny_unknown_fields)]
284pub enum RepositoryAction {
285    Merge {
286        source_ref: Vec<u8>,
287        source_oid: String,
288    },
289    ContinueMerge {
290        session: Option<OperationID>,
291        expected_heads: Vec<String>,
292        message: String,
293    },
294    AbortMerge {
295        session: OperationID,
296    },
297    SwitchBranch {
298        name: Vec<u8>,
299        expected_oid: String,
300    },
301    CreateBranch {
302        name: Vec<u8>,
303        source_ref: Vec<u8>,
304        start_oid: String,
305    },
306    RenameBranch {
307        name: Vec<u8>,
308        new_name: Vec<u8>,
309        expected_oid: String,
310    },
311    CreateTag {
312        name: Vec<u8>,
313        source_ref: Vec<u8>,
314        start_oid: String,
315    },
316    DeleteBranch {
317        name: Vec<u8>,
318        expected_oid: String,
319    },
320}
321
322impl GitMutation {
323    pub fn changes_worktree(&self) -> bool {
324        matches!(
325            self,
326            Self::Checkout { .. }
327                | Self::PullFastForward { .. }
328                | Self::Repository(
329                    RepositoryAction::SwitchBranch { .. }
330                        | RepositoryAction::Merge { .. }
331                        | RepositoryAction::ContinueMerge { .. }
332                        | RepositoryAction::AbortMerge { .. }
333                )
334        )
335    }
336}
337
338#[derive(serde::Serialize, serde::Deserialize, Clone, Debug, PartialEq, Eq)]
339pub enum RepositoryOutcome {
340    RefsUpdated,
341    CheckedOut,
342    Merged { oid: String },
343    MergePending { merge: GitMergeState },
344    MergeAborted,
345}