@@ -29,11 +29,14 @@ use anyhow::{bail, Context as _};
2929use cargo_util:: paths;
3030use flate2:: read:: GzDecoder ;
3131use flate2:: { Compression , GzBuilder } ;
32- use serde:: Serialize ;
3332use tar:: { Archive , Builder , EntryType , Header , HeaderMode } ;
3433use tracing:: debug;
3534use unicase:: Ascii as UncasedAscii ;
3635
36+ mod vcs;
37+ use self :: vcs:: check_repo_state;
38+ use self :: vcs:: VcsInfo ;
39+
3740#[ derive( Clone ) ]
3841pub struct PackageOpts < ' gctx > {
3942 pub gctx : & ' gctx GlobalContext ,
@@ -78,21 +81,6 @@ enum GeneratedFile {
7881 VcsInfo ( VcsInfo ) ,
7982}
8083
81- #[ derive( Serialize ) ]
82- struct VcsInfo {
83- git : GitVcsInfo ,
84- /// Path to the package within repo (empty string if root). / not \
85- path_in_vcs : String ,
86- }
87-
88- #[ derive( Serialize ) ]
89- struct GitVcsInfo {
90- sha1 : String ,
91- /// Indicate whether or not the Git worktree is dirty.
92- #[ serde( skip_serializing_if = "std::ops::Not::not" ) ]
93- dirty : bool ,
94- }
95-
9684// Builds a tarball and places it in the output directory.
9785#[ tracing:: instrument( skip_all) ]
9886fn create_package (
@@ -728,226 +716,6 @@ fn check_metadata(pkg: &Package, gctx: &GlobalContext) -> CargoResult<()> {
728716 Ok ( ( ) )
729717}
730718
731- /// Checks if the package source is in a *git* DVCS repository. If *git*, and
732- /// the source is *dirty* (e.g., has uncommitted changes), and `--allow-dirty`
733- /// has not been passed, then `bail!` with an informative message. Otherwise
734- /// return the sha1 hash of the current *HEAD* commit, or `None` if no repo is
735- /// found.
736- #[ tracing:: instrument( skip_all) ]
737- fn check_repo_state (
738- p : & Package ,
739- src_files : & [ PathBuf ] ,
740- gctx : & GlobalContext ,
741- opts : & PackageOpts < ' _ > ,
742- ) -> CargoResult < Option < VcsInfo > > {
743- let Ok ( repo) = git2:: Repository :: discover ( p. root ( ) ) else {
744- gctx. shell ( ) . verbose ( |shell| {
745- shell. warn ( format ! ( "no (git) VCS found for `{}`" , p. root( ) . display( ) ) )
746- } ) ?;
747- // No Git repo found. Have to assume it is clean.
748- return Ok ( None ) ;
749- } ;
750-
751- let Some ( workdir) = repo. workdir ( ) else {
752- debug ! (
753- "no (git) workdir found for repo at `{}`" ,
754- repo. path( ) . display( )
755- ) ;
756- // No git workdir. Have to assume it is clean.
757- return Ok ( None ) ;
758- } ;
759-
760- debug ! ( "found a git repo at `{}`" , workdir. display( ) ) ;
761- let path = p. manifest_path ( ) ;
762- let path = paths:: strip_prefix_canonical ( path, workdir) . unwrap_or_else ( |_| path. to_path_buf ( ) ) ;
763- let Ok ( status) = repo. status_file ( & path) else {
764- gctx. shell ( ) . verbose ( |shell| {
765- shell. warn ( format ! (
766- "no (git) Cargo.toml found at `{}` in workdir `{}`" ,
767- path. display( ) ,
768- workdir. display( )
769- ) )
770- } ) ?;
771- // No checked-in `Cargo.toml` found. This package may be irrelevant.
772- // Have to assume it is clean.
773- return Ok ( None ) ;
774- } ;
775-
776- if !( status & git2:: Status :: IGNORED ) . is_empty ( ) {
777- gctx. shell ( ) . verbose ( |shell| {
778- shell. warn ( format ! (
779- "found (git) Cargo.toml ignored at `{}` in workdir `{}`" ,
780- path. display( ) ,
781- workdir. display( )
782- ) )
783- } ) ?;
784- // An ignored `Cargo.toml` found. This package may be irrelevant.
785- // Have to assume it is clean.
786- return Ok ( None ) ;
787- }
788-
789- debug ! (
790- "found (git) Cargo.toml at `{}` in workdir `{}`" ,
791- path. display( ) ,
792- workdir. display( ) ,
793- ) ;
794- let path_in_vcs = path
795- . parent ( )
796- . and_then ( |p| p. to_str ( ) )
797- . unwrap_or ( "" )
798- . replace ( "\\ " , "/" ) ;
799- let Some ( git) = git ( p, gctx, src_files, & repo, & opts) ? else {
800- // If the git repo lacks essensial field like `sha1`, and since this field exists from the beginning,
801- // then don't generate the corresponding file in order to maintain consistency with past behavior.
802- return Ok ( None ) ;
803- } ;
804-
805- return Ok ( Some ( VcsInfo { git, path_in_vcs } ) ) ;
806-
807- fn git (
808- pkg : & Package ,
809- gctx : & GlobalContext ,
810- src_files : & [ PathBuf ] ,
811- repo : & git2:: Repository ,
812- opts : & PackageOpts < ' _ > ,
813- ) -> CargoResult < Option < GitVcsInfo > > {
814- // This is a collection of any dirty or untracked files. This covers:
815- // - new/modified/deleted/renamed/type change (index or worktree)
816- // - untracked files (which are "new" worktree files)
817- // - ignored (in case the user has an `include` directive that
818- // conflicts with .gitignore).
819- let mut dirty_files = Vec :: new ( ) ;
820- collect_statuses ( repo, & mut dirty_files) ?;
821- // Include each submodule so that the error message can provide
822- // specifically *which* files in a submodule are modified.
823- status_submodules ( repo, & mut dirty_files) ?;
824-
825- // Find the intersection of dirty in git, and the src_files that would
826- // be packaged. This is a lazy n^2 check, but seems fine with
827- // thousands of files.
828- let cwd = gctx. cwd ( ) ;
829- let mut dirty_src_files: Vec < _ > = src_files
830- . iter ( )
831- . filter ( |src_file| dirty_files. iter ( ) . any ( |path| src_file. starts_with ( path) ) )
832- . chain ( dirty_metadata_paths ( pkg, repo) ?. iter ( ) )
833- . map ( |path| {
834- pathdiff:: diff_paths ( path, cwd)
835- . as_ref ( )
836- . unwrap_or ( path)
837- . display ( )
838- . to_string ( )
839- } )
840- . collect ( ) ;
841- let dirty = !dirty_src_files. is_empty ( ) ;
842- if !dirty || opts. allow_dirty {
843- // Must check whetherthe repo has no commit firstly, otherwise `revparse_single` would fail on bare commit repo.
844- // Due to lacking the `sha1` field, it's better not record the `GitVcsInfo` for consistency.
845- if repo. is_empty ( ) ? {
846- return Ok ( None ) ;
847- }
848- let rev_obj = repo. revparse_single ( "HEAD" ) ?;
849- Ok ( Some ( GitVcsInfo {
850- sha1 : rev_obj. id ( ) . to_string ( ) ,
851- dirty,
852- } ) )
853- } else {
854- dirty_src_files. sort_unstable ( ) ;
855- anyhow:: bail!(
856- "{} files in the working directory contain changes that were \
857- not yet committed into git:\n \n {}\n \n \
858- to proceed despite this and include the uncommitted changes, pass the `--allow-dirty` flag",
859- dirty_src_files. len( ) ,
860- dirty_src_files. join( "\n " )
861- )
862- }
863- }
864-
865- /// Checks whether files at paths specified in `package.readme` and
866- /// `package.license-file` have been modified.
867- ///
868- /// This is required because those paths may link to a file outside the
869- /// current package root, but still under the git workdir, affecting the
870- /// final packaged `.crate` file.
871- fn dirty_metadata_paths ( pkg : & Package , repo : & git2:: Repository ) -> CargoResult < Vec < PathBuf > > {
872- let mut dirty_files = Vec :: new ( ) ;
873- let workdir = repo. workdir ( ) . unwrap ( ) ;
874- let root = pkg. root ( ) ;
875- let meta = pkg. manifest ( ) . metadata ( ) ;
876- for path in [ & meta. license_file , & meta. readme ] {
877- let Some ( path) = path. as_deref ( ) . map ( Path :: new) else {
878- continue ;
879- } ;
880- let abs_path = paths:: normalize_path ( & root. join ( path) ) ;
881- if paths:: strip_prefix_canonical ( abs_path. as_path ( ) , root) . is_ok ( ) {
882- // Inside package root. Don't bother checking git status.
883- continue ;
884- }
885- if let Ok ( rel_path) = paths:: strip_prefix_canonical ( abs_path. as_path ( ) , workdir) {
886- // Outside package root but under git workdir,
887- if repo. status_file ( & rel_path) ? != git2:: Status :: CURRENT {
888- dirty_files. push ( if abs_path. is_symlink ( ) {
889- // For symlinks, shows paths to symlink sources
890- workdir. join ( rel_path)
891- } else {
892- abs_path
893- } ) ;
894- }
895- }
896- }
897- Ok ( dirty_files)
898- }
899-
900- // Helper to collect dirty statuses for a single repo.
901- fn collect_statuses (
902- repo : & git2:: Repository ,
903- dirty_files : & mut Vec < PathBuf > ,
904- ) -> CargoResult < ( ) > {
905- let mut status_opts = git2:: StatusOptions :: new ( ) ;
906- // Exclude submodules, as they are being handled manually by recursing
907- // into each one so that details about specific files can be
908- // retrieved.
909- status_opts
910- . exclude_submodules ( true )
911- . include_ignored ( true )
912- . include_untracked ( true ) ;
913- let repo_statuses = repo. statuses ( Some ( & mut status_opts) ) . with_context ( || {
914- format ! (
915- "failed to retrieve git status from repo {}" ,
916- repo. path( ) . display( )
917- )
918- } ) ?;
919- let workdir = repo. workdir ( ) . unwrap ( ) ;
920- let this_dirty = repo_statuses. iter ( ) . filter_map ( |entry| {
921- let path = entry. path ( ) . expect ( "valid utf-8 path" ) ;
922- if path. ends_with ( "Cargo.lock" ) && entry. status ( ) == git2:: Status :: IGNORED {
923- // It is OK to include Cargo.lock even if it is ignored.
924- return None ;
925- }
926- // Use an absolute path, so that comparing paths is easier
927- // (particularly with submodules).
928- Some ( workdir. join ( path) )
929- } ) ;
930- dirty_files. extend ( this_dirty) ;
931- Ok ( ( ) )
932- }
933-
934- // Helper to collect dirty statuses while recursing into submodules.
935- fn status_submodules (
936- repo : & git2:: Repository ,
937- dirty_files : & mut Vec < PathBuf > ,
938- ) -> CargoResult < ( ) > {
939- for submodule in repo. submodules ( ) ? {
940- // Ignore submodules that don't open, they are probably not initialized.
941- // If its files are required, then the verification step should fail.
942- if let Ok ( sub_repo) = submodule. open ( ) {
943- status_submodules ( & sub_repo, dirty_files) ?;
944- collect_statuses ( & sub_repo, dirty_files) ?;
945- }
946- }
947- Ok ( ( ) )
948- }
949- }
950-
951719/// Compresses and packages a list of [`ArchiveFile`]s and writes into the given file.
952720///
953721/// Returns the uncompressed size of the contents of the new archive file.
0 commit comments