• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

demosdemon / git-remote-codecommit / 25416910549

06 May 2026 04:43AM UTC coverage: 92.333% (-2.3%) from 94.621%
25416910549

Pull #19

github

web-flow
Merge 06f3cc139 into 5687d957b
Pull Request #19: ci(deps): bump aws-actions/configure-aws-credentials from 6.1.0 to 6.1.1 in the github-actions group

831 of 900 relevant lines covered (92.33%)

578.38 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

88.82
/crates/git-remote-codecommit/src/main.rs
1
#![cfg_attr(
2
    windows_process_exit_code_from,
3
    feature(windows_process_exit_code_from)
4
)]
5
#![cfg_attr(bool_to_result, feature(bool_to_result))]
6

7
mod canonical_request;
8
mod credential_scope;
9
mod datetime;
10
mod hex;
11
mod hostname;
12
mod logging;
13
mod nightly;
14
mod sdk_context;
15
mod string_to_sign;
16
mod uri;
17
mod urlsafe;
18
mod username;
19

20
use std::process::ExitCode;
21
use std::time::SystemTime;
22

23
use anyhow::Context;
24
use clap::Parser;
25
use hmac::KeyInit;
26
use hmac::Mac;
27
use hmac::digest::FixedOutput;
28
use tracing::debug;
29
use tracing::trace;
30

31
use self::canonical_request::CanonicalRequest;
32
use self::credential_scope::CredentialScope;
33
use self::datetime::TimestampExt;
34
use self::hex::IntoU256Hex;
35
use self::hostname::CliHostname;
36
use self::hostname::Hostname;
37
use self::hostname::InferredHostname;
38
use self::sdk_context::SdkContext;
39
use self::string_to_sign::StringToSign;
40
use self::uri::ParsedUri;
41
use self::urlsafe::UrlSafeQuote;
42
use self::username::Username;
43

44
const SERVICE: &str = "codecommit";
45

46
const URL_PATH_PREFIX: &str = "v1/repos";
47

48
#[derive(Debug, Clone, Parser)]
49
#[command(name = "git-remote-codecommit", version, about)]
50
/// A Git remote helper for AWS `CodeCommit`.
51
///
52
/// This is normally invoked by git any time it needs to interact with a remote
53
/// with the `codecommit://` scheme.
54
///
55
/// <https://git-scm.com/docs/gitremote-helpers>
56
///
57
/// Git invokes the helper with one or two arguments; however, this helper
58
/// requires both arguments to be present. See the url above for more details;
59
/// but briefly:
60
///
61
/// - The first argument is the name of the remote. In most cases, this is the
62
///   name of the remote configured in the git repo. However, this can also be
63
///   the URL to the remote if URL was encountered on the command line.
64
///
65
/// - The second argument is the url of the remote. Git will not provide this if
66
///   the remote is configured in the config as `remote.<name>.vcs = codecommit`
67
///   and `remote.<name>.url` is not set. This is not supported.
68
///
69
/// ## URL format
70
///
71
/// This helper accepts the following URLs:
72
///
73
/// - `codecommit://[<profile>@]<repository>`: Use the default AWS region. Use
74
///   the specified profile otherwise use the default.
75
///
76
/// - `codecommit::<region>://[<profile>@]<repository>`: Override the AWS
77
///   region.
78
///
79
///   - Note: Git strips the `codecommit::` prefix when invoking the helper and
80
///     the remote uses the region form.
81
struct Cli {
82
    /// Override the default AWS endpoint for `CodeCommit`.
83
    ///
84
    /// If not provided, the default is
85
    /// `git-codecommit.${region}.${aws-partition}`.
86
    ///
87
    /// Where `${region}` is taken from the environment or profile and
88
    /// `${aws-partition}` is `amazonaws.com` for AWS regions and
89
    /// `amazonaws.com.cn` for AWS China regions.
90
    #[arg(long, env, value_name = "HOSTNAME[:PORT]")]
91
    code_commit_endpoint: Option<CliHostname>,
92

93
    /// The first argument to the git-remote helper.
94
    remote_name: String,
95

96
    /// The second argument to the git-remote helper.
97
    remote_uri: String,
98
}
99

