Release notes
User-visible changes are recorded here. The command-line interface is a versioned contract: command and option names, exit and error codes, JSON payloads and machine behaviour are classified as Added, Changed, Fixed or Breaking. The release gate reads the same classifications. Desktop changes carry the (GUI) marker. Text output is prose and is included only when omitting it would surprise a reader.
The 1.0.0 section is what the first stable release holds, not the history of the beta builds that led to it: what changed from one beta build to the next is in the GitHub issues each fix closes and in the commits. A change after it is written under an Unreleased section until its release names it.
1.0.1 - 9 November 2026
Security updates until November 2031. RESTORE
handles vulnerabilities in pb-diff 1.0.1 until then, five years from its
release (licence terms, 10.2). Report one to
security@pbdiff.com.
Changed
- Licence terms version 2026-11-09. Four clauses say what a trial now asks for and who receives it: 1.4, 5.1, 7.2 and 7.5; the nine clauses approved specifically are the same. Every computer is asked to accept the new version and approve those clauses once, the next time pb-diff starts, and nothing stops meanwhile.
- (GUI) A trial asks for your name, company and country, in a window of its own. Start a 30-day trial opens the trial's form in its own window, your details on the left and the licence terms on the right, short enough not to scroll on a 1366 x 768 laptop. It asks, besides the e-mail address, for a first name, a last name, a company and a country chosen from a list, all required. A notice under them says that they go to RESTORE S.R.L. and its authorised distributors to follow up the trial, with a link to the privacy policy, and the optional consent box now reads "RESTORE and its authorised distributors may contact me about pb-diff offers and news." pb-diff 1.0.0 cannot send these details: once the licensing service requires them, a trial is started from this version or a later one. Once a trial has started, pb-diff support writes to its address once: when it ends, and the three ways to reach support during it.
1.0.0 - 5 November 2026
Security updates until November 2031. RESTORE
handles vulnerabilities in pb-diff 1.0 until then, five years from its
release (licence terms, 10.2). Report one to
security@pbdiff.com.
The command-line contract is frozen for 1.x. The
commands, options, exit and error codes and JSON payloads of
pb-diff-cli and pb-diff-ci at payload
schemaVersion 1, with the published schemas, change in a
1.x release only by addition; anything that would break a pipeline
written against 1.0 waits for 2.0.
Added
(GUI) Report a Problem. Help ▸ Report a Problem sends a support ticket to RESTORE with this computer's licence, so that no account is needed: a subject, a description and the address to reply to - the licence's own, shown read-only, or for a licence that names none the one the person writes
- with pb-diff's version and commit, the Windows version and the kind
of licence. Only if the person agrees, it also carries what
pb-diff-ci validateandcheckfind in up to three libraries or folders they choose, which names objects and findings and never shows their content, with file names for paths. The window shows the whole report before Send, sends nothing without it, and answers with the ticket's number; when the support service cannot be reached it says so and gives the portal andsupport@pbdiff.com. The Help's new page, Getting help and reporting a problem, says who is served, what to send and never to send, and that a security vulnerability goes tosecurity@pbdiff.com. (#131, #133)
- with pb-diff's version and commit, the Windows version and the kind
of licence. Only if the person agrees, it also carries what
validatechecks many libraries at once. It takes any number of paths: libraries, folders of them, and a workspace, target, Solution or project, which it expands into the libraries they list, in search-path order. Each library ends in one state -valid,warnings,damaged,unreadableormissing(named by a library list and not there) - and the worst one is the exit code. A library too damaged to open is a result rather than the end of the run.--outputand--report-format markdown|html|csvalso write the result for a job summary;pb-diff-ciends it with its footer. The new error codeNO_LIBRARIESrefuses an input that stands for no library. (#114)(GUI) Verify Libraries. Tools ▸ Verify Libraries opens a Library Verification tab that checks many libraries at once - added as files or a folder, dropped on the tab, or taken from both lists of the File Selection - without blocking the window and with a way to stop. Each library is a row with its state, its error and warning counts and its worst finding; the findings of the one selected are listed beneath with the object each is about; Open Library opens it on its own tab with its findings, and Save Report writes the result as Markdown, HTML or CSV. Validate Library on a library's tab no longer holds up the window, and its findings name their object. (#114)
(GUI) The line between the versions can be dragged. In a File Compare, between Old and New, and in a three-way merge, between File1, the Base and File2, the line between two versions moves with the mouse, under a resize cursor, to give one more room; each keeps its line numbers and eight characters, the rows stay aligned and scroll as one. A double-click on the line or View ▸ Equal Column Widths puts them back to equal widths, and Ctrl+Shift+Left and Ctrl+Shift+Right move it from the keyboard. Each tab keeps its own proportion, and a new tab opens with the one the last tab of its kind was left at; Reset Document pane layouts in Options forgets it. (#126)
--ends the options. Every argument after it is a positional taken as written, so a file whose name starts with-or@can be named, and the value of an option is never read as a command file:--line "@x"is the text@x. (#106)pb-diff-ci.exe, the tool for build machines. One file, installed besidepb-diff-cli.exeand shipped in the ZIP, that runs copied on its own to any Windows machine, hosted CI runners included. It needs no licence and never exits 5, makes no network call of any kind, and never shows the content of an object: its commands areinfo,list,validate,compare-lib,reportwith thesummaryandsummary-changeskinds,check,schemaandhelp, each with the name, options, payload and exit codes it has inpb-diff-cli. Every report it writes, but JSON, ends with a line saying it was generated bypb-diff-ciand where the detail of each change is shown. Its usage terms are on the licence page. Thehelpschema'stoolnames either tool.compare-liband the summary reports compare folders. Each side is a library, a folder or a single file - aws_objectsfolder, a PowerBuilder 2025 Solution or one of its PBL folders, a folder of exported objects - compared as the desktop application's Compare does, on the same code: the same rows, the same pairing and the samenoteson names two inputs share, compiled forms set aside and folders not walked.--pairnames a row by object name or by address, andcompare-libtakes the noise options ofcompare:--ignore-case,--ignore-xml-attribute-order,--ignore-xml-namespacesand--ignore-xml-commentsbeside-wand--no-normalise-case. Thesummaryandsummary-changesreports take--mode,--pairand the same options, and list the notes. Each entry of thecompare-libpayload gainsoldPathandnewPath, the address of each side, and the payload gainsnotes.checkreports sources left broken. Over files and folders, it lists the parse diagnostics - conflict markers, an unclosed block or DataWindow statement, a file that is not its format - with the file, the code, the message and the first line, and never a line of the file: exit 0 when nothing was noticed, 1 with findings, 2 for an unusable input, and the new error codeNO_SOURCESfor an input that holds nothing to check. In both tools; its payload is the newcheck.jsonschema.--report-format markdownrenders thesummaryandsummary-changesreports as GitHub-flavoured Markdown for a CI job summary or a pull-request comment, and an--outputfile ending in.mdchooses it. Thereportschema'sreportFormatgainsmarkdown.(GUI) A long compare shows its progress and can be cancelled. The status bar shows the stage, the count and a progress bar while a Compare Summary compares, and Cancel stops it, leaving the summary as it was. (#67)
(GUI) Structured comparison of PowerBuilder objects. pb-diff parses an export into its hierarchy - declarations, variables, functions, events, each control with its properties and scripts - and compares it section by section, so that reordering alone is not a difference and what did change is attached to the item it belongs to. DataWindows are compared down to each property, database column, band and the Retrieve SQL. A query is compared clause by clause - each table, column, join, condition and sort key - and a data pipeline setting by setting and column by column, retrieve query included; a project entry by entry, each library on its own (#58, #59). XML, XAML, JavaScript and INI files are parsed too, and anything else is compared as text. The panes always show the exact source, at every level of the tree. A change minimap, change navigation, the Only Changed Items and Only Changed Lines filters, Line Compare and Clipboard Compare complete the File Compare.
PowerBuilder's build files are compared by what they hold. A resource file (
.pbr) is compared resource by resource, grouped as DataWindow objects, pictures, icons and pointers: the order of its lines is not a difference, and a name whose case changed is, because PowerBuilder matches a resource by its name as written. A workspace (.pbw) is compared target by target and setting by setting, and a target (.pbt) by its application, its projects and its library list, each library named beneath the list and a library moved in the search path reported. A PowerBuilder 2025 solution (.pbsln) is compared project by project, and a project (.pbproj) by its settings, its deploy configurations and its library list. A PowerGen project (.gen) is compared application by application, best-effort. These files are taken from a folder with the objects beside them, and a merge writes one back in the encoding it was read in.parseandcomparereport each with a file type and node kinds of its own, listed in theparseschema. (#60, #61, #62, #63)(GUI) PowerBuilder libraries read directly, and never written.
.pbland.pbdfiles, and the libraries inside a built.exeor.dll, are opened with no PowerBuilder installed: browsed, validated, exported as importable.sr*files, and compared entry by entry in a Compare Summary, against another library or a folder of exports. Renamed objects are paired by hand, never guessed.(GUI) PowerBuilder 2025 Solutions. Their object sources are compared, reported on and merged, and a merged source builds in the Solution. The same object read from a library and from a Solution compares identical, which is the comparison a migration needs.
(GUI) Two-way and three-way merge. A File Compare takes items or single change blocks back from the old version; a three-way merge carries File1's and File2's changes into the Base they share, with Auto Merge, a Merge Actions list and conflicts left to the person merging. Both keep a control's
.createand.destroyscripts and control list consistent, never write conflict markers, and copy byte for byte whatever they did not decide. The result is an export file that PowerBuilder 19 and later imports; no merge writes into a library.(GUI) Integration with the PowerBuilder IDE and source-control clients.
pb-diff.exetakes switches for a diff and merge tool (-SDI,-Mode,-File1,-File2,-Base,-Save,-Detect, command files), compares two folders named on its command line, and opens one comparison session a client can wait on. Tools ▸ Set up TortoiseGit and Tools ▸ Set up TortoiseSVN register pb-diff with those clients and take the registration back.(GUI) Five reports: Comparison, Comparison Changes, Full File, File Changes and Current Script, printed or saved as PDF, HTML, CSV, JSON or RTF.
(GUI) The desktop application. One window of tabs with one menu and one labelled tool bar, New Comparison, File Selection for many objects at once, saved selections, a Recent list of every kind of comparison, light and dark themes, and an Options dialog whose every setting is kept in a readable
settings.json, per user or beside a portable copy.(GUI) A font for the navigation tree. Options ▸ Text settings sets a font family and a size, 6 to 32 point, for the hierarchy tree of the File Compare and of the Three-Way Merge, apart from the editor font. The default is the interface font at 9 point, which draws the tree as before. Pressing OK applies it to the comparisons already open; the rows and the status markers scale with it, the expand arrows and the indentation less, so that a name deep in the tree keeps its room, and the zoom commands leave it alone. In
settings.jsonit isnavigation.fontFamily(null for the interface font) andnavigation.fontSize. (#127)(GUI) pb-diff Help. Short pages, opened by F1 for the tab or dialog in front, with tutorials on the Order Desk examples installed beside it, a page for every window, and an offline search box. The Command-Line Tool Reference, the release notes and the licence share its look.
pb-diff-cli, the headless tool.info,list,export,extract,validate,parse,compare,compare-lib,merge3,report,check,tokens,license,schemaandhelp, with deterministic JSON under--format json, sixteen published schemas, stable exit codes (0 to 5) and error codes, and no window, prompt or settings file.compareandmerge3report what is wrong with an input, such as conflict markers left in it, ininputDiagnostics. An agent skill ships inskills/pb-diff.(GUI) A licence per computer. pb-diff opens no comparison without a valid licence: at start-up it shows the Licence window, and Help ▸ Licence opens it at any time, to activate this computer with a licence key, move a licence from another computer, deactivate it, or check it online. The licence is verified about once a day and holds 7 days without a connection; a licence moved or suspended while pb-diff runs leaves open work alone. Started by a source-control client with no valid licence, pb-diff says why and exits with code 5. Help ▸ Check for Updates, and a check at start-up at most once a day, offer a later version's download page. (#56)
A licence per machine.
pb-diff-cli license status|activate|deactivateshows, activates and frees this machine's licence, with--labelto name the machine and--take-overto move a licence active elsewhere; its payload has the schemalicense. Activation and deactivation fail with exit code 5 andLICENSE_ACTIVE_ELSEWHEREorLICENSING_UNREACHABLE. A build machine needs no licence: it runspb-diff-ci. A licence file given inPBDIFF_LICENSEorPBDIFF_LICENSE_FILEis verified offline on every invocation; such files are RESTORE's own, for its pipelines, and are not offered to customers. (#56)(GUI) A 30-day trial started from the Licence window, and Buy. With no licence, the Licence window offers Start a 30-day trial, Enter a licence key and Buy. The trial asks for an e-mail address, the acceptance of the licence terms and, optionally, the consent to be contacted, and starts at once, with no key and no confirmation e-mail; one trial per e-mail address and per computer, for 30 days from its start. During the trial the window shows the address, when it ends and the days left; Buy opens pbdiff.com/buy, and a bought key replaces the trial on the same computer. Without the licensing service the window gives
info@pbdiff.comto ask for a trial.pb-diff-cliwith no licence says, in the hint ofLICENSE_REQUIRED, that a trial is started from the desktop application and where to buy; a trial that ended says so, and where to buy. (P30)(GUI) The licence terms are accepted, and their clauses specifically approved, apart. The Licence window asks for two confirmations, both required and neither ticked: I accept the pb-diff licence terms, and the specific approval of the clauses Articles 1341 and 1342 of the Italian Civil Code require, listed by number and title as the licensing service states them. They are asked when a trial is started and when a key is activated, unless this computer has already approved the version in force; when the terms change, at the next start, without stopping work. The acceptance - the computer's fingerprint, the terms' version and when each box was ticked - is recorded with the licence. The consent to be contacted is now Contact me about pb-diff., and is RESTORE's alone. (D1, D14)
(GUI) A trademark note. Help ▸ About pb-diff and the Help's Licence page say whose trademarks PowerBuilder, PowerScript and DataWindow are, and that pb-diff is an independent product of RESTORE S.R.L., not affiliated with, endorsed or sponsored by SAP or Appeon.
Breaking
license activaterefuses a machine that has not approved the licence terms in force, with exit code 5 and the new error codeTERMS_APPROVAL_REQUIRED, whose hint points to the desktop application's Licence window, the only place that asks for them. A machine that approved them, with a trial or a key activated there, is not refused; nor is any when the terms cannot be read. A script that activated a key on a fresh machine now does it once in the desktop application. (D1)validatefails when a list or folder it was given could not be followed. A workspace naming a target that is not there, a target or project whose library list cannot be read, or a folder that cannot be read to the bottom used to be a note withvalid: trueand exit 0, so a typo in a workspace passed a gate having verified fewer libraries. Each now makes the run invalid, exit 2, andsummary.unverifiedcounts them. (#121)The
validatepayload lists libraries. It was one container'scontainer,flavour,valid,errorCount,warningCountandfindings; it is nowinputs,valid,summary(the count of libraries in each state),libraries- each withcontainer,state,flavour,listedBy,errorCount,warningCount,findingsand, for an unreadable one,error- andnotes, however many paths were given. A.pblor.pbdthat cannot be read as a library is no longer the errorNOT_A_CONTAINERbut a library in theunreadablestate, with exit code 2 and the payload on standard output. (#114)Output that is not JSON is refused under
--format json, and--stdouttakes no--out.compare --unifiedandexport/extract --stdoutwrite text or bytes, and under--format jsonthey were written anyway, so a caller parsing the JSON it asked for got a patch or an object;--stdoutwith--outwrote the object to standard output and left an empty file. All three are nowBAD_ARGUMENTS. (#106)A folder where a file is expected is not
FILE_NOT_FOUND.info,list,validate,exportandextractanswerNOT_A_CONTAINER, andcompare,parse,tokens,merge3and the file reportsBAD_ARGUMENTSwith a hint namingcompare-lib; the folder is there, and "not found" sent a reader looking for a typo.tokensgiven a whole PowerBuilder library, or a file together with--line, isBAD_ARGUMENTStoo: the library's directory blocks came back as thousands of lines of text, and the file was ignored. (#106)An option that takes no value never takes the next argument.
check --quiet a.srw b.srwused to reada.srwas the value of--quietand check onlyb.srw; now both are checked. A flag is given a value only after an equals sign,--quiet=false;--quiet false, which was never documented, now readsfalseas an input. (#81)Too many inputs are refused. A command given more positional arguments than it takes exits 2 with
BAD_ARGUMENTS, where the extra ones used to be ignored:compare a b ccomparedaandb. (#81)A named entry that is not there is
ENTRY_NOT_FOUND.list,exportandextractexit 2 when an exact entry they were given does not exist, wherelistreturned an empty list andextractdropped it; a wildcard that matches nothing is still an empty, successful selection. (#81)merge3'swrittensays whether a file was written. It is false without--output, where it used to say that a result existed. (#81)The
compare-libpayload names its two sidesoldandnew, each an object with thepathgiven and itskind(library,folderorfile), in place of the two strings that named the containers, oldContainer and newContainer;compare-lib.jsonsays so. A side is no longer always a container.Every command but
help,schemaandlicenseneeds a valid licence. Without one it exits with the new exit code 5 andLICENSE_REQUIRED, before it reads an input or writes an output, and the message says why and how to activate. The error envelope's schema,error.json, gains the licence codes (LICENSE_REQUIRED,LICENSING_UNREACHABLE,LICENSE_ACTIVE_ELSEWHERE) and lists every code the two tools report,NO_SOURCESamong them. (#56)The headless executable is
pb-diff-cli.exe, no longerpbdiff.exe. The package ships only the new name, and the installer's PATH option points to it.compare,merge3,parseand the reports of one object refuse a library named on its own, with exit code 2 andBAD_ARGUMENTS: a library holds objects and is not one. Usecompare-lib,list, a library report, or an entry address such asui.pbl!w_main.srw.The report schema's kind no longer lists
ComparisonCurrentFilter. The sixth report was withdrawn; the five official reports remain.
Changed
A DataWindow line the parser does not recognise is kept and compared, as a text node, where it used to be dropped;
checkandparsereport it with the new diagnostic codeparse.unrecognised-line, which carries the line number and none of its text, so a file holding one is no longer clean andcheckexits 1 for it. (#84)helplists-?and the languagestokens --langtakes.-?was accepted beside--helpand-hwithout being in the catalogue, and--langnow carries its seven values invalues. (#112)merge3 -wand--ignore-casesay what they do to a merge. Under them a change only in whitespace or in case is not a change, so the merge keeps the base's text for it; their descriptions inhelpnow say so, and the reference explains it. The behaviour is unchanged and now tested in both side orders. (#111)--outis written only when the command has run to its end. The file used to be created before the command ran, so one that failed - a missing input, a refused entry - left an empty file in place of the previous result. It is now written in one replacement once the command finishes, whatever its exit code, and a command that fails with an error leaves it as it was. (#106)A DataWindow band's controls group is keyed by its band. In
parseandcompare, the group of a band's controls isgroup:<band>:controls-group:detail:controls,group:header:controls- instead of one
group:controlsper band, which made every three-way merge of a DataWindow report the key as duplicated indiagnostics. (#99)
- instead of one
merge3calls a merge carrying conflict markers unclean. pb-diff never writes a marker, but a merge copies what it did not decide, and markers one of the three files already had reached the output of a merge reported clean, with exit code 0, and onlyinputDiagnosticssaying so. Now the merge exits 3 with amerge.conflict-markerswarning and writes nothing unless--force-partialis given;warnings[].codegains that value in themerge3schema. The desktop three-way merge saves such a result with the same warning. (#100, 3W-13)compare-libcompares a library's objects from their exports, as the desktop application does. An object whose comment alone changed, or the OLE data of its.bincompanion alone, is now modified, wherecompare-libread the bare payload and called it unchanged while the desktop said modified. The two engines are one.The meanings of exit codes 0 and 1 name
check: 0 is also nothing noticed, and 1 also findings, in the catalogue and every table.A
parse.fallbackmessage no longer quotes an exception's text. XML that is not well formed is reported with its line and position, and a parser that failed with its exception's type, so that no message can carry a line of the file (checkshows them where no text of an object may appear).(GUI) The Licence window says that a licence file given in the environment is for the command line only, rather than naming a CI licence: CI licences are no longer offered, since a CI job runs
pb-diff-ci, which needs none.(GUI) The Compare Summary's notes never hide the grid. They are grouped by kind and side under a count, in a band that scrolls rather than grows and folds with Hide notes. A few hundred notes used to push the grid out of the window. (#66)
(GUI) A folder compare takes each object once. A
.pbdbeside the.pblit was built from, a compiled object whose source is on the same side, and PowerBuilder'sws_objectscopy below the folder compared no longer make every name ambiguous: the source is compared, and a note says what was left out. Two folders of one application used to pair almost nothing. (#65)A file is the format its extension names. A
.pblor.pbdthat is not a PowerBuilder library is refused by every command withNOT_A_CONTAINERand the reason, wherecompare,parseandreportansweredBAD_ARGUMENTS; a build file or an object export whose content is not its format is compared as text with aparse.fallbackdiagnostic, and never read as another format guessed from its content. (GUI) Such a library opens nothing and is named, and a folder compare leaves it out and lists it in its notes, beside any file compared as text because it is not its format. (#64)--detect Htypes the build files from their content when a name no longer says what a file is, as Git's temporary files do not: a workspace, a target, a Solution, a project and a PowerGen project. (GUI) Tools ▸ Set up TortoiseGit and Set up TortoiseSVN register pb-diff as the diff and merge tool for.pbw,.pbt,.pbsln,.pbproj,.pbrand.gentoo. (#60-#63)A Solution's
.pbslnand.pbprojare build files, not generic XML.parseandcomparereport them as the file typesSolutionandSolutionProjectinstead ofXml, with projects, settings, deploy configurations and libraries for nodes; the XML options no longer apply to them, and a folder of sources now includes them.merge3changes only what it decided in them. (#62)A workspace (
.pbw) or target (.pbt) is compared, no longer refused.compare,parse,report,merge3andtokensread one as they read any other file, where they exited with code 2 andBAD_ARGUMENTS; a command that reads a library answersNOT_A_CONTAINERfor one, as for any file that is not a library.@app.pbwis still refused as a command file, with exit code 2 andBAD_ARGUMENTS. The desktop application compares them on every route, and keeps refusing one named as a command file. (#61)A PowerBuilder 2025 Solution's PBL folder is named for what it is.
info,list,export,extractandvalidateanswerNOT_A_CONTAINERinstead ofFILE_NOT_FOUND;compare,parse,merge3and the object reports answerBAD_ARGUMENTS, with a hint namingcompare-lib, which compares the folder. The exit code, 2, is unchanged.merge3 --outputkeeps a UTF-8 Base in UTF-8, as a PowerBuilder 2025 Solution source is, with its byte-order mark or its absence and no$PBExportHeader$added; every other Base is written UTF-16LE with a byte-order mark and CRLF. The desktop merges save the same way.--detect Htypes a PowerBuilder 2025 Solution source from its content, which has no export header, except an object inherited from another object of the application.reportrenders from fixed defaults, not from the desktop settings, so one command line gives the same bytes on every machine, and its object reports print the exact source the compare panes show.
Fixed
(GUI) The update check and the Licence window say what is really wrong. When this computer's clock is more than five minutes from the licensing service's, its answers cannot be trusted, and the window and the update check said the service could not be reached; they now say that the clock is wrong, as
pb-diff-clialready did. A beta build under a licence that is offered releases only was told it was the latest version; it is now told that this licence is offered releases only. A trial hears of beta builds until the first stable release.(GUI) The tool bar fits the window it is in. The tab's commands went under the chevron all together, and in the default window the File Compare's eleven did, because the window's client area is narrower than the window. Each command now goes on its own: in a window too narrow for every label, the buttons show their icons alone - the four change-navigation buttons first - keeping their names and tool tips, and only then do commands go under the chevron, one at a time from the end. The overflow opens inside the window in one column, Ctrl+F reaches the search box when it is there, and the two filters read Changed Items and Changed Lines. (#128)
A DataWindow merge puts what a side added first where that side put it. A control written first after
table(...)went inside the table, beforeretrieve=; a new first column went after the table's closing parenthesis; deleting the first column lefttable(on a line of its own. All three merged clean into a DataWindow PowerBuilder will not import. A new node now follows the node that ends last before it, and a node that opens its container goes on the container's first line; restoring a deleted DataWindow object in a two-way merge no longer appends its properties as stray lines. (#116)A DataWindow column reorder is merged, or is a conflict. Moving a column changes no node, so a reorder merged to the Base's order and was called clean, and with the control ids it swapped the result was inconsistent. A side's reorder is now copied with that side's whole table when the other side left the table alone, and is a conflict when the other side changed the table or reordered it differently. The derived DataWindow overview no longer conflicts when both sides add a column. (#117)
Repeated XML elements and INI sections no longer lose a change. Siblings that share a name are told apart by position, so when both sides changed such a group - each deleting a different
<item>- one deletion was lost in a merge called clean; it is now a conflict. An INI section written twice is two sections, each with a key of its own (section:name[2]from the second on), where only the first used to be merged. A CDATA section no longer makes its element one unit, so unrelated changes beside it merge. (#118)validate --outputnever writes over a file the inputs stand for. Given a workspace or a Solution, it compared--outputonly with the paths as given, and wrote the result over a target or project the workspace listed. It now refuses, withBAD_ARGUMENTS, every workspace, target, Solution and project it read and every library it reached; the desktop's Save Report does the same. (#119)Smaller
validatefaults. A workspace target number too large for one is a list that cannot be read, where it wasINTERNAL_ERROR; a path a list names is spelled with the list's own separators; a note'sdetailis described per kind in the schema and the reference; and the Help's example oversrc\*.pbluses PowerShell, since no shell expands the wildcard for it. (#121)The release run can accept a candidate. The private library-series tests skipped on the hosted runner and the gate refuses every skip but Inno Setup's; they now carry
kind=privateand the release run excludes them. A rehearsal is no longer stopped by the placeholder licence page, and the checklist names the three items a tagless rehearsal refuses by design. The coverage the release ratchet reads is measured in Debug, as its baseline is. (#120)Merges keep more of what one side wrote, and conflict less over controls. A comment written straight after
end variables,end functionor any block terminator gained a blank line before it; a block taken from a side after the whole conflict had been taken from the other dropped both sides' other changes; and two sides that each added a different control to one window conflicted over the forward declarations, the instance variables and the create and destroy scripts, which are now merged with both controls and rewritten consistently. (#122)Faults in what pb-diff reads are said for what they are.
validateon a library appended to an image reportedINTERNAL_ERRORfor a pointer before the library; it is now the errorBLOCK_BEFORE_CONTAINER. An XML file nested past the limit is compared as text at once, where a hundred thousand levels took minutes. A library another program holds isIO_ERRORincompare-liband the summary reports, as ininfoandlist. An entry whose chain is shorter than it declares isIO_ERRORinparseandcomparerather than read short, and a library compare adds the noteunreadable-entrybeside its row. A block an object holds once, written twice, is kept twice under its own key with the diagnosticparse.duplicate-block. A compiled library is judged by what it holds, so one appended to an image getsBITMAP_UNMAINTAINEDas a.pbddoes. (#123)(GUI) Smaller Library Verification and Library tab defects. Paths that stand for no library, given as the tab opens, are noted at once; the list of inputs scrolls instead of pushing the grid off the tab; a drop while a verification runs says to wait or stop; the previous run's counts and notes are cleared when a new one starts; Refresh is unavailable on a Library tab while it validates; a library that cannot be listed keeps saying so when opened from a verification; and the File Selection's button reads Verify Libraries. (#124)
Two stale sentences. The reference no longer describes
license statusas showing "the CI licence in use", and a merge refused because a file was read as text says why, the parser's own reason included, so a file nested past the limit is not taken for a malformed one. (#125)An HTML report is the same bytes on every machine. Its stylesheet took the line endings of the checkout pb-diff was built from, so one input could give a report with CRLF inside the
<style>block and another with LF; it is LF throughout, as the rest of the report always was (AI-3).Smaller CLI faults. (#106)
- An empty argument or option value - an unset variable in a script -
is
BAD_ARGUMENTS; it wasINTERNAL_ERROR, exit 4, reported as a bug in pb-diff. - An output path naming a file stream (
app.pbl:notes) is refused: it wrote into the library's file past the library check (D09). compare --unifiedno longer counts a phantom empty last line, and prints nothing for two files the compare calls identical, where it printed the spacing it normalises away with exit code 0. The reference no longer calls it a patch: its line numbers are the pretty-print's.compare-liband the summary reports give back paths under a folder named with/written with/, not with\joined on.license activateanddeactivaterefuse an--outnaming a library before they contact the service, and a clock five minutes or more from the service's is reported as a clock problem, not as an unreachable service.- The compare schema says that hunk and row line numbers count within the node.
- An empty argument or option value - an unset variable in a script -
is
External function declarations are compared and merged. A window or user object that declares external functions in
type prototypesand also has functions of its own lost the first block: both became the oneprototypesnode, the second replaced the first, and a changed declaration - another DLL, another alias - compared as identical whilemerge3kept the base and reported a clean merge. The declarations are now their own node, keyexternalfunctions, kindExternalFunctions, inparse,compare,merge3and the reports, and in the desktop application. (#96, PRS-3)A merge of a DataWindow column keeps it inside
table(...). A decision about one table column - a changeddbname, a column added or deleted on one side - composed the wholetablestatement again, and the column went after the parenthesis that closes it, in a merge reported clean: a DataWindow PowerBuilder does not import. The statement is now rewritten in place: the text of the column that changed is replaced where it stands, an added column goes after the one before it with that line's indentation, and every other character is copied. Inmerge3and in the desktop two-way and three-way merges. (#97, MRG-5)A merge rewrites only what it decided. A routine, script, declaration block or DataWindow statement the merge takes from one side is now that side's text exactly - its trailing blanks, its continuation lines, its own line breaks - and one both sides changed where they do not overlap keeps every line neither touched. Composed from the parsed lines, they lost trailing blanks and had continuations joined; a
this.Control[]list written over several lines came back on one. Nodes added together at one point - a menu item'stypeblock and its scripts - are written in the order the side that added them wrote them: a new menu item's scripts used to come before itstypeblock. An added block keeps the blank lines its side wrote around it - a comment written directly above a function stays directly above it - and one added last in a file no longer leaves a blank line after it. Over the Order Desk objects, a change made on one side now merges to that side's file byte for byte, in both side orders. (#99, MRG-5)A merged INI, XML or text file keeps its encoding. Only a PowerBuilder object export is saved in UTF-16LE; any other file - INI, XML, text, as build files already did - is saved in the encoding it was read in. An ANSI file came back UTF-16LE from a merge that decided nothing, and an XML file whose declaration still named
windows-1252was no longer well formed. Inmerge3and in both desktop merges. (#99, S10)A change inside a control or an XML element the other side deleted is a conflict. An event added to a button one side removed, or an element added inside one it removed, merged cleanly and was written without its container - after the window's own scripts, where the event read as the window's - or lost with it. It is now a conflict in
merge3and both desktop merges, and a partial result leaves it out with its container, saying so in itsmerge.unresolved-conflictmessage; an event or element kept in the desktop merge while its container is left out is reported, the result is not clean, and nothing is written in the wrong place. (#99, 3W-8)(GUI) A control kept against the side that deleted it is created again. In the three-way merge, keeping a button one side deleted - its property block and its events - left the
.createand.destroyscripts as that side merged them, without the button, and the merge raised no warning: the object would not compile. The scripts are now brought back into line with the controls the merged object holds whichever side they came from, acreatecall taken without its control included, and the declarations the merge does not invent are reported undermerge.undeclared-control. (#115, MRG-4, 3W-12)An XML element one side changed is merged as that side wrote it. A decided element no longer gains
xmlns=""or a repeated default namespace, a self-closing tag stays self-closing, an element added on a line of its own gets a line of its own with its side's indentation, and a deleted one no longer leaves a line of blanks. A change made on one side of an XML file now merges to that side's file byte for byte. (#99, MRG-5)Four changes the parsers missed are now found. (#105)
- A PowerScript block comment holding another one no longer ends at
the inner
*/: comments nest, and a nested comment holdingend functioncut the routine short, so a change after it was filed under the wrong node. - In a
.pbprojor.pbsln, every attribute of a library, every comment in the library list and the attributes of the root element are compared and merged; only the paths were, and a merge kept the base for the rest. The root element is a node of its own. - White space at the start of a line that continues a string with
&is part of the string, as PowerBuilder reads it, and no longer folded away in a variables or property block. - DataWindow
group(...)statements are paired bylevel, so removing the first group is that group deleted rather than the second one modified; their key isstatement:group:<level>.
- A PowerScript block comment holding another one no longer ends at
the inner
Deeply nested XML no longer crashes or fails as an internal error. An XML file a few kilobytes long nested about 1,500 deep overflowed the stack and ended the process - in a folder compare too, and in the desktop application - and one nested 30 deep made
parseandcompare --format jsonexit 4 withINTERNAL_ERROR. A file nesting elements more than 200 deep is now compared as text, with aparse.fallbackdiagnostic saying so, and JSON payloads are written to any depth the parsers produce. (#104)Three-way merges no longer lose a change and call the result clean. Three cases did, in
merge3and in the desktop three-way merge (#98):- an item deleted on one side and changed on the other is a conflict, and so is a line added beside lines the other side deleted - a key added to an INI section the other side removed used to land in the section before it;
merge3refuses withMERGE_REFUSEDwhen one of the three files could not be read as its format and was read as text, where it was merged node by node against the other two and wrote neither version;- taking a change block in a conflict also takes the changes either side made in that item where they do not conflict, which were dropped while the item read as resolved.
Smaller desktop and report defects. (#108)
- (GUI) Answering No to the exit question - with Confirm on exit on, or an unsaved merge open - no longer stops the comparisons the window is still running.
- (GUI) Saving a report or a file selection over a PowerBuilder
library is refused, as saving a merge already was (D09); a report saved
as
app.pblreplaced the library. - (GUI) With a node selected that holds no content of its own - a deleted control - the status bar counts it as the first changed item beneath it instead of saying "Changed item 0".
- (GUI) Options accepted while a comparison is running reach it: it runs again when it finishes.
- (GUI) Enter in the Options number boxes keeps the value typed.
- (GUI) In New Comparison, one file dropped at a time fills File1, Base and File2 in that order.
- The Comparison Changes Report's header counts the whole comparison:
it said "Unchanged 0" when the objects it leaves out were unchanged. In
report summary-changesand in the desktop.
(GUI) A three-way merge shows what it will save. Refreshing with F5 after a decision, and confirming the discard, left the old rows and the discarded merged text on show while Save wrote the new session; typing in the merged pane then saved the stale text as an edit. The new session is now shown. A refresh that fails leaves nothing of the old merge behind, and closing the tab no longer asks about work that is gone. Coming back to a three-way tab - after a Line Compare, or from another merge - keeps the change the reader was on and the block the next take lands on; it moved to the item's first change, and Take from Right could take the whole item. (#107)
A library another program has open can be read, and one that cannot be read is said to be so. Libraries were opened refusing any other writer, so while the PowerBuilder IDE held a workspace's libraries no command could read them, and
compare-libcalled such a library "not a PowerBuilder library" (NOT_A_CONTAINER). They are now read whoever else has them open; a file that really cannot be read - locked exclusively, access denied - isIO_ERROR. In the desktop application an access-denied library ended the program with every unsaved merge in it; it is now reported, and an error nothing else caught is reported too, with the program kept running so that the work can be saved. (#103)A damaged library is no longer read short or called healthy. (#102)
exportandextractrefuse, withIO_ERRORand before writing anything, an entry whose data chain holds fewer bytes than it declares; they wrote a short file under its name with exit code 0. The desktop Export does the same. Reading and comparing are unchanged.validatereportsCHAIN_SHAREDwhen two entries' chains reach one block - export wrote one object's bytes under the other's name, and such a library validated - and walks each block once.- A
FRE*chain longer than a file of that size needs isBITMAP_READ, where following it let a hostile 70 MB file take 1.5 GB of memory. - A
.pbdwhose bitmap marks only an empty library's blocks is the warningBITMAP_UNMAINTAINED, not the errorBITMAP_UNMARKED_BLOCK: Appeon's ownpb_sdk.pbdships that way.
validateaccepts the libraries PowerBuilder appends to EXE and DLL files. Their free-block bitmap numbers the library's own blocks, from where the library starts in the file, and it was read as numbering the file's: every library inside a built executable - PowerBuilder's ownpbvm.dllincluded - was reported withBITMAP_UNMARKED_BLOCKand exit code 2. In both tools and in the desktop application. (#101, PBL-1)validateno longer fails healthy libraries that have grown past 2 MB. Each bitmap block after the first is now found by following the chain from the one before, where PowerBuilder put it. Before, it was assumed to be at a fixed offset, a multiple of 0x1F8000, and a block that happened to be free there was reported asBITMAP_UNMARKED_BLOCK, with exit code 2, on libraries PowerBuilder saves and builds without complaint. A block in use that is marked free is still an error, wherever it is, and so is a bitmap block that does not mark itself. When more than one block is unmarked, the finding'soffsetis the lowest of them. In both tools. (#95)(GUI) A screen reader reads the comparison line by line. In the File Compare and the three-way merge, moving the caret from the keyboard - Up and Down, Page Up and Page Down, Ctrl+Home and Ctrl+End, the next or previous change, Find - makes Narrator or NVDA read the row: its side, its line number, whether it is unchanged, added, deleted, modified or in conflict, and its text as the file holds it; on a fold, how many unchanged lines it hides and that Enter expands it. The mouse announces nothing. (#90, GEN-12)
A merge of a file compared as text keeps its line endings. In a file whose lines end differently - CRLF, LF and CR mixed - the lines a merge did not decide keep their own endings, and a decided line keeps the one its side had, where they were all given one. (#93)
PDF reports show every script. A PDF report embeds the subset of Sarasa Mono SC its text uses, so Chinese, Japanese, Korean, Cyrillic, Greek and every accented Latin letter print as they are, where everything outside Windows-1252 used to become
?and a change from one such character to another vanished. The font ships once with the product, infonts/. A character the font has no glyph for, or every character outside Windows-1252 when the font is not there -pb-diff-cicopied on its own - is written as<U+XXXX>and the first page says so, pointing to the HTML report. Identical inputs still give identical bytes. (#80)CSV reports cannot run formulas. A cell that would start with
=,+,-or@, or with a tab or a carriage return, is written with a leading', so a spreadsheet shows it as text instead of evaluating it; counts and sizes are unchanged. (#79)An address in a folder whose name holds
!works. The desktop application and every CLI command that takes an entry address -list,export,extract,parse,compare,merge3and the library checks - readC:\release!1\ui.pbl!w_main.srwas the objectw_main.srwofC:\release!1\ui.pbl, where the path used to be cut at its first!; a whole library in such a folder is refused as a library, and a missing one is named in full inFILE_NOT_FOUND. (#89, #94)(GUI) Smaller fixes. Line Compare scrolls long inputs inside their boxes; clicking the minimap of a folded comparison goes to the change clicked; copying one side no longer adds blank lines where the other side had lines; moving the window to a monitor of another scale redraws the comparison sharply; the minimap follows a change of highlight colours; Find and change navigation leave a focused fold; double-clicking a column header of the object picker no longer accepts it; a closed licence dialog no longer keeps a typed key in memory; swapping the sides of a selection swaps its renamed pairs too; and the Options choices are captioned in words. (#89)
pb-diff-ciandcheckshow nothing of a.pbslnor.pbprojthey refuse. The refusal of a file whose root element is not the one expected named that element; it now names only what was expected. (#78)The CLI refuses what it should.
tokensrefuses a library given as a file withNOT_A_CONTAINER, asparsedoes;--resync-threshold NaNand a--kindother thansource,compiledorresourceareBAD_ARGUMENTS; an enormous--contextis limited to the file instead of failing withINTERNAL_ERROR; andlist --commentsshows the comments in its text output too. (#81)No output replaces a PowerBuilder library.
--out,--outputandmerge3 --outputrefuse a library or a file that holds one, and--outandreport --outputalso a path that is one of the inputs, with exit 2 andBAD_ARGUMENTS, before the file is opened:--outused to truncate it first. A merge in place,merge3 --outputnaming one of its inputs, is still allowed. The desktop application refuses library destinations for Save, Save As and-Save.exportandextractrefuse an entry name that is not a plain file name, and two entries that would be written to the same file, withIO_ERRORbefore writing anything; the desktop export refuses an entry name that would leave the chosen folder. A merge is written to a temporary file and then moved into place, so a failed write leaves the existing file as it was. (#70)Three-way merge. An insertion at the start of a block the other side replaced no longer brings the replaced lines back; taking one block of a script no longer counts every conflict of that script as resolved, so a save is clean only when each conflict has a decision; and an insertion chosen inside a replaced block gives the same result on screen as in the saved file. (#71)
Restoring or removing a menu item keeps the menu's
Item[]registration, instead of adding aControl[]list a menu does not have. (#72)XML merges write well-formed XML, copying what the merge did not decide, compact
.pbslnand.pbprojfiles included; and text moved across a child element in mixed content is a difference. (#73)A malformed library is reported, not a crash. An impossible payload length, a directory entry that runs past its block and a data block off its alignment are structural errors with their codes, and the CLI answers exit 2 and
NOT_A_CONTAINERwhere it answered 4 andINTERNAL_ERROR. Painter calls nested more than 128 deep are kept as text rather than recursed into. (#75)(GUI) Merge work is not lost. Typing in the three-way merged text, and undoing or resetting a decision after a save, mark the document as unsaved; Undo and Redo in the merged text act on the merge; and merge decisions and Compare Summary pairings are unavailable while a refresh runs, instead of being replaced by it. (#74)
(GUI) A saved report or selection that is not well formed is refused with a message instead of failing when it is shown, and an invalid window position or editor font size in the settings is ignored or brought into range. (#75)
(GUI) A narrow compare pane at a large zoom no longer stops the window. (#76)
Licence deactivation and renewal fail safely.
license deactivateand a takeover succeed only when the service confirms the deletion, or answers that the machine is already gone; any other answer keeps this machine's activation and reports the failure, where a signed error used to be taken as success. A renewal still in flight when the licence is deactivated or activated again no longer brings the old certificate back. A renewal that cannot be saved, or a licence store held by another process, leaves the current licence in force instead of failing the command withIO_ERROR, and the licence check keeps to its time limit; a suspension or an expiry the service reports still takes effect at once. (#87)(GUI) Setting up TortoiseGit or TortoiseSVN can always be undone. The previous value of every setting is saved before the first one is changed, and after each one, so a failure or an interrupted update part-way through still leaves everything restorable. (#88)
Library comparisons see every change, and only once. A compiled object, a resource and a Solution
.bincompanion are compared byte for byte, never as text, so two payloads that differ only in their line ends are no longer reported unchanged. A summary counts an object as modified when its comment or its companion changed, as the detailed comparison does. Two rename requests naming the same object are reported as unresolved instead of pairing it twice. The metadata comparison mode still compares sizes and dates only. (#82)Diff output is exact. A unified diff with no context gives an insertion or a deletion the line positions
patchandgit applyexpect; an unchanged row whose two sides differ only in what an option ignores carries each side's own text, incompare --format jsonas on screen; Copy as Unified Diff honours the comparison's options; and a cancelled compare stops while it is still reading the folders. (#83)Parsing keeps what it used to lose. Spaces inside a quoted path in a workspace, a target or a PowerGen project are compared as written; an
end functioninside a block comment no longer ends the function; a multi-line JavaScript template no longer ends a function at a}it holds; a BOM-less UTF-16 export is recognised with non-ASCII characters near its start; and a merge of a BOM-less UTF-16 build file that decided nothing gives back the same bytes. (#84)An XML merge keeps the chosen order of a new element and the comments, processing instructions and text around it. (#92)
The installer gives back only what it changed. Uninstalling, or clearing Add to PATH on a reinstall, removes the PATH entry the installer added and no other - an entry that was already there stays - and a PATH that did not exist is removed rather than left empty. The file associations restore every value they replaced, the open command and the icon included, and delete only the keys they created. (#86)
The CLI compatibility check covers both tools' schema constraints. It refuses a manifest for the other tool, detects changes to published date formats, numeric bounds and array minima, and refuses schema vocabulary it does not understand instead of calling the change prose-only. Explicitly open output enumerations may grow as AI-22 permits. (#69)
(GUI) The notes groups and the progress bar follow the theme. They were drawn in Windows' own style - white filled circles and green on a near-white track, the brightest things in a Dark window. The groups now carry the tree's chevron and the bar is a thin accent line that sweeps while the amount of work is not known. (#68)
(GUI) A notes group heading shows
ws_objectsas written. The heading was read as an access key and lost its underscore, so it saidwsobjects. (#66)--quietsuppresses successful human-readable output, where it used to do nothing. JSON, raw payloads, errors and files named with--outare unchanged, and it takes precedence over--verbose.(GUI) The package ships no framework translations. The interface is English only, and WPF's own texts no longer appear in the language of the Windows it runs on. (#55)
Pre-1.0 development
Tags v0.1.0 through v0.9.3 are development
checkpoints, not public releases, and so are the beta builds from 0.10.0
on, which are not tagged. Their history is in the commits, the GitHub
issues and the decision records under docs/decisions/; it
is intentionally not reproduced here.