<?xml version="1.0" encoding="UTF-8"?>
<feed xmlns="http://www.w3.org/2005/Atom" xml:lang="en">
    <title>DedupCommando - DedupCommando release notes</title>
    <subtitle>Safety-focused Linux CLI and TUI for finding duplicate files and reclaiming storage.</subtitle>
    <link rel="self" type="application/atom+xml" href="https://dedcom.dequzzy.io/en/changelog/atom.xml"/>
    <link rel="alternate" type="text/html" href="https://dedcom.dequzzy.io/en/changelog/"/>
    <generator uri="https://www.getzola.org/">Zola</generator>
    <updated>2026-09-29T00:00:00+00:00</updated>
    <id>https://dedcom.dequzzy.io/en/changelog/atom.xml</id>
    <entry xml:lang="en">
        <title>DedupCommando v0.9.2 release notes</title>
        <published>2026-09-29T00:00:00+00:00</published>
        <updated>2026-09-29T00:00:00+00:00</updated>
        
        <author>
          <name>
            
              Unknown
            
          </name>
        </author>
        
        <link rel="alternate" type="text/html" href="https://dedcom.dequzzy.io/en/changelog/v0.9.2/"/>
        <id>https://dedcom.dequzzy.io/en/changelog/v0.9.2/</id>
        
        <content type="html" xml:base="https://dedcom.dequzzy.io/en/changelog/v0.9.2/">&lt;!-- Generated by scripts&#x2F;sync_docs.py from docs&#x2F;release-notes&#x2F;v0.9.2.md at v0.9.2 in the DedupCommando repository. Edit the source, not this file. --&gt;
