@@ -343,22 +343,25 @@ func Stat(root string, directory string, options StatOptions, globs []string) ([
343
343
344
344
// GetOptions controls parts of Get()'s behavior.
345
345
type GetOptions struct {
346
- UIDMap , GIDMap []idtools.IDMap // map from hostIDs to containerIDs in the output archive
347
- Excludes []string // contents to pretend don't exist, using the OS-specific path separator
348
- ExpandArchives bool // extract the contents of named items that are archives
349
- ChownDirs * idtools.IDPair // set ownership on directories. no effect on archives being extracted
350
- ChmodDirs * os.FileMode // set permissions on directories. no effect on archives being extracted
351
- ChownFiles * idtools.IDPair // set ownership of files. no effect on archives being extracted
352
- ChmodFiles * os.FileMode // set permissions on files. no effect on archives being extracted
353
- StripSetuidBit bool // strip the setuid bit off of items being copied. no effect on archives being extracted
354
- StripSetgidBit bool // strip the setgid bit off of items being copied. no effect on archives being extracted
355
- StripStickyBit bool // strip the sticky bit off of items being copied. no effect on archives being extracted
356
- StripXattrs bool // don't record extended attributes of items being copied. no effect on archives being extracted
357
- KeepDirectoryNames bool // don't strip the top directory's basename from the paths of items in subdirectories
358
- Rename map [string ]string // rename items with the specified names, or under the specified names
359
- NoDerefSymlinks bool // don't follow symlinks when globs match them
360
- IgnoreUnreadable bool // ignore errors reading items, instead of returning an error
361
- NoCrossDevice bool // if a subdirectory is a mountpoint with a different device number, include it but skip its contents
346
+ UIDMap , GIDMap []idtools.IDMap // map from hostIDs to containerIDs in the output archive
347
+ Excludes []string // contents to pretend don't exist, using the OS-specific path separator
348
+ ExpandArchives bool // extract the contents of named items that are archives
349
+ ChownDirs * idtools.IDPair // set ownership on directories. no effect on archives being extracted
350
+ ChmodDirs * os.FileMode // set permissions on directories. no effect on archives being extracted
351
+ ChownFiles * idtools.IDPair // set ownership of files. no effect on archives being extracted
352
+ ChmodFiles * os.FileMode // set permissions on files. no effect on archives being extracted
353
+ // ParentsPrefixToRemove is removed from source path and what is left is used as name to maintain
354
+ // the sources parent directory in the destination if is empty is used only file name as name in tar.
355
+ ParentsPrefixToRemove string
356
+ StripSetuidBit bool // strip the setuid bit off of items being copied. no effect on archives being extracted
357
+ StripSetgidBit bool // strip the setgid bit off of items being copied. no effect on archives being extracted
358
+ StripStickyBit bool // strip the sticky bit off of items being copied. no effect on archives being extracted
359
+ StripXattrs bool // don't record extended attributes of items being copied. no effect on archives being extracted
360
+ KeepDirectoryNames bool // don't strip the top directory's basename from the paths of items in subdirectories
361
+ Rename map [string ]string // rename items with the specified names, or under the specified names
362
+ NoDerefSymlinks bool // don't follow symlinks when globs match them
363
+ IgnoreUnreadable bool // ignore errors reading items, instead of returning an error
364
+ NoCrossDevice bool // if a subdirectory is a mountpoint with a different device number, include it but skip its contents
362
365
}
363
366
364
367
// Get produces an archive containing items that match the specified glob
@@ -1215,6 +1218,7 @@ func copierHandlerGet(bulkWriter io.Writer, req request, pm *fileutils.PatternMa
1215
1218
return errorResponse ("copier: get: error reading info about directory %q: %v" , req .Directory , err )
1216
1219
}
1217
1220
cb := func () error {
1221
+ alreadyCopied := map [string ]struct {}{}
1218
1222
tw := tar .NewWriter (bulkWriter )
1219
1223
defer tw .Close ()
1220
1224
hardlinkChecker := new (hardlinkChecker )
@@ -1354,6 +1358,9 @@ func copierHandlerGet(bulkWriter io.Writer, req request, pm *fileutils.PatternMa
1354
1358
ok = filepath .SkipDir
1355
1359
}
1356
1360
}
1361
+ if len (req .GetOptions .ParentsPrefixToRemove ) > 0 {
1362
+ rel = filepath .Clean (strings .TrimPrefix (path , filepath .Join (req .Directory , req .GetOptions .ParentsPrefixToRemove )))
1363
+ }
1357
1364
// add the item to the outgoing tar stream
1358
1365
if err := copierHandlerGetOne (info , symlinkTarget , rel , path , options , tw , hardlinkChecker , idMappings ); err != nil {
1359
1366
if req .GetOptions .IgnoreUnreadable && errorIsPermission (err ) {
@@ -1379,17 +1386,37 @@ func copierHandlerGet(bulkWriter io.Writer, req request, pm *fileutils.PatternMa
1379
1386
if skip {
1380
1387
continue
1381
1388
}
1382
- // add the item to the outgoing tar stream. in
1383
- // cases where this was a symlink that we
1384
- // dereferenced, be sure to use the name of the
1385
- // link.
1386
- if err := copierHandlerGetOne (info , "" , filepath .Base (queue [i ]), item , req .GetOptions , tw , hardlinkChecker , idMappings ); err != nil {
1389
+
1390
+ copyFunc := func (name string , path string , fileInfo os.FileInfo ) error {
1391
+ // add the item to the outgoing tar stream. in
1392
+ // cases where this was a symlink that we
1393
+ // dereferenced, be sure to use the name of the
1394
+ // link.
1395
+ if err := copierHandlerGetOne (fileInfo , "" , name , path , req .GetOptions , tw , hardlinkChecker , idMappings ); err != nil {
1396
+ return err
1397
+ }
1398
+ itemsCopied ++
1399
+ return nil
1400
+ }
1401
+
1402
+ name := filepath .Base (queue [i ])
1403
+ if len (req .GetOptions .ParentsPrefixToRemove ) > 0 {
1404
+ name = filepath .Clean (strings .TrimPrefix (item , filepath .Join (req .Directory , req .GetOptions .ParentsPrefixToRemove )))
1405
+ alreadyCopied , err = copyParentsDirs (name , item , alreadyCopied , copyFunc )
1406
+ if err != nil {
1407
+ if req .GetOptions .IgnoreUnreadable && errorIsPermission (err ) {
1408
+ continue
1409
+ }
1410
+ return fmt .Errorf ("copier: get: %q: %w" , queue [i ], err )
1411
+ }
1412
+ }
1413
+
1414
+ if err := copyFunc (name , item , info ); err != nil {
1387
1415
if req .GetOptions .IgnoreUnreadable && errorIsPermission (err ) {
1388
1416
continue
1389
1417
}
1390
1418
return fmt .Errorf ("copier: get: %q: %w" , queue [i ], err )
1391
1419
}
1392
- itemsCopied ++
1393
1420
}
1394
1421
}
1395
1422
if itemsCopied == 0 {
@@ -1400,6 +1427,31 @@ func copierHandlerGet(bulkWriter io.Writer, req request, pm *fileutils.PatternMa
1400
1427
return & response {Stat : statResponse .Stat , Get : getResponse {}}, cb , nil
1401
1428
}
1402
1429
1430
+ func copyParentsDirs (name string , path string , alreadyCopied map [string ]struct {}, copy func (string , string , os.FileInfo ) error ) (map [string ]struct {}, error ) {
1431
+ parentName := filepath .Dir (name )
1432
+ parentPath := filepath .Dir (path )
1433
+ for parentName != "/" && parentName != "." {
1434
+ if _ , ok := alreadyCopied [parentPath ]; ok {
1435
+ parentName = filepath .Dir (parentName )
1436
+ parentPath = filepath .Dir (parentPath )
1437
+ continue
1438
+ }
1439
+
1440
+ parentInfo , err := os .Lstat (parentPath )
1441
+ if err != nil {
1442
+ return alreadyCopied , fmt .Errorf ("copier: get: lstat %q: %w" , parentPath , err )
1443
+ }
1444
+ if err := copy (strings .TrimPrefix (parentName , "/" ), parentPath , parentInfo ); err != nil {
1445
+ return alreadyCopied , err
1446
+ }
1447
+
1448
+ alreadyCopied [parentPath ] = struct {}{}
1449
+ parentName = filepath .Dir (parentName )
1450
+ parentPath = filepath .Dir (parentPath )
1451
+ }
1452
+ return alreadyCopied , nil
1453
+ }
1454
+
1403
1455
func handleRename (rename map [string ]string , name string ) string {
1404
1456
if rename == nil {
1405
1457
return name
0 commit comments