Skip to main content

uv_configuration/
vcs.rs

1use std::io::Write;
2use std::path::{Path, PathBuf};
3use std::process::Stdio;
4
5use serde::Deserialize;
6use uv_git::GIT;
7
8#[derive(Debug, thiserror::Error)]
9pub enum VersionControlError {
10    #[error("Attempted to initialize a Git repository, but `git` was not found in PATH")]
11    GitNotInstalled,
12    #[error("Failed to initialize Git repository at `{0}`\nstdout: {1}\nstderr: {2}")]
13    GitInit(PathBuf, String, String),
14    #[error("`git` command failed")]
15    GitCommand(#[source] std::io::Error),
16    #[error(transparent)]
17    Io(#[from] std::io::Error),
18}
19
20/// The version control system to use.
21#[derive(Clone, Copy, Debug, PartialEq, Default, Deserialize)]
22#[serde(deny_unknown_fields, rename_all = "kebab-case")]
23#[cfg_attr(feature = "clap", derive(clap::ValueEnum))]
24#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
25pub enum VersionControlSystem {
26    /// Use Git for version control.
27    #[default]
28    Git,
29    /// Do not use any version control system.
30    None,
31}
32
33impl VersionControlSystem {
34    /// Initializes the VCS system based on the provided path.
35    pub fn init(&self, path: &Path) -> Result<(), VersionControlError> {
36        match self {
37            Self::Git => {
38                let Ok(git) = GIT.as_ref() else {
39                    return Err(VersionControlError::GitNotInstalled);
40                };
41
42                let output = git
43                    .build_command()
44                    .arg("init")
45                    .current_dir(path)
46                    .stdout(Stdio::piped())
47                    .stderr(Stdio::piped())
48                    .output()
49                    .map_err(VersionControlError::GitCommand)?;
50                if !output.status.success() {
51                    let stdout = String::from_utf8_lossy(&output.stdout);
52                    let stderr = String::from_utf8_lossy(&output.stderr);
53                    return Err(VersionControlError::GitInit(
54                        path.to_path_buf(),
55                        stdout.to_string(),
56                        stderr.to_string(),
57                    ));
58                }
59
60                // Create the `.gitignore`, if it doesn't exist.
61                match fs_err::OpenOptions::new()
62                    .write(true)
63                    .create_new(true)
64                    .open(path.join(".gitignore"))
65                {
66                    Ok(mut file) => file.write_all(GITIGNORE.as_bytes())?,
67                    Err(err) if err.kind() == std::io::ErrorKind::AlreadyExists => (),
68                    Err(err) => return Err(err.into()),
69                }
70
71                Ok(())
72            }
73            Self::None => Ok(()),
74        }
75    }
76}
77
78impl std::fmt::Display for VersionControlSystem {
79    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
80        match self {
81            Self::Git => write!(f, "git"),
82            Self::None => write!(f, "none"),
83        }
84    }
85}
86
87const GITIGNORE: &str = "# Python-generated files
88__pycache__/
89*.py[oc]
90build/
91dist/
92wheels/
93*.egg-info
94
95# Virtual environments
96.venv
97";
98
99/// Setting for Git LFS (Large File Storage) support.
100#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
101pub enum GitLfsSetting {
102    /// Git LFS is disabled (default).
103    #[default]
104    Disabled,
105    /// Git LFS is enabled. Tracks whether it came from an environment variable.
106    Enabled { from_env: bool },
107}
108
109impl GitLfsSetting {
110    pub fn new(from_arg: Option<bool>, from_env: Option<bool>) -> Self {
111        match (from_arg, from_env) {
112            (Some(true), _) => Self::Enabled { from_env: false },
113            (_, Some(true)) => Self::Enabled { from_env: true },
114            _ => Self::Disabled,
115        }
116    }
117}
118
119impl From<GitLfsSetting> for Option<bool> {
120    fn from(setting: GitLfsSetting) -> Self {
121        match setting {
122            GitLfsSetting::Enabled { .. } => Some(true),
123            GitLfsSetting::Disabled => None,
124        }
125    }
126}