100
fn main() -> anyhow::Result<ExitCode> {
78✔
101
    crate::logging::init_logging();
78✔
102
    trace!("initialized logging");
78✔
103

104
    let Cli {
105
        code_commit_endpoint,
78✔
106
        remote_name,
78✔
107
        remote_uri,
78✔
108
    } = Cli::parse();
78✔
109
    debug!(
78✔
110
        ?code_commit_endpoint,
111
        ?remote_name,
112
        ?remote_uri,
113
        "parsed cli arguments"
114
    );
115

116
    let parsed_uri = ParsedUri::new(&remote_uri).context("failed to parse uri")?;
78✔
117
    debug!(?parsed_uri, "parsed uri");
78✔
118

119
    let sdk_context = SdkContext::load_context_sync(parsed_uri.region(), parsed_uri.profile())?;
78✔
120
    debug!(?sdk_context, "loaded sdk context");
×
121

122
    let url = generate_url(
×
123
        SystemTime::now(),
×
124
        &parsed_uri,
×
125
        code_commit_endpoint.as_ref(),
×
126
        &sdk_context,
×
127
    );
128
    debug!(?url, "generated url");
×
129

130
    let mut command = std::process::Command::new("git");
×
131
    command
×
132
        .arg("remote-https")
×
133
        .arg(&remote_name)
×
134
        .arg(&url)
×
135
        .stdin(std::process::Stdio::inherit())
×
136
        .stdout(std::process::Stdio::inherit())
×
137
        .stderr(std::process::Stdio::inherit());
×
138

139
    exec_replace(command)
×
140
}
78✔
141

142
#[cfg(unix)]
143
fn exec_replace(mut cmd: std::process::Command) -> anyhow::Result<ExitCode> {
144
    use std::os::unix::process::CommandExt;
145
    let err = cmd.exec();
146
    anyhow::bail!("failed to execute git: {err}")
147
}
148

149
#[cfg(windows)]
150
fn exec_replace(mut cmd: std::process::Command) -> anyhow::Result<ExitCode> {
151
    #![expect(unsafe_code)]
152
    use windows_sys::Win32::Foundation::FALSE;
153
    use windows_sys::Win32::Foundation::TRUE;
154
    use windows_sys::Win32::System::Console::SetConsoleCtrlHandler;
155
    use windows_sys::core::BOOL;
156

157
    use crate::nightly::ExitCodeExt;
158

159
    unsafe extern "system" fn ctrlc_handler(_: u32) -> BOOL {
160
        // Do nothing; let the child process handle it.
161
        TRUE
162
    }
163

164
    // windows and other non-unix platforms don't support `execvp`, so we can't
165
    // replace the current process. Instead, we need to spawn a new process and
166
    // set up the pipes.
167

168
    // SAFETY: We setup a ctrlc handler and ignore it because on windows, this
169
    // signal is sent to all processes attached to the console, including the
170
    // parent process. Therefore, by ignoring the ctrl-c, we let the child
171
    // handle the signal and exit. We can reap the process normally.
172
    if unsafe { SetConsoleCtrlHandler(Some(ctrlc_handler), TRUE) } == FALSE {
173
        anyhow::bail!("failed to set ctrl-c handler");
174
    }
175

176
    let exit = cmd
177
        .spawn()
178
        .context("failed to spawn git process")?
179
        .wait()
180
        .context("failed to wait for subprocess")?;
181

182
    #[allow(
183
        clippy::cast_sign_loss,
184
        reason = "originally a u32, cast to i32 for stdlib api, then cast back to u32 for ExitCode"
185
    )]
186
    // https://github.com/rust-lang/rust/blob/2bd7a97871a74d4333bd3edb6564136167ac604b/library/std/src/sys/process/windows.rs#L751-L761
187
    // imp::ExitStatus::code for Windows always returns Some
188
    let code = exit.code().context("windows exit code must return Some")? as u32;
189
    Ok(ExitCode::from_raw(code))
190
}
191

192
#[cfg(not(any(unix, windows)))]
193
fn exec_replace(mut cmd: std::process::Command) -> anyhow::Result<ExitCode> {
194
    let exit = cmd
195
        .spawn()
196
        .context("failed to spawn git process")?
197
        .wait()
198
        .context("failed to wait for subprocess")?;
199

200
    if exit.success() {
201
        Ok(ExitCode::SUCCESS)
202
    } else {
203
        Ok(ExitCode::FAILURE)
204
    }
205
}
206

