@@ -152,6 +152,9 @@ use crate::{
152152 HashAndFormat ,
153153} ;
154154
155+ /// Maximum number of external paths we track per blob.
156+ const MAX_EXTERNAL_PATHS : usize = 8 ;
157+
155158/// Create a 16 byte unique ID.
156159fn new_uuid ( ) -> [ u8 ; 16 ] {
157160 use rand:: RngCore ;
@@ -1239,18 +1242,21 @@ async fn export_path_impl(
12391242 }
12401243 } ;
12411244 trace ! ( "exporting {} to {}" , cmd. hash. to_hex( ) , target. display( ) ) ;
1242- let data = match data_location {
1243- DataLocation :: Inline ( data) => MemOrFile :: Mem ( data) ,
1244- DataLocation :: Owned ( size) => {
1245- MemOrFile :: File ( ( ctx. options ( ) . path . data_path ( & cmd. hash ) , size) )
1246- }
1247- DataLocation :: External ( paths, size) => MemOrFile :: File ( (
1248- paths
1249- . into_iter ( )
1250- . next ( )
1251- . ok_or_else ( || io:: Error :: new ( io:: ErrorKind :: NotFound , "no external data path" ) ) ?,
1252- size,
1253- ) ) ,
1245+ let ( data, mut external) = match data_location {
1246+ DataLocation :: Inline ( data) => ( MemOrFile :: Mem ( data) , vec ! [ ] ) ,
1247+ DataLocation :: Owned ( size) => (
1248+ MemOrFile :: File ( ( ctx. options ( ) . path . data_path ( & cmd. hash ) , size) ) ,
1249+ vec ! [ ] ,
1250+ ) ,
1251+ DataLocation :: External ( paths, size) => (
1252+ MemOrFile :: File ( (
1253+ paths. first ( ) . cloned ( ) . ok_or_else ( || {
1254+ io:: Error :: new ( io:: ErrorKind :: NotFound , "no external data path" )
1255+ } ) ?,
1256+ size,
1257+ ) ) ,
1258+ paths,
1259+ ) ,
12541260 } ;
12551261 let size = match & data {
12561262 MemOrFile :: Mem ( data) => data. len ( ) as u64 ,
@@ -1274,21 +1280,40 @@ async fn export_path_impl(
12741280 ) ;
12751281 }
12761282 ExportMode :: TryReference => {
1277- match std:: fs:: rename ( & source_path, & target) {
1278- Ok ( ( ) ) => { }
1279- Err ( cause) => {
1280- const ERR_CROSS : i32 = 18 ;
1281- if cause. raw_os_error ( ) == Some ( ERR_CROSS ) {
1282- let source = fs:: File :: open ( & source_path) ?;
1283- let mut target = fs:: File :: create ( & target) ?;
1284- copy_with_progress ( & source, size, & mut target, tx) . await ?;
1285- } else {
1286- return Err ( cause. into ( ) ) ;
1283+ if !external. is_empty ( ) {
1284+ // the file already exists externally, so we need to copy it.
1285+ // if the OS supports reflink, we might as well use that.
1286+ let res =
1287+ reflink_or_copy_with_progress ( & source_path, & target, size, tx) . await ?;
1288+ trace ! (
1289+ "exported {} also to {}, {res:?}" ,
1290+ source_path. display( ) ,
1291+ target. display( )
1292+ ) ;
1293+ external. push ( target) ;
1294+ external. sort ( ) ;
1295+ external. dedup ( ) ;
1296+ external. truncate ( MAX_EXTERNAL_PATHS ) ;
1297+ } else {
1298+ // the file was previously owned, so we can just move it.
1299+ // if that fails with ERR_CROSS, we fall back to copy.
1300+ match std:: fs:: rename ( & source_path, & target) {
1301+ Ok ( ( ) ) => { }
1302+ Err ( cause) => {
1303+ const ERR_CROSS : i32 = 18 ;
1304+ if cause. raw_os_error ( ) == Some ( ERR_CROSS ) {
1305+ reflink_or_copy_with_progress ( & source_path, & target, size, tx)
1306+ . await ?;
1307+ } else {
1308+ return Err ( cause. into ( ) ) ;
1309+ }
12871310 }
12881311 }
1289- }
1312+ external. push ( target) ;
1313+ } ;
1314+ // setting the new entry state will also take care of deleting the owned data file!
12901315 ctx. set ( EntryState :: Complete {
1291- data_location : DataLocation :: External ( vec ! [ target ] , size) ,
1316+ data_location : DataLocation :: External ( external , size) ,
12921317 outboard_location,
12931318 } )
12941319 . await ?;
@@ -1421,6 +1446,12 @@ pub struct FsStore {
14211446 db : tokio:: sync:: mpsc:: Sender < InternalCommand > ,
14221447}
14231448
1449+ impl From < FsStore > for Store {
1450+ fn from ( value : FsStore ) -> Self {
1451+ Store :: from_sender ( value. sender )
1452+ }
1453+ }
1454+
14241455impl Deref for FsStore {
14251456 type Target = Store ;
14261457
0 commit comments