@@ -29,11 +29,14 @@ use anyhow::{bail, Context as _};
29
29
use cargo_util:: paths;
30
30
use flate2:: read:: GzDecoder ;
31
31
use flate2:: { Compression , GzBuilder } ;
32
- use serde:: Serialize ;
33
32
use tar:: { Archive , Builder , EntryType , Header , HeaderMode } ;
34
33
use tracing:: debug;
35
34
use unicase:: Ascii as UncasedAscii ;
36
35
36
+ mod vcs;
37
+ use self :: vcs:: check_repo_state;
38
+ use self :: vcs:: VcsInfo ;
39
+
37
40
#[ derive( Clone ) ]
38
41
pub struct PackageOpts < ' gctx > {
39
42
pub gctx : & ' gctx GlobalContext ,
@@ -78,21 +81,6 @@ enum GeneratedFile {
78
81
VcsInfo ( VcsInfo ) ,
79
82
}
80
83
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
-
96
84
// Builds a tarball and places it in the output directory.
97
85
#[ tracing:: instrument( skip_all) ]
98
86
fn create_package (
@@ -728,226 +716,6 @@ fn check_metadata(pkg: &Package, gctx: &GlobalContext) -> CargoResult<()> {
728
716
Ok ( ( ) )
729
717
}
730
718
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
-
951
719
/// Compresses and packages a list of [`ArchiveFile`]s and writes into the given file.
952
720
///
953
721
/// Returns the uncompressed size of the contents of the new archive file.
0 commit comments