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
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
//! Gives the build the version that it reports.
//!
//! # Why this file exists
//!
//! `main` holds `version = "0.0.0-dev"` in Cargo.toml, and no pull request
//! changes it. The number of a release lives on the tag, and on the one commit
//! that the tag names.
//!
//! `CARGO_PKG_VERSION` is therefore `0.0.0-dev` in every build that a person
//! makes, and a version that never changes makes two faults:
//!
//! * qex compares the version of the CLI against the version of the
//! coordinator, and it warns when the two differ. That warning exists to
//! catch a person who builds qex and installs it WHILE THEY WORK. With one
//! number for every build it can never fire again.
//! * A fault report from a build that no release holds would name no commit.
//!
//! This file gives each build a name of its own, and it puts the commit in it.
//!
//! # It says which commit it is, and it claims NOTHING else
//!
//! 0.0.0-dev+g98513e2 this commit
//! 0.0.0-dev+g98513e2.dirty this commit, with changes that are not committed
//!
//! An earlier version of this file wrote `0.7.3-alpha-2`. That number CLAIMS to
//! be a preview of the release 0.7.3, and to make the claim this file held a
//! second copy of the rules for the tags and the bump — a copy that can disagree
//! with the copy that makes the release. `0.0.0-dev+g98513e2` claims only which
//! commit it is. That is the one thing that a development build can say
//! honestly, and it is the thing that a fault report needs.
//!
//! The text after `+` is SemVer build metadata, which a comparison of versions
//! ignores. It never reaches Cargo.toml, so cargo has no opinion about it.
//! `capabilities::parse` reads the whole string as `(0, 0, 0)`, which is below
//! the capability floor, and `version::is_development` names it a development
//! build so that qex warns about it and does not refuse it.
//!
//! # Two rules, and the file says which one it is
//!
//! 1. `CARGO_PKG_VERSION` IS NOT `0.0.0-dev` — return it, and never run git.
//! The commit that a tag names holds the real number in its Cargo.toml, so
//! this covers a release build, the crates.io tarball, and any archive of
//! a tag. Each of them states its own version, and no repository above it
//! can say otherwise.
//! 2. IT IS `0.0.0-dev` — a development build, and the commit says which one:
//!
//! 0.0.0-dev+g98513e2 the repository whose root is this package
//! 0.0.0-dev+g98513e2.dirty the same, with changes that nobody committed
//! 0.0.0-dev+unknown anything else
//!
//! NOTHING COMES IN THROUGH THE ENVIRONMENT. An earlier version of this file
//! read `QEX_VERSION`, which the release workflow set from the tag. That was
//! one more way for a build to report a number that its own files do not hold.
//! The number is a fact about the tree, and rule 1 reads it from the tree.
//!
//! # GIT WALKS UP THE DIRECTORY TREE, SO IT ANSWERS FOR A STRANGER
//!
//! `git rev-parse HEAD` answers from the first repository ABOVE the current
//! directory, and it says nothing about which package that repository holds. A
//! package that is unpacked inside somebody else's repository therefore takes
//! the commit of that repository, and the binary then reports a hash that names
//! a commit in a project that its user never saw. A fault report from it sends
//! a reader to a commit that does not exist.
//!
//! This is not a rare shape. Each of these puts the package inside a repository:
//!
//! * `cargo install --git https://github.com/stephenc/qex --tag v0.8.0`. THE
//! CHECKOUT THAT CARGO MAKES IS ITSELF A GIT REPOSITORY, so without a test
//! every such install reports the hash of cargo's own clone.
//! * `cargo publish`, which builds the package again in
//! `target/package/qex-X.Y.Z/`, inside the checkout that it came from.
//! * `cargo install qex` for a user whose `$HOME` is a repository of dotfiles.
//! The registry unpacks under `~/.cargo/registry/src/`, so git finds `$HOME`.
//!
//! TWO RULES CLOSE THIS, and each of them alone leaves a hole:
//!
//! A. Look for git only when `CARGO_PKG_VERSION` is `0.0.0-dev`. Every case
//! above with a real number in Cargo.toml — the crates.io tarball, any
//! build of a commit that a tag names — then never runs git at all.
//! B. When you do look, the repository must be OURS: the root of the
//! repository must be the root of this package. `cargo install --git`
//! unpacks a source tree that says `0.0.0-dev`, so rule A does not cover
//! it and rule B does.
//!
//! Rule A costs a development build nothing, because `main` holds `0.0.0-dev`.
//! Rule B costs it nothing either, because the root of a checkout of qex IS the
//! root of the package. A git WORKTREE also passes: `--show-toplevel` gives the
//! root of the worktree, and our worktrees hold the package at their root.
//!
//! # `0.0.0-dev+unknown`, and why it is not a bare `0.0.0-dev`
//!
//! `+unknown` says what is true: this is a development build, and it could not
//! learn which commit it holds. It is ONE answer for two states — no git at
//! all, and a repository that is not ours — because a reader of a fault report
//! does the same thing for both of them.
//!
//! `capabilities::parse` reads it as `(0, 0, 0)` and `version::is_development`
//! names it a development build, because the rule is three numbers and then
//! `-dev`, and everything after that is build metadata. Such a build is thus
//! warned about, and never refused.
//!
//! # The version it CALCULATES must never stop a build
//!
//! git can be absent and the directory can hold no repository. Every path that
//! looks for a commit gives an answer, and none of them fails.
//!
//! `refuse_below_the_floor` is the one deliberate exception, and it is not one
//! of those paths. It reads the number that a person put in `Cargo.toml`, and
//! it stops a build that would give a binary that qex itself refuses. Read it
//! for the reason. **Do not take that panic away on the strength of this
//! heading**: the rule here is about the commit that this file looks for, and
//! not about a number that somebody wrote by hand.
use Path;
use Command;
/// The number that `main` holds, and that nothing else holds.
const DEVELOPMENT: &str = "0.0.0-dev";
/// The first version that answers the capability handshake.
///
/// A build below this number cannot say what it is unable to obey, so qex can
/// make no promise about it. That is the whole meaning of the number: it is not
/// a statement about which versions somebody wants to support.
///
/// This must agree with `capabilities::CAPABILITY_FLOOR`. A build script cannot
/// read a constant of the crate that it builds, so the number is here as well,
/// and `main` writes it into the build. The test
/// `the_floor_of_the_build_agrees_with_the_floor_of_the_code` then holds the
/// two together, so they cannot go apart in silence.
const CAPABILITY_FLOOR: = ;
/// Stops a build that would give a binary that qex itself refuses.
///
/// `capabilities::check_floor` refuses a coordinator below `CAPABILITY_FLOOR`,
/// because a build below it comes from before the capability handshake and
/// cannot say what it is unable to do. A binary that carries such a number is
/// therefore of no use: its own CLI refuses it, and the person who installed it
/// meets that with no cause.
///
/// `0.0.0-dev` is NOT this case. Its numbers are below the floor, and
/// `version::is_development` names it, so qex warns about such a build and runs
/// it.
///
/// The fault that this prevents is a number that somebody writes into
/// `Cargo.toml` by hand, and a release that a fault in the version rules makes
/// too small. It stops at the build, where a person reads the reason, and not
/// at the first command after an install.
/// The files that change the answer.
///
/// A build script that names any file at all takes the whole responsibility:
/// cargo then stops looking at the rest of the package. The list must therefore
/// hold the source as well, and not the git files only.
///
/// The answer holds two facts: which commit HEAD names, and whether the tree
/// holds changes that nobody committed.
///
/// * `.git/HEAD` changes when a person moves to another branch or commit.
/// * The refs change when a person makes a commit, because HEAD then still
/// names the same branch and that branch names a new commit. `packed-refs`
/// holds the same refs after `git gc`.
/// * The source changes when a person edits it, which is what makes the tree
/// dirty.
///
/// A CHANGE TO A FILE THAT IS NOT IN THIS LIST CAN LEAVE `.dirty` BEHIND. A
/// change to README.md makes the tree dirty, cargo does not run this script
/// again, and the binary keeps the string that it had. That is the correct
/// answer: the PROGRAM did not change, so the binary that reports the earlier
/// string is the same program that reported it. The string names a build, and
/// two builds of one program are one build.
/// The version, and whether git gave it.
/// Says whether the repository at `toplevel` is the one that holds this package.
///
/// CANONICALISE BOTH NAMES. A symbolic link anywhere in the path gives two
/// names for one directory, and a comparison of the text would then refuse a
/// repository that is ours. A name that this code cannot resolve gives `false`,
/// which is the safe direction: an unknown answer is better than a hash from a
/// repository that belongs to somebody else.
/// Runs one git command, and gives nothing when git cannot answer.