@@ -5,6 +5,7 @@ const Filter = @import("Fetch.zig").Filter;
55
66allocator : std.mem.Allocator ,
77root : fs.Dir ,
8+ package_sub_path : ? []const  u8  =  null ,
89errors : Errors ,
910
1011pub  const  Error  =  union (enum ) {
@@ -69,7 +70,7 @@ pub const Errors = struct {
6970        } });
7071    }
7172
72-     pub   fn  filterWith (self : * Errors , filter : Filter ) ! void  {
73+     fn  filterWith (self : * Errors , filter : Filter ) ! void  {
7374        var  i  =  self .list .items .len ;
7475        while  (i  >  0 ) {
7576            i  -=  1 ;
@@ -80,6 +81,25 @@ pub const Errors = struct {
8081            }
8182        }
8283    }
84+ 
85+     fn  stripRoot (self : * Errors ) ! void  {
86+         if  (self .count () ==  0 ) return ;
87+ 
88+         var  old_list  =  self .list ;
89+         self .list  =  .{};
90+         for  (old_list .items ) | item |  {
91+             switch  (item ) {
92+                 .unable_to_create_sym_link  = >  | info |  {
93+                     try  self .symLink ("" , stripComponents (info .target_path , 1 ), info .sym_link_path , info .code );
94+                 },
95+                 .unable_to_create_file  = >  | info |  {
96+                     try  self .createFile ("" , stripComponents (info .file_name , 1 ), info .code );
97+                 },
98+             }
99+             self .free (item );
100+         }
101+         old_list .deinit (self .allocator );
102+     }
83103};
84104
85105pub  fn  init (allocator : std.mem.Allocator , root : fs.Dir ) Self  {
@@ -92,13 +112,14 @@ pub fn init(allocator: std.mem.Allocator, root: fs.Dir) Self {
92112
93113pub  fn  deinit (self : * Self ) void  {
94114    self .errors .deinit ();
115+     if  (self .package_sub_path ) | package_sub_path |  {
116+         self .allocator .free (package_sub_path );
117+     }
95118}
96119
97120const  Self  =  @This ();
98121
99122pub  fn  tarball (self : * Self , reader : anytype ) ! void  {
100-     const  strip_components  =  1 ;
101- 
102123    var  file_name_buffer : [std .fs .MAX_PATH_BYTES ]u8  =  undefined ;
103124    var  link_name_buffer : [std .fs .MAX_PATH_BYTES ]u8  =  undefined ;
104125    var  iter  =  std .tar .iterator (reader , .{
@@ -114,22 +135,59 @@ pub fn tarball(self: *Self, reader: anytype) !void {
114135                .directory  = >  {}, // skip empty 
115136                .file  = >  {
116137                    if  (entry .size  ==  0  and  entry .name .len  ==  0 ) continue ;
117-                     const  file_name  =  stripComponents (entry .name , strip_components );
118-                     if  (file_name .len  ==  0 ) return  error .BadFileName ;
119-                     if  (try  self .createFile ("" , file_name )) | file |  {
138+                     if  (try  self .createFile ("" , entry .name )) | file |  {
120139                        defer  file .close ();
121140                        try  entry .writeAll (file );
122141                    }
123142                },
124143                .sym_link  = >  {
125-                     const  file_name  =  stripComponents (entry .name , strip_components );
126-                     if  (file_name .len  ==  0 ) return  error .BadFileName ;
127-                     const  link_name  =  entry .link_name ;
128-                     try  self .symLink ("" , link_name , file_name );
144+                     try  self .symLink ("" , entry .link_name , entry .name );
129145                },
130146            }
131147        } else  break ;
132148    }
149+     try  self .findPackageSubPath ();
150+ }
151+ 
152+ fn  findPackageSubPath (self : * Self ) ! void  {
153+     var  iter  =  self .root .iterate ();
154+     if  (try  iter .next ()) | entry |  {
155+         if  (try  iter .next () !=  null ) return ;
156+         if  (entry .kind  ==  .directory ) { // single directory below root 
157+             self .package_sub_path  =  try  self .allocator .dupe (u8 , entry .name );
158+             try  self .errors .stripRoot ();
159+         }
160+     }
161+ }
162+ 
163+ test  findPackageSubPath  {
164+     var  tmp  =  testing .tmpDir (.{ .iterate  =  true  });
165+     defer  tmp .cleanup ();
166+ 
167+     // folder1 
168+     //     ├── folder2 
169+     //     ├── file1 
170+     // 
171+     try  tmp .dir .makePath ("folder1/folder2" );
172+     (try  tmp .dir .createFile ("folder1/file1" , .{})).close ();
173+ 
174+     var  unpack  =  init (testing .allocator , tmp .dir );
175+     try  unpack .findPackageSubPath ();
176+     // start at root returns folder1 as package root 
177+     try  testing .expectEqualStrings ("folder1" , unpack .package_sub_path .? );
178+     unpack .deinit ();
179+ 
180+     // start at folder1 returns null 
181+     unpack  =  init (testing .allocator , try  tmp .dir .openDir ("folder1" , .{ .iterate  =  true  }));
182+     try  unpack .findPackageSubPath ();
183+     try  testing .expect (unpack .package_sub_path  ==  null );
184+     unpack .deinit ();
185+ 
186+     // start at folder1/folder2 returns null 
187+     unpack  =  init (testing .allocator , try  tmp .dir .openDir ("folder1/folder2" , .{ .iterate  =  true  }));
188+     try  unpack .findPackageSubPath ();
189+     try  testing .expect (unpack .package_sub_path  ==  null );
190+     unpack .deinit ();
133191}
134192
135193pub  fn  gitPack (self : * Self , commit_oid : git.Oid , reader : anytype ) ! void  {
@@ -322,22 +380,36 @@ test tarball {
322380    };
323381    var  buf : [paths .len  *  @sizeOf (TarHeader )]u8  =  undefined ;
324382
325-     // create tarball 
326-     try  createTarball (paths , & buf );
383+     // tarball with leading root folder 
384+     {
385+         try  createTarball ("package_root" , paths , & buf );
386+         var  tmp  =  testing .tmpDir (.{ .iterate  =  true  });
387+         defer  tmp .cleanup ();
327388
328-     var  tmp  =  testing .tmpDir (.{ .iterate  =  true  });
329-     defer  tmp .cleanup ();
389+         var  fbs  =  std .io .fixedBufferStream (& buf );
390+ 
391+         var  unpack  =  Unpack .init (testing .allocator , tmp .dir );
392+         defer  unpack .deinit ();
393+         try  unpack .tarball (fbs .reader ());
394+         try  testing .expectEqualStrings ("package_root" , unpack .package_sub_path .? );
330395
331-     // unpack tarball to tmp dir, will strip root dir 
396+         try  expectDirFiles (try  tmp .dir .openDir ("package_root" , .{ .iterate  =  true  }), paths );
397+     }
398+     // tarball without root 
332399    {
400+         try  createTarball ("" , paths , & buf );
401+         var  tmp  =  testing .tmpDir (.{ .iterate  =  true  });
402+         defer  tmp .cleanup ();
403+ 
333404        var  fbs  =  std .io .fixedBufferStream (& buf );
334405
335406        var  unpack  =  Unpack .init (testing .allocator , tmp .dir );
336407        defer  unpack .deinit ();
337408        try  unpack .tarball (fbs .reader ());
338-     } 
409+          try   testing . expect ( unpack . package_sub_path   ==   null ); 
339410
340-     try  expectDirFiles (tmp .dir , paths );
411+         try  expectDirFiles (tmp .dir , paths );
412+     }
341413}
342414
343415test  directory  {
@@ -357,14 +429,14 @@ test directory {
357429        f .close ();
358430    }
359431
360-     var  dest  =  testing .tmpDir (.{ .iterate  =  true  });
361-     defer  dest .cleanup ();
432+     var  tmp  =  testing .tmpDir (.{ .iterate  =  true  });
433+     defer  tmp .cleanup ();
362434
363-     var  unpack  =  Unpack .init (testing .allocator , dest .dir );
435+     var  unpack  =  Unpack .init (testing .allocator , tmp .dir );
364436    defer  unpack .deinit ();
365437    try  unpack .directory (source .dir );
366438
367-     try  expectDirFiles (dest .dir , paths );
439+     try  expectDirFiles (tmp .dir , paths );
368440}
369441
370442test  "collect/filter errors"  {
@@ -378,7 +450,7 @@ test "collect/filter errors" {
378450        "dir1/file1" ,
379451    };
380452    var  buf : [paths .len  *  @sizeOf (TarHeader )]u8  =  undefined ;
381-     try  createTarball (paths , & buf );
453+     try  createTarball ("package_root" ,  paths , & buf );
382454
383455    var  tmp  =  testing .tmpDir (.{ .iterate  =  true  });
384456    defer  tmp .cleanup ();
@@ -388,12 +460,12 @@ test "collect/filter errors" {
388460    defer  unpack .deinit ();
389461    try  unpack .tarball (fbs .reader ());
390462    try  testing .expect (unpack .hasErrors ());
391- 
392-     try  expectDirFiles (tmp .dir , paths [0.. 2]);
463+      try   testing . expectEqualStrings ( "package_root" ,  unpack . package_sub_path .? ); 
464+     try  expectDirFiles (try   tmp .dir . openDir ( "package_root" , .{ . iterate   =   true  }) , paths [0.. 2]);
393465
394466    try  testing .expectEqual (2 , unpack .errors .count ());
395-     try  testing .expectEqualStrings (paths [ 2 ] , unpack .errors .list .items [0 ].unable_to_create_file .file_name );
396-     try  testing .expectEqualStrings (paths [ 3 ] , unpack .errors .list .items [1 ].unable_to_create_file .file_name );
467+     try  testing .expectEqualStrings ("dir/file" , unpack .errors .list .items [0 ].unable_to_create_file .file_name );
468+     try  testing .expectEqualStrings ("dir1/file1" , unpack .errors .list .items [1 ].unable_to_create_file .file_name );
397469
398470    {
399471        var  filter : Filter  =  .{};
@@ -420,13 +492,17 @@ test "collect/filter errors" {
420492    }
421493}
422494
423- fn  createTarball (paths : []const  []const  u8 , buf : []u8 ) ! void  {
495+ fn  createTarball (prefix : [] const   u8 ,  paths : []const  []const  u8 , buf : []u8 ) ! void  {
424496    var  fbs  =  std .io .fixedBufferStream (buf );
425497    const  writer  =  fbs .writer ();
426498    for  (paths ) | path |  {
427499        var  hdr  =  TarHeader .init ();
428500        hdr .typeflag  =  .regular ;
429-         try  hdr .setPath ("stripped_root" , path );
501+         if  (prefix .len  >  0 ) {
502+             try  hdr .setPath (prefix , path );
503+         } else  {
504+             hdr .setName (path );
505+         }
430506        try  hdr .updateChecksum ();
431507        try  writer .writeAll (std .mem .asBytes (& hdr ));
432508    }
0 commit comments