--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/tests/test-fix.t Sat Mar 03 14:08:44 2018 -0800
@@ -0,0 +1,969 @@
+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]
+ > uppercase-whole-file:command=sed -e 's/.*/\U&/'
+ > uppercase-whole-file:fileset=set:**.whole
+ > uppercase-changed-lines:command=sed
+ > uppercase-changed-lines:linerange=-e '{first},{last} s/.*/\U&/'
+ > uppercase-changed-lines:fileset=set:**.changed
+ > 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.
+
+ If revisions are specified with --rev, those revisions will be checked,
+ and they may be replaced with new revisions that have fixed file content.
+ It is desirable to specify all descendants of each specified revision, so
+ that the fixes propagate to the descendants. If all descendants are fixed
+ at the same time, no merging, rebasing, or evolution will be required.
+
+ If --working-dir is used, files with uncommitted changes in the working
+ copy will be fixed. If the checked-out revision is also fixed, the working
+ directory will update to the replacement revision.
+
+ 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):
+
+ --base REV [+] revisions to diff against (overrides automatic selection,
+ and applies to every revision being fixed)
+ -r --rev REV [+] revisions to fix
+ -w --working-dir fix the working directory
+ --whole always fix every line of a file
+
+ (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}
+ clang-format:fileset=set:**.cpp or **.hpp
+
+ 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
+ the fixed file content is expected on standard output. If there is any output
+ on standard error, the file will not be affected. Some values may be
+ substituted into the command:
+
+ {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
+
+ The :fileset suboption determines which files will be passed through each
+ configured tool. See 'hg help fileset' for possible values. If there are file
+ arguments to 'hg fix', the intersection of these filesets is used.
+
+ There is also a configurable limit for the maximum size of file that will be
+ processed by 'hg fix':
+
+ [fix]
+ maxfilesize=2MB
+
+ 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
+ (use --rev or --working-dir)
+ [255]
+ $ hg fix --whole
+ abort: no changesets specified
+ (use --rev or --working-dir)
+ [255]
+ $ hg fix --base 0
+ abort: no changesets specified
+ (use --rev or --working-dir)
+ [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
+ abort: can't fix immutable changeset 0:6470986d2e7b
+ [255]
+ $ hg fix -r 0 --working-dir
+ abort: can't fix immutable changeset 0:6470986d2e7b
+ [255]
+ $ 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 ..
+
+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
+ $ 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
+
+It would be better if this also fixed the unknown file.
+ $ cat *.whole
+ ADDED
+ CLEAN
+ ignored
+ MODIFIED!!!
+ unknown
+
+ $ 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}" \
+ > --config "fix.fail:fileset=foo.txt" \
+ > 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
+ $ 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 ..
+
+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
+ $ OLD_MTIME=`stat -c %Y foo.whole`
+ $ sleep 1 # mtime has a resolution of one second.
+ $ hg fix --working-dir
+ $ NEW_MTIME=`stat -c %Y foo.whole`
+ $ test $OLD_MTIME = $NEW_MTIME
+
+ $ cd ..
+
+When a fixer prints to stderr, we assume that it has failed. We should show the
+error messages to the user, and we should not let the failing fixer affect the
+file it was fixing (many code formatters might emit error messages on stderr
+and nothing on stdout, which would cause us the clear the file). We show the
+user which fixer failed and which revision, but we assume that the fixer will
+print the filename if it is relevant.
+
+ $ hg init showstderr
+ $ cd showstderr
+
+ $ printf "hello\n" > hello.txt
+ $ hg add
+ adding hello.txt
+ $ hg --config "fix.fail:command=printf 'HELLO\n' ; \
+ > printf '{rootpath}: some\nerror' >&2" \
+ > --config "fix.fail:fileset=hello.txt" \
+ > fix --working-dir
+ [wdir] fail: hello.txt: some
+ [wdir] fail: error
+ $ cat hello.txt
+ hello
+
+ $ 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
+ rebasing 1:c3b6dc0e177a "foo 2" (tip)
+ merging foo.whole
+ warning: conflicts while merging foo.whole! (edit, then use 'hg resolve --mark')
+ unresolved conflicts (see hg resolve, then hg rebase --continue)
+ [1]
+
+ $ hg --config extensions.rebase= fix --working-dir
+ abort: unresolved conflicts
+ (use 'hg resolve')
+ [255]
+
+ $ hg --config extensions.rebase= fix -r .
+ abort: rebase in progress
+ (use 'hg rebase --continue' or 'hg rebase --abort')
+ [255]
+
+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"
+
+ $ hg log --graph --template '{node|shortest} {files}'
+ @ bc05 bar.whole
+ |
+ o 4fd2 foo.whole
+ |
+ o f9ac foo.whole
+
+ $ hg fix -r 0:2
+ $ hg log --graph --template '{node|shortest} {files}'
+ o 3801 bar.whole
+ |
+ o 38cc
+ |
+ | @ bc05 bar.whole
+ | |
+ | x 4fd2 foo.whole
+ |/
+ o f9ac foo.whole
+
+
+ $ 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
+ subprocess: sed -e '1,1 s/.*/\U&/' -e '3,3 s/.*/\U&/'
+
+ $ 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"
+ $ hg debugobsolete `hg parents --template '{node}'`
+ obsoleted 1 changesets
+ $ hg --hidden fix -r 0
+ abort: fixing obsolete revision could cause divergence
+ [255]
+
+ $ hg --hidden fix -r 0 --config experimental.evolution.allowdivergence=true
+ $ 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}'" \
+ > --config "fix.fail:fileset=foo/bar" \
+ > 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 '.^'
+ abort: can only fix a changeset together with all its descendants
+ [255]
+ $ hg fix -r '.^'
+ 1 new orphan changesets
+ $ hg cat -r 2 foo.whole
+ ONE
+
+ $ cd ..
+