Repository: borgbackup/borg

Issue:
2.0.0b3: attribute nfiles not found
<!--
Thank you for reporting an issue.

*IMPORTANT* -  *before* creating a new issue please look around:
 - Borgbackup documentation: http://borgbackup.readthedocs.io/en/stable/index.html
 - FAQ: https://borgbackup.readthedocs.io/en/stable/faq.html
 and
 - open issues in Github tracker: https://github.com/borgbackup/borg/issues
  
If you cannot find a similar problem, then create a new issue.

Please fill in as much of the template as possible.
-->

## Have you checked borgbackup docs, FAQ, and open Github issues?

yes

## Is this a BUG / ISSUE report or a QUESTION?

bug

## System information. For client/server mode post info for both machines.

#### Your borg version (borg -V).

```
$ borg -V
borg 2.0.0b3
```

#### Operating system (distribution) and version.

Debian 11

#### Full borg commandline that lead to the problem (leave away excludes and passwords)

```
user@tmp-borg:~$ BORG_PASSPHRASE=abc borg rcreate -e repokey-blake2-chacha20-poly1305 -r test

IMPORTANT: you will need both KEY AND PASSPHRASE to access this repo!
If you used a repokey mode, the key is stored in the repo, but you should back it up separately.
Use "borg key export" to export the key, optionally in printable format.
Write down the passphrase. Store both at safe place(s).

user@tmp-borg:~$ tar -C /etc -c fstab | BORG_PASSPHRASE=abc borg -r test import-tar test -
user@tmp-borg:~$ BORG_PASSPHRASE=abc borg info -r test -a test
Local Exception
Traceback (most recent call last):
  File "borg/archiver/__init__.py", line 594, in main
  File "borg/archiver/__init__.py", line 522, in run
  File "borg/archiver/_common.py", line 155, in wrapper
  File "borg/archiver/info_cmd.py", line 34, in do_info
  File "borg/archive.py", line 558, in info
  File "borg/archive.py", line 714, in calc_stats
  File "src/borg/item.pyx", line 219, in borg.item.PropDictProperty.__get__
AttributeError: attribute nfiles not found

Platform: Linux tmp-borg 5.19.9-1.fc32.qubes.x86_64 #1 SMP PREEMPT_DYNAMIC Fri Sep 16 14:54:56 CEST 2022 x86_64
Linux: Unknown Linux  
Borg: 2.0.0b3  Python: CPython 3.9.14 msgpack: 1.0.4 fuse: llfuse 1.4.2 [pyfuse3,llfuse]
PID: 121613  CWD: /home/user
sys.argv: ['borg', 'info', '-r', 'test', '-a', 'test']
SSH_ORIGINAL_COMMAND: None
```

## Describe the problem you're observing.

`borg info` fails on archives created via `borg import-tar`.

<!--

If this complaint relates to borg performance, please include CRUD benchmark
results and any steps you took to troubleshoot.
How to run benchmark: http://borgbackup.readthedocs.io/en/stable/usage/benchmark.html

*IMPORTANT* - Please mark logs and text output from terminal commands 
or else Github will not display them correctly. 
An example is provided below.

Example:
```
this is an example how log text should be marked (wrap it with ```)
```
-->



Discussion:
Hmm, guess `borg create` puts the additional stats metadata into the archive (like `nfiles`), but looks like `borg import-tar` doesn't.

Not sure about the state of this in borg 1.2.x, this should be checked also.
At least they are all there in `items.pyx` (which also got a bigger change recently):
```
    size = PropDictProperty(int)
    nfiles = PropDictProperty(int)
    size_parts = PropDictProperty(int)
    nfiles_parts = PropDictProperty(int)
```

Resolving patch:
diff --git a/src/borg/archive.py b/src/borg/archive.py
--- a/src/borg/archive.py
+++ b/src/borg/archive.py
@@ -653,15 +653,18 @@ def save(self, name=None, comment=None, timestamp=None, stats=None, additional_m
             "time_end": end.isoformat(timespec="microseconds"),
             "chunker_params": self.chunker_params,
         }
-        if stats is not None:
-            metadata.update(
-                {
-                    "size": stats.osize,
-                    "nfiles": stats.nfiles,
-                    "size_parts": stats.osize_parts,
-                    "nfiles_parts": stats.nfiles_parts,
-                }
-            )
+        # we always want to create archives with the addtl. metadata (nfiles, etc.),
+        # because borg info relies on them. so, either use the given stats (from args)
+        # or fall back to self.stats if it was not given.
+        stats = stats or self.stats
+        metadata.update(
+            {
+                "size": stats.osize,
+                "nfiles": stats.nfiles,
+                "size_parts": stats.osize_parts,
+                "nfiles_parts": stats.nfiles_parts,
+            }
+        )
         metadata.update(additional_metadata or {})
         metadata = ArchiveItem(metadata)
         data = self.key.pack_and_authenticate_metadata(metadata.as_dict(), context=b"archive")
@@ -2320,9 +2323,7 @@ def save(self, archive, target, comment=None, replace_original=True):
                 "recreate_cmdline": sys.argv,
             }
 
-        target.save(
-            comment=comment, timestamp=self.timestamp, stats=target.stats, additional_metadata=additional_metadata
-        )
+        target.save(comment=comment, timestamp=self.timestamp, additional_metadata=additional_metadata)
         if replace_original:
             archive.delete(Statistics(), progress=self.progress)
             target.rename(archive.name)
diff --git a/src/borg/archiver/create_cmd.py b/src/borg/archiver/create_cmd.py
--- a/src/borg/archiver/create_cmd.py
+++ b/src/borg/archiver/create_cmd.py
@@ -190,7 +190,7 @@ def create_inner(archive, cache, fso):
                     # we already have a checkpoint archive in this case.
                     self.print_error("Got Ctrl-C / SIGINT.")
                 else:
-                    archive.save(comment=args.comment, timestamp=args.timestamp, stats=archive.stats)
+                    archive.save(comment=args.comment, timestamp=args.timestamp)
                     args.stats |= args.json
                     if args.stats:
                         if args.json:
diff --git a/src/borg/archiver/transfer_cmd.py b/src/borg/archiver/transfer_cmd.py
--- a/src/borg/archiver/transfer_cmd.py
+++ b/src/borg/archiver/transfer_cmd.py
@@ -100,7 +100,7 @@ def do_transfer(self, args, *, repository, manifest, cache, other_repository=Non
                         archive.add_item(upgrader.upgrade_item(item=item))
                 if not dry_run:
                     additional_metadata = upgrader.upgrade_archive_metadata(metadata=other_archive.metadata)
-                    archive.save(stats=archive.stats, additional_metadata=additional_metadata)
+                    archive.save(additional_metadata=additional_metadata)
                     print(
                         f"{name}: finished. "
                         f"transfer_size: {format_file_size(transfer_size)} "


Test patch: