test-fix.t
1715 lines
| 44.3 KiB
| text/troff
|
Tads3Lexer
/ tests / test-fix.t
Danny Hooper
|
r37578 | A script that implements uppercasing of specific lines in a file. This | ||
approximates the behavior of code formatters well enough for our tests. | ||||
$ UPPERCASEPY="$TESTTMP/uppercase.py" | ||||
$ cat > $UPPERCASEPY <<EOF | ||||
Martin von Zweigbergk
|
r48565 | > import re | ||
Danny Hooper
|
r37578 | > import sys | ||
> from mercurial.utils.procutil import setbinary | ||||
> setbinary(sys.stdin) | ||||
> setbinary(sys.stdout) | ||||
Matt Harbison
|
r47862 | > stdin = getattr(sys.stdin, 'buffer', sys.stdin) | ||
> stdout = getattr(sys.stdout, 'buffer', sys.stdout) | ||||
Danny Hooper
|
r37578 | > lines = set() | ||
Martin von Zweigbergk
|
r48565 | > def format(text): | ||
> return re.sub(b' +', b' ', text.upper()) | ||||
Danny Hooper
|
r37578 | > for arg in sys.argv[1:]: | ||
> if arg == 'all': | ||||
Martin von Zweigbergk
|
r48565 | > stdout.write(format(stdin.read())) | ||
Danny Hooper
|
r37578 | > sys.exit(0) | ||
> else: | ||||
> first, last = arg.split('-') | ||||
> lines.update(range(int(first), int(last) + 1)) | ||||
Matt Harbison
|
r47862 | > for i, line in enumerate(stdin.readlines()): | ||
Danny Hooper
|
r37578 | > if i + 1 in lines: | ||
Martin von Zweigbergk
|
r48565 | > stdout.write(format(line)) | ||
Danny Hooper
|
r37578 | > else: | ||
Matt Harbison
|
r47862 | > stdout.write(line) | ||
Danny Hooper
|
r37578 | > EOF | ||
$ TESTLINES="foo\nbar\nbaz\nqux\n" | ||||
Matt Harbison
|
r39743 | $ printf $TESTLINES | "$PYTHON" $UPPERCASEPY | ||
Danny Hooper
|
r37578 | foo | ||
bar | ||||
baz | ||||
qux | ||||
Matt Harbison
|
r39743 | $ printf $TESTLINES | "$PYTHON" $UPPERCASEPY all | ||
Danny Hooper
|
r37578 | FOO | ||
BAR | ||||
BAZ | ||||
QUX | ||||
Matt Harbison
|
r39743 | $ printf $TESTLINES | "$PYTHON" $UPPERCASEPY 1-1 | ||
Danny Hooper
|
r37578 | FOO | ||
bar | ||||
baz | ||||
qux | ||||
Matt Harbison
|
r39743 | $ printf $TESTLINES | "$PYTHON" $UPPERCASEPY 1-2 | ||
Danny Hooper
|
r37578 | FOO | ||
BAR | ||||
baz | ||||
qux | ||||
Matt Harbison
|
r39743 | $ printf $TESTLINES | "$PYTHON" $UPPERCASEPY 2-3 | ||
Danny Hooper
|
r37578 | foo | ||
BAR | ||||
BAZ | ||||
qux | ||||
Matt Harbison
|
r39743 | $ printf $TESTLINES | "$PYTHON" $UPPERCASEPY 2-2 4-4 | ||
Danny Hooper
|
r37578 | foo | ||
BAR | ||||
baz | ||||
QUX | ||||
Danny Hooper
|
r37200 | Set up the config with two simple fixers: one that fixes specific line ranges, | ||
and one that always fixes the whole file. They both "fix" files by converting | ||||
letters to uppercase. They use different file extensions, so each test case can | ||||
choose which behavior to use by naming files. | ||||
$ cat >> $HGRCPATH <<EOF | ||||
> [extensions] | ||||
> fix = | ||||
> [experimental] | ||||
> evolution.createmarkers=True | ||||
> evolution.allowunstable=True | ||||
> [fix] | ||||
Matt Harbison
|
r39743 | > uppercase-whole-file:command="$PYTHON" $UPPERCASEPY all | ||
Danny Hooper
|
r40569 | > uppercase-whole-file:pattern=set:**.whole | ||
Matt Harbison
|
r39743 | > uppercase-changed-lines:command="$PYTHON" $UPPERCASEPY | ||
Danny Hooper
|
r37578 | > uppercase-changed-lines:linerange={first}-{last} | ||
Danny Hooper
|
r40569 | > uppercase-changed-lines:pattern=set:**.changed | ||
Danny Hooper
|
r37200 | > EOF | ||
Help text for fix. | ||||
$ hg help fix | ||||
hg fix [OPTION]... [FILE]... | ||||
rewrite file content in changesets or working directory | ||||
Runs any configured tools to fix the content of files. Only affects files | ||||
with changes, unless file arguments are provided. Only affects changed | ||||
lines of files, unless the --whole flag is used. Some tools may always | ||||
affect the whole file regardless of --whole. | ||||
Martin von Zweigbergk
|
r45777 | If --working-dir is used, files with uncommitted changes in the working | ||
copy will be fixed. Note that no backup are made. | ||||
Danny Hooper
|
r37200 | |||
Martin von Zweigbergk
|
r45777 | If revisions are specified with --source, those revisions and their | ||
descendants will be checked, and they may be replaced with new revisions | ||||
that have fixed file content. By automatically including the descendants, | ||||
no merging, rebasing, or evolution will be required. If an ancestor of the | ||||
working copy is included, then the working copy itself will also be fixed, | ||||
and the working copy will be updated to the fixed parent. | ||||
Danny Hooper
|
r37200 | |||
When determining what lines of each file to fix at each revision, the | ||||
whole set of revisions being fixed is considered, so that fixes to earlier | ||||
revisions are not forgotten in later ones. The --base flag can be used to | ||||
override this default behavior, though it is not usually desirable to do | ||||
so. | ||||
(use 'hg help -e fix' to show help for the fix extension) | ||||
options ([+] can be repeated): | ||||
Martin von Zweigbergk
|
r45064 | --all fix all non-public non-obsolete revisions | ||
--base REV [+] revisions to diff against (overrides automatic selection, | ||||
and applies to every revision being fixed) | ||||
-s --source REV [+] fix the specified revisions and their descendants | ||||
-w --working-dir fix the working directory | ||||
--whole always fix every line of a file | ||||
Danny Hooper
|
r37200 | |||
(some details hidden, use --verbose to show complete help) | ||||
$ hg help -e fix | ||||
fix extension - rewrite file content in changesets or working copy | ||||
(EXPERIMENTAL) | ||||
Provides a command that runs configured tools on the contents of modified | ||||
files, writing back any fixes to the working copy or replacing changesets. | ||||
Here is an example configuration that causes 'hg fix' to apply automatic | ||||
formatting fixes to modified lines in C++ code: | ||||
[fix] | ||||
clang-format:command=clang-format --assume-filename={rootpath} | ||||
clang-format:linerange=--lines={first}:{last} | ||||
Danny Hooper
|
r40569 | clang-format:pattern=set:**.cpp or **.hpp | ||
Danny Hooper
|
r37200 | |||
The :command suboption forms the first part of the shell command that will be | ||||
used to fix a file. The content of the file is passed on standard input, and | ||||
Danny Hooper
|
r40568 | the fixed file content is expected on standard output. Any output on standard | ||
error will be displayed as a warning. If the exit status is not zero, the file | ||||
will not be affected. A placeholder warning is displayed if there is a non- | ||||
zero exit status but no standard error output. Some values may be substituted | ||||
into the command: | ||||
Danny Hooper
|
r37200 | |||
{rootpath} The path of the file being fixed, relative to the repo root | ||||
{basename} The name of the file being fixed, without the directory path | ||||
If the :linerange suboption is set, the tool will only be run if there are | ||||
changed lines in a file. The value of this suboption is appended to the shell | ||||
command once for every range of changed lines in the file. Some values may be | ||||
substituted into the command: | ||||
{first} The 1-based line number of the first line in the modified range | ||||
{last} The 1-based line number of the last line in the modified range | ||||
Danny Hooper
|
r43001 | Deleted sections of a file will be ignored by :linerange, because there is no | ||
corresponding line range in the version being fixed. | ||||
By default, tools that set :linerange will only be executed if there is at | ||||
least one changed line range. This is meant to prevent accidents like running | ||||
a code formatter in such a way that it unexpectedly reformats the whole file. | ||||
If such a tool needs to operate on unchanged files, it should set the | ||||
:skipclean suboption to false. | ||||
Danny Hooper
|
r40569 | The :pattern suboption determines which files will be passed through each | ||
Martin von Zweigbergk
|
r43501 | configured tool. See 'hg help patterns' for possible values. However, all | ||
patterns are relative to the repo root, even if that text says they are | ||||
relative to the current working directory. If there are file arguments to 'hg | ||||
fix', the intersection of these patterns is used. | ||||
Danny Hooper
|
r37200 | |||
There is also a configurable limit for the maximum size of file that will be | ||||
processed by 'hg fix': | ||||
[fix] | ||||
Danny Hooper
|
r40568 | maxfilesize = 2MB | ||
Normally, execution of configured tools will continue after a failure | ||||
(indicated by a non-zero exit status). It can also be configured to abort | ||||
after the first such failure, so that no files will be affected if any tool | ||||
fails. This abort will also cause 'hg fix' to exit with a non-zero status: | ||||
[fix] | ||||
failure = abort | ||||
Danny Hooper
|
r37200 | |||
Danny Hooper
|
r40599 | When multiple tools are configured to affect a file, they execute in an order | ||
defined by the :priority suboption. The priority suboption has a default value | ||||
of zero for each tool. Tools are executed in order of descending priority. The | ||||
execution order of tools with equal priority is unspecified. For example, you | ||||
could use the 'sort' and 'head' utilities to keep only the 10 smallest numbers | ||||
in a text file by ensuring that 'sort' runs before 'head': | ||||
[fix] | ||||
Danny Hooper
|
r41162 | sort:command = sort -n | ||
head:command = head -n 10 | ||||
Danny Hooper
|
r40599 | sort:pattern = numbers.txt | ||
head:pattern = numbers.txt | ||||
sort:priority = 2 | ||||
head:priority = 1 | ||||
To account for changes made by each tool, the line numbers used for | ||||
incremental formatting are recomputed before executing the next tool. So, each | ||||
tool may see different values for the arguments added by the :linerange | ||||
suboption. | ||||
Danny Hooper
|
r42372 | Each fixer tool is allowed to return some metadata in addition to the fixed | ||
file content. The metadata must be placed before the file content on stdout, | ||||
separated from the file content by a zero byte. The metadata is parsed as a | ||||
JSON value (so, it should be UTF-8 encoded and contain no zero bytes). A fixer | ||||
tool is expected to produce this metadata encoding if and only if the | ||||
:metadata suboption is true: | ||||
[fix] | ||||
tool:command = tool --prepend-json-metadata | ||||
tool:metadata = true | ||||
The metadata values are passed to hooks, which can be used to print summaries | ||||
or perform other post-fixing work. The supported hooks are: | ||||
"postfixfile" | ||||
Run once for each file in each revision where any fixer tools made changes | ||||
to the file content. Provides "$HG_REV" and "$HG_PATH" to identify the file, | ||||
and "$HG_METADATA" with a map of fixer names to metadata values from fixer | ||||
tools that affected the file. Fixer tools that didn't affect the file have a | ||||
timeless
|
r44500 | value of None. Only fixer tools that executed are present in the metadata. | ||
Danny Hooper
|
r42372 | |||
"postfix" | ||||
Run once after all files and revisions have been handled. Provides | ||||
"$HG_REPLACEMENTS" with information about what revisions were created and | ||||
made obsolete. Provides a boolean "$HG_WDIRWRITTEN" to indicate whether any | ||||
files in the working copy were updated. Provides a list "$HG_METADATA" | ||||
mapping fixer tool names to lists of metadata values returned from | ||||
executions that modified a file. This aggregates the same metadata | ||||
previously passed to the "postfixfile" hook. | ||||
timeless
|
r44500 | Fixer tools are run in the repository's root directory. This allows them to | ||
Danny Hooper
|
r42900 | read configuration files from the working copy, or even write to the working | ||
copy. The working copy is not updated to match the revision being fixed. In | ||||
fact, several revisions may be fixed in parallel. Writes to the working copy | ||||
are not amended into the revision being fixed; fixer tools should always write | ||||
fixed file content back to stdout as documented above. | ||||
Danny Hooper
|
r37200 | list of commands: | ||
fix rewrite file content in changesets or working directory | ||||
(use 'hg help -v -e fix' to show built-in aliases and global options) | ||||
There is no default behavior in the absence of --rev and --working-dir. | ||||
$ hg init badusage | ||||
$ cd badusage | ||||
$ hg fix | ||||
abort: no changesets specified | ||||
Martin von Zweigbergk
|
r46409 | (use --source or --working-dir) | ||
Danny Hooper
|
r37200 | [255] | ||
$ hg fix --whole | ||||
abort: no changesets specified | ||||
Martin von Zweigbergk
|
r46409 | (use --source or --working-dir) | ||
Danny Hooper
|
r37200 | [255] | ||
$ hg fix --base 0 | ||||
abort: no changesets specified | ||||
Martin von Zweigbergk
|
r46409 | (use --source or --working-dir) | ||
Danny Hooper
|
r37200 | [255] | ||
Fixing a public revision isn't allowed. It should abort early enough that | ||||
nothing happens, even to the working directory. | ||||
$ printf "hello\n" > hello.whole | ||||
$ hg commit -Aqm "hello" | ||||
$ hg phase -r 0 --public | ||||
$ hg fix -r 0 | ||||
Martin von Zweigbergk
|
r47835 | abort: cannot fix public changesets: 6470986d2e7b | ||
Martin von Zweigbergk
|
r44388 | (see 'hg help phases' for details) | ||
Martin von Zweigbergk
|
r46457 | [10] | ||
Danny Hooper
|
r37200 | $ hg fix -r 0 --working-dir | ||
Martin von Zweigbergk
|
r47835 | abort: cannot fix public changesets: 6470986d2e7b | ||
Martin von Zweigbergk
|
r44388 | (see 'hg help phases' for details) | ||
Martin von Zweigbergk
|
r46457 | [10] | ||
Danny Hooper
|
r37200 | $ hg cat -r tip hello.whole | ||
hello | ||||
$ cat hello.whole | ||||
hello | ||||
$ cd .. | ||||
Fixing a clean working directory should do nothing. Even the --whole flag | ||||
shouldn't cause any clean files to be fixed. Specifying a clean file explicitly | ||||
should only fix it if the fixer always fixes the whole file. The combination of | ||||
an explicit filename and --whole should format the entire file regardless. | ||||
$ hg init fixcleanwdir | ||||
$ cd fixcleanwdir | ||||
$ printf "hello\n" > hello.changed | ||||
$ printf "world\n" > hello.whole | ||||
$ hg commit -Aqm "foo" | ||||
$ hg fix --working-dir | ||||
$ hg diff | ||||
$ hg fix --working-dir --whole | ||||
$ hg diff | ||||
$ hg fix --working-dir * | ||||
$ cat * | ||||
hello | ||||
WORLD | ||||
$ hg revert --all --no-backup | ||||
reverting hello.whole | ||||
$ hg fix --working-dir * --whole | ||||
$ cat * | ||||
HELLO | ||||
WORLD | ||||
The same ideas apply to fixing a revision, so we create a revision that doesn't | ||||
modify either of the files in question and try fixing it. This also tests that | ||||
we ignore a file that doesn't match any configured fixer. | ||||
$ hg revert --all --no-backup | ||||
reverting hello.changed | ||||
reverting hello.whole | ||||
$ printf "unimportant\n" > some.file | ||||
$ hg commit -Aqm "some other file" | ||||
$ hg fix -r . | ||||
$ hg cat -r tip * | ||||
hello | ||||
world | ||||
unimportant | ||||
$ hg fix -r . --whole | ||||
$ hg cat -r tip * | ||||
hello | ||||
world | ||||
unimportant | ||||
$ hg fix -r . * | ||||
$ hg cat -r tip * | ||||
hello | ||||
WORLD | ||||
unimportant | ||||
$ hg fix -r . * --whole --config experimental.evolution.allowdivergence=true | ||||
2 new content-divergent changesets | ||||
$ hg cat -r tip * | ||||
HELLO | ||||
WORLD | ||||
unimportant | ||||
$ cd .. | ||||
Fixing the working directory should still work if there are no revisions. | ||||
$ hg init norevisions | ||||
$ cd norevisions | ||||
$ printf "something\n" > something.whole | ||||
$ hg add | ||||
adding something.whole | ||||
$ hg fix --working-dir | ||||
$ cat something.whole | ||||
SOMETHING | ||||
$ cd .. | ||||
Martin von Zweigbergk
|
r48565 | Test that the working copy is reported clean if formatting of the parent makes | ||
it clean. | ||||
$ hg init wc-already-formatted | ||||
$ cd wc-already-formatted | ||||
$ printf "hello world\n" > hello.whole | ||||
$ hg commit -Am initial | ||||
adding hello.whole | ||||
$ hg fix -w * | ||||
$ hg st | ||||
M hello.whole | ||||
$ hg fix -s . * | ||||
$ hg st | ||||
$ hg diff | ||||
$ cd .. | ||||
Danny Hooper
|
r37200 | Test the effect of fixing the working directory for each possible status, with | ||
and without providing explicit file arguments. | ||||
$ hg init implicitlyfixstatus | ||||
$ cd implicitlyfixstatus | ||||
$ printf "modified\n" > modified.whole | ||||
$ printf "removed\n" > removed.whole | ||||
$ printf "deleted\n" > deleted.whole | ||||
$ printf "clean\n" > clean.whole | ||||
$ printf "ignored.whole" > .hgignore | ||||
$ hg commit -Aqm "stuff" | ||||
$ printf "modified!!!\n" > modified.whole | ||||
$ printf "unknown\n" > unknown.whole | ||||
$ printf "ignored\n" > ignored.whole | ||||
$ printf "added\n" > added.whole | ||||
$ hg add added.whole | ||||
$ hg remove removed.whole | ||||
$ rm deleted.whole | ||||
$ hg status --all | ||||
M modified.whole | ||||
A added.whole | ||||
R removed.whole | ||||
! deleted.whole | ||||
? unknown.whole | ||||
I ignored.whole | ||||
C .hgignore | ||||
C clean.whole | ||||
$ hg fix --working-dir | ||||
$ hg status --all | ||||
M modified.whole | ||||
A added.whole | ||||
R removed.whole | ||||
! deleted.whole | ||||
? unknown.whole | ||||
I ignored.whole | ||||
C .hgignore | ||||
C clean.whole | ||||
$ cat *.whole | ||||
ADDED | ||||
clean | ||||
ignored | ||||
MODIFIED!!! | ||||
unknown | ||||
$ printf "modified!!!\n" > modified.whole | ||||
$ printf "added\n" > added.whole | ||||
Danny Hooper
|
r42175 | |||
Listing the files explicitly causes untracked files to also be fixed, but | ||||
ignored files are still unaffected. | ||||
Danny Hooper
|
r37200 | $ hg fix --working-dir *.whole | ||
$ hg status --all | ||||
M clean.whole | ||||
M modified.whole | ||||
A added.whole | ||||
R removed.whole | ||||
! deleted.whole | ||||
? unknown.whole | ||||
I ignored.whole | ||||
C .hgignore | ||||
$ cat *.whole | ||||
ADDED | ||||
CLEAN | ||||
ignored | ||||
MODIFIED!!! | ||||
Danny Hooper
|
r42175 | UNKNOWN | ||
Danny Hooper
|
r37200 | |||
$ cd .. | ||||
Test that incremental fixing works on files with additions, deletions, and | ||||
changes in multiple line ranges. Note that deletions do not generally cause | ||||
neighboring lines to be fixed, so we don't return a line range for purely | ||||
deleted sections. In the future we should support a :deletion config that | ||||
allows fixers to know where deletions are located. | ||||
$ hg init incrementalfixedlines | ||||
$ cd incrementalfixedlines | ||||
$ printf "a\nb\nc\nd\ne\nf\ng\n" > foo.txt | ||||
$ hg commit -Aqm "foo" | ||||
$ printf "zz\na\nc\ndd\nee\nff\nf\ngg\n" > foo.txt | ||||
$ hg --config "fix.fail:command=echo" \ | ||||
> --config "fix.fail:linerange={first}:{last}" \ | ||||
Danny Hooper
|
r40569 | > --config "fix.fail:pattern=foo.txt" \ | ||
Danny Hooper
|
r37200 | > fix --working-dir | ||
$ cat foo.txt | ||||
1:1 4:6 8:8 | ||||
$ cd .. | ||||
Test that --whole fixes all lines regardless of the diffs present. | ||||
$ hg init wholeignoresdiffs | ||||
$ cd wholeignoresdiffs | ||||
$ printf "a\nb\nc\nd\ne\nf\ng\n" > foo.changed | ||||
$ hg commit -Aqm "foo" | ||||
$ printf "zz\na\nc\ndd\nee\nff\nf\ngg\n" > foo.changed | ||||
Danny Hooper
|
r42890 | |||
$ hg fix --working-dir | ||||
$ cat foo.changed | ||||
ZZ | ||||
a | ||||
c | ||||
DD | ||||
EE | ||||
FF | ||||
f | ||||
GG | ||||
Danny Hooper
|
r37200 | $ hg fix --working-dir --whole | ||
$ cat foo.changed | ||||
ZZ | ||||
A | ||||
C | ||||
DD | ||||
EE | ||||
FF | ||||
F | ||||
GG | ||||
$ cd .. | ||||
We should do nothing with symlinks, and their targets should be unaffected. Any | ||||
other behavior would be more complicated to implement and harder to document. | ||||
#if symlink | ||||
$ hg init dontmesswithsymlinks | ||||
$ cd dontmesswithsymlinks | ||||
$ printf "hello\n" > hello.whole | ||||
$ ln -s hello.whole hellolink | ||||
$ hg add | ||||
adding hello.whole | ||||
adding hellolink | ||||
$ hg fix --working-dir hellolink | ||||
$ hg status | ||||
A hello.whole | ||||
A hellolink | ||||
$ cd .. | ||||
#endif | ||||
We should allow fixers to run on binary files, even though this doesn't sound | ||||
like a common use case. There's not much benefit to disallowing it, and users | ||||
can add "and not binary()" to their filesets if needed. The Mercurial | ||||
philosophy is generally to not handle binary files specially anyway. | ||||
$ hg init cantouchbinaryfiles | ||||
$ cd cantouchbinaryfiles | ||||
$ printf "hello\0\n" > hello.whole | ||||
$ hg add | ||||
adding hello.whole | ||||
$ hg fix --working-dir 'set:binary()' | ||||
$ cat hello.whole | ||||
HELLO\x00 (esc) | ||||
$ cd .. | ||||
We have a config for the maximum size of file we will attempt to fix. This can | ||||
be helpful to avoid running unsuspecting fixer tools on huge inputs, which | ||||
could happen by accident without a well considered configuration. A more | ||||
precise configuration could use the size() fileset function if one global limit | ||||
is undesired. | ||||
$ hg init maxfilesize | ||||
$ cd maxfilesize | ||||
$ printf "this file is huge\n" > hello.whole | ||||
$ hg add | ||||
adding hello.whole | ||||
$ hg --config fix.maxfilesize=10 fix --working-dir | ||||
ignoring file larger than 10 bytes: hello.whole | ||||
$ cat hello.whole | ||||
this file is huge | ||||
$ cd .. | ||||
If we specify a file to fix, other files should be left alone, even if they | ||||
have changes. | ||||
$ hg init fixonlywhatitellyouto | ||||
$ cd fixonlywhatitellyouto | ||||
$ printf "fix me!\n" > fixme.whole | ||||
$ printf "not me.\n" > notme.whole | ||||
$ hg add | ||||
adding fixme.whole | ||||
adding notme.whole | ||||
$ hg fix --working-dir fixme.whole | ||||
$ cat *.whole | ||||
FIX ME! | ||||
not me. | ||||
$ cd .. | ||||
Danny Hooper
|
r42890 | If we try to fix a missing file, we still fix other files. | ||
$ hg init fixmissingfile | ||||
$ cd fixmissingfile | ||||
$ printf "fix me!\n" > foo.whole | ||||
$ hg add | ||||
adding foo.whole | ||||
$ hg fix --working-dir foo.whole bar.whole | ||||
bar.whole: $ENOENT$ | ||||
$ cat *.whole | ||||
FIX ME! | ||||
$ cd .. | ||||
Danny Hooper
|
r37200 | Specifying a directory name should fix all its files and subdirectories. | ||
$ hg init fixdirectory | ||||
$ cd fixdirectory | ||||
$ mkdir -p dir1/dir2 | ||||
$ printf "foo\n" > foo.whole | ||||
$ printf "bar\n" > dir1/bar.whole | ||||
$ printf "baz\n" > dir1/dir2/baz.whole | ||||
$ hg add | ||||
adding dir1/bar.whole | ||||
adding dir1/dir2/baz.whole | ||||
adding foo.whole | ||||
$ hg fix --working-dir dir1 | ||||
$ cat foo.whole dir1/bar.whole dir1/dir2/baz.whole | ||||
foo | ||||
BAR | ||||
BAZ | ||||
$ cd .. | ||||
Fixing a file in the working directory that needs no fixes should not actually | ||||
write back to the file, so for example the mtime shouldn't change. | ||||
$ hg init donttouchunfixedfiles | ||||
$ cd donttouchunfixedfiles | ||||
$ printf "NO FIX NEEDED\n" > foo.whole | ||||
$ hg add | ||||
adding foo.whole | ||||
Yuya Nishihara
|
r37816 | $ cp -p foo.whole foo.whole.orig | ||
Yuya Nishihara
|
r37827 | $ cp -p foo.whole.orig foo.whole | ||
Augie Fackler
|
r37611 | $ sleep 2 # mtime has a resolution of one or two seconds. | ||
Danny Hooper
|
r37200 | $ hg fix --working-dir | ||
Yuya Nishihara
|
r37816 | $ f foo.whole.orig --newer foo.whole | ||
foo.whole.orig: newer than foo.whole | ||||
Danny Hooper
|
r37200 | |||
$ cd .. | ||||
Danny Hooper
|
r39003 | When a fixer prints to stderr, we don't assume that it has failed. We show the | ||
error messages to the user, and we still let the fixer affect the file it was | ||||
fixing if its exit code is zero. Some code formatters might emit error messages | ||||
on stderr and nothing on stdout, which would cause us the clear the file, | ||||
except that they also exit with a non-zero code. We show the user which fixer | ||||
emitted the stderr, and which revision, but we assume that the fixer will print | ||||
Danny Hooper
|
r40568 | the filename if it is relevant (since the issue may be non-specific). There is | ||
also a config to abort (without affecting any files whatsoever) if we see any | ||||
tool with a non-zero exit status. | ||||
Danny Hooper
|
r37200 | |||
$ hg init showstderr | ||||
$ cd showstderr | ||||
$ printf "hello\n" > hello.txt | ||||
$ hg add | ||||
adding hello.txt | ||||
Danny Hooper
|
r40568 | $ cat > $TESTTMP/work.sh <<'EOF' | ||
Matt Harbison
|
r37809 | > printf 'HELLO\n' | ||
Danny Hooper
|
r40568 | > printf "$@: some\nerror that didn't stop the tool" >&2 | ||
Danny Hooper
|
r39003 | > exit 0 # success despite the stderr output | ||
Matt Harbison
|
r37809 | > EOF | ||
Danny Hooper
|
r40568 | $ hg --config "fix.work:command=sh $TESTTMP/work.sh {rootpath}" \ | ||
Danny Hooper
|
r40569 | > --config "fix.work:pattern=hello.txt" \ | ||
Danny Hooper
|
r40568 | > fix --working-dir | ||
[wdir] work: hello.txt: some | ||||
[wdir] work: error that didn't stop the tool | ||||
$ cat hello.txt | ||||
HELLO | ||||
$ printf "goodbye\n" > hello.txt | ||||
$ printf "foo\n" > foo.whole | ||||
$ hg add | ||||
adding foo.whole | ||||
$ cat > $TESTTMP/fail.sh <<'EOF' | ||||
> printf 'GOODBYE\n' | ||||
> printf "$@: some\nerror that did stop the tool\n" >&2 | ||||
> exit 42 # success despite the stdout output | ||||
> EOF | ||||
$ hg --config "fix.fail:command=sh $TESTTMP/fail.sh {rootpath}" \ | ||||
Danny Hooper
|
r40569 | > --config "fix.fail:pattern=hello.txt" \ | ||
Danny Hooper
|
r40568 | > --config "fix.failure=abort" \ | ||
> fix --working-dir | ||||
[wdir] fail: hello.txt: some | ||||
[wdir] fail: error that did stop the tool | ||||
abort: no fixes will be applied | ||||
(use --config fix.failure=continue to apply any successful fixes anyway) | ||||
[255] | ||||
$ cat hello.txt | ||||
goodbye | ||||
$ cat foo.whole | ||||
foo | ||||
Danny Hooper
|
r39003 | $ hg --config "fix.fail:command=sh $TESTTMP/fail.sh {rootpath}" \ | ||
Danny Hooper
|
r40569 | > --config "fix.fail:pattern=hello.txt" \ | ||
Danny Hooper
|
r37200 | > fix --working-dir | ||
[wdir] fail: hello.txt: some | ||||
Danny Hooper
|
r40568 | [wdir] fail: error that did stop the tool | ||
Danny Hooper
|
r39003 | $ cat hello.txt | ||
goodbye | ||||
Danny Hooper
|
r40568 | $ cat foo.whole | ||
FOO | ||||
Danny Hooper
|
r39003 | |||
$ hg --config "fix.fail:command=exit 42" \ | ||||
Danny Hooper
|
r40569 | > --config "fix.fail:pattern=hello.txt" \ | ||
Danny Hooper
|
r39003 | > fix --working-dir | ||
[wdir] fail: exited with status 42 | ||||
Danny Hooper
|
r37200 | |||
$ cd .. | ||||
Fixing the working directory and its parent revision at the same time should | ||||
check out the replacement revision for the parent. This prevents any new | ||||
uncommitted changes from appearing. We test this for a clean working directory | ||||
and a dirty one. In both cases, all lines/files changed since the grandparent | ||||
will be fixed. The grandparent is the "baserev" for both the parent and the | ||||
working copy. | ||||
$ hg init fixdotandcleanwdir | ||||
$ cd fixdotandcleanwdir | ||||
$ printf "hello\n" > hello.whole | ||||
$ printf "world\n" > world.whole | ||||
$ hg commit -Aqm "the parent commit" | ||||
$ hg parents --template '{rev} {desc}\n' | ||||
0 the parent commit | ||||
$ hg fix --working-dir -r . | ||||
$ hg parents --template '{rev} {desc}\n' | ||||
1 the parent commit | ||||
$ hg cat -r . *.whole | ||||
HELLO | ||||
WORLD | ||||
$ cat *.whole | ||||
HELLO | ||||
WORLD | ||||
$ hg status | ||||
$ cd .. | ||||
Same test with a dirty working copy. | ||||
$ hg init fixdotanddirtywdir | ||||
$ cd fixdotanddirtywdir | ||||
$ printf "hello\n" > hello.whole | ||||
$ printf "world\n" > world.whole | ||||
$ hg commit -Aqm "the parent commit" | ||||
$ printf "hello,\n" > hello.whole | ||||
$ printf "world!\n" > world.whole | ||||
$ hg parents --template '{rev} {desc}\n' | ||||
0 the parent commit | ||||
$ hg fix --working-dir -r . | ||||
$ hg parents --template '{rev} {desc}\n' | ||||
1 the parent commit | ||||
$ hg cat -r . *.whole | ||||
HELLO | ||||
WORLD | ||||
$ cat *.whole | ||||
HELLO, | ||||
WORLD! | ||||
$ hg status | ||||
M hello.whole | ||||
M world.whole | ||||
$ cd .. | ||||
When we have a chain of commits that change mutually exclusive lines of code, | ||||
we should be able to do incremental fixing that causes each commit in the chain | ||||
to include fixes made to the previous commits. This prevents children from | ||||
backing out the fixes made in their parents. A dirty working directory is | ||||
conceptually similar to another commit in the chain. | ||||
$ hg init incrementallyfixchain | ||||
$ cd incrementallyfixchain | ||||
$ cat > file.changed <<EOF | ||||
> first | ||||
> second | ||||
> third | ||||
> fourth | ||||
> fifth | ||||
> EOF | ||||
$ hg commit -Aqm "the common ancestor (the baserev)" | ||||
$ cat > file.changed <<EOF | ||||
> first (changed) | ||||
> second | ||||
> third | ||||
> fourth | ||||
> fifth | ||||
> EOF | ||||
$ hg commit -Aqm "the first commit to fix" | ||||
$ cat > file.changed <<EOF | ||||
> first (changed) | ||||
> second | ||||
> third (changed) | ||||
> fourth | ||||
> fifth | ||||
> EOF | ||||
$ hg commit -Aqm "the second commit to fix" | ||||
$ cat > file.changed <<EOF | ||||
> first (changed) | ||||
> second | ||||
> third (changed) | ||||
> fourth | ||||
> fifth (changed) | ||||
> EOF | ||||
$ hg fix -r . -r '.^' --working-dir | ||||
$ hg parents --template '{rev}\n' | ||||
4 | ||||
$ hg cat -r '.^^' file.changed | ||||
first | ||||
second | ||||
third | ||||
fourth | ||||
fifth | ||||
$ hg cat -r '.^' file.changed | ||||
FIRST (CHANGED) | ||||
second | ||||
third | ||||
fourth | ||||
fifth | ||||
$ hg cat -r . file.changed | ||||
FIRST (CHANGED) | ||||
second | ||||
THIRD (CHANGED) | ||||
fourth | ||||
fifth | ||||
$ cat file.changed | ||||
FIRST (CHANGED) | ||||
second | ||||
THIRD (CHANGED) | ||||
fourth | ||||
FIFTH (CHANGED) | ||||
$ cd .. | ||||
If we incrementally fix a merge commit, we should fix any lines that changed | ||||
versus either parent. You could imagine only fixing the intersection or some | ||||
other subset, but this is necessary if either parent is being fixed. It | ||||
prevents us from forgetting fixes made in either parent. | ||||
$ hg init incrementallyfixmergecommit | ||||
$ cd incrementallyfixmergecommit | ||||
$ printf "a\nb\nc\n" > file.changed | ||||
$ hg commit -Aqm "ancestor" | ||||
$ printf "aa\nb\nc\n" > file.changed | ||||
$ hg commit -m "change a" | ||||
$ hg checkout '.^' | ||||
1 files updated, 0 files merged, 0 files removed, 0 files unresolved | ||||
$ printf "a\nb\ncc\n" > file.changed | ||||
$ hg commit -m "change c" | ||||
created new head | ||||
$ hg merge | ||||
merging file.changed | ||||
0 files updated, 1 files merged, 0 files removed, 0 files unresolved | ||||
(branch merge, don't forget to commit) | ||||
$ hg commit -m "merge" | ||||
$ hg cat -r . file.changed | ||||
aa | ||||
b | ||||
cc | ||||
$ hg fix -r . --working-dir | ||||
$ hg cat -r . file.changed | ||||
AA | ||||
b | ||||
CC | ||||
$ cd .. | ||||
Abort fixing revisions if there is an unfinished operation. We don't want to | ||||
make things worse by editing files or stripping/obsoleting things. Also abort | ||||
fixing the working directory if there are unresolved merge conflicts. | ||||
$ hg init abortunresolved | ||||
$ cd abortunresolved | ||||
$ echo "foo1" > foo.whole | ||||
$ hg commit -Aqm "foo 1" | ||||
$ hg update null | ||||
0 files updated, 0 files merged, 1 files removed, 0 files unresolved | ||||
$ echo "foo2" > foo.whole | ||||
$ hg commit -Aqm "foo 2" | ||||
$ hg --config extensions.rebase= rebase -r 1 -d 0 | ||||
Martin von Zweigbergk
|
r46356 | rebasing 1:c3b6dc0e177a tip "foo 2" | ||
Danny Hooper
|
r37200 | merging foo.whole | ||
warning: conflicts while merging foo.whole! (edit, then use 'hg resolve --mark') | ||||
Daniel Ploch
|
r45710 | unresolved conflicts (see 'hg resolve', then 'hg rebase --continue') | ||
Martin von Zweigbergk
|
r46430 | [240] | ||
Danny Hooper
|
r37200 | |||
$ hg --config extensions.rebase= fix --working-dir | ||||
abort: unresolved conflicts | ||||
(use 'hg resolve') | ||||
[255] | ||||
$ hg --config extensions.rebase= fix -r . | ||||
abort: rebase in progress | ||||
Kyle Lippincott
|
r45774 | (use 'hg rebase --continue', 'hg rebase --abort', or 'hg rebase --stop') | ||
Martin von Zweigbergk
|
r46444 | [20] | ||
Danny Hooper
|
r37200 | |||
Danny Hooper
|
r42555 | $ cd .. | ||
Danny Hooper
|
r37200 | When fixing a file that was renamed, we should diff against the source of the | ||
rename for incremental fixing and we should correctly reproduce the rename in | ||||
the replacement revision. | ||||
$ hg init fixrenamecommit | ||||
$ cd fixrenamecommit | ||||
$ printf "a\nb\nc\n" > source.changed | ||||
$ hg commit -Aqm "source revision" | ||||
$ hg move source.changed dest.changed | ||||
$ printf "a\nb\ncc\n" > dest.changed | ||||
$ hg commit -m "dest revision" | ||||
$ hg fix -r . | ||||
$ hg log -r tip --copies --template "{file_copies}\n" | ||||
dest.changed (source.changed) | ||||
$ hg cat -r tip dest.changed | ||||
a | ||||
b | ||||
CC | ||||
$ cd .. | ||||
When fixing revisions that remove files we must ensure that the replacement | ||||
actually removes the file, whereas it could accidentally leave it unchanged or | ||||
write an empty string to it. | ||||
$ hg init fixremovedfile | ||||
$ cd fixremovedfile | ||||
$ printf "foo\n" > foo.whole | ||||
$ printf "bar\n" > bar.whole | ||||
$ hg commit -Aqm "add files" | ||||
$ hg remove bar.whole | ||||
$ hg commit -m "remove file" | ||||
$ hg status --change . | ||||
R bar.whole | ||||
$ hg fix -r . foo.whole | ||||
$ hg status --change tip | ||||
M foo.whole | ||||
R bar.whole | ||||
$ cd .. | ||||
If fixing a revision finds no fixes to make, no replacement revision should be | ||||
created. | ||||
$ hg init nofixesneeded | ||||
$ cd nofixesneeded | ||||
$ printf "FOO\n" > foo.whole | ||||
$ hg commit -Aqm "add file" | ||||
$ hg log --template '{rev}\n' | ||||
0 | ||||
$ hg fix -r . | ||||
$ hg log --template '{rev}\n' | ||||
0 | ||||
$ cd .. | ||||
If fixing a commit reverts all the changes in the commit, we replace it with a | ||||
commit that changes no files. | ||||
$ hg init nochangesleft | ||||
$ cd nochangesleft | ||||
$ printf "FOO\n" > foo.whole | ||||
$ hg commit -Aqm "add file" | ||||
$ printf "foo\n" > foo.whole | ||||
$ hg commit -m "edit file" | ||||
$ hg status --change . | ||||
M foo.whole | ||||
$ hg fix -r . | ||||
$ hg status --change tip | ||||
$ cd .. | ||||
If we fix a parent and child revision together, the child revision must be | ||||
replaced if the parent is replaced, even if the diffs of the child needed no | ||||
fixes. However, we're free to not replace revisions that need no fixes and have | ||||
no ancestors that are replaced. | ||||
$ hg init mustreplacechild | ||||
$ cd mustreplacechild | ||||
$ printf "FOO\n" > foo.whole | ||||
$ hg commit -Aqm "add foo" | ||||
$ printf "foo\n" > foo.whole | ||||
$ hg commit -m "edit foo" | ||||
$ printf "BAR\n" > bar.whole | ||||
$ hg commit -Aqm "add bar" | ||||
Danny Hooper
|
r40603 | $ hg log --graph --template '{rev} {files}' | ||
@ 2 bar.whole | ||||
Danny Hooper
|
r37200 | | | ||
Danny Hooper
|
r40603 | o 1 foo.whole | ||
Danny Hooper
|
r37200 | | | ||
Danny Hooper
|
r40603 | o 0 foo.whole | ||
Danny Hooper
|
r37200 | |||
$ hg fix -r 0:2 | ||||
Danny Hooper
|
r40603 | $ hg log --graph --template '{rev} {files}' | ||
o 4 bar.whole | ||||
Danny Hooper
|
r37200 | | | ||
Danny Hooper
|
r40603 | o 3 | ||
Danny Hooper
|
r37200 | | | ||
Danny Hooper
|
r40603 | | @ 2 bar.whole | ||
Danny Hooper
|
r37200 | | | | ||
Danny Hooper
|
r40603 | | x 1 foo.whole | ||
Danny Hooper
|
r37200 | |/ | ||
Danny Hooper
|
r40603 | o 0 foo.whole | ||
Danny Hooper
|
r37200 | |||
$ cd .. | ||||
It's also possible that the child needs absolutely no changes, but we still | ||||
need to replace it to update its parent. If we skipped replacing the child | ||||
because it had no file content changes, it would become an orphan for no good | ||||
reason. | ||||
$ hg init mustreplacechildevenifnop | ||||
$ cd mustreplacechildevenifnop | ||||
$ printf "Foo\n" > foo.whole | ||||
$ hg commit -Aqm "add a bad foo" | ||||
$ printf "FOO\n" > foo.whole | ||||
$ hg commit -m "add a good foo" | ||||
$ hg fix -r . -r '.^' | ||||
$ hg log --graph --template '{rev} {desc}' | ||||
o 3 add a good foo | ||||
| | ||||
o 2 add a bad foo | ||||
@ 1 add a good foo | ||||
| | ||||
x 0 add a bad foo | ||||
$ cd .. | ||||
Similar to the case above, the child revision may become empty as a result of | ||||
fixing its parent. We should still create an empty replacement child. | ||||
TODO: determine how this should interact with ui.allowemptycommit given that | ||||
the empty replacement could have children. | ||||
$ hg init mustreplacechildevenifempty | ||||
$ cd mustreplacechildevenifempty | ||||
$ printf "foo\n" > foo.whole | ||||
$ hg commit -Aqm "add foo" | ||||
$ printf "Foo\n" > foo.whole | ||||
$ hg commit -m "edit foo" | ||||
$ hg fix -r . -r '.^' | ||||
$ hg log --graph --template '{rev} {desc}\n' --stat | ||||
o 3 edit foo | ||||
| | ||||
o 2 add foo | ||||
foo.whole | 1 + | ||||
1 files changed, 1 insertions(+), 0 deletions(-) | ||||
@ 1 edit foo | ||||
| foo.whole | 2 +- | ||||
| 1 files changed, 1 insertions(+), 1 deletions(-) | ||||
| | ||||
x 0 add foo | ||||
foo.whole | 1 + | ||||
1 files changed, 1 insertions(+), 0 deletions(-) | ||||
$ cd .. | ||||
Fixing a secret commit should replace it with another secret commit. | ||||
$ hg init fixsecretcommit | ||||
$ cd fixsecretcommit | ||||
$ printf "foo\n" > foo.whole | ||||
$ hg commit -Aqm "add foo" --secret | ||||
$ hg fix -r . | ||||
$ hg log --template '{rev} {phase}\n' | ||||
1 secret | ||||
0 secret | ||||
$ cd .. | ||||
We should also preserve phase when fixing a draft commit while the user has | ||||
their default set to secret. | ||||
$ hg init respectphasesnewcommit | ||||
$ cd respectphasesnewcommit | ||||
$ printf "foo\n" > foo.whole | ||||
$ hg commit -Aqm "add foo" | ||||
$ hg --config phases.newcommit=secret fix -r . | ||||
$ hg log --template '{rev} {phase}\n' | ||||
1 draft | ||||
0 draft | ||||
$ cd .. | ||||
Debug output should show what fixer commands are being subprocessed, which is | ||||
useful for anyone trying to set up a new config. | ||||
$ hg init debugoutput | ||||
$ cd debugoutput | ||||
$ printf "foo\nbar\nbaz\n" > foo.changed | ||||
$ hg commit -Aqm "foo" | ||||
$ printf "Foo\nbar\nBaz\n" > foo.changed | ||||
$ hg --debug fix --working-dir | ||||
Danny Hooper
|
r37578 | subprocess: * $TESTTMP/uppercase.py 1-1 3-3 (glob) | ||
Danny Hooper
|
r37200 | |||
$ cd .. | ||||
Fixing an obsolete revision can cause divergence, so we abort unless the user | ||||
configures to allow it. This is not yet smart enough to know whether there is a | ||||
successor, but even then it is not likely intentional or idiomatic to fix an | ||||
obsolete revision. | ||||
$ hg init abortobsoleterev | ||||
$ cd abortobsoleterev | ||||
$ printf "foo\n" > foo.changed | ||||
$ hg commit -Aqm "foo" | ||||
Martin von Zweigbergk
|
r47594 | $ hg ci --amend -m rewritten | ||
Danny Hooper
|
r37200 | $ hg --hidden fix -r 0 | ||
abort: fixing obsolete revision could cause divergence | ||||
[255] | ||||
$ hg --hidden fix -r 0 --config experimental.evolution.allowdivergence=true | ||||
Martin von Zweigbergk
|
r47594 | 2 new content-divergent changesets | ||
Danny Hooper
|
r37200 | $ hg cat -r tip foo.changed | ||
FOO | ||||
$ cd .. | ||||
Test all of the available substitution values for fixer commands. | ||||
$ hg init substitution | ||||
$ cd substitution | ||||
$ mkdir foo | ||||
$ printf "hello\ngoodbye\n" > foo/bar | ||||
$ hg add | ||||
adding foo/bar | ||||
$ hg --config "fix.fail:command=printf '%s\n' '{rootpath}' '{basename}'" \ | ||||
> --config "fix.fail:linerange='{first}' '{last}'" \ | ||||
Danny Hooper
|
r40569 | > --config "fix.fail:pattern=foo/bar" \ | ||
Danny Hooper
|
r37200 | > fix --working-dir | ||
$ cat foo/bar | ||||
foo/bar | ||||
bar | ||||
1 | ||||
2 | ||||
$ cd .. | ||||
The --base flag should allow picking the revisions to diff against for changed | ||||
files and incremental line formatting. | ||||
$ hg init baseflag | ||||
$ cd baseflag | ||||
$ printf "one\ntwo\n" > foo.changed | ||||
$ printf "bar\n" > bar.changed | ||||
$ hg commit -Aqm "first" | ||||
$ printf "one\nTwo\n" > foo.changed | ||||
$ hg commit -m "second" | ||||
$ hg fix -w --base . | ||||
$ hg status | ||||
$ hg fix -w --base null | ||||
$ cat foo.changed | ||||
ONE | ||||
TWO | ||||
$ cat bar.changed | ||||
BAR | ||||
$ cd .. | ||||
If the user asks to fix the parent of another commit, they are asking to create | ||||
an orphan. We must respect experimental.evolution.allowunstable. | ||||
$ hg init allowunstable | ||||
$ cd allowunstable | ||||
$ printf "one\n" > foo.whole | ||||
$ hg commit -Aqm "first" | ||||
$ printf "two\n" > foo.whole | ||||
$ hg commit -m "second" | ||||
$ hg --config experimental.evolution.allowunstable=False fix -r '.^' | ||||
Martin von Zweigbergk
|
r47836 | abort: cannot fix changeset, as that will orphan 1 descendants | ||
Martin von Zweigbergk
|
r47782 | (see 'hg help evolution.instability') | ||
Martin von Zweigbergk
|
r46457 | [10] | ||
Danny Hooper
|
r37200 | $ hg fix -r '.^' | ||
1 new orphan changesets | ||||
$ hg cat -r 2 foo.whole | ||||
ONE | ||||
$ cd .. | ||||
Danny Hooper
|
r38609 | The --base flag affects the set of files being fixed. So while the --whole flag | ||
makes the base irrelevant for changed line ranges, it still changes the | ||||
meaning and effect of the command. In this example, no files or lines are fixed | ||||
until we specify the base, but then we do fix unchanged lines. | ||||
$ hg init basewhole | ||||
$ cd basewhole | ||||
$ printf "foo1\n" > foo.changed | ||||
$ hg commit -Aqm "first" | ||||
$ printf "foo2\n" >> foo.changed | ||||
$ printf "bar\n" > bar.changed | ||||
$ hg commit -Aqm "second" | ||||
$ hg fix --working-dir --whole | ||||
$ cat *.changed | ||||
bar | ||||
foo1 | ||||
foo2 | ||||
$ hg fix --working-dir --base 0 --whole | ||||
$ cat *.changed | ||||
BAR | ||||
FOO1 | ||||
FOO2 | ||||
$ cd .. | ||||
Danny Hooper
|
r40569 | |||
Danny Hooper
|
r40599 | The execution order of tools can be controlled. This example doesn't work if | ||
you sort after truncating, but the config defines the correct order while the | ||||
definitions are out of order (which might imply the incorrect order given the | ||||
implementation of fix). The goal is to use multiple tools to select the lowest | ||||
5 numbers in the file. | ||||
$ hg init priorityexample | ||||
$ cd priorityexample | ||||
$ cat >> .hg/hgrc <<EOF | ||||
> [fix] | ||||
Danny Hooper
|
r41162 | > head:command = head -n 5 | ||
Danny Hooper
|
r40599 | > head:pattern = numbers.txt | ||
> head:priority = 1 | ||||
Danny Hooper
|
r41162 | > sort:command = sort -n | ||
Danny Hooper
|
r40599 | > sort:pattern = numbers.txt | ||
> sort:priority = 2 | ||||
> EOF | ||||
$ printf "8\n2\n3\n6\n7\n4\n9\n5\n1\n0\n" > numbers.txt | ||||
$ hg add -q | ||||
$ hg fix -w | ||||
$ cat numbers.txt | ||||
0 | ||||
1 | ||||
2 | ||||
3 | ||||
4 | ||||
And of course we should be able to break this by reversing the execution order. | ||||
Test negative priorities while we're at it. | ||||
$ cat >> .hg/hgrc <<EOF | ||||
> [fix] | ||||
> head:priority = -1 | ||||
> sort:priority = -2 | ||||
> EOF | ||||
$ printf "8\n2\n3\n6\n7\n4\n9\n5\n1\n0\n" > numbers.txt | ||||
$ hg fix -w | ||||
$ cat numbers.txt | ||||
2 | ||||
3 | ||||
6 | ||||
7 | ||||
8 | ||||
$ cd .. | ||||
Danny Hooper
|
r40604 | |||
It's possible for repeated applications of a fixer tool to create cycles in the | ||||
generated content of a file. For example, two users with different versions of | ||||
a code formatter might fight over the formatting when they run hg fix. In the | ||||
absence of other changes, this means we could produce commits with the same | ||||
hash in subsequent runs of hg fix. This is a problem unless we support | ||||
obsolescence cycles well. We avoid this by adding an extra field to the | ||||
successor which forces it to have a new hash. That's why this test creates | ||||
three revisions instead of two. | ||||
$ hg init cyclictool | ||||
$ cd cyclictool | ||||
$ cat >> .hg/hgrc <<EOF | ||||
> [fix] | ||||
> swapletters:command = tr ab ba | ||||
> swapletters:pattern = foo | ||||
> EOF | ||||
$ echo ab > foo | ||||
$ hg commit -Aqm foo | ||||
$ hg fix -r 0 | ||||
$ hg fix -r 1 | ||||
$ hg cat -r 0 foo --hidden | ||||
ab | ||||
$ hg cat -r 1 foo --hidden | ||||
ba | ||||
$ hg cat -r 2 foo | ||||
ab | ||||
$ cd .. | ||||
Danny Hooper
|
r42900 | We run fixer tools in the repo root so they can look for config files or other | ||
important things in the working directory. This does NOT mean we are | ||||
reconstructing a working copy of every revision being fixed; we're just giving | ||||
the tool knowledge of the repo's location in case it can do something | ||||
reasonable with that. | ||||
$ hg init subprocesscwd | ||||
$ cd subprocesscwd | ||||
$ cat >> .hg/hgrc <<EOF | ||||
> [fix] | ||||
Matt Harbison
|
r43208 | > printcwd:command = "$PYTHON" -c "import os; print(os.getcwd())" | ||
Martin von Zweigbergk
|
r43500 | > printcwd:pattern = relpath:foo/bar | ||
Matt Harbison
|
r44400 | > filesetpwd:command = "$PYTHON" -c "import os; print('fs: ' + os.getcwd())" | ||
> filesetpwd:pattern = set:**quux | ||||
Danny Hooper
|
r42900 | > EOF | ||
$ mkdir foo | ||||
$ printf "bar\n" > foo/bar | ||||
Matt Harbison
|
r44400 | $ printf "quux\n" > quux | ||
Danny Hooper
|
r42900 | $ hg commit -Aqm blah | ||
$ hg fix -w -r . foo/bar | ||||
$ hg cat -r tip foo/bar | ||||
$TESTTMP/subprocesscwd | ||||
$ cat foo/bar | ||||
$TESTTMP/subprocesscwd | ||||
$ cd foo | ||||
$ hg fix -w -r . bar | ||||
Matt Harbison
|
r44400 | $ hg cat -r tip bar ../quux | ||
Danny Hooper
|
r42900 | $TESTTMP/subprocesscwd | ||
Matt Harbison
|
r44400 | quux | ||
$ cat bar ../quux | ||||
Danny Hooper
|
r42900 | $TESTTMP/subprocesscwd | ||
Matt Harbison
|
r44400 | quux | ||
Martin von Zweigbergk
|
r43500 | $ echo modified > bar | ||
$ hg fix -w bar | ||||
$ cat bar | ||||
Martin von Zweigbergk
|
r43501 | $TESTTMP/subprocesscwd | ||
Danny Hooper
|
r42900 | |||
Matt Harbison
|
r44400 | Apparently fixing p1() and its descendants doesn't include wdir() unless | ||
explicitly stated. | ||||
$ hg fix -r '.::' | ||||
$ hg cat -r . ../quux | ||||
quux | ||||
$ hg cat -r tip ../quux | ||||
Matt Harbison
|
r44461 | fs: $TESTTMP/subprocesscwd | ||
Matt Harbison
|
r44400 | $ cat ../quux | ||
quux | ||||
Clean files are not fixed unless explicitly named | ||||
$ echo 'dirty' > ../quux | ||||
$ hg fix --working-dir | ||||
$ cat ../quux | ||||
Matt Harbison
|
r44461 | fs: $TESTTMP/subprocesscwd | ||
Matt Harbison
|
r44400 | |||
Danny Hooper
|
r42900 | $ cd ../.. | ||
Danny Hooper
|
r42882 | Tools configured without a pattern are ignored. It would be too dangerous to | ||
run them on all files, because this might happen while testing a configuration | ||||
that also deletes all of the file content. There is no reasonable subset of the | ||||
files to use as a default. Users should be explicit about what files are | ||||
affected by a tool. This test also confirms that we don't crash when the | ||||
pattern config is missing, and that we only warn about it once. | ||||
$ hg init nopatternconfigured | ||||
$ cd nopatternconfigured | ||||
$ printf "foo" > foo | ||||
$ printf "bar" > bar | ||||
$ hg add -q | ||||
$ hg fix --debug --working-dir --config "fix.nopattern:command=echo fixed" | ||||
fixer tool has no pattern configuration: nopattern | ||||
$ cat foo bar | ||||
foobar (no-eol) | ||||
Martin von Zweigbergk
|
r43494 | $ hg fix --debug --working-dir --config "fix.nocommand:pattern=foo.bar" | ||
fixer tool has no command configuration: nocommand | ||||
Danny Hooper
|
r42882 | |||
$ cd .. | ||||
Danny Hooper
|
r43328 | Tools can be disabled. Disabled tools do nothing but print a debug message. | ||
$ hg init disabled | ||||
$ cd disabled | ||||
$ printf "foo\n" > foo | ||||
$ hg add -q | ||||
$ hg fix --debug --working-dir --config "fix.disabled:command=echo fixed" \ | ||||
> --config "fix.disabled:pattern=foo" \ | ||||
> --config "fix.disabled:enabled=false" | ||||
ignoring disabled fixer tool: disabled | ||||
$ cat foo | ||||
foo | ||||
$ cd .. | ||||
Danny Hooper
|
r42881 | Test that we can configure a fixer to affect all files regardless of the cwd. | ||
The way we invoke matching must not prohibit this. | ||||
$ hg init affectallfiles | ||||
$ cd affectallfiles | ||||
$ mkdir foo bar | ||||
$ printf "foo" > foo/file | ||||
$ printf "bar" > bar/file | ||||
$ printf "baz" > baz_file | ||||
$ hg add -q | ||||
$ cd bar | ||||
$ hg fix --working-dir --config "fix.cooltool:command=echo fixed" \ | ||||
Martin von Zweigbergk
|
r43500 | > --config "fix.cooltool:pattern=glob:**" | ||
Danny Hooper
|
r42881 | $ cd .. | ||
$ cat foo/file | ||||
fixed | ||||
$ cat bar/file | ||||
fixed | ||||
$ cat baz_file | ||||
fixed | ||||
$ cd .. | ||||
Danny Hooper
|
r43001 | |||
Tools should be able to run on unchanged files, even if they set :linerange. | ||||
This includes a corner case where deleted chunks of a file are not considered | ||||
changes. | ||||
$ hg init skipclean | ||||
$ cd skipclean | ||||
$ printf "a\nb\nc\n" > foo | ||||
$ printf "a\nb\nc\n" > bar | ||||
$ printf "a\nb\nc\n" > baz | ||||
$ hg commit -Aqm "base" | ||||
$ printf "a\nc\n" > foo | ||||
$ printf "a\nx\nc\n" > baz | ||||
Matt Harbison
|
r43208 | $ cat >> print.py <<EOF | ||
> import sys | ||||
> for a in sys.argv[1:]: | ||||
> print(a) | ||||
> EOF | ||||
Danny Hooper
|
r43001 | $ hg fix --working-dir foo bar baz \ | ||
Matt Harbison
|
r43208 | > --config "fix.changedlines:command=\"$PYTHON\" print.py \"Line ranges:\"" \ | ||
> --config 'fix.changedlines:linerange="{first} through {last}"' \ | ||||
Martin von Zweigbergk
|
r43500 | > --config 'fix.changedlines:pattern=glob:**' \ | ||
Danny Hooper
|
r43001 | > --config 'fix.changedlines:skipclean=false' | ||
$ cat foo | ||||
Line ranges: | ||||
$ cat bar | ||||
Line ranges: | ||||
$ cat baz | ||||
Line ranges: | ||||
2 through 2 | ||||
$ cd .. | ||||
Kyle Lippincott
|
r44414 | |||
Test various cases around merges. We were previously dropping files if they were | ||||
created on only the p2 side of the merge, so let's test permutations of: | ||||
* added, was fixed | ||||
* added, considered for fixing but was already good | ||||
* added, not considered for fixing | ||||
* modified, was fixed | ||||
* modified, considered for fixing but was already good | ||||
* modified, not considered for fixing | ||||
Before the bug was fixed where we would drop files, this test demonstrated the | ||||
following issues: | ||||
* new_in_r1.ignored, new_in_r1_already_good.changed, and | ||||
> mod_in_r1_already_good.changed were NOT in the manifest for the merge commit | ||||
* mod_in_r1.ignored had its contents from r0, NOT r1. | ||||
We're also setting a named branch for every commit to demonstrate that the | ||||
branch is kept intact and there aren't issues updating to another branch in the | ||||
middle of fix. | ||||
$ hg init merge_keeps_files | ||||
$ cd merge_keeps_files | ||||
$ for f in r0 mod_in_r1 mod_in_r2 mod_in_merge mod_in_child; do | ||||
> for c in changed whole ignored; do | ||||
> printf "hello\n" > $f.$c | ||||
> done | ||||
> printf "HELLO\n" > "mod_in_${f}_already_good.changed" | ||||
> done | ||||
$ hg branch -q r0 | ||||
$ hg ci -Aqm 'r0' | ||||
$ hg phase -p | ||||
$ make_test_files() { | ||||
> printf "world\n" >> "mod_in_$1.changed" | ||||
> printf "world\n" >> "mod_in_$1.whole" | ||||
> printf "world\n" >> "mod_in_$1.ignored" | ||||
> printf "WORLD\n" >> "mod_in_$1_already_good.changed" | ||||
> printf "new in $1\n" > "new_in_$1.changed" | ||||
> printf "new in $1\n" > "new_in_$1.whole" | ||||
> printf "new in $1\n" > "new_in_$1.ignored" | ||||
> printf "ALREADY GOOD, NEW IN THIS REV\n" > "new_in_$1_already_good.changed" | ||||
> } | ||||
$ make_test_commit() { | ||||
> make_test_files "$1" | ||||
> hg branch -q "$1" | ||||
> hg ci -Aqm "$2" | ||||
> } | ||||
$ make_test_commit r1 "merge me, pt1" | ||||
$ hg co -q ".^" | ||||
$ make_test_commit r2 "merge me, pt2" | ||||
$ hg merge -qr 1 | ||||
$ make_test_commit merge "evil merge" | ||||
$ make_test_commit child "child of merge" | ||||
$ make_test_files wdir | ||||
$ hg fix -r 'not public()' -w | ||||
$ hg log -G -T'{rev}:{shortest(node,8)}: branch:{branch} desc:{desc}' | ||||
@ 8:c22ce900: branch:child desc:child of merge | ||||
| | ||||
o 7:5a30615a: branch:merge desc:evil merge | ||||
|\ | ||||
| o 6:4e5acdc4: branch:r2 desc:merge me, pt2 | ||||
| | | ||||
o | 5:eea01878: branch:r1 desc:merge me, pt1 | ||||
|/ | ||||
o 0:0c548d87: branch:r0 desc:r0 | ||||
$ hg files -r tip | ||||
mod_in_child.changed | ||||
mod_in_child.ignored | ||||
mod_in_child.whole | ||||
mod_in_child_already_good.changed | ||||
mod_in_merge.changed | ||||
mod_in_merge.ignored | ||||
mod_in_merge.whole | ||||
mod_in_merge_already_good.changed | ||||
mod_in_mod_in_child_already_good.changed | ||||
mod_in_mod_in_merge_already_good.changed | ||||
mod_in_mod_in_r1_already_good.changed | ||||
mod_in_mod_in_r2_already_good.changed | ||||
mod_in_r0_already_good.changed | ||||
mod_in_r1.changed | ||||
mod_in_r1.ignored | ||||
mod_in_r1.whole | ||||
mod_in_r1_already_good.changed | ||||
mod_in_r2.changed | ||||
mod_in_r2.ignored | ||||
mod_in_r2.whole | ||||
mod_in_r2_already_good.changed | ||||
new_in_child.changed | ||||
new_in_child.ignored | ||||
new_in_child.whole | ||||
new_in_child_already_good.changed | ||||
new_in_merge.changed | ||||
new_in_merge.ignored | ||||
new_in_merge.whole | ||||
new_in_merge_already_good.changed | ||||
new_in_r1.changed | ||||
new_in_r1.ignored | ||||
new_in_r1.whole | ||||
new_in_r1_already_good.changed | ||||
new_in_r2.changed | ||||
new_in_r2.ignored | ||||
new_in_r2.whole | ||||
new_in_r2_already_good.changed | ||||
r0.changed | ||||
r0.ignored | ||||
r0.whole | ||||
$ for f in "$(hg files -r tip)"; do hg cat -r tip $f -T'{path}:\n{data}\n'; done | ||||
mod_in_child.changed: | ||||
hello | ||||
WORLD | ||||
mod_in_child.ignored: | ||||
hello | ||||
world | ||||
mod_in_child.whole: | ||||
HELLO | ||||
WORLD | ||||
mod_in_child_already_good.changed: | ||||
WORLD | ||||
mod_in_merge.changed: | ||||
hello | ||||
WORLD | ||||
mod_in_merge.ignored: | ||||
hello | ||||
world | ||||
mod_in_merge.whole: | ||||
HELLO | ||||
WORLD | ||||
mod_in_merge_already_good.changed: | ||||
WORLD | ||||
mod_in_mod_in_child_already_good.changed: | ||||
HELLO | ||||
mod_in_mod_in_merge_already_good.changed: | ||||
HELLO | ||||
mod_in_mod_in_r1_already_good.changed: | ||||
HELLO | ||||
mod_in_mod_in_r2_already_good.changed: | ||||
HELLO | ||||
mod_in_r0_already_good.changed: | ||||
HELLO | ||||
mod_in_r1.changed: | ||||
hello | ||||
WORLD | ||||
mod_in_r1.ignored: | ||||
hello | ||||
world | ||||
mod_in_r1.whole: | ||||
HELLO | ||||
WORLD | ||||
mod_in_r1_already_good.changed: | ||||
WORLD | ||||
mod_in_r2.changed: | ||||
hello | ||||
WORLD | ||||
mod_in_r2.ignored: | ||||
hello | ||||
world | ||||
mod_in_r2.whole: | ||||
HELLO | ||||
WORLD | ||||
mod_in_r2_already_good.changed: | ||||
WORLD | ||||
new_in_child.changed: | ||||
NEW IN CHILD | ||||
new_in_child.ignored: | ||||
new in child | ||||
new_in_child.whole: | ||||
NEW IN CHILD | ||||
new_in_child_already_good.changed: | ||||
ALREADY GOOD, NEW IN THIS REV | ||||
new_in_merge.changed: | ||||
NEW IN MERGE | ||||
new_in_merge.ignored: | ||||
new in merge | ||||
new_in_merge.whole: | ||||
NEW IN MERGE | ||||
new_in_merge_already_good.changed: | ||||
ALREADY GOOD, NEW IN THIS REV | ||||
new_in_r1.changed: | ||||
NEW IN R1 | ||||
new_in_r1.ignored: | ||||
new in r1 | ||||
new_in_r1.whole: | ||||
NEW IN R1 | ||||
new_in_r1_already_good.changed: | ||||
ALREADY GOOD, NEW IN THIS REV | ||||
new_in_r2.changed: | ||||
NEW IN R2 | ||||
new_in_r2.ignored: | ||||
new in r2 | ||||
new_in_r2.whole: | ||||
NEW IN R2 | ||||
new_in_r2_already_good.changed: | ||||
ALREADY GOOD, NEW IN THIS REV | ||||
r0.changed: | ||||
hello | ||||
r0.ignored: | ||||
hello | ||||
r0.whole: | ||||
hello | ||||