207
fn generate_url(
156✔
208
    timestamp: SystemTime,
156✔
209
    parsed_uri: &ParsedUri<'_>,
156✔
210
    override_endpoint: Option<&CliHostname>,
156✔
211
    sdk_context: &SdkContext,
156✔
212
) -> String {
156✔
213
    let hostname = override_endpoint.map_or_else(
156✔
214
        || Hostname::Inferred(InferredHostname::new(sdk_context.region().as_ref())),
78✔
215
        |cli| Hostname::Cli(cli.clone()),
78✔
216
    );
217
    debug!(%hostname, "using hostname for codecommit endpoint");
156✔
218

219
    let username = Username {
156✔
220
        access_key_id: sdk_context.credentials().access_key_id(),
156✔
221
        session_token: sdk_context.credentials().session_token(),
156✔
222
    }
156✔
223
    .to_string();
156✔
224
    debug!(?username, "generated username");
156✔
225

226
    let signature = generate_signature(timestamp, &hostname, parsed_uri.repository(), sdk_context);
156✔
227
    debug!(?signature, "generated signature");
156✔
228

229
    format!(
156✔
230
        "https://{username}:{signature}@{hostname}/{URL_PATH_PREFIX}/{repo}",
48✔
231
        username = UrlSafeQuote(&username),
156✔
232
        repo = UrlSafeQuote(parsed_uri.repository()),
156✔
233
    )
234
}
156✔
235

236
fn generate_signature(
156✔
237
    timestamp: SystemTime,
156✔
238
    hostname: &Hostname<'_>,
156✔
239
    repo: &str,
156✔
240
    context: &SdkContext,
156✔
241
) -> String {
156✔
242
    let region = context.region().as_ref();
156✔
243

244
    let string_to_sign = StringToSign {
156✔
245
        timestamp,
156✔
246
        credential_scope: CredentialScope { timestamp, region },
156✔
247
        canonical_request: CanonicalRequest { repo, hostname },
156✔
248
    };
156✔
249

250
    if tracing::enabled!(tracing::Level::DEBUG) {
156✔
251
        let canonical_request = string_to_sign.canonical_request.to_string();
×
252
        debug!(?canonical_request, "canonical request for signature");
×
253
    }
156✔
254

255
    let string_to_sign = string_to_sign.to_string();
156✔
256
    debug!(?string_to_sign, "string to sign");
156✔
257

258
    let signing_key = aws_sigv4::sign::v4::generate_signing_key(
156✔
259
        context.credentials().secret_access_key(),
156✔
260
        timestamp,
156✔
261
        region,
156✔
262
        SERVICE,
156✔
263
    );
264

265
    let signature = hmac::Hmac::<sha2::Sha256>::new_from_slice(signing_key.as_ref())
156✔
266
        .expect("HMAC can take key of any size")
156✔
267
        .chain_update(string_to_sign.as_bytes())
156✔
268
        .finalize_fixed();
156✔
269

270
    format!(
156✔
271
        "{}Z{}",
48✔
272
        timestamp.sigv4_timestamp(),
156✔
273
        signature.into_u256_hex()
156✔
274
    )
275
}
156✔
276

