Resolve the CLI target (bare hash, hash prefix, or `urn:atomic:change: `) to a change `Hash`.
(repo: &Repository, target: &str)
| 171 | /// Resolve the CLI target (bare hash, hash prefix, or `urn:atomic:change:<b32>`) |
| 172 | /// to a change `Hash`. |
| 173 | fn resolve_change_target(repo: &Repository, target: &str) -> CliResult<Hash> { |
| 174 | const URN_PREFIX: &str = "urn:atomic:change:"; |
| 175 | if let Some(b32) = target.strip_prefix(URN_PREFIX) { |
| 176 | return Hash::from_base32(b32.as_bytes()).ok_or_else(|| CliError::InvalidArgument { |
| 177 | message: format!("invalid change base32 in URN: {target}"), |
| 178 | }); |
| 179 | } |
| 180 | |
| 181 | // Bare full hash or prefix: match against the change store by base32 prefix |
| 182 | // (mirrors ChangeCmd::resolve_hash_prefix). |
| 183 | let mut matches: Vec<Hash> = Vec::new(); |
| 184 | for result in repo.iter_changes() { |
| 185 | let hash = result.map_err(|e| CliError::Internal(anyhow::anyhow!("{}", e)))?; |
| 186 | if hash.to_base32().starts_with(target) { |
| 187 | matches.push(hash); |
| 188 | } |
| 189 | } |
| 190 | match matches.len() { |
| 191 | 0 => { |
| 192 | // No recorded change matched. A graph can explain a change that was |
| 193 | // never recorded/made internal (the REV_DEPS fallback case), so try |
| 194 | // parsing the target as a full base32 change hash directly. |
| 195 | Hash::from_base32(target.as_bytes()).ok_or_else(|| CliError::ChangeNotFound { |
| 196 | hash: target.to_string(), |
| 197 | }) |
| 198 | } |
| 199 | 1 => Ok(matches[0]), |
| 200 | _ => { |
| 201 | let list: Vec<String> = matches.iter().map(|h| h.to_base32()).collect(); |
| 202 | Err(CliError::AmbiguousHash { |
| 203 | hash: format!("{} (matches: {})", target, list.join(", ")), |
| 204 | }) |
| 205 | } |
| 206 | } |
| 207 | } |
| 208 | |
| 209 | /// Resolve the PERSON's signing identity + keypair, exactly as |
| 210 | /// `atomic intent attest` does. |