Repository: borgbackup/borg

Issue:
borg diff contains path only entry
<!--
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).

1.2.0

#### Operating system (distribution) and version.

Arch linux

#### Hardware / network configuration, and filesystems used.

Backups are copied to an HDD via ssh.

#### How much data is handled by borg?

50G

#### Full borg commandline that lead to the problem (leave away excludes and passwords)


## Describe the problem you're observing.

#### Can you reproduce the problem? If so, describe how. If not, describe troubleshooting steps you took before opening the issue.

I'm using Vorta and discovered something odd: when I `borg diff` some lines are just paths without the change type and size:
```
                    home/jakob/Zotero/pipes/1647851804595
```
According to https://github.com/borgbase/vorta/issues/1228 this is not intended behaviour. Is this correct?

#### Include any warning/errors/backtraces from the system logs

<!--

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:
We should at least have the borg commandline that leads to this issue.
```
borg diff --info user@ip:/mnt/backup/b1/::c940-arch-2022-03-19-185905 c940-arch-2022-03-21-172543
``` 
is the command. I don't want to paste the entire output
Is a change in the result of `borg diff` without a change type a valid behaviour of borg? @ThomasWaldmann 
I couldn't find anything about such changes in the docs.
`home/jakob/Zotero/pipes/1647851804595` can you give me the `stat` output for that file?

is that a regular file? or a FIFO (fs pipe)?

It is a FIFO. The files comes from Zotero - a program to manage references. While the program is running there is a file in `pipes`. When closed the directory is empty.
```
$ stat Zotero/pipes/1650188949339
 Datei: Zotero/pipes/1650188949339
 Größe: 0               Blöcke: 0          EA Block: 4096   FIFO
Device: 254,2   Inode: 1611751691  Links: 1
Zugriff: (0600/prw-------)  Uid: ( 1000/   jakob)   Gid: (  998/   wheel)
Zugriff: 2022-04-17 11:49:10.187720668 +0200
Modifiziert: 2022-04-17 11:49:10.187720668 +0200
Geändert: 2022-04-17 11:49:10.187720668 +0200
Geburt: 2022-04-17 11:49:10.187720668 +0200
```
OK, so there is no content to diff. We have to check `borg diff` what to do best in such a case.
presence change ("added" / "removed") for fifos, blk and chr devs was not yet supported by borg diff.
@herrwusel can you test #6615 ?
Without the patch:
```
$ borg diff --info --log-json --json-lines user@ip:/mnt/backup/jakob-c940/::c940-arch-2022-03-18-132013 c940-arch-2022-03-31-190858-13 | grep pipes
Enter passphrase for key ssh://user@ip/mnt/backup/jakob-c940:
{"path": "home/jakob/Zotero/pipes/1648456406234", "changes": []}
{"path": "home/jakob/Zotero/pipes/1644833074860", "changes": []}
```

with the patch: 
```
$ borg diff --info --log-json --json-lines user@ip:/mnt/backup/jakob-c940/::c940-arch-2022-03-18-132013 c940-arch-2022-03-31-190858-13 | grep pipes

Enter passphrase for key ssh://user@ip/mnt/backup/jakob-c940:
{"path": "home/jakob/Zotero/pipes/1648456406234", "changes": [{"type": "added fifo"}]}
{"path": "home/jakob/Zotero/pipes/1644833074860", "changes": [{"type": "removed fifo"}]}
```

Vorta still gives an error but I don't know if it is Vorta's [parsing](https://github.com/borgbase/vorta/blob/c36ac81d5ea22c576d128a2869c1ee5ad7e3d19f/src/vorta/views/diff_result.py#L120).
@m3nu  https://github.com/borgbase/vorta/blob/c36ac81d5ea22c576d128a2869c1ee5ad7e3d19f/src/vorta/views/diff_result.py#L128 guess that line needs to be extended to include the new file types, see changes in my PR:

https://github.com/borgbackup/borg/pull/6615/files#diff-b5cbc1d4e0c3e39d2dcdc73182d7b40db606673c366ec56aaa60d0bb22759d5cR441

Resolving patch:
diff --git a/src/borg/archiver.py b/src/borg/archiver.py
--- a/src/borg/archiver.py
+++ b/src/borg/archiver.py
@@ -1083,15 +1083,22 @@ def get_mode(item):
         def has_hardlink_master(item, hardlink_masters):
             return hardlinkable(item.mode) and item.get('source') in hardlink_masters
 
+        def compare_presence(item1, item2, item_type):
+            if item2.get('deleted') and not item1.get('deleted'):
+                chg = 'removed ' + item_type
+                return ({"type": chg}, chg)
+            elif item1.get('deleted') and not item2.get('deleted'):
+                chg = 'added ' + item_type
+                return ({"type": chg}, chg)
+
         def compare_link(item1, item2):
             # These are the simple link cases. For special cases, e.g. if a
             # regular file is replaced with a link or vice versa, it is
             # indicated in compare_mode instead.
-            if item1.get('deleted'):
-                return ({"type": 'added link'}, 'added link')
-            elif item2.get('deleted'):
-                return ({"type": 'removed link'}, 'removed link')
-            elif 'source' in item1 and 'source' in item2 and item1.source != item2.source:
+            pd = compare_presence(item1, item2, 'link')
+            if pd is not None:
+                return pd
+            if 'source' in item1 and 'source' in item2 and item1.source != item2.source:
                 return ({"type": 'changed link'}, 'changed link')
 
         def contents_changed(item1, item2):
@@ -1129,12 +1136,6 @@ def compare_content(path, item1, item2):
                         '{:>9} {:>9}'.format(format_file_size(added, precision=1, sign=True),
                         format_file_size(-removed, precision=1, sign=True)))
 
-        def compare_directory(item1, item2):
-            if item2.get('deleted') and not item1.get('deleted'):
-                return ({"type": 'removed directory'}, 'removed directory')
-            elif item1.get('deleted') and not item2.get('deleted'):
-                return ({"type": 'added directory'}, 'added directory')
-
         def compare_owner(item1, item2):
             user1, group1 = get_owner(item1)
             user2, group2 = get_owner(item2)
@@ -1168,7 +1169,16 @@ def compare_items(output, path, item1, item2, hardlink_masters, deleted=False):
                 changes.append(compare_content(path, item1, item2))
 
             if get_mode(item1)[0] == 'd' or get_mode(item2)[0] == 'd':
-                changes.append(compare_directory(item1, item2))
+                changes.append(compare_presence(item1, item2, 'directory'))
+
+            if get_mode(item1)[0] == 'b' or get_mode(item2)[0] == 'b':
+                changes.append(compare_presence(item1, item2, 'blkdev'))
+
+            if get_mode(item1)[0] == 'c' or get_mode(item2)[0] == 'c':
+                changes.append(compare_presence(item1, item2, 'chrdev'))
+
+            if get_mode(item1)[0] == 's' or get_mode(item2)[0] == 's':
+                changes.append(compare_presence(item1, item2, 'fifo'))
 
             if not deleted:
                 changes.append(compare_owner(item1, item2))


Test patch: