"C:\foo" names the root of drive C, but "C:foo" names foo relative to
whatever the current directory on C happens to be. Classifying the
second as absolute means it is resolved from a root it does not name.
Rejecting absolute names was also the only thing keeping a
drive-relative name out of a patch, so is_safe_patch_path() now refuses
a colon itself. A colon never appears in a Windows file name, since
"file:stream" names an alternate data stream of "file" rather than a
file of that name.
Tests reach for the filesystem helpers exported by the patching code to
check that a file exists or to make a directory for a fixture. Route
those checks through helpers in the library the tests already link, so
that the tests do not care how the patching code spells its filesystem
access.
This keeps a later change to those helpers out of every test file.
Define WIN32_LEAN_AND_MEAN and NOMINMAX privately for every project
target. Include the Shell API after its Windows header prerequisite now
that it is no longer provided transitively.
This reduces the SDK surface and avoids min and max macros without
passing the policy to consumers.
Several compatibility cases use characters that Windows filenames cannot
contain. Explicitly consume the generated patch path in their empty
Windows branches.
Windows reports errors and process metadata with DWORD while the
standard error and test-process interfaces use int. Keep unavoidable
conversions in private Win32 helpers and use explicit casts for the
remaining API boundaries.
Quoted paths encode each filename byte with at most three octal digits.
Accumulate in a wider type and reject values above 0377 instead of
relying on an implicit narrowing conversion and wraparound.
Read strings through their char representation before converting each
value to an unsigned byte. This keeps escaping independent of whether
plain char is signed.
The process helper polls at most stdout and stderr, so its descriptor
count always fits nfds_t. Express that conversion explicitly instead of
relying on platform specific type widths.
Making a temporary directory is only ever done to give a test somewhere
to run. Keeping it in the library the tests already link means the
patching code does not carry an entry point nothing in it uses.
-o specifies where to write the patched contents rather than a file to
modify in place. When a deletion patch produces empty output, write an
empty file instead of deleting the explicitly named output and its
parent directories.
Only an added file had its parent directories created, so renaming or
copying into a directory which does not exist yet failed when the
result could not be staged next to its destination.
Every regular write for a Git patch was held back until the whole input
had been read, so a later patch in the same input could not see what an
earlier one had written. Two commits touching the same file could then
fail to apply, and worse, the second patch then committed the file it
had read unchanged, silently undoing the first.
Only a rename or a copy needs to be held back, so that two renames which
swap a pair of files both read the original content. Install everything
else straight away.
Fuzz can match a hunk whose trailing context runs past the end of the
file, since that context is dropped rather than compared. Those lines
were still written out from the input, reading it out of range, and the
resulting std::out_of_range escaped as the diagnostic "**** vector".
Skip context and removals which have no line left to consume.
A hunk with no surrounding context is placed wherever its range claims
it belongs, which may be beyond the end of the file, or before the start
of it once an offset from an earlier hunk is taken into account.
Applying such a patch read the input lines out of range, and the
resulting std::out_of_range escaped as the diagnostic "**** vector".
Clamp the guess into the file, so that a location is always somewhere
the applier can write.
Both paths which remove a file did so without a backup, so -b silently
preserved nothing. A backup was only ever taken by
write_patched_result_to_file, and only for the output path, which is not
reached at all when nothing is written.
Take the backup on both paths while the file is still there.
A name taken from a patch was used as is, so an absolute name or one
which kept a '..' component after -p stripping could read and write
anywhere on the filesystem.
This seems to have been settled on as 'unsafe', so refuse such paths
after stripping, so that stripping may still turn an unsafe name into a
safe one.
A file mode holds a file type above its permission bits, and POSIX
requires selecting that type with S_IFMT and comparing it for equality.
is_symlink instead tested whether 0120000's bits were set, which also
matched git's 0160000 "gitlink" mode, since 0120000's type bits are a
subset of 0160000's.
Hunks at the start or end of a file may have less context on that side.
The locator previously ignored this and could apply them elsewhere with
--fuzz=0.
Keep them at the start or end until enough fuzz is allowed.
I think this also explains/fixes the behavior I was previously confused
about when a hunk from the beginning of a file is found later. The
missing leading context marks the beginning of the original file, so
moving the hunk requires fuzz even when the remaining context matches.
The same applies to missing trailing context at the end of a file.
Rejected hunk ranges are adjusted by line-count changes from earlier
hunks before they are written. The failure diagnostic added the same
adjustment again, reporting a line one too large after an insertion.
Use the already-adjusted hunk range when reporting a failure.
GNU comparison was controlled per test executable. That kept process
tests in test_unit from being compared, while enabling comparison there
would also run its unit tests twice.
Introduce COMPAT_TEST for cases that run against both sb_patch and GNU
patch. Keep PATCH_TEST for project-specific behavior and TEST for unit
coverage. Mark stable GNU differences as expected compatibility failures.
Keep host-dependent ed behavior project-only because GNU delegates those
scripts to an external editor.
Merge the normal and conditional PTY tests into one patch_tests runner
now that executable boundaries no longer control GNU comparison. Keep
test_oom separate because it runs patch_oom.
An add patch describes an empty or nonexistent input. When the target
already exists, --force still attempts the hunk, but the user was not
told why the patch no longer applied cleanly. Start reporting a warning
for this situation.
Create the parents of the fully computed backup name before staging
it. This supports nested output paths and backup prefixes that place the
backup in a separate directory tree.
Install library artifacts using GNUInstallDirs variables and rely on
the target install interface for include paths. Add an external consumer
test with non-default library and include directories.
Do not truncate or make a destination writable before its replacement
and requested backup are ready. Stage patched contents in a temporary
file beside the destination. Preserve its mode and rename it into place
only when the replacement is ready.
This leaves a destination unchanged if preparing its replacement or
backup fails, and prevents partially written contents from becoming
visible. Git-style writes remain deferred while their inputs are needed,
but completed files remain applied if a later patch fails.
Add skip_test(reason), which aborts the running test and reports it as
skipped rather than passed or failed. The single test runner exits with 77
for a skip, and CTest is told via SKIP_RETURN_CODE 77 to mark it skipped.
run_all_tests counts and prints skips separately.
This lets tests that require a capability the environment may lack (such as
creating symbolic links without privilege) opt out cleanly instead of
failing.
Add native parsing and application for scripts produced by diff -e.
Support numeric append, change, and delete commands, including ranges,
along with the s/.// command emitted when inserted text begins with a
literal dot.
Detect ed scripts automatically or select them explicitly with -e.
Execute commands sequentially against an in-memory copy of the input,
avoiding a dependency on the external ed utility and preventing invalid
addresses from partially rewriting the destination.
This intentionally only implements the grammar emitted by diff -e rather
than the complete ed command language. Ed scripts contain no original
text or file names, so the target normally needs to be supplied as an
operand, and reversing with -R is unsupported (as mentioned by POSIX).
The --ifdef option is also rejected for ed patches.
Commit fcf718f stopped inferring additions from empty old ranges when
file headers were present. As a result, patches creating files without a
/dev/null header were classified as changes, leaving the nonexistent
destination unselected and causing patch to prompt for a file.
Restore addition inference for zero-start old ranges while retaining the
rule that an empty new range with named headers only empties the file
rather than deleting it.
File::open replaced the current FILE pointer without first closing it,
leaking the previous handle. It also retained the cached EOF and error
state, so successfully reopening a File after reaching EOF could leave
the new stream unusable.
Open the new file before modifying the existing stream. On success,
close the previous handle, install the new one, and reset the cached
state. If opening fails, preserve the existing handle, position, and
stream state.
Current call sites only open default-constructed File objects, so this
is a latent public API issue rather than a bug triggered by existing
usage.
Normal diff ranges specify inclusive start and end line numbers. We were
storing the old range endpoint directly as the number of lines, causing
a range such as 5,7c5,7 to expect seven old lines instead of three and
reject an otherwise valid patch.
Convert inclusive endpoints to line counts using checked arithmetic for
both old and new ranges. Reject descending ranges and ranges whose line
count would overflow.
A unified diff that removes all lines from a file uses a new file range of
0,0, but that does not mean the file should be deleted. We were inferring
Operation::Delete from that hunk header alone, so applying such a patch
removed the target file entirely instead of leaving it as an empty file.
Match traditional patch behavior by only inferring add/delete from /dev/null
headers when file paths are present, and keep the old hunk-range fallback for
headerless cases.
Since dry run was not writing to the output file, the file size was
being determined as non-empty. To fix this, simply check for this on the
temporary file which has been written to, even on a dry run.
It seems that for this case we are assuming that everything is fine, and
will apply the patch even though:
1. The file already exists! (GNU patch seems to warn for this case)
2. Even after confirming that we do not mind that the file already
exists, we do not detect that there the content in that file
_should_ cause that patch to fail.
The cmdline parser can now be used for parsing commandlines other than
just what we expect for patch. This is done by factoring out a class
that has a virtual process_option function. Inheriting command line
handlers can implement this to interpret the given option in the
application specific method.
In particular, this has the nice side effect of slightly simplifying
parse_git_header_name to not have a dead check for the git header.
We are also able to remove LineParser::end() and LineParser::current()
with these changes.
Finally, this allows us to get rid of the substr with magic number
checks when parsing file names.
Instead of our previous approach of aborting with an exception. While
making this change, also handle a few more cases of the mode string
being invalid.