grit/command/
archive.rs

1use std::{env, ffi};
2
3use crate::{
4    error::{Error, Result},
5    git,
6};
7
8#[derive(Clone, Copy)]
9enum RemovalPolicy {
10    /// Do not remove any branches.
11    None,
12    /// Remove local branches only.
13    Local,
14    /// Remove both local and remote branches.
15    Remote,
16}
17
18/// # Errors
19///
20/// Returns an error if `git` cannot be found, or returns bad status.
21async fn archive_imp(branch: &ffi::OsStr, policy: RemovalPolicy) -> Result<()> {
22    let old = [ffi::OsStr::new("refs"), ffi::OsStr::new("heads"), branch]
23        .iter()
24        .collect::<std::path::PathBuf>();
25    let new = [ffi::OsStr::new("refs"), ffi::OsStr::new("archive"), branch]
26        .iter()
27        .collect::<std::path::PathBuf>();
28
29    println!("mk {}", new.display());
30    git::git([
31        ffi::OsStr::new("update-ref"),
32        new.as_os_str(),
33        old.as_os_str(),
34    ])
35    .await?;
36
37    if let RemovalPolicy::None = policy {
38        return Ok(());
39    }
40
41    // Remove the remote branch, if so ordered. This must be done _before_
42    // removing the local branch, so that we can use the local branch to
43    // identify the correct remote.
44    if let RemovalPolicy::Remote = policy
45        && let Some(upstream) = git::upstream(branch).await
46    {
47        let (remote, name) = upstream.split_once('/').unwrap();
48        println!("rm {upstream}");
49        git::git(["push", remote, &format!(":{name}")]).await?;
50    }
51
52    // Remove the local branch, if so ordered. Force the removal, even if the
53    // branch is not fully merged, since we've just saved a ref to it.
54    println!("rm {}", branch.display());
55    git::git([ffi::OsStr::new("branch"), ffi::OsStr::new("-D"), branch]).await?;
56    Ok(())
57}
58
59/// Archives branches by moving them to refs/archive/.
60///
61/// # Errors
62///
63/// Returns an error if no branch name is provided, or git fails.
64pub async fn archive(args: env::ArgsOs) -> Result<()> {
65    let mut policy = RemovalPolicy::None;
66
67    let args = args
68        .filter(|arg| match arg.to_str() {
69            Some("-d") => {
70                policy = RemovalPolicy::Local;
71                false
72            }
73            Some("-D") => {
74                policy = RemovalPolicy::Remote;
75                false
76            }
77            _ => true,
78        })
79        .collect::<Vec<_>>();
80
81    if args.is_empty() {
82        return Err(Error::BranchName);
83    }
84
85    for arg in args {
86        archive_imp(&arg, policy).await?;
87    }
88
89    Ok(())
90}