hologit 0.50.1 → 0.50.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/Cargo.lock +103 -10
- package/Cargo.toml +1 -1
- package/holo-tree/Cargo.toml +4 -1
- package/holo-tree/src/error.rs +44 -0
- package/holo-tree/src/repo.rs +160 -24
- package/holo-tree/src/tree.rs +159 -40
- package/holo-tree/tests/clear_children.rs +95 -0
- package/holo-tree/tests/helpers/mod.rs +25 -0
- package/holo-tree/tests/repo_ops.rs +130 -0
- package/holo-tree/tests/write_child.rs +281 -0
- package/holo-tree-napi/Cargo.toml +22 -0
- package/holo-tree-napi/README.md +144 -0
- package/holo-tree-napi/build.rs +3 -0
- package/holo-tree-napi/index.d.ts +170 -0
- package/holo-tree-napi/index.js +317 -0
- package/holo-tree-napi/npm/darwin-arm64/README.md +3 -0
- package/holo-tree-napi/npm/darwin-arm64/package.json +24 -0
- package/holo-tree-napi/npm/darwin-x64/README.md +3 -0
- package/holo-tree-napi/npm/darwin-x64/package.json +24 -0
- package/holo-tree-napi/npm/linux-arm64-gnu/README.md +3 -0
- package/holo-tree-napi/npm/linux-arm64-gnu/package.json +27 -0
- package/holo-tree-napi/npm/linux-x64-gnu/README.md +3 -0
- package/holo-tree-napi/npm/linux-x64-gnu/package.json +27 -0
- package/holo-tree-napi/npm/linux-x64-musl/README.md +3 -0
- package/holo-tree-napi/npm/linux-x64-musl/package.json +27 -0
- package/holo-tree-napi/npm/win32-x64-msvc/README.md +3 -0
- package/holo-tree-napi/npm/win32-x64-msvc/package.json +24 -0
- package/holo-tree-napi/package-lock.json +89 -0
- package/holo-tree-napi/package.json +53 -0
- package/holo-tree-napi/src/lib.rs +456 -0
- package/index.d.ts +1 -0
- package/package.json +1 -1
package/Cargo.lock
CHANGED
|
@@ -193,6 +193,15 @@ version = "1.0.5"
|
|
|
193
193
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
194
194
|
checksum = "1d07550c9036bf2ae0c684c4297d503f838287c83c53686d05370d0e139ae570"
|
|
195
195
|
|
|
196
|
+
[[package]]
|
|
197
|
+
name = "convert_case"
|
|
198
|
+
version = "0.6.0"
|
|
199
|
+
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
200
|
+
checksum = "ec182b0ca2f35d8fc196cf3404988fd8b8c739a4d270ff118a398feb0cbec1ca"
|
|
201
|
+
dependencies = [
|
|
202
|
+
"unicode-segmentation",
|
|
203
|
+
]
|
|
204
|
+
|
|
196
205
|
[[package]]
|
|
197
206
|
name = "cpufeatures"
|
|
198
207
|
version = "0.2.17"
|
|
@@ -236,6 +245,16 @@ dependencies = [
|
|
|
236
245
|
"typenum",
|
|
237
246
|
]
|
|
238
247
|
|
|
248
|
+
[[package]]
|
|
249
|
+
name = "ctor"
|
|
250
|
+
version = "0.2.9"
|
|
251
|
+
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
252
|
+
checksum = "32a2785755761f3ddc1492979ce1e48d2c00d09311c39e4466429188f3dd6501"
|
|
253
|
+
dependencies = [
|
|
254
|
+
"quote",
|
|
255
|
+
"syn",
|
|
256
|
+
]
|
|
257
|
+
|
|
239
258
|
[[package]]
|
|
240
259
|
name = "dashmap"
|
|
241
260
|
version = "6.1.0"
|
|
@@ -427,7 +446,6 @@ dependencies = [
|
|
|
427
446
|
"gix-submodule",
|
|
428
447
|
"gix-tempfile",
|
|
429
448
|
"gix-trace",
|
|
430
|
-
"gix-transport",
|
|
431
449
|
"gix-traverse",
|
|
432
450
|
"gix-url",
|
|
433
451
|
"gix-utils",
|
|
@@ -939,9 +957,7 @@ dependencies = [
|
|
|
939
957
|
"gix-hashtable",
|
|
940
958
|
"gix-object",
|
|
941
959
|
"gix-path",
|
|
942
|
-
"gix-tempfile",
|
|
943
960
|
"memmap2",
|
|
944
|
-
"parking_lot",
|
|
945
961
|
"smallvec",
|
|
946
962
|
"thiserror",
|
|
947
963
|
"uluru",
|
|
@@ -1006,18 +1022,11 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
|
1006
1022
|
checksum = "aa4bee82db63ec635996b96efae71cf467c155fa3f34a556184373224a26c4fd"
|
|
1007
1023
|
dependencies = [
|
|
1008
1024
|
"bstr",
|
|
1009
|
-
"gix-credentials",
|
|
1010
1025
|
"gix-date",
|
|
1011
1026
|
"gix-features",
|
|
1012
1027
|
"gix-hash",
|
|
1013
|
-
"gix-lock",
|
|
1014
|
-
"gix-negotiate",
|
|
1015
|
-
"gix-object",
|
|
1016
1028
|
"gix-ref",
|
|
1017
|
-
"gix-refspec",
|
|
1018
|
-
"gix-revwalk",
|
|
1019
1029
|
"gix-shallow",
|
|
1020
|
-
"gix-trace",
|
|
1021
1030
|
"gix-transport",
|
|
1022
1031
|
"gix-utils",
|
|
1023
1032
|
"maybe-async",
|
|
@@ -1401,6 +1410,17 @@ dependencies = [
|
|
|
1401
1410
|
"toml",
|
|
1402
1411
|
]
|
|
1403
1412
|
|
|
1413
|
+
[[package]]
|
|
1414
|
+
name = "holo-tree-napi"
|
|
1415
|
+
version = "0.1.0"
|
|
1416
|
+
dependencies = [
|
|
1417
|
+
"gix",
|
|
1418
|
+
"holo-tree",
|
|
1419
|
+
"napi",
|
|
1420
|
+
"napi-build",
|
|
1421
|
+
"napi-derive",
|
|
1422
|
+
]
|
|
1423
|
+
|
|
1404
1424
|
[[package]]
|
|
1405
1425
|
name = "human_format"
|
|
1406
1426
|
version = "1.2.1"
|
|
@@ -1611,6 +1631,16 @@ version = "0.2.183"
|
|
|
1611
1631
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
1612
1632
|
checksum = "b5b646652bf6661599e1da8901b3b9522896f01e736bad5f723fe7a3a27f899d"
|
|
1613
1633
|
|
|
1634
|
+
[[package]]
|
|
1635
|
+
name = "libloading"
|
|
1636
|
+
version = "0.8.9"
|
|
1637
|
+
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
1638
|
+
checksum = "d7c4b02199fee7c5d21a5ae7d8cfa79a6ef5bb2fc834d6e9058e89c825efdc55"
|
|
1639
|
+
dependencies = [
|
|
1640
|
+
"cfg-if",
|
|
1641
|
+
"windows-link",
|
|
1642
|
+
]
|
|
1643
|
+
|
|
1614
1644
|
[[package]]
|
|
1615
1645
|
name = "libredox"
|
|
1616
1646
|
version = "0.1.15"
|
|
@@ -1676,6 +1706,63 @@ dependencies = [
|
|
|
1676
1706
|
"libc",
|
|
1677
1707
|
]
|
|
1678
1708
|
|
|
1709
|
+
[[package]]
|
|
1710
|
+
name = "napi"
|
|
1711
|
+
version = "2.16.17"
|
|
1712
|
+
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
1713
|
+
checksum = "55740c4ae1d8696773c78fdafd5d0e5fe9bc9f1b071c7ba493ba5c413a9184f3"
|
|
1714
|
+
dependencies = [
|
|
1715
|
+
"bitflags",
|
|
1716
|
+
"ctor",
|
|
1717
|
+
"napi-derive",
|
|
1718
|
+
"napi-sys",
|
|
1719
|
+
"once_cell",
|
|
1720
|
+
]
|
|
1721
|
+
|
|
1722
|
+
[[package]]
|
|
1723
|
+
name = "napi-build"
|
|
1724
|
+
version = "2.3.2"
|
|
1725
|
+
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
1726
|
+
checksum = "c9c366d2c8c60b86fa632df75f745509b52f9128f91a6bad4c796e44abb505e1"
|
|
1727
|
+
|
|
1728
|
+
[[package]]
|
|
1729
|
+
name = "napi-derive"
|
|
1730
|
+
version = "2.16.13"
|
|
1731
|
+
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
1732
|
+
checksum = "7cbe2585d8ac223f7d34f13701434b9d5f4eb9c332cccce8dee57ea18ab8ab0c"
|
|
1733
|
+
dependencies = [
|
|
1734
|
+
"cfg-if",
|
|
1735
|
+
"convert_case",
|
|
1736
|
+
"napi-derive-backend",
|
|
1737
|
+
"proc-macro2",
|
|
1738
|
+
"quote",
|
|
1739
|
+
"syn",
|
|
1740
|
+
]
|
|
1741
|
+
|
|
1742
|
+
[[package]]
|
|
1743
|
+
name = "napi-derive-backend"
|
|
1744
|
+
version = "1.0.75"
|
|
1745
|
+
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
1746
|
+
checksum = "1639aaa9eeb76e91c6ae66da8ce3e89e921cd3885e99ec85f4abacae72fc91bf"
|
|
1747
|
+
dependencies = [
|
|
1748
|
+
"convert_case",
|
|
1749
|
+
"once_cell",
|
|
1750
|
+
"proc-macro2",
|
|
1751
|
+
"quote",
|
|
1752
|
+
"regex",
|
|
1753
|
+
"semver",
|
|
1754
|
+
"syn",
|
|
1755
|
+
]
|
|
1756
|
+
|
|
1757
|
+
[[package]]
|
|
1758
|
+
name = "napi-sys"
|
|
1759
|
+
version = "2.4.0"
|
|
1760
|
+
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
1761
|
+
checksum = "427802e8ec3a734331fec1035594a210ce1ff4dc5bc1950530920ab717964ea3"
|
|
1762
|
+
dependencies = [
|
|
1763
|
+
"libloading",
|
|
1764
|
+
]
|
|
1765
|
+
|
|
1679
1766
|
[[package]]
|
|
1680
1767
|
name = "nonempty"
|
|
1681
1768
|
version = "0.12.0"
|
|
@@ -2165,6 +2252,12 @@ dependencies = [
|
|
|
2165
2252
|
"tinyvec",
|
|
2166
2253
|
]
|
|
2167
2254
|
|
|
2255
|
+
[[package]]
|
|
2256
|
+
name = "unicode-segmentation"
|
|
2257
|
+
version = "1.13.3"
|
|
2258
|
+
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
2259
|
+
checksum = "c6f5d3c3b1bf09027a88a6bc961fc00497d651009560b5463668dc81b0fa87a8"
|
|
2260
|
+
|
|
2168
2261
|
[[package]]
|
|
2169
2262
|
name = "unicode-xid"
|
|
2170
2263
|
version = "0.2.6"
|
package/Cargo.toml
CHANGED
package/holo-tree/Cargo.toml
CHANGED
|
@@ -6,7 +6,10 @@ description = "Mutable in-memory git trees — read, merge, write via gix"
|
|
|
6
6
|
license = "MIT"
|
|
7
7
|
|
|
8
8
|
[dependencies]
|
|
9
|
-
|
|
9
|
+
# Tree/object manipulation only — no networking, so gix defaults suffice
|
|
10
|
+
# (drops the TLS/OpenSSL pull-in of `blocking-network-client`, easing
|
|
11
|
+
# cross-compilation for the holo-tree-napi binding).
|
|
12
|
+
gix = { version = "0.83" }
|
|
10
13
|
globset = "0.4"
|
|
11
14
|
toml = "0.8"
|
|
12
15
|
serde = { version = "1", features = ["derive"] }
|
package/holo-tree/src/error.rs
CHANGED
|
@@ -18,6 +18,24 @@ pub enum Error {
|
|
|
18
18
|
Toml { path: String, message: String },
|
|
19
19
|
}
|
|
20
20
|
|
|
21
|
+
impl Error {
|
|
22
|
+
/// A stable, machine-matchable code for this error variant.
|
|
23
|
+
///
|
|
24
|
+
/// Unlike the human-readable `Display` string (which embeds variable
|
|
25
|
+
/// context), these codes are part of the API contract: downstream
|
|
26
|
+
/// consumers — notably the napi binding and gitsheets — match on them to
|
|
27
|
+
/// map substrate failures onto their own typed errors. Keep them stable.
|
|
28
|
+
pub fn code(&self) -> &'static str {
|
|
29
|
+
match self {
|
|
30
|
+
Error::Git(_) => "GIT",
|
|
31
|
+
Error::NotATree(_) => "NOT_A_TREE",
|
|
32
|
+
Error::PathNotFound { .. } => "PATH_NOT_FOUND",
|
|
33
|
+
Error::Glob(_) => "GLOB",
|
|
34
|
+
Error::Toml { .. } => "TOML",
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
|
|
21
39
|
pub type Result<T> = std::result::Result<T, Error>;
|
|
22
40
|
|
|
23
41
|
// ── gix error conversions ──────────────────────────────────────────────────
|
|
@@ -45,3 +63,29 @@ impl From<gix::reference::find::existing::Error> for Error {
|
|
|
45
63
|
Error::Git(e.to_string())
|
|
46
64
|
}
|
|
47
65
|
}
|
|
66
|
+
|
|
67
|
+
#[cfg(test)]
|
|
68
|
+
mod tests {
|
|
69
|
+
use super::Error;
|
|
70
|
+
|
|
71
|
+
#[test]
|
|
72
|
+
fn codes_are_stable_per_variant() {
|
|
73
|
+
assert_eq!(Error::Git("x".into()).code(), "GIT");
|
|
74
|
+
assert_eq!(Error::NotATree("x".into()).code(), "NOT_A_TREE");
|
|
75
|
+
assert_eq!(
|
|
76
|
+
Error::PathNotFound {
|
|
77
|
+
component: "x".into()
|
|
78
|
+
}
|
|
79
|
+
.code(),
|
|
80
|
+
"PATH_NOT_FOUND"
|
|
81
|
+
);
|
|
82
|
+
assert_eq!(
|
|
83
|
+
Error::Toml {
|
|
84
|
+
path: "p".into(),
|
|
85
|
+
message: "m".into()
|
|
86
|
+
}
|
|
87
|
+
.code(),
|
|
88
|
+
"TOML"
|
|
89
|
+
);
|
|
90
|
+
}
|
|
91
|
+
}
|
package/holo-tree/src/repo.rs
CHANGED
|
@@ -81,33 +81,41 @@ pub fn create_tree_from_path(
|
|
|
81
81
|
|
|
82
82
|
/// Create a git commit pointing to a tree.
|
|
83
83
|
///
|
|
84
|
-
///
|
|
85
|
-
/// git config or `
|
|
86
|
-
///
|
|
84
|
+
/// Identity resolution, per field, is: explicit `author`/`committer` argument →
|
|
85
|
+
/// the repository's configured identity (git config or `GIT_AUTHOR_*`/
|
|
86
|
+
/// `GIT_COMMITTER_*` env) → a "holo-tree" fallback. Passing explicit signatures
|
|
87
|
+
/// (with timestamps) is what lets an embedding consumer reproduce a specific
|
|
88
|
+
/// commit bit-for-bit — e.g. match `git commit-tree` under pinned dates.
|
|
87
89
|
pub fn commit_tree(
|
|
88
90
|
repo: &gix::Repository,
|
|
89
91
|
tree_hash: ObjectId,
|
|
90
92
|
parents: &[ObjectId],
|
|
91
93
|
message: &str,
|
|
94
|
+
author: Option<gix::actor::Signature>,
|
|
95
|
+
committer: Option<gix::actor::Signature>,
|
|
92
96
|
) -> Result<ObjectId> {
|
|
93
97
|
use gix::objs::Commit;
|
|
94
98
|
|
|
95
|
-
let author =
|
|
96
|
-
.
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
99
|
+
let author = author
|
|
100
|
+
.or_else(|| {
|
|
101
|
+
repo.author()
|
|
102
|
+
.and_then(|r| r.ok())
|
|
103
|
+
.map(|s| s.to_owned())
|
|
104
|
+
.transpose()
|
|
105
|
+
.ok()
|
|
106
|
+
.flatten()
|
|
107
|
+
})
|
|
102
108
|
.unwrap_or_else(default_signature);
|
|
103
109
|
|
|
104
|
-
let committer =
|
|
105
|
-
.
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
110
|
+
let committer = committer
|
|
111
|
+
.or_else(|| {
|
|
112
|
+
repo.committer()
|
|
113
|
+
.and_then(|r| r.ok())
|
|
114
|
+
.map(|s| s.to_owned())
|
|
115
|
+
.transpose()
|
|
116
|
+
.ok()
|
|
117
|
+
.flatten()
|
|
118
|
+
})
|
|
111
119
|
.unwrap_or_else(default_signature);
|
|
112
120
|
|
|
113
121
|
let commit = Commit {
|
|
@@ -126,22 +134,131 @@ pub fn commit_tree(
|
|
|
126
134
|
Ok(id.detach())
|
|
127
135
|
}
|
|
128
136
|
|
|
137
|
+
/// Resolve a ref / rev-spec (branch, tag, `HEAD`, hash, …) to its object hash,
|
|
138
|
+
/// peeling annotated tags down to the object they point at (typically a commit).
|
|
139
|
+
///
|
|
140
|
+
/// Returns `Ok(None)` when the ref does not resolve — an unknown name, an
|
|
141
|
+
/// unborn branch, or any spec gix can't parse to a single object. That is the
|
|
142
|
+
/// natural "does this ref exist?" contract a caller wants from a resolver
|
|
143
|
+
/// (gitsheets uses it to discover the current commit before a compare-and-swap
|
|
144
|
+
/// `update_ref`). Genuine ODB failures *after* a spec resolves still surface as
|
|
145
|
+
/// `Err`.
|
|
146
|
+
pub fn resolve_ref(repo: &gix::Repository, git_ref: &str) -> Result<Option<ObjectId>> {
|
|
147
|
+
let spec = match repo.rev_parse_single(git_ref) {
|
|
148
|
+
Ok(s) => s,
|
|
149
|
+
Err(_) => return Ok(None),
|
|
150
|
+
};
|
|
151
|
+
let mut obj = spec.object().map_err(|e| Error::Git(e.to_string()))?;
|
|
152
|
+
|
|
153
|
+
// Peel annotated tags to their target object.
|
|
154
|
+
while obj.kind == gix::object::Kind::Tag {
|
|
155
|
+
let tag = obj
|
|
156
|
+
.try_into_tag()
|
|
157
|
+
.map_err(|_| Error::Git("failed to parse tag".into()))?;
|
|
158
|
+
let target = tag
|
|
159
|
+
.target_id()
|
|
160
|
+
.map_err(|e| Error::Git(e.to_string()))?
|
|
161
|
+
.detach();
|
|
162
|
+
obj = repo.find_object(target)?;
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
Ok(Some(obj.id))
|
|
166
|
+
}
|
|
167
|
+
|
|
129
168
|
/// Update a git ref to point at a new object.
|
|
169
|
+
///
|
|
170
|
+
/// Accepts the same leniency as `git update-ref`: a bare branch name (e.g.
|
|
171
|
+
/// `main`) is qualified to `refs/heads/main`. Already-qualified names (anything
|
|
172
|
+
/// containing `/`, like `refs/heads/x` or `refs/tags/x`) and all-caps pseudo-refs
|
|
173
|
+
/// (e.g. `HEAD`) pass through unchanged. Without this, gix's `reference()`
|
|
174
|
+
/// rejects a standalone lowercase name ("Standalone references must be all
|
|
175
|
+
/// uppercased").
|
|
176
|
+
///
|
|
177
|
+
/// When `expected_old` is `Some`, this is a **compare-and-swap**: the update
|
|
178
|
+
/// only succeeds if the ref currently resolves to exactly that object
|
|
179
|
+
/// (`PreviousValue::MustExistAndMatch`), so a concurrent writer that moved the
|
|
180
|
+
/// ref out from under the caller makes the swap fail loudly rather than clobber
|
|
181
|
+
/// their commit. When `None`, the ref is set unconditionally
|
|
182
|
+
/// (`PreviousValue::Any`), matching the original force behavior.
|
|
183
|
+
///
|
|
184
|
+
/// The reflog identity is derived from the **committer of the commit the ref now
|
|
185
|
+
/// points at**, falling back to a stable `holo-tree` default for non-commit
|
|
186
|
+
/// targets or unreadable commits. It never reads ambient git config
|
|
187
|
+
/// (`user.name` / `user.email`). gix's convenience `reference()` does reach for
|
|
188
|
+
/// ambient config to stamp the reflog, and fails with "The reflog could not be
|
|
189
|
+
/// created or updated" when none is set — so an embedding consumer that supplies
|
|
190
|
+
/// a fully-specified commit (matching [`commit_tree`]'s explicit-identity
|
|
191
|
+
/// contract) could still see the *ref update* fail on an unconfigured runner.
|
|
192
|
+
/// Sourcing the identity from the commit itself keeps the whole operation
|
|
193
|
+
/// independent of machine config.
|
|
130
194
|
pub fn update_ref(
|
|
131
195
|
repo: &gix::Repository,
|
|
132
196
|
refname: &str,
|
|
133
197
|
target: ObjectId,
|
|
198
|
+
expected_old: Option<ObjectId>,
|
|
134
199
|
) -> Result<()> {
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
200
|
+
use gix::refs::transaction::{Change, LogChange, RefEdit, RefLog};
|
|
201
|
+
|
|
202
|
+
let qualified = qualify_ref(refname);
|
|
203
|
+
let previous = match expected_old {
|
|
204
|
+
Some(old) => gix::refs::transaction::PreviousValue::MustExistAndMatch(
|
|
205
|
+
gix::refs::Target::Object(old),
|
|
206
|
+
),
|
|
207
|
+
None => gix::refs::transaction::PreviousValue::Any,
|
|
208
|
+
};
|
|
209
|
+
|
|
210
|
+
let name: gix::refs::FullName = qualified
|
|
211
|
+
.as_ref()
|
|
212
|
+
.try_into()
|
|
213
|
+
.map_err(|e: gix::refs::name::Error| Error::Git(e.to_string()))?;
|
|
214
|
+
|
|
215
|
+
// Reflog identity: the target commit's committer, else a stable default.
|
|
216
|
+
// Read the commit up front so its data outlives the borrowed `SignatureRef`
|
|
217
|
+
// we hand to the transaction below.
|
|
218
|
+
let commit = repo
|
|
219
|
+
.find_object(target)
|
|
220
|
+
.ok()
|
|
221
|
+
.and_then(|obj| obj.try_into_commit().ok());
|
|
222
|
+
let fallback = default_signature();
|
|
223
|
+
let mut time_buf = gix::date::parse::TimeBuf::default();
|
|
224
|
+
let committer = commit
|
|
225
|
+
.as_ref()
|
|
226
|
+
.and_then(|c| c.committer().ok())
|
|
227
|
+
.unwrap_or_else(|| fallback.to_ref(&mut time_buf));
|
|
228
|
+
|
|
229
|
+
let edit = RefEdit {
|
|
230
|
+
change: Change::Update {
|
|
231
|
+
log: LogChange {
|
|
232
|
+
mode: RefLog::AndReference,
|
|
233
|
+
force_create_reflog: false,
|
|
234
|
+
message: "holo-tree".into(),
|
|
235
|
+
},
|
|
236
|
+
expected: previous,
|
|
237
|
+
new: gix::refs::Target::Object(target),
|
|
238
|
+
},
|
|
239
|
+
name,
|
|
240
|
+
deref: false,
|
|
241
|
+
};
|
|
242
|
+
|
|
243
|
+
repo.edit_references_as(Some(edit), Some(committer))
|
|
244
|
+
.map_err(|e| Error::Git(e.to_string()))?;
|
|
142
245
|
Ok(())
|
|
143
246
|
}
|
|
144
247
|
|
|
248
|
+
/// Map a bare branch name to a fully-qualified ref, matching `git update-ref`'s
|
|
249
|
+
/// leniency. See [`update_ref`].
|
|
250
|
+
fn qualify_ref(refname: &str) -> std::borrow::Cow<'_, str> {
|
|
251
|
+
let is_pseudo_ref = !refname.is_empty()
|
|
252
|
+
&& refname
|
|
253
|
+
.bytes()
|
|
254
|
+
.all(|b| b.is_ascii_uppercase() || b == b'_');
|
|
255
|
+
if refname.contains('/') || is_pseudo_ref {
|
|
256
|
+
std::borrow::Cow::Borrowed(refname)
|
|
257
|
+
} else {
|
|
258
|
+
std::borrow::Cow::Owned(format!("refs/heads/{refname}"))
|
|
259
|
+
}
|
|
260
|
+
}
|
|
261
|
+
|
|
145
262
|
/// Fallback signature when git config has no author/committer.
|
|
146
263
|
fn default_signature() -> gix::actor::Signature {
|
|
147
264
|
gix::actor::SignatureRef {
|
|
@@ -158,3 +275,22 @@ fn default_signature() -> gix::actor::Signature {
|
|
|
158
275
|
.to_owned()
|
|
159
276
|
.expect("valid fallback signature")
|
|
160
277
|
}
|
|
278
|
+
|
|
279
|
+
#[cfg(test)]
|
|
280
|
+
mod tests {
|
|
281
|
+
use super::qualify_ref;
|
|
282
|
+
|
|
283
|
+
#[test]
|
|
284
|
+
fn qualifies_bare_branch_names() {
|
|
285
|
+
assert_eq!(qualify_ref("main"), "refs/heads/main");
|
|
286
|
+
assert_eq!(qualify_ref("feature-x"), "refs/heads/feature-x");
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
#[test]
|
|
290
|
+
fn passes_through_qualified_and_pseudo_refs() {
|
|
291
|
+
assert_eq!(qualify_ref("refs/heads/main"), "refs/heads/main");
|
|
292
|
+
assert_eq!(qualify_ref("refs/tags/v1"), "refs/tags/v1");
|
|
293
|
+
assert_eq!(qualify_ref("HEAD"), "HEAD");
|
|
294
|
+
assert_eq!(qualify_ref("FETCH_HEAD"), "FETCH_HEAD");
|
|
295
|
+
}
|
|
296
|
+
}
|