277
#[cfg(test)]
278
mod tests {
279
    use aws_config::BehaviorVersion;
280
    use aws_config::SdkConfig;
281
    use aws_credential_types::Credentials;
282

283
    use super::*;
284

285
    async fn load_test_sdk_config() -> SdkConfig {
78✔
286
        aws_config::ConfigLoader::default()
78✔
287
            .behavior_version(BehaviorVersion::latest())
78✔
288
            .region("us-east-1")
78✔
289
            .credentials_provider(Credentials::for_tests())
78✔
290
            .load()
78✔
291
            .await
78✔
292
    }
78✔
293

294
    async fn load_test_sdk_config_with_session_token() -> SdkConfig {
78✔
295
        aws_config::ConfigLoader::default()
78✔
296
            .behavior_version(BehaviorVersion::latest())
78✔
297
            .region("us-east-1")
78✔
298
            .credentials_provider(Credentials::for_tests_with_session_token())
78✔
299
            .load()
78✔
300
            .await
78✔
301
    }
78✔
302

303
    #[test]
304
    fn test_generate_url() {
39✔
305
        let sdk_context = tokio::runtime::Builder::new_current_thread()
39✔
306
            .enable_all()
39✔
307
            .build()
39✔
308
            .expect("failed to build tokio runtime")
39✔
309
            .block_on(async {
39✔
310
                let config = load_test_sdk_config().await;
39✔
311
                SdkContext::from_sdk_config(config).await
39✔
312
            })
39✔
313
            .expect("failed to load context");
39✔
314

315
        let parsed_url = ParsedUri::new("codecommit://my-repo").expect("valid URI");
39✔
316

317
        let url = generate_url(SystemTime::UNIX_EPOCH, &parsed_url, None, &sdk_context);
39✔
318

319
        assert_eq!(
39✔
320
            url,
321
            "https://ANOTREAL:19700101T000000Zf840ae3ff903ddb92c450d0e3567fe97ef4aa98bd6636905df48c3beee97d21d@git-codecommit.us-east-1.amazonaws.com/v1/repos/my-repo"
322
        );
323
    }
39✔
324

325
    #[test]
326
    fn test_generate_url_with_override() {
39✔
327
        let sdk_context = tokio::runtime::Builder::new_current_thread()
39✔
328
            .enable_all()
39✔
329
            .build()
39✔
330
            .expect("failed to build tokio runtime")
39✔
331
            .block_on(async {
39✔
332
                let config = load_test_sdk_config().await;
39✔
333
                SdkContext::from_sdk_config(config).await
39✔
334
            })
39✔
335
            .expect("failed to load context");
39✔
336

337
        let parsed_url = ParsedUri::new("codecommit://my-repo").expect("valid URI");
39✔
338

339
        let url = generate_url(
39✔
340
            SystemTime::UNIX_EPOCH,
341
            &parsed_url,
39✔
342
            Some(&"localhost:8443".parse().expect("valid cli hostname")),
39✔
343
            &sdk_context,
39✔
344
        );
345

346
        assert_eq!(
39✔
347
            url,
348
            "https://ANOTREAL:19700101T000000Za305b3ce69941e8f0773a2257d9059df41dfc3a4d2563a42948e84ec4825ec06@localhost:8443/v1/repos/my-repo"
349
        );
350
    }
39✔
351

352
    #[test]
353
    fn test_generate_url_with_session_token() {
39✔
354
        let sdk_context = tokio::runtime::Builder::new_current_thread()
39✔
355
            .enable_all()
39✔
356
            .build()
39✔
357
            .expect("failed to build tokio runtime")
39✔
358
            .block_on(async {
39✔
359
                let config = load_test_sdk_config_with_session_token().await;
39✔
360
                SdkContext::from_sdk_config(config).await
39✔
361
            })
39✔
362
            .expect("failed to load context");
39✔
363

364
        let parsed_url = ParsedUri::new("codecommit://my-repo").expect("valid URI");
39✔
365

366
        let url = generate_url(SystemTime::UNIX_EPOCH, &parsed_url, None, &sdk_context);
39✔
367

368
        assert_eq!(
39✔
369
            url,
370
            "https://ANOTREAL%25notarealsessiontoken:19700101T000000Zf840ae3ff903ddb92c450d0e3567fe97ef4aa98bd6636905df48c3beee97d21d@git-codecommit.us-east-1.amazonaws.com/v1/repos/my-repo"
371
        );
372
    }
39✔
373

374
    #[test]
375
    fn test_generate_url_with_session_token_with_override() {
39✔
376
        let sdk_context = tokio::runtime::Builder::new_current_thread()
39✔
377
            .enable_all()
39✔
378
            .build()
39✔
379
            .expect("failed to build tokio runtime")
39✔
380
            .block_on(async {
39✔
381
                let config = load_test_sdk_config_with_session_token().await;
39✔
382
                SdkContext::from_sdk_config(config).await
39✔
383
            })
39✔
384
            .expect("failed to load context");
39✔
385

386
        let parsed_url = ParsedUri::new("codecommit://my-repo").expect("valid URI");
39✔
387

388
        let url = generate_url(
39✔
389
            SystemTime::UNIX_EPOCH,
390
            &parsed_url,
39✔
391
            Some(&"localhost:8443".parse().expect("valid cli hostname")),
39✔
392
            &sdk_context,
39✔
393
        );
394

395
        assert_eq!(
39✔
396
            url,
397
            "https://ANOTREAL%25notarealsessiontoken:19700101T000000Za305b3ce69941e8f0773a2257d9059df41dfc3a4d2563a42948e84ec4825ec06@localhost:8443/v1/repos/my-repo"
398
        );
399
    }
39✔
400
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc