Skip to content

fix(cmd,csync): anchor --exclude patterns at the sync root regardless of filename#10284

Open
mosandlt wants to merge 1 commit into
nextcloud:masterfrom
mosandlt:fix-exclude-cli-anchor-basepath
Open

fix(cmd,csync): anchor --exclude patterns at the sync root regardless of filename#10284
mosandlt wants to merge 1 commit into
nextcloud:masterfrom
mosandlt:fix-exclude-cli-anchor-basepath

Conversation

@mosandlt

@mosandlt mosandlt commented Jul 2, 2026

Copy link
Copy Markdown
Contributor

Summary

nextcloudcmd --exclude <path> silently produced zero exclusions for every pattern in the file, unless <path> happened to be literally named sync-exclude.lst — with no warning or error anywhere. Any other filename (a typo'd extension, a descriptive name, a per-tool name like myapp-exclude.lst) caused every single pattern to be matched against the wrong base directory, so none of them ever excluded anything under the sync root.

Root cause

ExcludedFiles::addExcludeFilePath() in src/csync/csync_exclude.cpp anchors patterns either at the sync root (_localPath) or at the exclude file's own containing directory, based on a filename check: literally sync-exclude.lst → sync root, anything else → own directory.

That heuristic was introduced in 5788f35 (2020-12-09, "Skip sync exclude file from list of exclude files if it doesn't exist") to merge two previously-separate code paths: the GUI's global exclude list (always named sync-exclude.lst, lives outside any sync folder, needs sync-root anchoring) and a per-directory .sync-exclude.lst discovered during traversal (needs anchoring at its own directory). It was a side effect of that merge, not a deliberate choice — the commit's own description and linked issue are unrelated to anchoring behavior.

src/cmd/cmd.cpp passes the CLI's --exclude value into the same function unconditionally, so it inherited this GUI-specific heuristic even though CLI users can name their exclude file anything.

Reported independently by multiple users, never properly root-caused:

Fix

Add an explicit anchorToLocalPath parameter to addExcludeFilePath() (default false, preserving current behavior everywhere else) and set it for the CLI's --exclude option in cmd.cpp, so its patterns are always anchored at the sync root regardless of the file's name.

Test plan

  • New unit test: testAddExcludeFilePath_anchorToLocalPath_bypassesFilenameHeuristic
  • Full ExcludedFilesTest suite: 25 passed, 0 failed
  • Manual repro against a live Nextcloud account: an --exclude file not named sync-exclude.lst previously had zero effect (excluded directories were downloaded anyway); after this fix they are correctly skipped (FileIgnored) with no behavior change for GUI-managed exclude lists

Checklist

AI (if applicable)

@mosandlt mosandlt force-pushed the fix-exclude-cli-anchor-basepath branch from 46bca62 to 770e9e8 Compare July 2, 2026 04:46

@mgallien mgallien left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nice idea
please have a look at my comments and let me know if things are not clear

Comment thread src/csync/csync_exclude.cpp Outdated
Comment thread src/csync/csync_exclude.h Outdated
Comment thread test/testexcludedfiles.cpp Outdated
@mgallien

mgallien commented Jul 2, 2026

Copy link
Copy Markdown
Collaborator

the PR description is missing some parts that are in the template including things relevant for our AI policy
can you add this back:

Checklist

AI (if applicable)

@mosandlt mosandlt force-pushed the fix-exclude-cli-anchor-basepath branch from 770e9e8 to 1e0a8af Compare July 2, 2026 10:12
@mosandlt

mosandlt commented Jul 2, 2026

Copy link
Copy Markdown
Contributor Author

Added the checklist back, including the AI disclosure section and an Assisted-by: ClaudeCode:claude-sonnet-5 trailer on the commit (force-pushed). Let me know if anything else is missing.

@mgallien

mgallien commented Jul 2, 2026

Copy link
Copy Markdown
Collaborator

Added the checklist back, including the AI disclosure section and an Assisted-by: ClaudeCode:claude-sonnet-5 trailer on the commit (force-pushed). Let me know if anything else is missing.

thanks for the quick reply
please also have a look at my review comments on the code itself

@mosandlt mosandlt force-pushed the fix-exclude-cli-anchor-basepath branch from 1e0a8af to e5db10e Compare July 2, 2026 17:44
@mosandlt

mosandlt commented Jul 2, 2026

Copy link
Copy Markdown
Contributor Author

Addressed all three review comments:

  • Replaced the bool anchorToLocalPath param with a scoped ExcludedFiles::ExcludeFileAnchor enum (FileDirectory / SyncRoot) for readability at call sites.
  • Kept the doc comment on addExcludeFilePath() in csync_exclude.h, updated for the enum.
  • Trimmed the redundant issue-reference comment in the test file (context is in the commit message).

Also rebased onto current master to pick up #10283 (which landed the missing---exclude-file fatal-error check) and merged that with this fix's sync-root anchoring in cmd.cpp. Force-pushed.

@mosandlt mosandlt force-pushed the fix-exclude-cli-anchor-basepath branch from e5db10e to a52f79a Compare July 4, 2026 19:05
@mosandlt

mosandlt commented Jul 4, 2026

Copy link
Copy Markdown
Contributor Author

Rebased onto current master. Also just double-checked against your 3 review comments — all already addressed in the current HEAD (the fixes must have landed after your review, before you could see the update):

  • csync_exclude.cpp: parameter is now an enum class (ExcludeFileAnchor::FileDirectory / SyncRoot) instead of a bool
  • csync_exclude.h: addExcludeFilePath() now has a full doc comment explaining the anchoring heuristic and when to use SyncRoot
  • testexcludedfiles.cpp: trimmed the test comment down to 3 lines describing only what the test does, moved the issue-number/root-cause context to the commit message where it already lived

Let me know if anything still needs another pass.

@mgallien

mgallien commented Jul 9, 2026

Copy link
Copy Markdown
Collaborator

Rebased onto current master. Also just double-checked against your 3 review comments — all already addressed in the current HEAD (the fixes must have landed after your review, before you could see the update):

* csync_exclude.cpp: parameter is now an enum class (ExcludeFileAnchor::FileDirectory / SyncRoot) instead of a bool

* csync_exclude.h: addExcludeFilePath() now has a full doc comment explaining the anchoring heuristic and when to use SyncRoot

* testexcludedfiles.cpp: trimmed the test comment down to 3 lines describing only what the test does, moved the issue-number/root-cause context to the commit message where it already lived

Let me know if anything still needs another pass.

@mosandlt
sorry for the delay
I understood that the PR will introduce a behavior changes in some specific cases
can you add a new command line option for the new behavior while keeping the former behavior for the existing command line option ?
I am pretty sure this PR will break existing setup

@mgallien mgallien force-pushed the fix-exclude-cli-anchor-basepath branch from a52f79a to 0900c00 Compare July 9, 2026 07:35
@github-actions

github-actions Bot commented Jul 9, 2026

Copy link
Copy Markdown
Contributor

Artifact containing the AppImage: nextcloud-appimage-pr-10284.zip

Digest: sha256:affaf0ccf1d363c894f907aa56f6e47f70723650632500742760b3eb2385d7a0

To test this change/fix you can download the above artifact file, unzip it, and run it.

Please make sure to quit your existing Nextcloud app and backup your data.

Summary
nextcloudcmd --exclude <path> silently produced zero exclusions for
every pattern in the file, unless <path> happened to be literally
named "sync-exclude.lst" -- with no warning or error anywhere. Any
other filename (a typo'd extension, a descriptive name, a per-tool
name like myapp-exclude.lst) caused every single pattern to be
matched against the wrong base directory, so none of them ever
excluded anything under the sync root.

Root cause
ExcludedFiles::addExcludeFilePath() in src/csync/csync_exclude.cpp
anchors patterns either at the sync root (_localPath) or at the
exclude file's own containing directory, based on a filename check:
literally "sync-exclude.lst" -> sync root, anything else -> own
directory. That heuristic was introduced in 5788f35 (2020-12-09,
"Skip sync exclude file from list of exclude files if it doesn't
exist") to merge two previously-separate code paths: the GUI's
global exclude list (always named sync-exclude.lst, lives outside
any sync folder, needs sync-root anchoring) and a per-directory
.sync-exclude.lst discovered during traversal (needs anchoring at
its own directory). It was a side effect of that merge, not a
deliberate choice -- the commit's own description and linked issue
are unrelated to anchoring behavior.

src/cmd/cmd.cpp passes the CLI's --exclude value into the same
function unconditionally, so it inherited this GUI-specific
heuristic even though CLI users can name their exclude file
anything. Reported independently by multiple users:
nextcloud#2916, nextcloud#7682, and a comment
diagnosing the exact same filename dependency on nextcloud#2916.

Fix
Added an explicit anchorToLocalPath parameter (now the
ExcludedFiles::ExcludeFileAnchor enum) to addExcludeFilePath()
in csync_exclude.cpp/.h (default FileDirectory, preserving current
behavior everywhere else).

Per @mgallien's review, --exclude's own anchoring behavior is left
unchanged (still FileDirectory / the historic filename heuristic) so
existing setups relying on it, however unintentionally, don't
silently change behavior. Instead cmd.cpp gets a new, separate
--exclude-anchored [file] option that always anchors at the sync
root (ExcludeFileAnchor::SyncRoot) regardless of the file's name --
use it instead of --exclude when patterns aren't matching because
the exclude file isn't literally named "sync-exclude.lst".

Test plan
- [x] New unit test:
      testAddExcludeFilePath_syncRootAnchor_bypassesFilenameHeuristic
      (covers both ExcludeFileAnchor values directly on ExcludedFiles)
- [x] Full ExcludedFilesTest suite: 25 passed, 0 failed
- [x] Manual repro against a live Nextcloud account: an exclude file
      not named sync-exclude.lst had zero effect via --exclude
      (unchanged, by design); passed via --exclude-anchored instead,
      its patterns are correctly applied (FileIgnored) with no
      behavior change for --exclude or GUI-managed exclude lists.

Signed-off-by: Thomas Mosandl <mosandl@concreativ.de>
Assisted-by: ClaudeCode:claude-sonnet-5
@mosandlt mosandlt force-pushed the fix-exclude-cli-anchor-basepath branch from 0900c00 to 01c6791 Compare July 9, 2026 08:14
@mosandlt

mosandlt commented Jul 9, 2026

Copy link
Copy Markdown
Contributor Author

Good point, thanks. Force-pushed: --exclude now keeps its original (pre-fix) filename-based anchoring behavior unchanged, so existing setups aren't affected. Added a new --exclude-anchored [file] option instead, which always anchors at the sync root regardless of the file's name — that's the one to use if patterns aren't matching because the exclude file isn't literally named sync-exclude.lst.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants