Skip to content

Commit 2f5788a

Browse files
committed
refactor(package): flatten nested functions in vcs check
1 parent 3907e2d commit 2f5788a

File tree

2 files changed

+129
-137
lines changed

2 files changed

+129
-137
lines changed

src/cargo/ops/cargo_package/mod.rs

Lines changed: 3 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -34,8 +34,6 @@ use tracing::debug;
3434
use unicase::Ascii as UncasedAscii;
3535

3636
mod vcs;
37-
use self::vcs::check_repo_state;
38-
use self::vcs::VcsInfo;
3937

4038
#[derive(Clone)]
4139
pub struct PackageOpts<'gctx> {
@@ -78,7 +76,7 @@ enum GeneratedFile {
7876
/// Generates `Cargo.lock` in some cases (like if there is a binary).
7977
Lockfile,
8078
/// Adds a `.cargo_vcs_info.json` file if in a (clean) git repo.
81-
VcsInfo(VcsInfo),
79+
VcsInfo(vcs::VcsInfo),
8280
}
8381

8482
// Builds a tarball and places it in the output directory.
@@ -384,7 +382,7 @@ fn prepare_archive(
384382
let src_files = src.list_files(pkg)?;
385383

386384
// Check (git) repository state, getting the current commit hash.
387-
let vcs_info = check_repo_state(pkg, &src_files, gctx, &opts)?;
385+
let vcs_info = vcs::check_repo_state(pkg, &src_files, gctx, &opts)?;
388386

389387
build_ar_list(ws, pkg, src_files, vcs_info)
390388
}
@@ -395,7 +393,7 @@ fn build_ar_list(
395393
ws: &Workspace<'_>,
396394
pkg: &Package,
397395
src_files: Vec<PathBuf>,
398-
vcs_info: Option<VcsInfo>,
396+
vcs_info: Option<vcs::VcsInfo>,
399397
) -> CargoResult<Vec<ArchiveFile>> {
400398
let mut result = HashMap::new();
401399
let root = pkg.root();

src/cargo/ops/cargo_package/vcs.rs

Lines changed: 126 additions & 132 deletions
Original file line numberDiff line numberDiff line change
@@ -102,147 +102,141 @@ pub fn check_repo_state(
102102
};
103103

104104
return Ok(Some(VcsInfo { git, path_in_vcs }));
105+
}
105106

106-
fn git(
107-
pkg: &Package,
108-
gctx: &GlobalContext,
109-
src_files: &[PathBuf],
110-
repo: &git2::Repository,
111-
opts: &PackageOpts<'_>,
112-
) -> CargoResult<Option<GitVcsInfo>> {
113-
// This is a collection of any dirty or untracked files. This covers:
114-
// - new/modified/deleted/renamed/type change (index or worktree)
115-
// - untracked files (which are "new" worktree files)
116-
// - ignored (in case the user has an `include` directive that
117-
// conflicts with .gitignore).
118-
let mut dirty_files = Vec::new();
119-
collect_statuses(repo, &mut dirty_files)?;
120-
// Include each submodule so that the error message can provide
121-
// specifically *which* files in a submodule are modified.
122-
status_submodules(repo, &mut dirty_files)?;
123-
124-
// Find the intersection of dirty in git, and the src_files that would
125-
// be packaged. This is a lazy n^2 check, but seems fine with
126-
// thousands of files.
127-
let cwd = gctx.cwd();
128-
let mut dirty_src_files: Vec<_> = src_files
129-
.iter()
130-
.filter(|src_file| dirty_files.iter().any(|path| src_file.starts_with(path)))
131-
.chain(dirty_metadata_paths(pkg, repo)?.iter())
132-
.map(|path| {
133-
pathdiff::diff_paths(path, cwd)
134-
.as_ref()
135-
.unwrap_or(path)
136-
.display()
137-
.to_string()
138-
})
139-
.collect();
140-
let dirty = !dirty_src_files.is_empty();
141-
if !dirty || opts.allow_dirty {
142-
// Must check whetherthe repo has no commit firstly, otherwise `revparse_single` would fail on bare commit repo.
143-
// Due to lacking the `sha1` field, it's better not record the `GitVcsInfo` for consistency.
144-
if repo.is_empty()? {
145-
return Ok(None);
146-
}
147-
let rev_obj = repo.revparse_single("HEAD")?;
148-
Ok(Some(GitVcsInfo {
149-
sha1: rev_obj.id().to_string(),
150-
dirty,
151-
}))
152-
} else {
153-
dirty_src_files.sort_unstable();
154-
anyhow::bail!(
155-
"{} files in the working directory contain changes that were \
156-
not yet committed into git:\n\n{}\n\n\
157-
to proceed despite this and include the uncommitted changes, pass the `--allow-dirty` flag",
158-
dirty_src_files.len(),
159-
dirty_src_files.join("\n")
160-
)
107+
fn git(
108+
pkg: &Package,
109+
gctx: &GlobalContext,
110+
src_files: &[PathBuf],
111+
repo: &git2::Repository,
112+
opts: &PackageOpts<'_>,
113+
) -> CargoResult<Option<GitVcsInfo>> {
114+
// This is a collection of any dirty or untracked files. This covers:
115+
// - new/modified/deleted/renamed/type change (index or worktree)
116+
// - untracked files (which are "new" worktree files)
117+
// - ignored (in case the user has an `include` directive that
118+
// conflicts with .gitignore).
119+
let mut dirty_files = Vec::new();
120+
collect_statuses(repo, &mut dirty_files)?;
121+
// Include each submodule so that the error message can provide
122+
// specifically *which* files in a submodule are modified.
123+
status_submodules(repo, &mut dirty_files)?;
124+
125+
// Find the intersection of dirty in git, and the src_files that would
126+
// be packaged. This is a lazy n^2 check, but seems fine with
127+
// thousands of files.
128+
let cwd = gctx.cwd();
129+
let mut dirty_src_files: Vec<_> = src_files
130+
.iter()
131+
.filter(|src_file| dirty_files.iter().any(|path| src_file.starts_with(path)))
132+
.chain(dirty_metadata_paths(pkg, repo)?.iter())
133+
.map(|path| {
134+
pathdiff::diff_paths(path, cwd)
135+
.as_ref()
136+
.unwrap_or(path)
137+
.display()
138+
.to_string()
139+
})
140+
.collect();
141+
let dirty = !dirty_src_files.is_empty();
142+
if !dirty || opts.allow_dirty {
143+
// Must check whetherthe repo has no commit firstly, otherwise `revparse_single` would fail on bare commit repo.
144+
// Due to lacking the `sha1` field, it's better not record the `GitVcsInfo` for consistency.
145+
if repo.is_empty()? {
146+
return Ok(None);
161147
}
148+
let rev_obj = repo.revparse_single("HEAD")?;
149+
Ok(Some(GitVcsInfo {
150+
sha1: rev_obj.id().to_string(),
151+
dirty,
152+
}))
153+
} else {
154+
dirty_src_files.sort_unstable();
155+
anyhow::bail!(
156+
"{} files in the working directory contain changes that were \
157+
not yet committed into git:\n\n{}\n\n\
158+
to proceed despite this and include the uncommitted changes, pass the `--allow-dirty` flag",
159+
dirty_src_files.len(),
160+
dirty_src_files.join("\n")
161+
)
162162
}
163+
}
163164

164-
/// Checks whether files at paths specified in `package.readme` and
165-
/// `package.license-file` have been modified.
166-
///
167-
/// This is required because those paths may link to a file outside the
168-
/// current package root, but still under the git workdir, affecting the
169-
/// final packaged `.crate` file.
170-
fn dirty_metadata_paths(pkg: &Package, repo: &git2::Repository) -> CargoResult<Vec<PathBuf>> {
171-
let mut dirty_files = Vec::new();
172-
let workdir = repo.workdir().unwrap();
173-
let root = pkg.root();
174-
let meta = pkg.manifest().metadata();
175-
for path in [&meta.license_file, &meta.readme] {
176-
let Some(path) = path.as_deref().map(Path::new) else {
177-
continue;
178-
};
179-
let abs_path = paths::normalize_path(&root.join(path));
180-
if paths::strip_prefix_canonical(abs_path.as_path(), root).is_ok() {
181-
// Inside package root. Don't bother checking git status.
182-
continue;
183-
}
184-
if let Ok(rel_path) = paths::strip_prefix_canonical(abs_path.as_path(), workdir) {
185-
// Outside package root but under git workdir,
186-
if repo.status_file(&rel_path)? != git2::Status::CURRENT {
187-
dirty_files.push(if abs_path.is_symlink() {
188-
// For symlinks, shows paths to symlink sources
189-
workdir.join(rel_path)
190-
} else {
191-
abs_path
192-
});
193-
}
165+
/// Checks whether files at paths specified in `package.readme` and
166+
/// `package.license-file` have been modified.
167+
///
168+
/// This is required because those paths may link to a file outside the
169+
/// current package root, but still under the git workdir, affecting the
170+
/// final packaged `.crate` file.
171+
fn dirty_metadata_paths(pkg: &Package, repo: &git2::Repository) -> CargoResult<Vec<PathBuf>> {
172+
let mut dirty_files = Vec::new();
173+
let workdir = repo.workdir().unwrap();
174+
let root = pkg.root();
175+
let meta = pkg.manifest().metadata();
176+
for path in [&meta.license_file, &meta.readme] {
177+
let Some(path) = path.as_deref().map(Path::new) else {
178+
continue;
179+
};
180+
let abs_path = paths::normalize_path(&root.join(path));
181+
if paths::strip_prefix_canonical(abs_path.as_path(), root).is_ok() {
182+
// Inside package root. Don't bother checking git status.
183+
continue;
184+
}
185+
if let Ok(rel_path) = paths::strip_prefix_canonical(abs_path.as_path(), workdir) {
186+
// Outside package root but under git workdir,
187+
if repo.status_file(&rel_path)? != git2::Status::CURRENT {
188+
dirty_files.push(if abs_path.is_symlink() {
189+
// For symlinks, shows paths to symlink sources
190+
workdir.join(rel_path)
191+
} else {
192+
abs_path
193+
});
194194
}
195195
}
196-
Ok(dirty_files)
197196
}
197+
Ok(dirty_files)
198+
}
198199

199-
// Helper to collect dirty statuses for a single repo.
200-
fn collect_statuses(
201-
repo: &git2::Repository,
202-
dirty_files: &mut Vec<PathBuf>,
203-
) -> CargoResult<()> {
204-
let mut status_opts = git2::StatusOptions::new();
205-
// Exclude submodules, as they are being handled manually by recursing
206-
// into each one so that details about specific files can be
207-
// retrieved.
208-
status_opts
209-
.exclude_submodules(true)
210-
.include_ignored(true)
211-
.include_untracked(true);
212-
let repo_statuses = repo.statuses(Some(&mut status_opts)).with_context(|| {
213-
format!(
214-
"failed to retrieve git status from repo {}",
215-
repo.path().display()
216-
)
217-
})?;
218-
let workdir = repo.workdir().unwrap();
219-
let this_dirty = repo_statuses.iter().filter_map(|entry| {
220-
let path = entry.path().expect("valid utf-8 path");
221-
if path.ends_with("Cargo.lock") && entry.status() == git2::Status::IGNORED {
222-
// It is OK to include Cargo.lock even if it is ignored.
223-
return None;
224-
}
225-
// Use an absolute path, so that comparing paths is easier
226-
// (particularly with submodules).
227-
Some(workdir.join(path))
228-
});
229-
dirty_files.extend(this_dirty);
230-
Ok(())
231-
}
200+
/// Helper to collect dirty statuses for a single repo.
201+
fn collect_statuses(repo: &git2::Repository, dirty_files: &mut Vec<PathBuf>) -> CargoResult<()> {
202+
let mut status_opts = git2::StatusOptions::new();
203+
// Exclude submodules, as they are being handled manually by recursing
204+
// into each one so that details about specific files can be
205+
// retrieved.
206+
status_opts
207+
.exclude_submodules(true)
208+
.include_ignored(true)
209+
.include_untracked(true);
210+
let repo_statuses = repo.statuses(Some(&mut status_opts)).with_context(|| {
211+
format!(
212+
"failed to retrieve git status from repo {}",
213+
repo.path().display()
214+
)
215+
})?;
216+
let workdir = repo.workdir().unwrap();
217+
let this_dirty = repo_statuses.iter().filter_map(|entry| {
218+
let path = entry.path().expect("valid utf-8 path");
219+
if path.ends_with("Cargo.lock") && entry.status() == git2::Status::IGNORED {
220+
// It is OK to include Cargo.lock even if it is ignored.
221+
return None;
222+
}
223+
// Use an absolute path, so that comparing paths is easier
224+
// (particularly with submodules).
225+
Some(workdir.join(path))
226+
});
227+
dirty_files.extend(this_dirty);
228+
Ok(())
229+
}
232230

233-
// Helper to collect dirty statuses while recursing into submodules.
234-
fn status_submodules(
235-
repo: &git2::Repository,
236-
dirty_files: &mut Vec<PathBuf>,
237-
) -> CargoResult<()> {
238-
for submodule in repo.submodules()? {
239-
// Ignore submodules that don't open, they are probably not initialized.
240-
// If its files are required, then the verification step should fail.
241-
if let Ok(sub_repo) = submodule.open() {
242-
status_submodules(&sub_repo, dirty_files)?;
243-
collect_statuses(&sub_repo, dirty_files)?;
244-
}
231+
/// Helper to collect dirty statuses while recursing into submodules.
232+
fn status_submodules(repo: &git2::Repository, dirty_files: &mut Vec<PathBuf>) -> CargoResult<()> {
233+
for submodule in repo.submodules()? {
234+
// Ignore submodules that don't open, they are probably not initialized.
235+
// If its files are required, then the verification step should fail.
236+
if let Ok(sub_repo) = submodule.open() {
237+
status_submodules(&sub_repo, dirty_files)?;
238+
collect_statuses(&sub_repo, dirty_files)?;
245239
}
246-
Ok(())
247240
}
241+
Ok(())
248242
}

0 commit comments

Comments
 (0)