cargo_fixit/util/vcs.rs
1use std::env;
2
3use anyhow::bail;
4use clap::Parser;
5
6use crate::CargoResult;
7
8#[derive(Parser, Debug)]
9pub struct VcsOpts {
10 /// Fix code even if a VCS was not detected
11 #[arg(long)]
12 pub allow_no_vcs: bool,
13
14 /// Fix code even if the working directory is dirty or has staged changes
15 #[arg(long)]
16 pub allow_dirty: bool,
17
18 /// Fix code even if the working directory has staged changes
19 #[arg(long)]
20 pub allow_staged: bool,
21}
22
23impl VcsOpts {
24 pub fn valid_vcs(&self) -> CargoResult<()> {
25 if self.allow_no_vcs {
26 return Ok(());
27 }
28 let cwd = env::current_dir()?;
29
30 let repo = git2::Repository::discover(&cwd).ok().filter(|r| {
31 if r.workdir().is_some_and(|workdir| workdir == cwd) {
32 true
33 } else {
34 !r.is_path_ignored(cwd).unwrap_or(false)
35 }
36 });
37
38 let Some(repo) = repo else {
39 bail!(
40 "no VCS found for this package and `cargo fix` can potentially \
41 perform destructive changes; if you'd like to suppress this \
42 error pass `--allow-no-vcs`"
43 );
44 };
45
46 if self.allow_staged && self.allow_dirty {
47 return Ok(());
48 }
49 let mut dirty_files = Vec::new();
50 let mut staged_files = Vec::new();
51
52 let mut repo_opts = git2::StatusOptions::new();
53 repo_opts.include_ignored(false);
54 repo_opts.include_untracked(true);
55 for status in repo.statuses(Some(&mut repo_opts))?.iter() {
56 if let Some(path) = status.path() {
57 match status.status() {
58 git2::Status::CURRENT => (),
59 git2::Status::INDEX_NEW
60 | git2::Status::INDEX_MODIFIED
61 | git2::Status::INDEX_DELETED
62 | git2::Status::INDEX_RENAMED
63 | git2::Status::INDEX_TYPECHANGE => {
64 if !self.allow_staged {
65 staged_files.push(path.to_owned());
66 }
67 }
68 _ => {
69 if !self.allow_dirty {
70 dirty_files.push(path.to_owned());
71 }
72 }
73 };
74 }
75 }
76
77 if dirty_files.is_empty() && staged_files.is_empty() {
78 return Ok(());
79 }
80
81 let mut files_list = String::new();
82 for file in dirty_files {
83 files_list.push_str(" * ");
84 files_list.push_str(&file);
85 files_list.push_str(" (dirty)\n");
86 }
87 for file in staged_files {
88 files_list.push_str(" * ");
89 files_list.push_str(&file);
90 files_list.push_str(" (staged)\n");
91 }
92
93 bail!(
94 "the working directory of this package has uncommitted changes, and \
95 `cargo fix` can potentially perform destructive changes; if you'd \
96 like to suppress this error pass `--allow-dirty`, \
97 or commit the changes to these files:\n\
98 \n\
99 {}\n\
100 ",
101 files_list
102 );
103 }
104}