Skip to content

Search & replace: dry run, page-only scope, skip symbolic links - #1639

Open
DominikNetcare wants to merge 27 commits into
unclebob:masterfrom
netcare-io:feature/replace-dry-run
Open

DominikNetcare wants to merge 27 commits into
unclebob:masterfrom
netcare-io:feature/replace-dry-run

Conversation

@DominikNetcare

@DominikNetcare DominikNetcare commented Nov 2, 2026 •

Copy link
Copy Markdown
Contributor

Summary

Makes search & replace safer:

  • Dry run: a Dry run button on both replace forms (dryRun input) lists the pages a replacement would change, with the changed lines before and after side by side and the replaced words highlighted. Nothing is saved.
  • Scope: the replace forms choose between this page (searchScope=pageOnly) and this page and children (default, as before).
  • Symbolic links: replace skips symbolic links unless Include symbolic links is checked (includeSymLinks). Replacing through a link changes the linked page itself, which is often outside the selected page and may be reached more than once, so a replacement could even be applied twice.

Depends on #1638, please merge after it (stacked on #1638, #1637 and #1636, shows their commits too until they are merged); the changes of this PR alone: netcare-io/fitnesse@feature/search-wildcards...feature/replace-dry-run

After #1638 is merged, I'll update this branch right away (merging master into it), so it merges cleanly.

Changes

  • Dry run:
    • the replacing observers compute the new content without saving (ContentReplacer), ChangedLines diffs old and new content line by line and the changed lines word by word;
    • the results get a Changes column (not sortable) with a before/after table per page;
    • the title starts with Dry run (nothing is saved);
    • Dry run is a <button>, which the themes show as secondary button, and being the first button it is also what pressing enter does.
  • Scope: searchScope=pageOnly searches/replaces the selected page only; it also works for search via REST. The parent pages and root still work for replace via REST, but are not offered on the form.
  • Symbolic links: checkbox on both replace forms, off by default.
  • Save as link: the results offered "To save this search as a link…" also for replace, where the link was empty (the form posts) and following it would replace again. Now it is only offered for searches with a query string.
  • Docs: includeSymLinks, dryRun and pageOnly added to the RestfulServices page, release notes added.

Behaviour change

Replace no longer follows symbolic links unless includeSymLinks is given.

No performance impact (replace always runs with one thread).

Tests

Unit tests for ChangedLines (changed, added and removed lines, word marks, escaping), dry run for text and method replace (shows changes, saves nothing), the scope options, symbolic links on and off, and the save-as-link condition. Unit tests and FitNesse.SuiteAcceptanceTests pass.

Text, method and properties search no longer follow symbolic links
unless "Include symbolic links" is checked (includeSymLinks input).
Following links finds the same pages again under other paths and can
make searches on large wikis slow. A search that starts at a symbolic
link still covers the linked pages.

The search results show whether a page is reached via a symbolic link,
mark every symbolic link in the page path and report how long the
search took.
Search and replace uses the search code, which now skips symbolic links by default. Replace has always changed the pages behind symbolic links too, and its form offers no option to include them, so it keeps doing so.
Finding the children of a page read the page data (content and
properties) to look up symbolic links, so even a title search read
every page file. Pages can now list their children without symbolic
links (WikiPage.getChildrenWithoutSymbolicLinks), which file based
pages do from the directory listing alone. Traversals use it when the
pruning strategy skips symbolic links.

Also stop looking up a symbolic link for every non-page file in a page
directory: symbolic links are collected from the properties anyway.

On a wiki with 50,000 pages, title search went from ~2.5s to ~1.3s.
Text, method and properties search accept a searchThreads input (1-4,
"Threads" on the search form). With more than one thread, the children
and page data of the pages to visit are loaded ahead on worker threads,
while pages are still matched and reported one at a time, on the
request thread and in the same order. 1, the default, searches
sequentially as before.

On a wiki with 50,000 pages, 4 threads made content and properties
search about 2.3 times and title search about 1.9 times faster.

# Conflicts:
#	src/fitnesse/responders/search/ResultResponder.java
The REST API still searches with one thread unless searchThreads is given.
Replace changes pages while traversing them, so pages must not be loaded ahead.
With "Use wildcards" checked (useWildcards input), an asterisk in the
search text matches any characters within a line, in title and content
search; * alone finds all pages in the search scope. Without it the
search text is still matched literally.
Replacing through a symbolic link changes the linked page itself, which
is often outside the selected page and may be reached more than once,
so the replacement could even be applied twice. Replace now skips
symbolic links unless "Include symbolic links" is checked on the
replace form (includeSymLinks input), like search.
The "Dry run" button on the replace forms (dryRun input) lists the
pages a replacement would change, each with its changed lines before
and after the replacement side by side, and saves nothing.
Replace already took the searchScope input, but its forms always replaced in the current page and its children.
It is a <button>, which the themes show as secondary button, unlike the primary submit inputs. Being the first button, it is also what pressing enter in the form does.
Before and after lines are compared word by word; the words that are replaced and the words replacing them are highlighted.
The replace forms choose between the current page only (searchScope=pageOnly) and the current page with its children, instead of offering the parent pages like the search form.
The changed lines are shown directly in two result columns, which are not sortable, instead of a table in one column.
The results of search and replace offered to save the request as a link. For replace, which the form posts, that link was empty, and following it would replace again. Now the link is only offered for searches with a query string.
This reverts commit b9d8e7e: one Changes column with a before/after table per page reads better than two separate result columns.
…formance

# Conflicts:
#	FitNesseRoot/FitNesse/ReleaseNotes/content.txt
# Conflicts:
#	FitNesseRoot/FitNesse/ReleaseNotes/content.txt
# Conflicts:
#	FitNesseRoot/FitNesse/ReleaseNotes/content.txt
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.

1 participant