&lt;p&gt;For anyone running 0.9.1. This release checks every action before the first snapshot is taken, makes a
saved plan script as careful as applying with &lt;code&gt;Y&lt;&#x2F;code&gt;, stops a scan within about a second, keeps hashing
fast in a large scan, refuses command lines it would half-ignore, and makes mouse clicks and keys act
on what the screen shows. The database stays at schema v6, so nothing is migrated.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;applying-actions&quot;&gt;Applying actions&lt;&#x2F;h2&gt;
&lt;ul&gt;
&lt;li&gt;Before the first snapshot, every action is checked: a read-only file system; &lt;code&gt;chattr +i&lt;&#x2F;code&gt; or &lt;code&gt;+a&lt;&#x2F;code&gt; on
the file, its directory or the quarantine directory; no space or an exhausted quota; a file reached
through a second mount (a bind mount) or lying outside its dataset&#x27;s mount point; for a hardlink,
the keeper as well. Such an action is refused with its reason and without reading any file; the
others run. A snapshot is taken only of the datasets where there is work.&lt;&#x2F;li&gt;
&lt;li&gt;If nothing in a batch can run, nothing changes and no snapshot is taken:
&lt;code&gt;nothing done — N actions cannot run: &amp;lt;reason&amp;gt;; no snapshot taken, marks kept; first: &amp;lt;path&amp;gt;&lt;&#x2F;code&gt;, and
the plan goes back to the window where it was confirmed.&lt;&#x2F;li&gt;
&lt;li&gt;Reflink on a host without block cloning (OpenZFS older than 2.2.1, or &lt;code&gt;zfs_bclone_enabled=0&lt;&#x2F;code&gt;), or on
a pool whose &lt;code&gt;feature@block_cloning&lt;&#x2F;code&gt; is disabled, is refused before the confirmation window, with
the number of marks and what to do instead.&lt;&#x2F;li&gt;
&lt;li&gt;A hardlink or reflink whose keeper is on another dataset is refused by the plan itself, before any
snapshot or read: &lt;code&gt;cannot hardlink or reflink across datasets (N marks) — …&lt;&#x2F;code&gt;. Reflink across the
datasets of one pool never worked; the manual no longer promises it.&lt;&#x2F;li&gt;
&lt;li&gt;Hardlink and reflink of a file whose name is up to the 255-byte limit no longer fail with
&lt;code&gt;File name too long&lt;&#x2F;code&gt;, and a CSV export can be written to a file with such a name.&lt;&#x2F;li&gt;
&lt;li&gt;A failed replacement names the real cause: the prepared replacement vanished, no space, the quota,
a read-only file system, an I&#x2F;O error. If another file took the original&#x27;s place, so that the
original cannot go back, the message gives its exact path in the quarantine. A failed database
write during a scan names the operating system&#x27;s error, and a problem with the database file or
the lock says what was found under that name.&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;h2 id=&quot;a-saved-plan-script&quot;&gt;A saved plan script&lt;&#x2F;h2&gt;
&lt;p&gt;The script that F11 → S saves now takes the same steps as applying with &lt;code&gt;Y&lt;&#x2F;code&gt;:&lt;&#x2F;p&gt;
&lt;ul&gt;
&lt;li&gt;Before each action it compares the file with its keeper byte for byte (&lt;code&gt;cmp&lt;&#x2F;code&gt;) and leaves alone a
file that has changed or become a symbolic link (&lt;code&gt;dedcom: skip &amp;lt;file&amp;gt;: …&lt;&#x2F;code&gt;).&lt;&#x2F;li&gt;
&lt;li&gt;The replacement, a link or a clone, is made beside the file first. Only then does the file go to
the quarantine, and it comes back if the replacement cannot be put in its place.&lt;&#x2F;li&gt;
&lt;li&gt;A reflink copy gets the owner, mode, times, extended attributes and ACL of the original.&lt;&#x2F;li&gt;
&lt;li&gt;An &lt;code&gt;mv -n&lt;&#x2F;code&gt; that moved nothing no longer counts as a deletion, and a file whose quarantine is on
another mount (a bind mount, or a link to another file system) is refused before the move.&lt;&#x2F;li&gt;
&lt;li&gt;One failed action no longer stops the rest. The script ends with
&lt;code&gt;dedcom: N of M actions not carried out — each is named above&lt;&#x2F;code&gt; and exit code 1.&lt;&#x2F;li&gt;
&lt;li&gt;The Commands tab shows one function call per action; the functions are defined at the top of the
script.&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;h2 id=&quot;scanning&quot;&gt;Scanning&lt;&#x2F;h2&gt;
&lt;ul&gt;
&lt;li&gt;A stop during hashing (Esc on the scan screen; Ctrl+C, &lt;code&gt;SIGTERM&lt;&#x2F;code&gt; or &lt;code&gt;SIGHUP&lt;&#x2F;code&gt; for &lt;code&gt;--scan&lt;&#x2F;code&gt;) takes
effect within about a second, even in the middle of a large file. Before, the scan first finished
reading every file of the current batch of up to 64: tens of minutes for a 500 GB disk image. The
files being read at that moment get no hash and are read again from the start on resume.&lt;&#x2F;li&gt;
&lt;li&gt;Hashing no longer slows down as a large scan goes on: 100,000 small files take 11 s instead of
6 minutes on this project&#x27;s test machine, and a scan of 2 million files on a home server hashes
about 300 files per second instead of 14.&lt;&#x2F;li&gt;
&lt;li&gt;&lt;code&gt;--scan&lt;&#x2F;code&gt; of a root that does not exist or cannot be read is refused with exit code 1 before the
lock and the database are touched, instead of ending as an empty &quot;successful&quot; scan that trimmed the
history. A resumed scan prints the settings it keeps and refuses a flag it would ignore;
&lt;code&gt;--no-resume&lt;&#x2F;code&gt; starts over.&lt;&#x2F;li&gt;
&lt;li&gt;A scan that finds no file under a root where earlier scans of the same roots found files, usually
the empty mount point of a dataset that did not mount, no longer sends those scans to the trash and
says &lt;code&gt;History kept: …&lt;&#x2F;code&gt;.&lt;&#x2F;li&gt;
&lt;li&gt;A directory that cannot be opened (removed during the scan, a path longer than 4,095 bytes, no
search permission, a disk error) is one walk error instead of the end of the scan: &lt;code&gt;Omissions: … walk errors&lt;&#x2F;code&gt;, &lt;code&gt;⚠ gaps&lt;&#x2F;code&gt; in the interface, and a &lt;code&gt;walk error: &amp;lt;path&amp;gt;: &amp;lt;reason&amp;gt;&lt;&#x2F;code&gt; line in &lt;code&gt;dedcom.log&lt;&#x2F;code&gt;.&lt;&#x2F;li&gt;
&lt;li&gt;&lt;code&gt;--include-ext tar.gz&lt;&#x2F;code&gt; finds &lt;code&gt;photos.tar.gz&lt;&#x2F;code&gt;, &lt;code&gt;&#x27;*.JPG&#x27;&lt;&#x2F;code&gt; means &lt;code&gt;jpg&lt;&#x2F;code&gt; on the command line and in
&lt;code&gt;presets.json&lt;&#x2F;code&gt;, and a value with &lt;code&gt;&#x2F;&lt;&#x2F;code&gt; is refused.&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;h2 id=&quot;the-command-line-and-configjson&quot;&gt;The command line and &lt;code&gt;config.json&lt;&#x2F;code&gt;&lt;&#x2F;h2&gt;
&lt;ul&gt;
&lt;li&gt;An argument that is not valid UTF-8 no longer stops dedcom with a panic: the state directory and
the export file accept such names, and a scan root gets a clear refusal.&lt;&#x2F;li&gt;
&lt;li&gt;Two modes at once, a repeated &lt;code&gt;--export-csv&lt;&#x2F;code&gt;, or a flag the run does not read are refused with exit
code 2 and an explanation, instead of half of the command being dropped. So are &lt;code&gt;--verify&lt;&#x2F;code&gt;,
&lt;code&gt;--merkle-dirs&lt;&#x2F;code&gt;, &lt;code&gt;--strict-verify&lt;&#x2F;code&gt; and &lt;code&gt;--no-resume&lt;&#x2F;code&gt; beside &lt;code&gt;--read-only&lt;&#x2F;code&gt;. A window started with
&lt;code&gt;--read-only&lt;&#x2F;code&gt; answers an action with &lt;code&gt;(started with --read-only)&lt;&#x2F;code&gt;.&lt;&#x2F;li&gt;
&lt;li&gt;Refusals show invisible direction-control characters escaped.&lt;&#x2F;li&gt;
&lt;li&gt;A &lt;code&gt;config.json&lt;&#x2F;code&gt; that cannot be read (not JSON, not an object, not a regular file) is no longer
rewritten and decides nothing. Each writing run says so in one line; automatic VACUUM and history
trimming are skipped until the file is fixed, and the lock policy falls back to &lt;code&gt;ask&lt;&#x2F;code&gt;. A field of
the wrong kind turns off only its own decision. &lt;code&gt;config.json&lt;&#x2F;code&gt; is now written by replacing the file,
with mode 0600.&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;h2 id=&quot;clicks-and-keys-act-on-what-is-drawn&quot;&gt;Clicks and keys act on what is drawn&lt;&#x2F;h2&gt;
&lt;ul&gt;
&lt;li&gt;A click on a function-key number in the commander&#x27;s bottom bar runs that key at any terminal
width. Before, at widths such as 80, 100, 160 and 200 columns, eight of the twelve numbers ran the
key to their left: &quot;9 Menu&quot; set a Delete mark, &quot;11 Exec&quot; quit.&lt;&#x2F;li&gt;
&lt;li&gt;A click or a double-click in a list of groups or of a group&#x27;s files, in the wizard and in the
commander, scrolled or not, takes the row drawn under the pointer. Before, the wizard took the row
below it, and a double-click saved the wrong keeper; in the commander such a click dropped the
cursor. PgUp and PgDn move by the visible part of the list.&lt;&#x2F;li&gt;
&lt;li&gt;In the wizard&#x27;s group view, 1 opens Folders and 2 opens Files, as labelled.&lt;&#x2F;li&gt;
&lt;li&gt;While the help, the start-up notice, the role prompt, a function-key window or a yes&#x2F;no question
is open, a click does nothing: close the window first. Before, a click under the help could set a
Delete mark. On the Triage Board, a click no longer moves the focus while a receiver waits for its
digit.&lt;&#x2F;li&gt;
&lt;li&gt;The &quot;Please wait&quot; box is no longer drawn over a question or a window that is waiting for a key.
Before, it could hide &quot;Move to trash?&quot; or &quot;Purge from trash?&quot;, where Enter means yes.&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;h2 id=&quot;a-late-answer-does-not-take-over-an-open-window&quot;&gt;A late answer does not take over an open window&lt;&#x2F;h2&gt;
&lt;ul&gt;
&lt;li&gt;When F2, F3 or F11 answers after you opened the F9 menu, another window, the help or the Triage
Board, its window no longer opens over them, and no scan starts from under them. The status line
says what arrived and which key to press again (&quot;Close this window, press F2 again: …&quot;).&lt;&#x2F;li&gt;
&lt;li&gt;A scan result that finishes opening after you opened a menu, a window or the Triage Board, or moved
to another screen, opens without switching the screen; the status line names the scan and says how
to show it. The help does not count: the groups open under it. Before, the group view took over,
and an Enter meant for the menu saved a keeper. A plan finished after you left the group view is
dropped; &lt;code&gt;r&lt;&#x2F;code&gt; builds it again.&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;h2 id=&quot;names-that-are-not-valid-utf-8&quot;&gt;Names that are not valid UTF-8&lt;&#x2F;h2&gt;
&lt;ul&gt;
&lt;li&gt;For a file whose name shows with a &lt;code&gt;?&lt;&#x2F;code&gt;, F3 no longer shows another file&#x27;s checksum and duplicates,
&quot;duplicates of the cursor&quot; no longer shows another file&#x27;s group, and a directory no longer shows
another directory&#x27;s total size. Both screens say instead that the scan has no such name, and why.&lt;&#x2F;li&gt;
&lt;li&gt;The manual (§11.4) no longer says such a name is exported. It says where these files are counted
(&lt;code&gt;Omissions:&lt;&#x2F;code&gt; in the &lt;code&gt;--scan&lt;&#x2F;code&gt; output, &lt;code&gt;⚠ gaps&lt;&#x2F;code&gt; in the TUI) and how to ask about one file (F3 in
the commander). The sample &lt;code&gt;--scan&lt;&#x2F;code&gt; output in §11.1 matches what the program prints.&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;h2 id=&quot;the-cursor-in-a-large-scan&quot;&gt;The cursor in a large scan&lt;&#x2F;h2&gt;
&lt;p&gt;In &quot;duplicates of the cursor&quot; a cursor step costs a fraction of a millisecond and no longer grows
with the scan; in 0.9.1 it was about 70 ms on a scan of a million files, on this project&#x27;s test
machine. Building the F11 plan no longer walks the scan for every mark and group.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;the-manual&quot;&gt;The manual&lt;&#x2F;h2&gt;
&lt;ul&gt;
&lt;li&gt;A new §8.9 on backups and the stores of other programs (Time Machine, restic, borg, kopia, Arq,
Duplicacy, Proxmox Backup Server, iPhone backups, virtual machine disks): do not scan a store, stop
the program before applying, apply with &lt;code&gt;Y&lt;&#x2F;code&gt; rather than a saved script, verify with the program&#x27;s
own tools before cleaning anything. Wherever the manual cleans the quarantine, the command is
&lt;code&gt;dedcom --purge-quarantine --yes&lt;&#x2F;code&gt;.&lt;&#x2F;li&gt;
&lt;li&gt;§11 gives a ready cron line that runs at low priority and checks that the pool is mounted.
Chapter 13 quotes every reason an action can be refused before the snapshots, and every line a
saved script prints; a test checks the quotes against the program.&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;h2 id=&quot;what-it-does-not-establish&quot;&gt;What it does not establish&lt;&#x2F;h2&gt;
&lt;ul&gt;
&lt;li&gt;Unicode formatting characters in names, such as bidirectional overrides and zero-width characters,
are not escaped on screen; a terminal with bidi support may reorder a row that shows one.
Command-line refusals now show them escaped.&lt;&#x2F;li&gt;
&lt;li&gt;A plan saved as &lt;code&gt;.sh&lt;&#x2F;code&gt; holds the real bytes of the names, as it must to run, so &lt;code&gt;cat&lt;&#x2F;code&gt; of that file
still passes an escape sequence in a name to the terminal. The Commands tab shows them escaped.&lt;&#x2F;li&gt;
&lt;li&gt;The confirmation window does not yet show which actions cannot run; their refusal comes after you
confirm. Planned for 0.9.3.&lt;&#x2F;li&gt;
&lt;li&gt;A stop is not seen during the &lt;code&gt;--verify&lt;&#x2F;code&gt; byte comparison, nor while &lt;code&gt;Y&lt;&#x2F;code&gt; re-reads both files before
an action: those finish first. Planned for 0.9.3.&lt;&#x2F;li&gt;
&lt;li&gt;&lt;code&gt;nohup&lt;&#x2F;code&gt; does not keep &lt;code&gt;--scan&lt;&#x2F;code&gt; running after an SSH session drops: the hang-up stops the scan. Run a
long scan over SSH in &lt;code&gt;tmux&lt;&#x2F;code&gt; or &lt;code&gt;screen&lt;&#x2F;code&gt;. Planned for 0.9.3.&lt;&#x2F;li&gt;
&lt;li&gt;A stopped &lt;code&gt;--scan&lt;&#x2F;code&gt; exits with code 0, as a finished one does. Planned for 0.9.3.&lt;&#x2F;li&gt;
&lt;li&gt;The memory warning before grouping advises Esc, but a stop is noticed only after grouping has built
its structures. Planned for 0.9.3.&lt;&#x2F;li&gt;
&lt;li&gt;A dataset below a scan root that did not mount is not noticed: the scan finds files in the rest of
the root and trims the history as usual.&lt;&#x2F;li&gt;
&lt;li&gt;A path within about 60 bytes of the 4,095-byte limit can still fail a hardlink or reflink after
the snapshot. A directory the walk entered but could not read the metadata of still stops the scan.&lt;&#x2F;li&gt;
&lt;li&gt;The first visit to a very large directory still costs one pass over its rows, seconds at the top
of a pool of millions of files.&lt;&#x2F;li&gt;
&lt;li&gt;A files panel shows only the marks set while it was open. Marks saved earlier show in &quot;group
files&quot; and on the F11 confirmation.&lt;&#x2F;li&gt;
&lt;li&gt;While a scan opens, the &quot;Please wait&quot; box still covers the middle of the screen, and keys act on
what is under it: in the commander that can be the file under the cursor.&lt;&#x2F;li&gt;
&lt;li&gt;If the list of scans is read again while you are on it, the cursor can go back to the first row,
and the next Enter or Del acts on that row; &quot;Move to trash?&quot; and &quot;Purge from trash?&quot; do not name
the scan. Planned for 0.9.3.&lt;&#x2F;li&gt;
&lt;li&gt;If a scan finishes but its result fails to open, the scanning screen stays, and only &lt;code&gt;q&lt;&#x2F;code&gt; leaves
it. Planned for 0.9.3.&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;h2 id=&quot;on-upgrade&quot;&gt;On upgrade&lt;&#x2F;h2&gt;
&lt;ul&gt;
&lt;li&gt;The 0.9.1 database opens as it is.&lt;&#x2F;li&gt;
&lt;li&gt;A command line that combines modes, repeats &lt;code&gt;--export-csv&lt;&#x2F;code&gt;, or passes a flag the run does not read
now fails with exit code 2. Check cron lines and aliases: &lt;code&gt;alias dedcom=&#x27;dedcom --strict-verify&#x27;&lt;&#x2F;code&gt;
now breaks &lt;code&gt;dedcom --stats&lt;&#x2F;code&gt; and &lt;code&gt;dedcom --read-only&lt;&#x2F;code&gt;.&lt;&#x2F;li&gt;
&lt;li&gt;An unfinished scan started by 0.9.1 with &lt;code&gt;--include-ext &#x27;*.jpg&#x27;&lt;&#x2F;code&gt; does not resume from the same
command line (it kept &lt;code&gt;*.jpg&lt;&#x2F;code&gt;, the new run asks for &lt;code&gt;jpg&lt;&#x2F;code&gt;): start it over with &lt;code&gt;--no-resume&lt;&#x2F;code&gt;.&lt;&#x2F;li&gt;
&lt;li&gt;A saved plan script needs &lt;code&gt;cmp&lt;&#x2F;code&gt; (diffutils) and &lt;code&gt;findmnt&lt;&#x2F;code&gt; (util-linux) besides coreutils, all three
required packages on Debian 12 and 13. It reads every file it works on and its keeper in full: a
pair of 100 GiB images on a pool of hard disks at 150–200 MB&#x2F;s takes 17–23 minutes.&lt;&#x2F;li&gt;
&lt;li&gt;Snapshots are taken only of the datasets where at least one action can run. The pools&#x27;
&lt;code&gt;feature@block_cloning&lt;&#x2F;code&gt; is read once at start: restart dedcom after changing it.&lt;&#x2F;li&gt;
&lt;li&gt;&lt;code&gt;dedcom --read-only&lt;&#x2F;code&gt; no longer starts when &lt;code&gt;dedcom.lock&lt;&#x2F;code&gt; is a directory.&lt;&#x2F;li&gt;
&lt;li&gt;After &lt;code&gt;--stats&lt;&#x2F;code&gt;, &lt;code&gt;--export-csv&lt;&#x2F;code&gt; or a &lt;code&gt;--read-only&lt;&#x2F;code&gt; window, &lt;code&gt;dedcom.db-wal&lt;&#x2F;code&gt; (0 bytes) and
&lt;code&gt;dedcom.db-shm&lt;&#x2F;code&gt; (32 KiB) can stay in the state directory. They are harmless and the next writing
run removes them; do not delete them by hand.&lt;&#x2F;li&gt;
&lt;li&gt;From 0.9.0-beta.4 or earlier, what the v0.9.1 notes say on upgrade applies as well.&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
</content>
        
    </entry>
    <entry xml:lang="en">
        <title>DedupCommando v0.9.1 release notes</title>
        <published>2026-09-23T00:00:00+00:00</published>
        <updated>2026-09-23T00:00:00+00:00</updated>
        
        <author>
          <name>
            
              Unknown
            
          </name>
        </author>
        
        <link rel="alternate" type="text/html" href="https://dedcom.dequzzy.io/en/changelog/v0.9.1/"/>
        <id>https://dedcom.dequzzy.io/en/changelog/v0.9.1/</id>
        
        <content type="html" xml:base="https://dedcom.dequzzy.io/en/changelog/v0.9.1/">&lt;!-- Generated by scripts&#x2F;sync_docs.py from docs&#x2F;release-notes&#x2F;v0.9.1.md at v0.9.2 in the DedupCommando repository. Edit the source, not this file. --&gt;
&lt;p&gt;For anyone running 0.9.0-beta.4 or an earlier pre-release. This release closes ways a file name or an
argument could reach the operator&#x27;s terminal or files, and makes the commander fast inside a large
scan. The database stays at schema v6, so nothing is migrated. The number drops the &lt;code&gt;-beta&lt;&#x2F;code&gt; suffix;
the leading 0 still marks the project as a beta.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;a-file-name-can-no-longer-drive-the-terminal&quot;&gt;A file name can no longer drive the terminal&lt;&#x2F;h2&gt;
&lt;p&gt;A file name holding an escape sequence could retitle the operator&#x27;s terminal or clear the screen
when the commander listed it. Every screen now shows paths escaped, the status line is escaped again
where it is drawn, and each frame is checked before it reaches the terminal. In a CSV export, control
characters in a name are written as &lt;code&gt;\n&lt;&#x2F;code&gt;, &lt;code&gt;\u{1b}&lt;&#x2F;code&gt; and so on: printing the file runs nothing, and each
record stays one line. The reason a scan stopped, the &lt;code&gt;--stats&lt;&#x2F;code&gt; environment line and error messages
are escaped as well.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;no-writing-through-links-or-into-a-directory-that-is-not-dedcoms&quot;&gt;No writing through links or into a directory that is not dedcom&#x27;s&lt;&#x2F;h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;--export-csv&lt;&#x2F;code&gt; onto a symlink replaced the symlink itself; as root, a typo could turn &lt;code&gt;&#x2F;dev&#x2F;stdout&lt;&#x2F;code&gt;
into a regular file until reboot. It now refuses, and the export never writes through a link.&lt;&#x2F;li&gt;
&lt;li&gt;&lt;code&gt;dedcom.lock&lt;&#x2F;code&gt; and &lt;code&gt;consent.json&lt;&#x2F;code&gt; were written through a symlink, a hardlink or a device node under
their name. Only a regular file with exactly one name is accepted now, and the refusal names what
was found.&lt;&#x2F;li&gt;
&lt;li&gt;&lt;code&gt;--state-dir&lt;&#x2F;code&gt; took any directory and changed its permissions to 0700. An existing directory that
holds neither dedcom&#x27;s database nor its lock, but holds something, is now refused with an
explanation, and so is an empty directory directly under &lt;code&gt;&#x2F;&lt;&#x2F;code&gt;. A &lt;code&gt;config.json&lt;&#x2F;code&gt; put into a new
directory by hand before the first start is refused too. &lt;code&gt;--stats&lt;&#x2F;code&gt; and &lt;code&gt;--export-csv&lt;&#x2F;code&gt; no longer
create the state directory.&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;h2 id=&quot;two-panics-fixed&quot;&gt;Two panics fixed&lt;&#x2F;h2&gt;
&lt;p&gt;F3 in a window lower than five rows panicked and left the terminal in raw mode. &lt;code&gt;dedcom --stats | head&lt;&#x2F;code&gt; exited with 101 and a panic; it now exits quietly.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;the-commander-inside-a-large-scan&quot;&gt;The commander inside a large scan&lt;&#x2F;h2&gt;
&lt;p&gt;Changing directory inside a scanned tree took seconds of CPU per change: on a scan of 20,000 files,
about 3.8 s in beta.4, about 20 ms now, and F3, opening a group and marking no longer wait behind it.
At the top of a large scan the panel no longer reads the whole subtree on every refresh: the first
visit to a directory is one pass over its rows, and going back up or coming back is immediate while
the database does not change.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;marks-show-and-do-what-the-database-holds&quot;&gt;Marks show and do what the database holds&lt;&#x2F;h2&gt;
&lt;ul&gt;
&lt;li&gt;&quot;group files&quot; and &quot;duplicates of the cursor&quot; show a mark as soon as it is saved; before, a new mark
appeared only after the cursor left the group and came back.&lt;&#x2F;li&gt;
&lt;li&gt;F9 → &quot;Clear all marks&quot; asks first and then clears every saved mark of the scan, keepers and marks
set in an earlier session included. Before, it cleared one panel on screen while the database kept
every mark, and F11 still built its plan from them.&lt;&#x2F;li&gt;
&lt;li&gt;A mark on a path that is not valid UTF-8 is refused, and the status says why: a scan never records
such a path, and the mark could land on another file whose path looks the same.&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;h2 id=&quot;the-manual&quot;&gt;The manual&lt;&#x2F;h2&gt;
&lt;p&gt;The backup and restore scripts in §12 work for databases from 0.9.0-beta.1 and beta.2, step 8 of the
quickstart can be followed with the keys it names, and the sample screens and the &lt;code&gt;--purge-quarantine&lt;&#x2F;code&gt;
output match the program.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;what-it-does-not-establish&quot;&gt;What it does not establish&lt;&#x2F;h2&gt;
&lt;ul&gt;
&lt;li&gt;Unicode formatting characters in names, such as bidirectional overrides and zero-width characters,
are not escaped; a terminal with bidi support may reorder a row that shows one.&lt;&#x2F;li&gt;
&lt;li&gt;A command-line argument that is not valid UTF-8 still stops dedcom with a panic at start-up.&lt;&#x2F;li&gt;
&lt;li&gt;A plan saved as &lt;code&gt;.sh&lt;&#x2F;code&gt; holds the real bytes of the names, as it must to run, so &lt;code&gt;cat&lt;&#x2F;code&gt; of that file
still passes an escape sequence in a name to the terminal. The Commands tab shows them escaped.&lt;&#x2F;li&gt;
&lt;li&gt;The first visit to a very large directory still costs one pass over its rows, seconds at the top of
a pool of millions of files.&lt;&#x2F;li&gt;
&lt;li&gt;In &quot;duplicates of the cursor&quot; each step of the cursor over a file reads that file&#x27;s group again:
about 70 ms on a scan of a million files, on this project&#x27;s test machine.&lt;&#x2F;li&gt;
&lt;li&gt;A files panel shows only the marks set while it was open. Marks saved earlier show in &quot;group files&quot;
and on the F11 confirmation.&lt;&#x2F;li&gt;
&lt;li&gt;For a name that is not valid UTF-8, F3 and &quot;duplicates of the cursor&quot; can answer for another file
whose name reads the same. Planned for 0.9.2.&lt;&#x2F;li&gt;
&lt;li&gt;If the F2 check of saved scans answers while the F9 menu is open, its window replaces the menu, and
an Enter meant for the menu opens that scan&#x27;s results, or resumes its unfinished scan. Planned for
0.9.2.&lt;&#x2F;li&gt;
&lt;li&gt;The manual (§11.4) says a name that is not valid UTF-8 is exported with &lt;code&gt;U+FFFD&lt;&#x2F;code&gt;; a scan does not
record such a name, so the export does not hold it. Corrected in 0.9.2.&lt;&#x2F;li&gt;
&lt;li&gt;Hardlink and reflink refuse names longer than about 213 bytes, and a path longer than 4,096 bytes
stops the scan. Both are planned for 0.9.2.&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;h2 id=&quot;on-upgrade&quot;&gt;On upgrade&lt;&#x2F;h2&gt;
&lt;p&gt;&lt;code&gt;--storage-type&lt;&#x2F;code&gt; accepts only &lt;code&gt;hdd&lt;&#x2F;code&gt;, &lt;code&gt;ssd&lt;&#x2F;code&gt; and &lt;code&gt;nvme&lt;&#x2F;code&gt;; any other value is now an error. A
&lt;code&gt;--state-dir&lt;&#x2F;code&gt; that dedcom does not recognise as its own is refused: point it to a new or empty
directory. beta.4 databases open as they are. From beta.1 to beta.3 the first start upgrades
&lt;code&gt;dedcom.db&lt;&#x2F;code&gt; to schema v6, as beta.4 did: back it up first with the procedure in the manual (§12,
&quot;Backing up and restoring &lt;code&gt;dedcom.db&lt;&#x2F;code&gt;&quot;).&lt;&#x2F;p&gt;
&lt;p&gt;With this release, 0.9.0-beta.1 to 0.9.0-beta.4 are no longer supported.&lt;&#x2F;p&gt;
</content>
        
    </entry>
    <entry xml:lang="en">
        <title>DedupCommando v0.9.0-beta.4 release notes</title>
        <published>2026-09-11T00:00:00+00:00</published>
        <updated>2026-09-11T00:00:00+00:00</updated>
        
        <author>
          <name>
            
              Unknown
            
          </name>
        </author>
        
        <link rel="alternate" type="text/html" href="https://dedcom.dequzzy.io/en/changelog/v0.9.0-beta.4/"/>
        <id>https://dedcom.dequzzy.io/en/changelog/v0.9.0-beta.4/</id>
        
        <content type="html" xml:base="https://dedcom.dequzzy.io/en/changelog/v0.9.0-beta.4/">&lt;!-- Generated by scripts&#x2F;sync_docs.py from docs&#x2F;release-notes&#x2F;v0.9.0-beta.4.md at v0.9.2 in the DedupCommando repository. Edit the source, not this file. --&gt;
&lt;p&gt;For anyone running 0.9.0-beta.3. Most of this release is about the checkpoint database,
&lt;code&gt;dedcom.db&lt;&#x2F;code&gt;: how its move journal stores pathnames, and what the shape guard accepts before it
writes anything. One fix changes what you see on disk and in the duplicate list.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;order-and-names-on-disk-no-longer-depend-on-the-machine&quot;&gt;Order and names on disk no longer depend on the machine&lt;&#x2F;h2&gt;
&lt;p&gt;Two habits made one tree come out differently on two machines. Directory order was taken from
&lt;code&gt;readdir&lt;&#x2F;code&gt;, and a file name was rebuilt through a lossy string that maps distinct bytes onto one.
Siblings are now ordered by their raw bytes, and a name keeps the bytes it had. A failed read no
longer counts as an answer to whether two files are duplicates, and a merge that leaves the source
behind is not reported as clean.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;the-checkpoint-moves-to-schema-v6&quot;&gt;The checkpoint moves to schema v6&lt;&#x2F;h2&gt;
&lt;p&gt;The first start of this build upgrades &lt;code&gt;dedcom.db&lt;&#x2F;code&gt; to schema v6 in one transaction. The
&lt;code&gt;move_event&lt;&#x2F;code&gt; journal used to pass pathnames through a lossy UTF-8 conversion, so two names that
differ only in bytes outside UTF-8 collapsed into one record. v6 stores both pathnames as raw bytes
and marks each row with &lt;code&gt;path_fidelity&lt;&#x2F;code&gt;: 1 for rows this build wrote, 0 for rows carried over from
the v5 journal.&lt;&#x2F;p&gt;
&lt;p&gt;The table is rebuilt only after its stored definition and everything attached to it (indexes,
triggers, views, foreign keys) prove to be what this product wrote. Anything else stops the upgrade
with a message, and the data and the schema version stay as they were.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;the-shape-guard-refuses-more-before-it-writes&quot;&gt;The shape guard refuses more before it writes&lt;&#x2F;h2&gt;
&lt;p&gt;Before the database is switched to WAL, the guard now refuses:&lt;&#x2F;p&gt;
&lt;ul&gt;
&lt;li&gt;a product name held in another letter case. SQLite resolves names without regard to ASCII case,
the guard compared them byte for byte, and a foreign &lt;code&gt;SCAN_ROOT&lt;&#x2F;code&gt; could be adopted;&lt;&#x2F;li&gt;
&lt;li&gt;two objects under one product name, such as a table and a trigger;&lt;&#x2F;li&gt;
&lt;li&gt;a hidden or generated column under a product column name, and a column of a later schema in a
checkpoint that declares an older one;&lt;&#x2F;li&gt;
&lt;li&gt;a virtual or shadow table (FTS, R*Tree and the like) under a product table name. It could carry
the right column names and be taken for the product&#x27;s own.&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;p&gt;A refused file keeps its bytes. Objects under names the product does not use, virtual tables
included, are left alone.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;also-in-this-release&quot;&gt;Also in this release&lt;&#x2F;h2&gt;
&lt;p&gt;The manual matches this release: the F9 menu, the wording of the size figure on the confirmation
and summary screens, and the session-retention default. A test now holds the manual to what the
code does. Two paths gained tests: the order of a walk over several roots that cannot be keyed, and
a refused &lt;code&gt;stat&lt;&#x2F;code&gt; while listing files of the same size.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;what-it-does-not-establish&quot;&gt;What it does not establish&lt;&#x2F;h2&gt;
&lt;ul&gt;
&lt;li&gt;Journal rows carried over from v5 keep the text v5 had. Bytes lost then are not recovered, and
those rows are not claimed to be exact.&lt;&#x2F;li&gt;
&lt;li&gt;The journal is still best-effort: a move whose record could not be written is not reported.&lt;&#x2F;li&gt;
&lt;li&gt;The declared types and defaults of required columns are not compared, and a &lt;code&gt;WITHOUT ROWID&lt;&#x2F;code&gt; or
&lt;code&gt;STRICT&lt;&#x2F;code&gt; table under a product name passes as an ordinary table.&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;h2 id=&quot;on-upgrade&quot;&gt;On upgrade&lt;&#x2F;h2&gt;
&lt;p&gt;Back up &lt;code&gt;dedcom.db&lt;&#x2F;code&gt; first with the procedure in the manual (§12, &quot;Backing up and restoring
&lt;code&gt;dedcom.db&lt;&#x2F;code&gt;&quot;). A plain &lt;code&gt;cp&lt;&#x2F;code&gt; misses the &lt;code&gt;-wal&lt;&#x2F;code&gt; file. beta.2 and beta.3 refuse to open a v6 database
before writing to it, so going back means restoring that backup.&lt;&#x2F;p&gt;
</content>
        
    </entry>
</feed>
