44//! risk first:
55//!
66//! 1. [tracked_files] — iterate the index instead of parsing `git ls-files --full-name`. **Done.**
7- //! 2. ` xvc_paths_dirty` — [`gix::Repository::status`] instead of `git status --porcelain`.
7+ //! 2. [ xvc_paths_dirty] — [`gix::Repository::status`] instead of `git status --porcelain`. **Done.**
88//! 3. `stage_xvc_paths` — write blobs through the filter pipeline and patch the index.
99//! 4. `commit_xvc_paths` — build the tree from `HEAD^{tree}` with [`gix::Repository::edit_tree`]
1010//! and commit it, so the user's staging area is never touched and no stash is needed.
1717
1818use std:: path:: Path ;
1919
20+ use super :: paths:: XvcGitPaths ;
2021use crate :: { Error , Result } ;
2122
2223/// List the files Git tracks under `xvc_directory`, as paths relative to `xvc_directory`.
@@ -43,20 +44,7 @@ use crate::{Error, Result};
4344/// [`crate::XvcPath`]s, which are relative to the Xvc root, so entries outside `xvc_directory`
4445/// are dropped and the rest are re-based onto it.
4546pub fn tracked_files ( xvc_directory : & Path ) -> Result < Vec < String > > {
46- let repo = gix:: discover ( xvc_directory) . map_err ( |e| Error :: GixError {
47- cause : e. to_string ( ) ,
48- } ) ?;
49-
50- let workdir = repo. workdir ( ) . ok_or_else ( || Error :: GixError {
51- cause : format ! (
52- "{} is inside a bare Git repository, which tracks no worktree files" ,
53- xvc_directory. display( )
54- ) ,
55- } ) ?;
56-
57- // Both sides are canonicalized before comparison: `gix` reports the worktree as configured,
58- // which may traverse symlinks differently than the path Xvc was given.
59- let prefix = subdirectory_prefix ( & workdir. canonicalize ( ) ?, & xvc_directory. canonicalize ( ) ?) ?;
47+ let ( repo, prefix) = repo_and_prefix ( xvc_directory) ?;
6048
6149 let index = repo. index_or_empty ( ) . map_err ( |e| Error :: GixIndexError {
6250 cause : e. to_string ( ) ,
@@ -80,6 +68,70 @@ pub fn tracked_files(xvc_directory: &Path) -> Result<Vec<String>> {
8068 Ok ( files)
8169}
8270
71+ /// Whether anything under the Xvc-owned path set differs from `HEAD` — staged, unstaged or
72+ /// untracked.
73+ ///
74+ /// This replaces the `git status --porcelain <xvc paths>` pre-check that decides whether
75+ /// [`super::handle_git_automation`] has any reason to commit or stage.
76+ ///
77+ /// The pathspecs are narrower than the ones the subprocess pre-check used; see
78+ /// [`XvcGitPaths::pathspecs`] for what changed and why.
79+ pub fn xvc_paths_dirty ( xvc_directory : & Path ) -> Result < bool > {
80+ let ( repo, prefix) = repo_and_prefix ( xvc_directory) ?;
81+
82+ let mut changes = repo
83+ . status ( gix:: progress:: Discard )
84+ . map_err ( |e| Error :: GixError {
85+ cause : e. to_string ( ) ,
86+ } ) ?
87+ // `Collapsed`, the default, would be cheaper — it reports a wholly new `.xvc/store/` as a
88+ // single directory, which is enough for a boolean. `Files` is used anyway so that this
89+ // check and the commit that follows it walk the tree the same way and cannot disagree
90+ // about whether there is anything to do.
91+ . untracked_files ( gix:: status:: UntrackedFiles :: Files )
92+ . index_worktree_submodules ( None )
93+ . into_iter ( XvcGitPaths :: pathspecs (
94+ prefix. as_deref ( ) . unwrap_or_default ( ) ,
95+ ) )
96+ . map_err ( |e| Error :: GixError {
97+ cause : e. to_string ( ) ,
98+ } ) ?;
99+
100+ // Only the first item matters. Dropping the iterator early interrupts and joins the producer
101+ // threads, so there is no need to drain it.
102+ match changes. next ( ) {
103+ None => Ok ( false ) ,
104+ Some ( Ok ( _) ) => Ok ( true ) ,
105+ Some ( Err ( e) ) => Err ( Error :: GixError {
106+ cause : e. to_string ( ) ,
107+ } ) ,
108+ }
109+ }
110+
111+ /// Open the repository containing `xvc_directory`, and work out where that directory sits inside
112+ /// it.
113+ ///
114+ /// Both paths are canonicalized before comparison: `gix` reports the worktree as configured, which
115+ /// may traverse symlinks differently than the path Xvc was given.
116+ fn repo_and_prefix ( xvc_directory : & Path ) -> Result < ( gix:: Repository , Option < String > ) > {
117+ let repo = gix:: discover ( xvc_directory) . map_err ( |e| Error :: GixError {
118+ cause : e. to_string ( ) ,
119+ } ) ?;
120+
121+ let workdir = repo
122+ . workdir ( )
123+ . ok_or_else ( || Error :: GixError {
124+ cause : format ! (
125+ "{} is inside a bare Git repository, which has no worktree" ,
126+ xvc_directory. display( )
127+ ) ,
128+ } ) ?
129+ . canonicalize ( ) ?;
130+
131+ let prefix = subdirectory_prefix ( & workdir, & xvc_directory. canonicalize ( ) ?) ?;
132+ Ok ( ( repo, prefix) )
133+ }
134+
83135/// The slash-separated path from `root` down to `dir`, with a trailing slash, or `None` when they
84136/// are the same directory.
85137///
0 commit comments