@@ -198,6 +198,15 @@ func (req *request) Excludes() []string {
198198 }
199199}
200200
201+ func (req * request ) Includes () []string {
202+ switch req .Request {
203+ case requestGet :
204+ return req .GetOptions .Includes
205+ default :
206+ return nil
207+ }
208+ }
209+
201210func (req * request ) UIDMap () []idtools.IDMap {
202211 switch req .Request {
203212 case requestEval :
@@ -405,6 +414,7 @@ type GetOptions struct {
405414 Timestamp * time.Time // timestamp to force on all contents
406415 DisallowWildcard bool // reject glob patterns in source paths
407416 AllowEmptyWildcard bool // don't error when glob patterns match nothing
417+ Includes []string // include only contents matching at least one of these patterns; Excludes take precedence
408418}
409419
410420// Get produces an archive containing items that match the specified glob
@@ -1072,11 +1082,19 @@ func copierHandler(bulkReader io.Reader, bulkWriter io.Writer, req request) (*re
10721082 // os.PathSeparator, implying that it expects OS-specific naming
10731083 // conventions.
10741084 excludes := req .Excludes ()
1075- pm , err := fileutils .NewPatternMatcher (excludes )
1085+ pmExcludes , err := fileutils .NewPatternMatcher (excludes )
10761086 if err != nil {
10771087 return nil , nil , fmt .Errorf ("processing excludes list %v: %w" , excludes , err )
10781088 }
10791089
1090+ var pmIncludes * fileutils.PatternMatcher
1091+ if includes := req .Includes (); len (includes ) > 0 {
1092+ pmIncludes , err = fileutils .NewPatternMatcher (includes )
1093+ if err != nil {
1094+ return nil , nil , fmt .Errorf ("processing includes list %v: %w" , includes , err )
1095+ }
1096+ }
1097+
10801098 var idMappings * idtools.IDMappings
10811099 uidMap , gidMap := req .UIDMap (), req .GIDMap ()
10821100 if len (uidMap ) > 0 && len (gidMap ) > 0 {
@@ -1090,10 +1108,10 @@ func copierHandler(bulkReader io.Reader, bulkWriter io.Writer, req request) (*re
10901108 resp := copierHandlerEval (req )
10911109 return resp , nil , nil
10921110 case requestStat :
1093- resp := copierHandlerStat (req , pm , idMappings )
1111+ resp := copierHandlerStat (req , pmExcludes , idMappings )
10941112 return resp , nil , nil
10951113 case requestGet :
1096- return copierHandlerGet (bulkWriter , req , pm , idMappings )
1114+ return copierHandlerGet (bulkWriter , req , pmExcludes , pmIncludes , idMappings )
10971115 case requestPut :
10981116 return copierHandlerPut (bulkReader , req , idMappings )
10991117 case requestMkdir :
@@ -1140,6 +1158,28 @@ func pathIsExcluded(root, path string, pm *fileutils.PatternMatcher) (string, bo
11401158 return rel , false , nil
11411159}
11421160
1161+ func pathIsIncluded (root , path string , pm * fileutils.PatternMatcher ) (bool , error ) {
1162+ rel , err := convertToRelSubdirectory (root , path )
1163+ if err != nil {
1164+ return false , fmt .Errorf ("copier: error computing path of %q relative to root %q: %w" , path , root , err )
1165+ }
1166+ if pm == nil {
1167+ return true , nil
1168+ }
1169+ if rel == "." {
1170+ // special case
1171+ return true , nil
1172+ }
1173+ // Matches uses filepath.FromSlash() to convert candidates before
1174+ // checking if they match the patterns it's been given, implying that
1175+ // it expects Unix-style paths.
1176+ matches , err := pm .Matches (filepath .ToSlash (rel )) //nolint:staticcheck
1177+ if err != nil {
1178+ return false , fmt .Errorf ("copier: error checking if %q is included: %w" , rel , err )
1179+ }
1180+ return matches , nil
1181+ }
1182+
11431183// resolvePath resolves symbolic links in paths, treating the specified
11441184// directory as the root.
11451185// Resolving the path this way, and using the result, is in no way secure
@@ -1221,7 +1261,7 @@ func containsWildcards(path string) bool {
12211261 return strings .ContainsAny (path , "*?[" )
12221262}
12231263
1224- func copierHandlerStat (req request , pm * fileutils.PatternMatcher , idMappings * idtools.IDMappings ) * response {
1264+ func copierHandlerStat (req request , pmExcludes * fileutils.PatternMatcher , idMappings * idtools.IDMappings ) * response {
12251265 errorResponse := func (fmtspec string , args ... any ) * response {
12261266 return & response {Error : fmt .Sprintf (fmtspec , args ... ), Stat : statResponse {}}
12271267 }
@@ -1250,7 +1290,7 @@ func copierHandlerStat(req request, pm *fileutils.PatternMatcher, idMappings *id
12501290 s .Globbed = make ([]string , 0 , len (globMatched ))
12511291 s .Results = make (map [string ]* StatForItem )
12521292 for _ , globbed := range globMatched {
1253- rel , excluded , err := pathIsExcluded (req .Root , globbed , pm )
1293+ rel , excluded , err := pathIsExcluded (req .Root , globbed , pmExcludes )
12541294 if err != nil {
12551295 return errorResponse ("copier: stat: %v" , err )
12561296 }
@@ -1310,7 +1350,7 @@ func copierHandlerStat(req request, pm *fileutils.PatternMatcher, idMappings *id
13101350 // could be a relative link) and in the context
13111351 // of the chroot
13121352 result .ImmediateTarget = immediateTarget
1313- resolvedTarget , err := resolvePath (req .Root , globbed , true , pm )
1353+ resolvedTarget , err := resolvePath (req .Root , globbed , true , pmExcludes )
13141354 if err != nil {
13151355 return errorResponse ("copier: stat: error resolving %q: %v" , globbed , err )
13161356 }
@@ -1403,8 +1443,8 @@ func checkLinks(item string, req request, info os.FileInfo) (string, os.FileInfo
14031443 return item , info , nil
14041444}
14051445
1406- func copierHandlerGet (bulkWriter io.Writer , req request , pm * fileutils.PatternMatcher , idMappings * idtools.IDMappings ) (* response , func () error , error ) {
1407- statResponse := copierHandlerStat (req , pm , idMappings )
1446+ func copierHandlerGet (bulkWriter io.Writer , req request , pmExcludes , pmIncludes * fileutils.PatternMatcher , idMappings * idtools.IDMappings ) (* response , func () error , error ) {
1447+ statResponse := copierHandlerStat (req , pmExcludes , idMappings )
14081448 errorResponse := func (fmtspec string , args ... any ) (* response , func () error , error ) {
14091449 return & response {Error : fmt .Sprintf (fmtspec , args ... ), Stat : statResponse .Stat , Get : getResponse {}}, nil , nil
14101450 }
@@ -1570,7 +1610,7 @@ func copierHandlerGet(bulkWriter io.Writer, req request, pm *fileutils.PatternMa
15701610 // skip the "." entry
15711611 return nil
15721612 }
1573- skippedPath , skip , err := pathIsExcluded (req .Root , path , pm )
1613+ skippedPath , skip , err := pathIsExcluded (req .Root , path , pmExcludes )
15741614 if err != nil {
15751615 return err
15761616 }
@@ -1581,14 +1621,14 @@ func copierHandlerGet(bulkWriter io.Writer, req request, pm *fileutils.PatternMa
15811621 // all, we don't need to
15821622 // descend into this particular
15831623 // directory if it's a directory
1584- if ! pm .Exclusions () {
1624+ if ! pmExcludes .Exclusions () {
15851625 return filepath .SkipDir
15861626 }
15871627 // if there are exclusion
15881628 // patterns for which this
15891629 // path is a prefix, we
15901630 // need to keep descending
1591- for _ , pattern := range pm .Patterns () {
1631+ for _ , pattern := range pmExcludes .Patterns () {
15921632 if ! pattern .Exclusion () {
15931633 continue
15941634 }
@@ -1611,6 +1651,16 @@ func copierHandlerGet(bulkWriter io.Writer, req request, pm *fileutils.PatternMa
16111651 // also be in the excludes list
16121652 return nil
16131653 }
1654+ if pmIncludes != nil && ! d .IsDir () {
1655+ included , err := pathIsIncluded (item , path , pmIncludes )
1656+ if err != nil {
1657+ return err
1658+ }
1659+
1660+ if ! included {
1661+ return nil
1662+ }
1663+ }
16141664 // if it's a symlink, read its target
16151665 symlinkTarget := ""
16161666 if d .Type () == os .ModeSymlink {
@@ -1656,14 +1706,24 @@ func copierHandlerGet(bulkWriter io.Writer, req request, pm *fileutils.PatternMa
16561706 }
16571707 itemsCopied ++
16581708 } else {
1659- _ , skip , err := pathIsExcluded (req .Root , item , pm )
1709+ _ , skip , err := pathIsExcluded (req .Root , item , pmExcludes )
16601710 if err != nil {
16611711 return err
16621712 }
16631713 if skip {
16641714 continue
16651715 }
16661716
1717+ if pmIncludes != nil {
1718+ included , err := pathIsIncluded (req .Root , item , pmIncludes )
1719+ if err != nil {
1720+ return err
1721+ }
1722+ if ! included {
1723+ continue
1724+ }
1725+ }
1726+
16671727 name := filepath .Base (queue [i ].glob )
16681728 if req .GetOptions .Parents {
16691729 name , err = convertToRelSubdirectory (req .Directory , queue [i ].glob )
0 commit comments