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
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
use io;
use ;
use Instant;
/// Cancellation and an optional absolute deadline for an owned transport.
///
/// Every core transfer takes one. Create it from an [`AtomicBool`] that another thread (a signal
/// handler, a UI's cancel button, a supervisor) can set, and optionally add a deadline. See
/// [Cancellation and deadlines](crate::transport#cancellation-and-deadlines) for what an
/// interruption leaves behind.
///
/// # Examples
///
/// A cancelled fetch stops before installing anything:
///
/// ```
/// use std::collections::BTreeSet;
/// use std::sync::atomic::{AtomicBool, Ordering};
/// use std::time::{Duration, Instant};
///
/// use girt::fetch::{FetchError, FetchLimits, FetchRequest, FetchWorkflowError, KnownHistory};
/// use girt::refs::Reflog;
/// use girt::remote::Remote;
/// use girt::transport::TransportControl;
/// # let (_directory, upstream, repo, _, _) = include!("../doctest/remote.rs");
///
/// let cancel = AtomicBool::new(false);
/// let control = TransportControl::new(&cancel)
/// .with_deadline(Some(Instant::now() + Duration::from_secs(60)));
///
/// // Normally another thread sets the flag while the transfer runs.
/// cancel.store(true, Ordering::Relaxed);
///
/// let origin = Remote::find(repo.config(), b"origin")?.expect("configured");
/// let request = FetchRequest::prepare(
/// repo.clone(),
/// origin.fetch_refspecs().clone(),
/// BTreeSet::new(),
/// Reflog::Preserve,
/// )?;
///
/// let result = request.receive_local(
/// upstream.git_dir(),
/// &KnownHistory::default(),
/// FetchLimits::default(),
/// control,
/// girt::fetch::LocalFetchObserver::default(),
/// );
///
/// assert!(matches!(
/// result,
/// Err(FetchWorkflowError::Transfer(FetchError::Cancelled))
/// ));
/// # Ok::<(), Box<dyn std::error::Error>>(())
/// ```
///
/// Native local adapters check this control between repository, graph, pack and publication steps.
/// The HTTP adapter polls it during async network waits; SSH uses it for async OpenSSH pipe and
/// exit waits. Each adapter documents its own scope and runtime requirements.
///
/// Set `cancel` from another thread and leave it set until the operation returns. The deadline
/// expires at the caller's chosen [`Instant`] and is never reset by traffic. This is not a hard
/// real-time bound: path resolution, caller callbacks, filesystem calls, hashing, decoding and
/// other synchronous computation cannot be forcibly interrupted. Network waits check at most
/// every 20 ms, subject to OS scheduling.
///
/// Cancellation wins when both controls are observed at one check. A completed protocol error or
/// status is not replaced by a later interruption. While waiting for exit, an already observable
/// exit takes precedence. A complete push report still needs EOF and successful server exit;
/// interruption before then retains its acknowledgements in an uncertain error.
///
/// SSH's owned process starts in a new process group on macOS/Linux and is cleaned up by its
/// adapter. Native local push similarly owns receive-hook processes on macOS/Linux; native local
/// fetch starts no process or worker. Hooks are trusted executable code. No path is an execution
/// sandbox.
///
/// Server stderr is drained and discarded, rather than inherited: a blocked diagnostic sink
/// must not stall the operation. Fetch sideband progress and push status messages remain available.
/// Generic protocol stream APIs cannot enforce interruption of caller-owned I/O.
pub
pub