Patchwork [4,of,4,V3] filemerge: add test cases for corner cases using internal:dumpjson

login
register
mail settings
Submitter Phillip Cohen
Date March 17, 2017, 2:17 a.m.
Message ID <62ab2ddcf1683c3bf2e4.1489717038@phillco-mbp.dhcp.thefacebook.com>
Download mbox | patch
Permalink /patch/19414/
State Changes Requested
Headers show

Comments

Phillip Cohen - March 17, 2017, 2:17 a.m.
# HG changeset patch
# User Phil Cohen <phillco@fb.com>
# Date 1489716623 25200
#      Thu Mar 16 19:10:23 2017 -0700
# Node ID 62ab2ddcf1683c3bf2e4dbb21dbe11d065bff93b
# Parent  36d378ac43dbbf559655c70b7cad873768a0e794
filemerge: add test cases for corner cases using internal:dumpjson
Ryan McElroy - March 20, 2017, 11:13 a.m.
Patch 3 looks good to me.

In this patch, I'd fold the test additions into the new test case you 
made in patch 3 -- a separate test for corner cases doesn't really make 
sense to me. Otherwise, the tests look good to me.

Looking forward to the next version of this series which I think will be 
in a queue-ready state :-)

On 3/17/17 2:17 AM, Phil Cohen wrote:
> # HG changeset patch
> # User Phil Cohen <phillco@fb.com>
> # Date 1489716623 25200
> #      Thu Mar 16 19:10:23 2017 -0700
> # Node ID 62ab2ddcf1683c3bf2e4dbb21dbe11d065bff93b
> # Parent  36d378ac43dbbf559655c70b7cad873768a0e794
> filemerge: add test cases for corner cases using internal:dumpjson
>
> diff --git a/mercurial/filemerge.py b/mercurial/filemerge.py
> --- a/mercurial/filemerge.py
> +++ b/mercurial/filemerge.py
> @@ -589,6 +589,8 @@
>           # `up -n` or `merge -f`), "local" must reflect that, not the underlying
>           # commit. Those contents are available in the .orig version, so we look
>           # there and mock up the schema to look like the other contexts.
> +        #
> +        # Test cases affected in test-merge-conflict-cornercases.t: #0
>           local = {
>               'contents': util.readfile(origfile),
>               'exists': True,
> @@ -605,6 +607,8 @@
>           # pre-merge properties including any local changes), so we can reuse
>           # that.
>           #
> +        # Affected test cases: #0b, #1, #6, #11, and #12.
> +        #
>           # Another alternative might be to use repo['.'][path] but that wouldn't
>           # have any dirty pre-merge changes.
>           #
> diff --git a/tests/test-merge-conflict-cornercases.t b/tests/test-merge-conflict-cornercases.t
> new file mode 100644
> --- /dev/null
> +++ b/tests/test-merge-conflict-cornercases.t
> @@ -0,0 +1,575 @@
> +Tests merge conflict corner cases (file-to-directory, binary-to-symlink, etc.)
> +"other" == source
> +"local" == dest
> +
> +Setup
> +  $ cat >> $HGRCPATH <<EOF
> +  > [extensions]
> +  > rebase=
> +  > EOF
> +
> +  $ reset() {
> +  >   rm -rf foo
> +  >   mkdir foo
> +  >   cd foo
> +  >   hg init
> +  >   echo "base" > file
> +  >   hg commit -Aqm "base"
> +  > }
> +  $ logg() {
> +  >   hg log -G -T '({rev}) {desc}\naffected: {files}\ndeleted: {file_dels}\n\n'
> +  > }
> +
> +Test case 0: A merge of just contents conflicts (not usually a corner case),
> +but the user had local changes and ran `merge -f`.
> +
> +tldr: Since we can premerge, the working copy is backed up to an origfile.
> +  $ reset
> +  $ echo "change" > file
> +  $ hg commit -Aqm "dest"
> +  $ hg up -q 0
> +  $ echo "other change" > file
> +  $ hg commit -Aqm "source"
> +  $ hg up -q 1
> +  $ logg
> +  o  (2) source
> +  |  affected: file
> +  |  deleted:
> +  |
> +  | @  (1) dest
> +  |/   affected: file
> +  |    deleted:
> +  |
> +  o  (0) base
> +     affected: file
> +     deleted:
> +
> +  $ echo "some local changes" > file
> +  $ hg merge 2 -f
> +  merging file
> +  warning: conflicts while merging file! (edit, then use 'hg resolve --mark')
> +  0 files updated, 0 files merged, 0 files removed, 1 files unresolved
> +  use 'hg resolve' to retry unresolved file merges or 'hg update -C .' to abandon
> +  [1]
> +
> +  $ hg resolve --tool=internal:dumpjson --all
> +  [
> +   {
> +    "conflicts": [{"base": {"contents": "base\n", "exists": true, "isexec": false, "issymlink": false}, "local": {"contents": "some local changes\n", "exists": true, "isexec": false, "issymlink": false}, "other": {"contents": "other change\n", "exists": true, "isexec": false, "issymlink": false}, "output": {"contents": "<<<<<<<workingcopy:fd7d10c36158-test:dest\nsomelocalchanges\n=======\notherchange\n>>>>>>> merge rev:    9b65ba2922f0 - test: source\n", "exists": true, "isexec": false, "issymlink": false, "path": "$TESTTMP/foo/file"}, "path": "file"}]
> +   }
> +  ]
> +
> +Test case 0b: Like #0 but with a corner case: source deleted, local changed
> +*and* had local changes using merge -f.
> +
> +tldr: Since we couldn't premerge, the working copy is left alone.
> +  $ reset
> +  $ echo "change" > file
> +  $ hg commit -Aqm "dest"
> +  $ hg up -q 0
> +  $ hg rm file
> +  $ hg commit -Aqm "source"
> +  $ hg up -q 1
> +  $ logg
> +  o  (2) source
> +  |  affected: file
> +  |  deleted: file
> +  |
> +  | @  (1) dest
> +  |/   affected: file
> +  |    deleted:
> +  |
> +  o  (0) base
> +     affected: file
> +     deleted:
> +
> +  $ echo "some local changes" > file
> +  $ chmod +x file
> +  $ hg merge 2 -f
> +  local [working copy] changed file which other [merge rev] deleted
> +  use (c)hanged version, (d)elete, or leave (u)nresolved? u
> +  0 files updated, 0 files merged, 0 files removed, 1 files unresolved
> +  use 'hg resolve' to retry unresolved file merges or 'hg update -C .' to abandon
> +  [1]
> +
> +  $ hg resolve --tool=internal:dumpjson --all
> +  [
> +   {
> +    "conflicts": [{"base": {"contents": "base\n", "exists": true, "isexec": false, "issymlink": false}, "local": {"contents": "some local changes\n", "exists": true, "isexec": true, "issymlink": false}, "other": {"exists": false}, "output": {"contents": "some local changes\n", "exists": true, "isexec": true, "issymlink": false, "path": "$TESTTMP/foo/foo/file"}, "path": "file"}]
> +   }
> +  ]
> +
> +Test case 1: Source deleted, dest changed
> +  $ reset
> +  $ echo "change" > file
> +  $ hg commit -Aqm "dest"
> +  $ hg up -q 0
> +  $ hg rm file
> +  $ hg commit -Aqm "source"
> +  $ hg up -q 1
> +  $ logg
> +  o  (2) source
> +  |  affected: file
> +  |  deleted: file
> +  |
> +  | @  (1) dest
> +  |/   affected: file
> +  |    deleted:
> +  |
> +  o  (0) base
> +     affected: file
> +     deleted:
> +
> +
> +  $ hg rebase -d 1 -s 2
> +  rebasing 2:25c2ef28f4c7 "source" (tip)
> +  local [dest] changed file which other [source] deleted
> +  use (c)hanged version, (d)elete, or leave (u)nresolved? u
> +  unresolved conflicts (see hg resolve, then hg rebase --continue)
> +  [1]
> +  $ hg resolve --tool=internal:dumpjson --all
> +  [
> +   {
> +    "conflicts": [{"base": {"contents": "base\n", "exists": true, "isexec": false, "issymlink": false}, "local": {"contents": "change\n", "exists": true, "isexec": false, "issymlink": false}, "other": {"exists": false}, "output": {"contents": "change\n", "exists": true, "isexec": false, "issymlink": false, "path": "$TESTTMP/foo/foo/foo/file"}, "path": "file"}]
> +   }
> +  ]
> +Test case 1b: Like #1 but with a merge, with local changes
> +  $ reset
> +  $ echo "change" > file
> +  $ hg commit -Aqm "dest"
> +  $ hg up -q 0
> +  $ hg rm file
> +  $ hg commit -Aqm "source"
> +  $ hg up -q 1
> +  $ logg
> +  o  (2) source
> +  |  affected: file
> +  |  deleted: file
> +  |
> +  | @  (1) dest
> +  |/   affected: file
> +  |    deleted:
> +  |
> +  o  (0) base
> +     affected: file
> +     deleted:
> +
> +  $ echo "some local changes" > file
> +  $ hg merge 2 -f
> +  local [working copy] changed file which other [merge rev] deleted
> +  use (c)hanged version, (d)elete, or leave (u)nresolved? u
> +  0 files updated, 0 files merged, 0 files removed, 1 files unresolved
> +  use 'hg resolve' to retry unresolved file merges or 'hg update -C .' to abandon
> +  [1]
> +
> +  $ hg resolve --tool=internal:dumpjson --all
> +  [
> +   {
> +    "conflicts": [{"base": {"contents": "base\n", "exists": true, "isexec": false, "issymlink": false}, "local": {"contents": "some local changes\n", "exists": true, "isexec": false, "issymlink": false}, "other": {"exists": false}, "output": {"contents": "some local changes\n", "exists": true, "isexec": false, "issymlink": false, "path": "$TESTTMP/foo/foo/foo/foo/file"}, "path": "file"}]
> +   }
> +  ]
> +Test case 2: Source changed, dest deleted
> +  $ cd ..
> +  $ reset
> +  $ echo "change" > file
> +  $ hg commit -Aqm "source"
> +  $ hg up -q 0
> +  $ hg rm file
> +  $ hg commit -Aqm "dest"
> +  $ hg up --q 2
> +  $ logg
> +  @  (2) dest
> +  |  affected: file
> +  |  deleted: file
> +  |
> +  | o  (1) source
> +  |/   affected: file
> +  |    deleted:
> +  |
> +  o  (0) base
> +     affected: file
> +     deleted:
> +
> +
> +  $ hg rebase -d 2 -s 1
> +  rebasing 1:ec87889f5f90 "source"
> +  other [source] changed file which local [dest] deleted
> +  use (c)hanged version, leave (d)eleted, or leave (u)nresolved? u
> +  unresolved conflicts (see hg resolve, then hg rebase --continue)
> +  [1]
> +  $ hg resolve --tool=internal:dumpjson --all
> +  [
> +   {
> +    "conflicts": [{"base": {"contents": "base\n", "exists": true, "isexec": false, "issymlink": false}, "local": {"exists": false}, "other": {"contents": "change\n", "exists": true, "isexec": false, "issymlink": false}, "output": {"exists": false, "path": "$TESTTMP/foo/foo/foo/foo/file"}, "path": "file"}]
> +   }
> +  ]
> +Test case 3: Source changed, dest moved
> +  $ cd ..
> +  $ reset
> +  $ echo "change" > file
> +  $ hg commit -Aqm "source"
> +  $ hg up -q 0
> +  $ hg mv file file_newloc
> +  $ hg commit -Aqm "dest"
> +  $ hg up -q 2
> +  $ logg
> +  @  (2) dest
> +  |  affected: file file_newloc
> +  |  deleted: file
> +  |
> +  | o  (1) source
> +  |/   affected: file
> +  |    deleted:
> +  |
> +  o  (0) base
> +     affected: file
> +     deleted:
> +
> +
> +  $ hg rebase -d 2 -s 1
> +  rebasing 1:ec87889f5f90 "source"
> +  merging file_newloc and file to file_newloc
> +  saved backup bundle to $TESTTMP/foo/foo/foo/foo/.hg/strip-backup/ec87889f5f90-e39a76b8-backup.hg (glob)
> +  $ hg up -q 2 # source
> +  $ cat file_newloc # Should follow:
> +  change
> +  $ hg resolve --tool=internal:dumpjson --all
> +  [
> +   {
> +    "conflicts": []
> +   }
> +  ]
> +Test case 4: Source changed, dest moved (w/o copytracing)
> +  $ cd ..
> +  $ reset
> +  $ echo "change" > file
> +  $ hg commit -Aqm "source"
> +  $ hg up -q 0
> +  $ hg mv file file_newloc
> +  $ hg commit -Aqm "dest"
> +  $ hg up -q 2
> +  $ logg
> +  @  (2) dest
> +  |  affected: file file_newloc
> +  |  deleted: file
> +  |
> +  | o  (1) source
> +  |/   affected: file
> +  |    deleted:
> +  |
> +  o  (0) base
> +     affected: file
> +     deleted:
> +
> +
> +  $ hg rebase -d 2 -s 1 --config experimental.disablecopytrace=True
> +  rebasing 1:ec87889f5f90 "source"
> +  other [source] changed file which local [dest] deleted
> +  use (c)hanged version, leave (d)eleted, or leave (u)nresolved? u
> +  unresolved conflicts (see hg resolve, then hg rebase --continue)
> +  [1]
> +  $ hg resolve --tool=internal:dumpjson --all
> +  [
> +   {
> +    "conflicts": [{"base": {"contents": "base\n", "exists": true, "isexec": false, "issymlink": false}, "local": {"exists": false}, "other": {"contents": "change\n", "exists": true, "isexec": false, "issymlink": false}, "output": {"exists": false, "path": "$TESTTMP/foo/foo/foo/foo/file"}, "path": "file"}]
> +   }
> +  ]
> +Test case 5: Source moved, dest changed
> +  $ cd ..
> +  $ reset
> +  $ hg mv file file_newloc
> +  $ hg commit -Aqm "source"
> +  $ hg up -q 0
> +  $ echo "change" > file
> +  $ hg commit -Aqm "dest"
> +  $ hg up -q 2
> +  $ logg
> +  @  (2) dest
> +  |  affected: file
> +  |  deleted:
> +  |
> +  | o  (1) source
> +  |/   affected: file file_newloc
> +  |    deleted: file
> +  |
> +  o  (0) base
> +     affected: file
> +     deleted:
> +
> +
> +  $ hg rebase -d 2 -s 1
> +  rebasing 1:e6e7483a8950 "source"
> +  merging file and file_newloc to file_newloc
> +  saved backup bundle to $TESTTMP/foo/foo/foo/foo/.hg/strip-backup/e6e7483a8950-8e128ac2-backup.hg (glob)
> +  $ hg up 2
> +  1 files updated, 0 files merged, 1 files removed, 0 files unresolved
> +  $ cat file_newloc
> +  change
> +  $ hg resolve --tool=internal:dumpjson --all
> +  [
> +   {
> +    "conflicts": []
> +   }
> +  ]
> +Test case 6: Source moved, dest changed (w/o copytracing)
> +  $ cd ..
> +  $ reset
> +  $ hg mv file file_newloc
> +  $ hg commit -Aqm "source"
> +  $ hg up -q 0
> +  $ echo "change" > file
> +  $ hg commit -Aqm "dest"
> +  $ hg up -q 2
> +  $ logg
> +  @  (2) dest
> +  |  affected: file
> +  |  deleted:
> +  |
> +  | o  (1) source
> +  |/   affected: file file_newloc
> +  |    deleted: file
> +  |
> +  o  (0) base
> +     affected: file
> +     deleted:
> +
> +
> +  $ hg rebase -d 2 -s 1 --config experimental.disablecopytrace=True
> +  rebasing 1:e6e7483a8950 "source"
> +  local [dest] changed file which other [source] deleted
> +  use (c)hanged version, (d)elete, or leave (u)nresolved? u
> +  unresolved conflicts (see hg resolve, then hg rebase --continue)
> +  [1]
> +  $ hg resolve --tool=internal:dumpjson --all
> +  [
> +   {
> +    "conflicts": [{"base": {"contents": "base\n", "exists": true, "isexec": false, "issymlink": false}, "local": {"contents": "change\n", "exists": true, "isexec": false, "issymlink": false}, "other": {"exists": false}, "output": {"contents": "change\n", "exists": true, "isexec": false, "issymlink": false, "path": "$TESTTMP/foo/foo/foo/foo/file"}, "path": "file"}]
> +   }
> +  ]
> +Test case 7: Source is a directory, dest is a file (base is still a file)
> +  $ cd ..
> +  $ reset
> +  $ hg rm file
> +  $ mkdir file # "file" is a stretch
> +  $ echo "this will cause problems" >> file/subfile
> +  $ hg commit -Aqm "source"
> +  $ hg up -q 0
> +  $ echo "change" > file
> +  $ hg commit -Aqm "dest"
> +  $ hg up -q 2
> +  $ logg
> +  @  (2) dest
> +  |  affected: file
> +  |  deleted:
> +  |
> +  | o  (1) source
> +  |/   affected: file file/subfile
> +  |    deleted: file
> +  |
> +  o  (0) base
> +     affected: file
> +     deleted:
> +
> +
> +  $ hg rebase -d 2 -s 1
> +  rebasing 1:ed93aeac6b3c "source"
> +  abort: Not a directory: '$TESTTMP/foo/foo/foo/foo/file/subfile'
> +  [255]
> +  $ hg resolve --tool=internal:dumpjson --all
> +  [
> +   {
> +    "conflicts": []
> +   }
> +  ]
> +Test case 8: Source is a file, dest is a directory (base is still a file)
> +  $ cd ..
> +  $ reset
> +  $ echo "change" > file
> +  $ hg commit -Aqm "source"
> +  $ hg up -q 0
> +  $ hg rm file
> +  $ mkdir file # "file"
> +  $ echo "this will cause problems" >> file/subfile
> +  $ hg commit -Aqm "dest"
> +  $ hg up -q 2
> +  $ logg
> +  @  (2) dest
> +  |  affected: file file/subfile
> +  |  deleted: file
> +  |
> +  | o  (1) source
> +  |/   affected: file
> +  |    deleted:
> +  |
> +  o  (0) base
> +     affected: file
> +     deleted:
> +
> +
> +  $ hg rebase -d 2 -s 1
> +  rebasing 1:ec87889f5f90 "source"
> +  abort: Operation not permitted: '$TESTTMP/foo/foo/foo/foo/file'
> +  [255]
> +  $ hg resolve --tool=internal:dumpjson --all
> +  [
> +   {
> +    "conflicts": []
> +   }
> +  ]
> +Test case 9: Source is a binary file, dest is a file (base is still a file)
> +  $ cd ..
> +  $ reset
> +  $ python -c 'f = open("file", "w"); f.write("\x00")'
> +  $ hg commit -Aqm "source"
> +  $ hg up -q 0
> +  $ echo "change" > file
> +  $ hg commit -Aqm "dest"
> +  $ hg up -q 2
> +  $ logg
> +  @  (2) dest
> +  |  affected: file
> +  |  deleted:
> +  |
> +  | o  (1) source
> +  |/   affected: file
> +  |    deleted:
> +  |
> +  o  (0) base
> +     affected: file
> +     deleted:
> +
> +
> +  $ hg rebase -d 2 -s 1
> +  rebasing 1:b6e55a03a5dc "source"
> +  merging file
> +  warning: ([^\s]+) looks like a binary file. (re)
> +  warning: conflicts while merging file! (edit, then use 'hg resolve --mark')
> +  unresolved conflicts (see hg resolve, then hg rebase --continue)
> +  [1]
> +  $ cat -v file # The local version should be left in the working copy
> +  change
> +  $ hg resolve --tool=internal:dumpjson --all
> +  [
> +   {
> +    "conflicts": [{"base": {"contents": "base\n", "exists": true, "isexec": false, "issymlink": false}, "local": {"contents": "change\n", "exists": true, "isexec": false, "issymlink": false}, "other": {"contents": "\u0000", "exists": true, "isexec": false, "issymlink": false}, "output": {"contents": "change\n", "exists": true, "isexec": false, "issymlink": false, "path": "$TESTTMP/foo/foo/foo/foo/file"}, "path": "file"}]
> +   }
> +  ]
> +Test case 10: Source is a file, dest is a binary file (base is still a file)
> +  $ cd ..
> +  $ reset
> +  $ echo "change" > file
> +  $ hg commit -Aqm "source"
> +  $ hg up -q 0
> +  $ python -c 'f = open("file", "w"); f.write("\x00")'
> +  $ hg commit -Aqm "dest"
> +  $ hg up -q 2
> +  $ logg
> +  @  (2) dest
> +  |  affected: file
> +  |  deleted:
> +  |
> +  | o  (1) source
> +  |/   affected: file
> +  |    deleted:
> +  |
> +  o  (0) base
> +     affected: file
> +     deleted:
> +
> +
> +  $ hg rebase -d 2 -s 1
> +  rebasing 1:ec87889f5f90 "source"
> +  merging file
> +  warning: ([^\s]+) looks like a binary file. (re)
> +  warning: conflicts while merging file! (edit, then use 'hg resolve --mark')
> +  unresolved conflicts (see hg resolve, then hg rebase --continue)
> +  [1]
> +  $ cat -v file
> +  ^@ (no-eol)
> +  $ hg resolve --tool=internal:dumpjson --all
> +  [
> +   {
> +    "conflicts": [{"base": {"contents": "base\n", "exists": true, "isexec": false, "issymlink": false}, "local": {"contents": "\u0000", "exists": true, "isexec": false, "issymlink": false}, "other": {"contents": "change\n", "exists": true, "isexec": false, "issymlink": false}, "output": {"contents": "\u0000", "exists": true, "isexec": false, "issymlink": false, "path": "$TESTTMP/foo/foo/foo/foo/file"}, "path": "file"}]
> +   }
> +  ]
> +Test case 11: Source is a symlink, dest is a file (base is still a file)
> +  $ cd ..
> +  $ reset
> +  $ rm file
> +  $ ln -s somepath file
> +  $ hg commit -Aqm "source"
> +  $ hg up -q 0
> +  $ echo "change" > file
> +  $ hg commit -Aqm "dest"
> +  $ hg up -q 2
> +  $ logg
> +  @  (2) dest
> +  |  affected: file
> +  |  deleted:
> +  |
> +  | o  (1) source
> +  |/   affected: file
> +  |    deleted:
> +  |
> +  o  (0) base
> +     affected: file
> +     deleted:
> +
> +
> +  $ hg rebase -d 2 -s 1
> +  rebasing 1:06aece48b59f "source"
> +  merging file
> +  warning: internal :merge cannot merge symlinks for file
> +  warning: conflicts while merging file! (edit, then use 'hg resolve --mark')
> +  unresolved conflicts (see hg resolve, then hg rebase --continue)
> +  [1]
> +  $ cat -v file
> +  change
> +  $ hg resolve --tool=internal:dumpjson --all
> +  [
> +   {
> +    "conflicts": [{"base": {"contents": "base\n", "exists": true, "isexec": false, "issymlink": false}, "local": {"contents": "change\n", "exists": true, "isexec": false, "issymlink": false}, "other": {"contents": "somepath", "exists": true, "isexec": false, "issymlink": true}, "output": {"contents": "change\n", "exists": true, "isexec": false, "issymlink": false, "path": "$TESTTMP/foo/foo/foo/foo/file"}, "path": "file"}]
> +   }
> +  ]
> +Test case 12: Source is a file, dest is a symlink (base is still a file)
> +  $ cd ..
> +  $ reset
> +  $ echo "change" > file
> +  $ hg commit -Aqm "source"
> +  $ hg up -q 0
> +  $ rm file
> +  $ ln -s somepath file
> +  $ hg commit -Aqm "dest"
> +  $ hg up -q 2
> +  $ logg
> +  @  (2) dest
> +  |  affected: file
> +  |  deleted:
> +  |
> +  | o  (1) source
> +  |/   affected: file
> +  |    deleted:
> +  |
> +  o  (0) base
> +     affected: file
> +     deleted:
> +
> +
> +  $ hg rebase -d 2 -s 1
> +  rebasing 1:ec87889f5f90 "source"
> +  merging file
> +  warning: internal :merge cannot merge symlinks for file
> +  warning: conflicts while merging file! (edit, then use 'hg resolve --mark')
> +  unresolved conflicts (see hg resolve, then hg rebase --continue)
> +  [1]
> +  $ cat -v file
> +  cat: file: No such file or directory
> +  [1]
> +  $ ls file
> +  file
> +  $ hg resolve --tool=internal:dumpjson --all
> +  [
> +   {
> +    "conflicts": [{"base": {"contents": "base\n", "exists": true, "isexec": false, "issymlink": false}, "local": {"contents": "somepath", "exists": true, "isexec": false, "issymlink": true}, "other": {"contents": "change\n", "exists": true, "isexec": false, "issymlink": false}, "output": {"contents": "somepath", "exists": true, "isexec": false, "issymlink": true, "path": "$TESTTMP/foo/foo/foo/foo/file"}, "path": "file"}]
> +   }
> +  ]
> _______________________________________________
> Mercurial-devel mailing list
> Mercurial-devel@mercurial-scm.org
> https://urldefense.proofpoint.com/v2/url?u=https-3A__www.mercurial-2Dscm.org_mailman_listinfo_mercurial-2Ddevel&d=DwIGaQ&c=5VD0RTtNlTh3ycd41b3MUw&r=Jw8rundaE7TbmqBYd1txIQ&m=_t4xvyJTHXGFDvTu3p6R_nIgUmMdHttb-VMlCi_eurs&s=M-_Thl2YX-gsg0mR0ai7KrBqaCpWMxi5MXB32FHMNFs&e=

Patch

diff --git a/mercurial/filemerge.py b/mercurial/filemerge.py
--- a/mercurial/filemerge.py
+++ b/mercurial/filemerge.py
@@ -589,6 +589,8 @@ 
         # `up -n` or `merge -f`), "local" must reflect that, not the underlying
         # commit. Those contents are available in the .orig version, so we look
         # there and mock up the schema to look like the other contexts.
+        #
+        # Test cases affected in test-merge-conflict-cornercases.t: #0
         local = {
             'contents': util.readfile(origfile),
             'exists': True,
@@ -605,6 +607,8 @@ 
         # pre-merge properties including any local changes), so we can reuse
         # that.
         #
+        # Affected test cases: #0b, #1, #6, #11, and #12.
+        #
         # Another alternative might be to use repo['.'][path] but that wouldn't
         # have any dirty pre-merge changes.
         #
diff --git a/tests/test-merge-conflict-cornercases.t b/tests/test-merge-conflict-cornercases.t
new file mode 100644
--- /dev/null
+++ b/tests/test-merge-conflict-cornercases.t
@@ -0,0 +1,575 @@ 
+Tests merge conflict corner cases (file-to-directory, binary-to-symlink, etc.)
+"other" == source
+"local" == dest
+
+Setup
+  $ cat >> $HGRCPATH <<EOF
+  > [extensions]
+  > rebase=
+  > EOF
+
+  $ reset() {
+  >   rm -rf foo
+  >   mkdir foo
+  >   cd foo
+  >   hg init
+  >   echo "base" > file
+  >   hg commit -Aqm "base"
+  > }
+  $ logg() {
+  >   hg log -G -T '({rev}) {desc}\naffected: {files}\ndeleted: {file_dels}\n\n'
+  > }
+
+Test case 0: A merge of just contents conflicts (not usually a corner case),
+but the user had local changes and ran `merge -f`.
+
+tldr: Since we can premerge, the working copy is backed up to an origfile.
+  $ reset
+  $ echo "change" > file
+  $ hg commit -Aqm "dest"
+  $ hg up -q 0
+  $ echo "other change" > file
+  $ hg commit -Aqm "source"
+  $ hg up -q 1
+  $ logg
+  o  (2) source
+  |  affected: file
+  |  deleted:
+  |
+  | @  (1) dest
+  |/   affected: file
+  |    deleted:
+  |
+  o  (0) base
+     affected: file
+     deleted:
+  
+  $ echo "some local changes" > file
+  $ hg merge 2 -f
+  merging file
+  warning: conflicts while merging file! (edit, then use 'hg resolve --mark')
+  0 files updated, 0 files merged, 0 files removed, 1 files unresolved
+  use 'hg resolve' to retry unresolved file merges or 'hg update -C .' to abandon
+  [1]
+
+  $ hg resolve --tool=internal:dumpjson --all
+  [
+   {
+    "conflicts": [{"base": {"contents": "base\n", "exists": true, "isexec": false, "issymlink": false}, "local": {"contents": "some local changes\n", "exists": true, "isexec": false, "issymlink": false}, "other": {"contents": "other change\n", "exists": true, "isexec": false, "issymlink": false}, "output": {"contents": "<<<<<<< working copy: fd7d10c36158 - test: dest\nsome local changes\n=======\nother change\n>>>>>>> merge rev:    9b65ba2922f0 - test: source\n", "exists": true, "isexec": false, "issymlink": false, "path": "$TESTTMP/foo/file"}, "path": "file"}]
+   }
+  ]
+
+Test case 0b: Like #0 but with a corner case: source deleted, local changed
+*and* had local changes using merge -f.
+
+tldr: Since we couldn't premerge, the working copy is left alone.
+  $ reset
+  $ echo "change" > file
+  $ hg commit -Aqm "dest"
+  $ hg up -q 0
+  $ hg rm file
+  $ hg commit -Aqm "source"
+  $ hg up -q 1
+  $ logg
+  o  (2) source
+  |  affected: file
+  |  deleted: file
+  |
+  | @  (1) dest
+  |/   affected: file
+  |    deleted:
+  |
+  o  (0) base
+     affected: file
+     deleted:
+  
+  $ echo "some local changes" > file
+  $ chmod +x file
+  $ hg merge 2 -f
+  local [working copy] changed file which other [merge rev] deleted
+  use (c)hanged version, (d)elete, or leave (u)nresolved? u
+  0 files updated, 0 files merged, 0 files removed, 1 files unresolved
+  use 'hg resolve' to retry unresolved file merges or 'hg update -C .' to abandon
+  [1]
+
+  $ hg resolve --tool=internal:dumpjson --all
+  [
+   {
+    "conflicts": [{"base": {"contents": "base\n", "exists": true, "isexec": false, "issymlink": false}, "local": {"contents": "some local changes\n", "exists": true, "isexec": true, "issymlink": false}, "other": {"exists": false}, "output": {"contents": "some local changes\n", "exists": true, "isexec": true, "issymlink": false, "path": "$TESTTMP/foo/foo/file"}, "path": "file"}]
+   }
+  ]
+
+Test case 1: Source deleted, dest changed
+  $ reset
+  $ echo "change" > file
+  $ hg commit -Aqm "dest"
+  $ hg up -q 0
+  $ hg rm file
+  $ hg commit -Aqm "source"
+  $ hg up -q 1
+  $ logg
+  o  (2) source
+  |  affected: file
+  |  deleted: file
+  |
+  | @  (1) dest
+  |/   affected: file
+  |    deleted:
+  |
+  o  (0) base
+     affected: file
+     deleted:
+  
+
+  $ hg rebase -d 1 -s 2
+  rebasing 2:25c2ef28f4c7 "source" (tip)
+  local [dest] changed file which other [source] deleted
+  use (c)hanged version, (d)elete, or leave (u)nresolved? u
+  unresolved conflicts (see hg resolve, then hg rebase --continue)
+  [1]
+  $ hg resolve --tool=internal:dumpjson --all
+  [
+   {
+    "conflicts": [{"base": {"contents": "base\n", "exists": true, "isexec": false, "issymlink": false}, "local": {"contents": "change\n", "exists": true, "isexec": false, "issymlink": false}, "other": {"exists": false}, "output": {"contents": "change\n", "exists": true, "isexec": false, "issymlink": false, "path": "$TESTTMP/foo/foo/foo/file"}, "path": "file"}]
+   }
+  ]
+Test case 1b: Like #1 but with a merge, with local changes
+  $ reset
+  $ echo "change" > file
+  $ hg commit -Aqm "dest"
+  $ hg up -q 0
+  $ hg rm file
+  $ hg commit -Aqm "source"
+  $ hg up -q 1
+  $ logg
+  o  (2) source
+  |  affected: file
+  |  deleted: file
+  |
+  | @  (1) dest
+  |/   affected: file
+  |    deleted:
+  |
+  o  (0) base
+     affected: file
+     deleted:
+  
+  $ echo "some local changes" > file
+  $ hg merge 2 -f
+  local [working copy] changed file which other [merge rev] deleted
+  use (c)hanged version, (d)elete, or leave (u)nresolved? u
+  0 files updated, 0 files merged, 0 files removed, 1 files unresolved
+  use 'hg resolve' to retry unresolved file merges or 'hg update -C .' to abandon
+  [1]
+
+  $ hg resolve --tool=internal:dumpjson --all
+  [
+   {
+    "conflicts": [{"base": {"contents": "base\n", "exists": true, "isexec": false, "issymlink": false}, "local": {"contents": "some local changes\n", "exists": true, "isexec": false, "issymlink": false}, "other": {"exists": false}, "output": {"contents": "some local changes\n", "exists": true, "isexec": false, "issymlink": false, "path": "$TESTTMP/foo/foo/foo/foo/file"}, "path": "file"}]
+   }
+  ]
+Test case 2: Source changed, dest deleted
+  $ cd ..
+  $ reset
+  $ echo "change" > file
+  $ hg commit -Aqm "source"
+  $ hg up -q 0
+  $ hg rm file
+  $ hg commit -Aqm "dest"
+  $ hg up --q 2
+  $ logg
+  @  (2) dest
+  |  affected: file
+  |  deleted: file
+  |
+  | o  (1) source
+  |/   affected: file
+  |    deleted:
+  |
+  o  (0) base
+     affected: file
+     deleted:
+  
+
+  $ hg rebase -d 2 -s 1
+  rebasing 1:ec87889f5f90 "source"
+  other [source] changed file which local [dest] deleted
+  use (c)hanged version, leave (d)eleted, or leave (u)nresolved? u
+  unresolved conflicts (see hg resolve, then hg rebase --continue)
+  [1]
+  $ hg resolve --tool=internal:dumpjson --all
+  [
+   {
+    "conflicts": [{"base": {"contents": "base\n", "exists": true, "isexec": false, "issymlink": false}, "local": {"exists": false}, "other": {"contents": "change\n", "exists": true, "isexec": false, "issymlink": false}, "output": {"exists": false, "path": "$TESTTMP/foo/foo/foo/foo/file"}, "path": "file"}]
+   }
+  ]
+Test case 3: Source changed, dest moved
+  $ cd ..
+  $ reset
+  $ echo "change" > file
+  $ hg commit -Aqm "source"
+  $ hg up -q 0
+  $ hg mv file file_newloc
+  $ hg commit -Aqm "dest"
+  $ hg up -q 2
+  $ logg
+  @  (2) dest
+  |  affected: file file_newloc
+  |  deleted: file
+  |
+  | o  (1) source
+  |/   affected: file
+  |    deleted:
+  |
+  o  (0) base
+     affected: file
+     deleted:
+  
+
+  $ hg rebase -d 2 -s 1
+  rebasing 1:ec87889f5f90 "source"
+  merging file_newloc and file to file_newloc
+  saved backup bundle to $TESTTMP/foo/foo/foo/foo/.hg/strip-backup/ec87889f5f90-e39a76b8-backup.hg (glob)
+  $ hg up -q 2 # source
+  $ cat file_newloc # Should follow:
+  change
+  $ hg resolve --tool=internal:dumpjson --all
+  [
+   {
+    "conflicts": []
+   }
+  ]
+Test case 4: Source changed, dest moved (w/o copytracing)
+  $ cd ..
+  $ reset
+  $ echo "change" > file
+  $ hg commit -Aqm "source"
+  $ hg up -q 0
+  $ hg mv file file_newloc
+  $ hg commit -Aqm "dest"
+  $ hg up -q 2
+  $ logg
+  @  (2) dest
+  |  affected: file file_newloc
+  |  deleted: file
+  |
+  | o  (1) source
+  |/   affected: file
+  |    deleted:
+  |
+  o  (0) base
+     affected: file
+     deleted:
+  
+
+  $ hg rebase -d 2 -s 1 --config experimental.disablecopytrace=True
+  rebasing 1:ec87889f5f90 "source"
+  other [source] changed file which local [dest] deleted
+  use (c)hanged version, leave (d)eleted, or leave (u)nresolved? u
+  unresolved conflicts (see hg resolve, then hg rebase --continue)
+  [1]
+  $ hg resolve --tool=internal:dumpjson --all
+  [
+   {
+    "conflicts": [{"base": {"contents": "base\n", "exists": true, "isexec": false, "issymlink": false}, "local": {"exists": false}, "other": {"contents": "change\n", "exists": true, "isexec": false, "issymlink": false}, "output": {"exists": false, "path": "$TESTTMP/foo/foo/foo/foo/file"}, "path": "file"}]
+   }
+  ]
+Test case 5: Source moved, dest changed
+  $ cd ..
+  $ reset
+  $ hg mv file file_newloc
+  $ hg commit -Aqm "source"
+  $ hg up -q 0
+  $ echo "change" > file
+  $ hg commit -Aqm "dest"
+  $ hg up -q 2
+  $ logg
+  @  (2) dest
+  |  affected: file
+  |  deleted:
+  |
+  | o  (1) source
+  |/   affected: file file_newloc
+  |    deleted: file
+  |
+  o  (0) base
+     affected: file
+     deleted:
+  
+
+  $ hg rebase -d 2 -s 1
+  rebasing 1:e6e7483a8950 "source"
+  merging file and file_newloc to file_newloc
+  saved backup bundle to $TESTTMP/foo/foo/foo/foo/.hg/strip-backup/e6e7483a8950-8e128ac2-backup.hg (glob)
+  $ hg up 2
+  1 files updated, 0 files merged, 1 files removed, 0 files unresolved
+  $ cat file_newloc
+  change
+  $ hg resolve --tool=internal:dumpjson --all
+  [
+   {
+    "conflicts": []
+   }
+  ]
+Test case 6: Source moved, dest changed (w/o copytracing)
+  $ cd ..
+  $ reset
+  $ hg mv file file_newloc
+  $ hg commit -Aqm "source"
+  $ hg up -q 0
+  $ echo "change" > file
+  $ hg commit -Aqm "dest"
+  $ hg up -q 2
+  $ logg
+  @  (2) dest
+  |  affected: file
+  |  deleted:
+  |
+  | o  (1) source
+  |/   affected: file file_newloc
+  |    deleted: file
+  |
+  o  (0) base
+     affected: file
+     deleted:
+  
+
+  $ hg rebase -d 2 -s 1 --config experimental.disablecopytrace=True
+  rebasing 1:e6e7483a8950 "source"
+  local [dest] changed file which other [source] deleted
+  use (c)hanged version, (d)elete, or leave (u)nresolved? u
+  unresolved conflicts (see hg resolve, then hg rebase --continue)
+  [1]
+  $ hg resolve --tool=internal:dumpjson --all
+  [
+   {
+    "conflicts": [{"base": {"contents": "base\n", "exists": true, "isexec": false, "issymlink": false}, "local": {"contents": "change\n", "exists": true, "isexec": false, "issymlink": false}, "other": {"exists": false}, "output": {"contents": "change\n", "exists": true, "isexec": false, "issymlink": false, "path": "$TESTTMP/foo/foo/foo/foo/file"}, "path": "file"}]
+   }
+  ]
+Test case 7: Source is a directory, dest is a file (base is still a file)
+  $ cd ..
+  $ reset
+  $ hg rm file
+  $ mkdir file # "file" is a stretch
+  $ echo "this will cause problems" >> file/subfile
+  $ hg commit -Aqm "source"
+  $ hg up -q 0
+  $ echo "change" > file
+  $ hg commit -Aqm "dest"
+  $ hg up -q 2
+  $ logg
+  @  (2) dest
+  |  affected: file
+  |  deleted:
+  |
+  | o  (1) source
+  |/   affected: file file/subfile
+  |    deleted: file
+  |
+  o  (0) base
+     affected: file
+     deleted:
+  
+
+  $ hg rebase -d 2 -s 1
+  rebasing 1:ed93aeac6b3c "source"
+  abort: Not a directory: '$TESTTMP/foo/foo/foo/foo/file/subfile'
+  [255]
+  $ hg resolve --tool=internal:dumpjson --all
+  [
+   {
+    "conflicts": []
+   }
+  ]
+Test case 8: Source is a file, dest is a directory (base is still a file)
+  $ cd ..
+  $ reset
+  $ echo "change" > file
+  $ hg commit -Aqm "source"
+  $ hg up -q 0
+  $ hg rm file
+  $ mkdir file # "file"
+  $ echo "this will cause problems" >> file/subfile
+  $ hg commit -Aqm "dest"
+  $ hg up -q 2
+  $ logg
+  @  (2) dest
+  |  affected: file file/subfile
+  |  deleted: file
+  |
+  | o  (1) source
+  |/   affected: file
+  |    deleted:
+  |
+  o  (0) base
+     affected: file
+     deleted:
+  
+
+  $ hg rebase -d 2 -s 1
+  rebasing 1:ec87889f5f90 "source"
+  abort: Operation not permitted: '$TESTTMP/foo/foo/foo/foo/file'
+  [255]
+  $ hg resolve --tool=internal:dumpjson --all
+  [
+   {
+    "conflicts": []
+   }
+  ]
+Test case 9: Source is a binary file, dest is a file (base is still a file)
+  $ cd ..
+  $ reset
+  $ python -c 'f = open("file", "w"); f.write("\x00")'
+  $ hg commit -Aqm "source"
+  $ hg up -q 0
+  $ echo "change" > file
+  $ hg commit -Aqm "dest"
+  $ hg up -q 2
+  $ logg
+  @  (2) dest
+  |  affected: file
+  |  deleted:
+  |
+  | o  (1) source
+  |/   affected: file
+  |    deleted:
+  |
+  o  (0) base
+     affected: file
+     deleted:
+  
+
+  $ hg rebase -d 2 -s 1
+  rebasing 1:b6e55a03a5dc "source"
+  merging file
+  warning: ([^\s]+) looks like a binary file. (re)
+  warning: conflicts while merging file! (edit, then use 'hg resolve --mark')
+  unresolved conflicts (see hg resolve, then hg rebase --continue)
+  [1]
+  $ cat -v file # The local version should be left in the working copy
+  change
+  $ hg resolve --tool=internal:dumpjson --all
+  [
+   {
+    "conflicts": [{"base": {"contents": "base\n", "exists": true, "isexec": false, "issymlink": false}, "local": {"contents": "change\n", "exists": true, "isexec": false, "issymlink": false}, "other": {"contents": "\u0000", "exists": true, "isexec": false, "issymlink": false}, "output": {"contents": "change\n", "exists": true, "isexec": false, "issymlink": false, "path": "$TESTTMP/foo/foo/foo/foo/file"}, "path": "file"}]
+   }
+  ]
+Test case 10: Source is a file, dest is a binary file (base is still a file)
+  $ cd ..
+  $ reset
+  $ echo "change" > file
+  $ hg commit -Aqm "source"
+  $ hg up -q 0
+  $ python -c 'f = open("file", "w"); f.write("\x00")'
+  $ hg commit -Aqm "dest"
+  $ hg up -q 2
+  $ logg
+  @  (2) dest
+  |  affected: file
+  |  deleted:
+  |
+  | o  (1) source
+  |/   affected: file
+  |    deleted:
+  |
+  o  (0) base
+     affected: file
+     deleted:
+  
+
+  $ hg rebase -d 2 -s 1
+  rebasing 1:ec87889f5f90 "source"
+  merging file
+  warning: ([^\s]+) looks like a binary file. (re)
+  warning: conflicts while merging file! (edit, then use 'hg resolve --mark')
+  unresolved conflicts (see hg resolve, then hg rebase --continue)
+  [1]
+  $ cat -v file
+  ^@ (no-eol)
+  $ hg resolve --tool=internal:dumpjson --all
+  [
+   {
+    "conflicts": [{"base": {"contents": "base\n", "exists": true, "isexec": false, "issymlink": false}, "local": {"contents": "\u0000", "exists": true, "isexec": false, "issymlink": false}, "other": {"contents": "change\n", "exists": true, "isexec": false, "issymlink": false}, "output": {"contents": "\u0000", "exists": true, "isexec": false, "issymlink": false, "path": "$TESTTMP/foo/foo/foo/foo/file"}, "path": "file"}]
+   }
+  ]
+Test case 11: Source is a symlink, dest is a file (base is still a file)
+  $ cd ..
+  $ reset
+  $ rm file
+  $ ln -s somepath file
+  $ hg commit -Aqm "source"
+  $ hg up -q 0
+  $ echo "change" > file
+  $ hg commit -Aqm "dest"
+  $ hg up -q 2
+  $ logg
+  @  (2) dest
+  |  affected: file
+  |  deleted:
+  |
+  | o  (1) source
+  |/   affected: file
+  |    deleted:
+  |
+  o  (0) base
+     affected: file
+     deleted:
+  
+
+  $ hg rebase -d 2 -s 1
+  rebasing 1:06aece48b59f "source"
+  merging file
+  warning: internal :merge cannot merge symlinks for file
+  warning: conflicts while merging file! (edit, then use 'hg resolve --mark')
+  unresolved conflicts (see hg resolve, then hg rebase --continue)
+  [1]
+  $ cat -v file
+  change
+  $ hg resolve --tool=internal:dumpjson --all
+  [
+   {
+    "conflicts": [{"base": {"contents": "base\n", "exists": true, "isexec": false, "issymlink": false}, "local": {"contents": "change\n", "exists": true, "isexec": false, "issymlink": false}, "other": {"contents": "somepath", "exists": true, "isexec": false, "issymlink": true}, "output": {"contents": "change\n", "exists": true, "isexec": false, "issymlink": false, "path": "$TESTTMP/foo/foo/foo/foo/file"}, "path": "file"}]
+   }
+  ]
+Test case 12: Source is a file, dest is a symlink (base is still a file)
+  $ cd ..
+  $ reset
+  $ echo "change" > file
+  $ hg commit -Aqm "source"
+  $ hg up -q 0
+  $ rm file
+  $ ln -s somepath file
+  $ hg commit -Aqm "dest"
+  $ hg up -q 2
+  $ logg
+  @  (2) dest
+  |  affected: file
+  |  deleted:
+  |
+  | o  (1) source
+  |/   affected: file
+  |    deleted:
+  |
+  o  (0) base
+     affected: file
+     deleted:
+  
+
+  $ hg rebase -d 2 -s 1
+  rebasing 1:ec87889f5f90 "source"
+  merging file
+  warning: internal :merge cannot merge symlinks for file
+  warning: conflicts while merging file! (edit, then use 'hg resolve --mark')
+  unresolved conflicts (see hg resolve, then hg rebase --continue)
+  [1]
+  $ cat -v file
+  cat: file: No such file or directory
+  [1]
+  $ ls file
+  file
+  $ hg resolve --tool=internal:dumpjson --all
+  [
+   {
+    "conflicts": [{"base": {"contents": "base\n", "exists": true, "isexec": false, "issymlink": false}, "local": {"contents": "somepath", "exists": true, "isexec": false, "issymlink": true}, "other": {"contents": "change\n", "exists": true, "isexec": false, "issymlink": false}, "output": {"contents": "somepath", "exists": true, "isexec": false, "issymlink": true, "path": "$TESTTMP/foo/foo/foo/foo/file"}, "path": "file"}]
+   }
+  ]