D5353: format-source: move the extension to core

pulkit (Pulkit Goyal) phabricator at mercurial-scm.org
Mon Dec 3 15:33:42 UTC 2018


pulkit created this revision.
Herald added a subscriber: mercurial-devel.
Herald added a reviewer: hg-reviewers.

REVISION SUMMARY
  Format-source is an extension which helps in applying and merging code
  formatting (or other changes).
  
  It provides a 'format-source' command which will
  format the source with tool provided using config options. While formating it
  stores information about the chunks being formatted and stores that in
  '.hg-format-source' file. The file should be checked in to the repo so that it
  can be shared with others.
  It wraps filemerging to read formatting information from '.hg-format-source'
  file which is checked into repo and then prevents conflicts related to
  formatting.
  
  It's ~300 lines of python code but the problem which it is solving is very
  important in big distributed projects.
  
  With this extension, we have all the functionality in core which is capable of
  doing source formatting. `hg annotate --skip` is one of the other important
  functionality here.
  
  It caught my interest while I was finding how we can prevent conflicts with mass
  b'' writing or other changes. I also found that Mozilla recently used it format
  their large C++ codebase to Google coding style.
  https://bugzilla.mozilla.org/show_bug.cgi?id=1511181
  
  The extension and tests are copied from e43153fb827 changeset of
  https://bitbucket.org/octobus/format-source repository.
  
  While moving this extension, following changes were made:
  
  - added absolute_import to testlib/json-pretty.py
  - fix test-check-code.t violations like too long lines
  - dropped old compatibility code
  - changed tested-with value to match core ext ones
  - removed minimum-hgversion and buglink infos

REPOSITORY
  rHG Mercurial

REVISION DETAIL
  https://phab.mercurial-scm.org/D5353

AFFECTED FILES
  hgext/formatsource.py
  tests/test-format-config.t
  tests/test-format-source.t
  tests/test-help.t
  tests/testlib/json-pretty.py

CHANGE DETAILS

diff --git a/tests/testlib/json-pretty.py b/tests/testlib/json-pretty.py
new file mode 100644
--- /dev/null
+++ b/tests/testlib/json-pretty.py
@@ -0,0 +1,47 @@
+r"""Command-line tool to validate and pretty-print JSON
+
+Usage::
+
+    $ echo '{"json":"obj"}' | python -m json.tool
+    {
+        "json": "obj"
+    }
+    $ echo '{ 1.2:3.4}' | python -m json.tool
+    Expecting property name enclosed in double quotes: line 1 column 3 (char 2)
+
+"""
+from __future__ import absolute_import
+import json
+import os
+import sys
+
+def main():
+    if len(sys.argv) == 1:
+        infile = sys.stdin
+        outfile = sys.stdout
+    elif len(sys.argv) == 2:
+        infile = open(sys.argv[1], 'rb')
+        outfile = sys.stdout
+    elif len(sys.argv) == 3:
+        infile = open(sys.argv[1], 'rb')
+        outfile = open(sys.argv[2], 'wb')
+    else:
+        raise SystemExit(sys.argv[0] + " [infile [outfile]]")
+    with infile:
+        try:
+            obj = json.load(infile)
+        except ValueError as e:
+            raise SystemExit(e)
+    if os.path.exists('.json-indent'):
+        with open('.json-indent') as fp:
+            indent = int(fp.read())
+    else:
+        indent = 4
+    with outfile:
+        json.dump(obj, outfile, sort_keys=True,
+                  indent=indent, separators=(',', ': '))
+        outfile.write('\n')
+
+
+if __name__ == '__main__':
+    main()
diff --git a/tests/test-help.t b/tests/test-help.t
--- a/tests/test-help.t
+++ b/tests/test-help.t
@@ -360,6 +360,7 @@
        eol           automatically manage newlines in repository files
        extdiff       command to allow external programs to compare revisions
        factotum      http authentication with factotum
+       formatsource  help dealing with code source reformating
        githelp       try mapping git commands to Mercurial commands
        gpg           commands to sign and verify changesets
        hgk           browse the repository in a graphical way
diff --git a/tests/test-format-source.t b/tests/test-format-source.t
new file mode 100644
--- /dev/null
+++ b/tests/test-format-source.t
@@ -0,0 +1,1376 @@
+
+Basic init
+
+  $ code_root=`dirname $TESTDIR`
+
+  $ cat << EOF >> $HGRCPATH
+  > [extensions]
+  > formatsource=
+  > rebase =
+  > strip =
+  > [format-source]
+  > json = $PYTHON -m json.tool
+  > [default]
+  > format-source=--date '0 0'
+  > [ui]
+  > merge = internal:merge3
+  > EOF
+
+  $ hg init test_repo
+  $ cd test_repo
+
+Commit various json file
+
+  $ cat << EOF > root-file.json
+  > {"key1": 1, "key2": [5,6,7,8], "key3": ["arthur", "babar", "celeste"]}
+  > EOF
+  $ mkdir dir-1
+  $ cat << EOF > dir-1/file-1.json
+  > {"key1": [42,53,78], "key2": [9,3,8,1], "key3": ["London", "Paris", "Tokyo"]}
+  > EOF
+  $ cat << EOF > dir-1/file-2.json
+  > {"key1": 1, "key2": [0, 1, 2, 3, 4, 5, 6, 7, 8, 9], "key3": [54]}
+  > EOF
+  $ mkdir dir-2
+  $ cat << EOF > dir-2/file-1.json
+  > {"key1": [44,50,82], "key2": [4, 8, -4, 5], "key3": ["Asia", "Europe"]}
+  > EOF
+  $ cat << EOF > dir-2/file-2.json
+  > {"key1": 6,
+  > "key2": [3, 5, -2, 3, 4, 11, 10, -4, 8, 9],
+  > "key3": [898, 32543, 2342]}
+  > EOF
+  $ cat << EOF > dir-2/file-3.json
+  > {"key1": "hello", "key2": [3, 1, 8, -1, 19, 2], "key3": "babar"}
+  > EOF
+  $ hg add .
+  adding dir-1/file-1.json
+  adding dir-1/file-2.json
+  adding dir-2/file-1.json
+  adding dir-2/file-2.json
+  adding dir-2/file-3.json
+  adding root-file.json
+  $ hg commit --message 'initial commit'
+
+format them (in multiple steps)
+
+  $ hg format-source --date '0 0' json glob:root-file.json -m 'format the root-file'
+  $ hg export
+  # HG changeset patch
+  # User test
+  # Date 0 0
+  #      Thu Jan 01 00:00:00 1970 +0000
+  # Node ID 4d5d2129291fb884920bc2dce4a5da973634a6e9
+  # Parent  f168d21ce7659a51518009b8aab78a222af2ddf8
+  format the root-file
+  
+  diff -r f168d21ce765 -r 4d5d2129291f .hg-format-source
+  --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+  +++ b/.hg-format-source	Thu Jan 01 00:00:00 1970 +0000
+  @@ -0,0 +1,1 @@
+  +{"pattern": "glob:root-file.json", "tool": "json"}
+  diff -r f168d21ce765 -r 4d5d2129291f root-file.json
+  --- a/root-file.json	Thu Jan 01 00:00:00 1970 +0000
+  +++ b/root-file.json	Thu Jan 01 00:00:00 1970 +0000
+  @@ -1,1 +1,14 @@
+  -{"key1": 1, "key2": [5,6,7,8], "key3": ["arthur", "babar", "celeste"]}
+  +{
+  +    "key1": 1,
+  +    "key2": [
+  +        5,
+  +        6,
+  +        7,
+  +        8
+  +    ],
+  +    "key3": [
+  +        "arthur",
+  +        "babar",
+  +        "celeste"
+  +    ]
+  +}
+  $ hg format-source --date '0 0' json 'glob:dir-1/**' -m 'format dir1 as a whole'
+  $ hg export
+  # HG changeset patch
+  # User test
+  # Date 0 0
+  #      Thu Jan 01 00:00:00 1970 +0000
+  # Node ID 279459a606de25c75ca2e2984a607ddf4a88ac29
+  # Parent  4d5d2129291fb884920bc2dce4a5da973634a6e9
+  format dir1 as a whole
+  
+  diff -r 4d5d2129291f -r 279459a606de .hg-format-source
+  --- a/.hg-format-source	Thu Jan 01 00:00:00 1970 +0000
+  +++ b/.hg-format-source	Thu Jan 01 00:00:00 1970 +0000
+  @@ -1,1 +1,2 @@
+   {"pattern": "glob:root-file.json", "tool": "json"}
+  +{"pattern": "glob:dir-1/**", "tool": "json"}
+  diff -r 4d5d2129291f -r 279459a606de dir-1/file-1.json
+  --- a/dir-1/file-1.json	Thu Jan 01 00:00:00 1970 +0000
+  +++ b/dir-1/file-1.json	Thu Jan 01 00:00:00 1970 +0000
+  @@ -1,1 +1,18 @@
+  -{"key1": [42,53,78], "key2": [9,3,8,1], "key3": ["London", "Paris", "Tokyo"]}
+  +{
+  +    "key1": [
+  +        42,
+  +        53,
+  +        78
+  +    ],
+  +    "key2": [
+  +        9,
+  +        3,
+  +        8,
+  +        1
+  +    ],
+  +    "key3": [
+  +        "London",
+  +        "Paris",
+  +        "Tokyo"
+  +    ]
+  +}
+  diff -r 4d5d2129291f -r 279459a606de dir-1/file-2.json
+  --- a/dir-1/file-2.json	Thu Jan 01 00:00:00 1970 +0000
+  +++ b/dir-1/file-2.json	Thu Jan 01 00:00:00 1970 +0000
+  @@ -1,1 +1,18 @@
+  -{"key1": 1, "key2": [0, 1, 2, 3, 4, 5, 6, 7, 8, 9], "key3": [54]}
+  +{
+  +    "key1": 1,
+  +    "key2": [
+  +        0,
+  +        1,
+  +        2,
+  +        3,
+  +        4,
+  +        5,
+  +        6,
+  +        7,
+  +        8,
+  +        9
+  +    ],
+  +    "key3": [
+  +        54
+  +    ]
+  +}
+  $ hg format-source --date '0 0' json glob:dir-2/file-1.json glob:dir-2/file-3.json -m 'format some dir2 with explicite pattern'
+  $ hg export
+  # HG changeset patch
+  # User test
+  # Date 0 0
+  #      Thu Jan 01 00:00:00 1970 +0000
+  # Node ID d29e366c2a4b03cc2c09c0581fed00147b5bbebc
+  # Parent  279459a606de25c75ca2e2984a607ddf4a88ac29
+  format some dir2 with explicite pattern
+  
+  diff -r 279459a606de -r d29e366c2a4b .hg-format-source
+  --- a/.hg-format-source	Thu Jan 01 00:00:00 1970 +0000
+  +++ b/.hg-format-source	Thu Jan 01 00:00:00 1970 +0000
+  @@ -1,2 +1,4 @@
+   {"pattern": "glob:root-file.json", "tool": "json"}
+   {"pattern": "glob:dir-1/**", "tool": "json"}
+  +{"pattern": "glob:dir-2/file-1.json", "tool": "json"}
+  +{"pattern": "glob:dir-2/file-3.json", "tool": "json"}
+  diff -r 279459a606de -r d29e366c2a4b dir-2/file-1.json
+  --- a/dir-2/file-1.json	Thu Jan 01 00:00:00 1970 +0000
+  +++ b/dir-2/file-1.json	Thu Jan 01 00:00:00 1970 +0000
+  @@ -1,1 +1,17 @@
+  -{"key1": [44,50,82], "key2": [4, 8, -4, 5], "key3": ["Asia", "Europe"]}
+  +{
+  +    "key1": [
+  +        44,
+  +        50,
+  +        82
+  +    ],
+  +    "key2": [
+  +        4,
+  +        8,
+  +        -4,
+  +        5
+  +    ],
+  +    "key3": [
+  +        "Asia",
+  +        "Europe"
+  +    ]
+  +}
+  diff -r 279459a606de -r d29e366c2a4b dir-2/file-3.json
+  --- a/dir-2/file-3.json	Thu Jan 01 00:00:00 1970 +0000
+  +++ b/dir-2/file-3.json	Thu Jan 01 00:00:00 1970 +0000
+  @@ -1,1 +1,12 @@
+  -{"key1": "hello", "key2": [3, 1, 8, -1, 19, 2], "key3": "babar"}
+  +{
+  +    "key1": "hello",
+  +    "key2": [
+  +        3,
+  +        1,
+  +        8,
+  +        -1,
+  +        19,
+  +        2
+  +    ],
+  +    "key3": "babar"
+  +}
+  $ hg log -G
+  @  changeset:   3:d29e366c2a4b
+  |  tag:         tip
+  |  user:        test
+  |  date:        Thu Jan 01 00:00:00 1970 +0000
+  |  summary:     format some dir2 with explicite pattern
+  |
+  o  changeset:   2:279459a606de
+  |  user:        test
+  |  date:        Thu Jan 01 00:00:00 1970 +0000
+  |  summary:     format dir1 as a whole
+  |
+  o  changeset:   1:4d5d2129291f
+  |  user:        test
+  |  date:        Thu Jan 01 00:00:00 1970 +0000
+  |  summary:     format the root-file
+  |
+  o  changeset:   0:f168d21ce765
+     user:        test
+     date:        Thu Jan 01 00:00:00 1970 +0000
+     summary:     initial commit
+  
+
+Test merging
+============
+
+Simple case, the root file
+
+  $ hg up 0
+  5 files updated, 0 files merged, 1 files removed, 0 files unresolved
+  $ cat << EOF > root-file.json
+  > {"key1": 1, "key2": [5,6,7,8], "key3": ["arthur", "babar", "celeste", "pomme"]}
+  > EOF
+  $ hg branch B_A
+  marked working directory as branch B_A
+  (branches are permanent and global, did you want a bookmark?)
+  $ hg ci -m 'update root-file'
+  $ hg merge 1
+  merging root-file.json
+  1 files updated, 1 files merged, 0 files removed, 0 files unresolved
+  (branch merge, don't forget to commit)
+  $ hg diff -r 'p1()'
+  diff -r 86b2ca2ae552 .hg-format-source
+  --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+  +++ b/.hg-format-source	* (glob)
+  @@ -0,0 +1,1 @@
+  +{"pattern": "glob:root-file.json", "tool": "json"}
+  diff -r 86b2ca2ae552 root-file.json
+  --- a/root-file.json	Thu Jan 01 00:00:00 1970 +0000
+  +++ b/root-file.json	* (glob)
+  @@ -1,1 +1,15 @@
+  -{"key1": 1, "key2": [5,6,7,8], "key3": ["arthur", "babar", "celeste", "pomme"]}
+  +{
+  +    "key1": 1,
+  +    "key2": [
+  +        5,
+  +        6,
+  +        7,
+  +        8
+  +    ],
+  +    "key3": [
+  +        "arthur",
+  +        "babar",
+  +        "celeste",
+  +        "pomme"
+  +    ]
+  +}
+  $ hg diff -r 'p2()'
+  diff -r 4d5d2129291f root-file.json
+  --- a/root-file.json	Thu Jan 01 00:00:00 1970 +0000
+  +++ b/root-file.json	* (glob)
+  @@ -9,6 +9,7 @@
+       "key3": [
+           "arthur",
+           "babar",
+  -        "celeste"
+  +        "celeste",
+  +        "pomme"
+       ]
+   }
+
+Same from the other direction
+
+  $ hg up -C 1
+  2 files updated, 0 files merged, 0 files removed, 0 files unresolved
+  $ hg merge B_A
+  merging root-file.json
+  0 files updated, 1 files merged, 0 files removed, 0 files unresolved
+  (branch merge, don't forget to commit)
+  $ hg diff -r 'p1()'
+  diff -r 4d5d2129291f root-file.json
+  --- a/root-file.json	Thu Jan 01 00:00:00 1970 +0000
+  +++ b/root-file.json	* (glob)
+  @@ -9,6 +9,7 @@
+       "key3": [
+           "arthur",
+           "babar",
+  -        "celeste"
+  +        "celeste",
+  +        "pomme"
+       ]
+   }
+  $ hg diff -r 'p2()'
+  diff -r 86b2ca2ae552 .hg-format-source
+  --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+  +++ b/.hg-format-source	* (glob)
+  @@ -0,0 +1,1 @@
+  +{"pattern": "glob:root-file.json", "tool": "json"}
+  diff -r 86b2ca2ae552 root-file.json
+  --- a/root-file.json	Thu Jan 01 00:00:00 1970 +0000
+  +++ b/root-file.json	* (glob)
+  @@ -1,1 +1,15 @@
+  -{"key1": 1, "key2": [5,6,7,8], "key3": ["arthur", "babar", "celeste", "pomme"]}
+  +{
+  +    "key1": 1,
+  +    "key2": [
+  +        5,
+  +        6,
+  +        7,
+  +        8
+  +    ],
+  +    "key3": [
+  +        "arthur",
+  +        "babar",
+  +        "celeste",
+  +        "pomme"
+  +    ]
+  +}
+
+Merging change on both side:
+
+  $ hg up -C 1
+  1 files updated, 0 files merged, 0 files removed, 0 files unresolved
+  $ hg branch B_B
+  marked working directory as branch B_B
+
+  $ echo '{"key1": 4, "key2": [5,6,7,8], "key3": ["arthur", "babar", "celeste"]}' | $PYTHON -m json.tool > root-file.json
+  $ hg ci -m 'update key 1'
+  $ hg merge B_A
+  merging root-file.json
+  0 files updated, 1 files merged, 0 files removed, 0 files unresolved
+  (branch merge, don't forget to commit)
+  $ hg diff -r 'p1()'
+  diff -r 217d6c23789b root-file.json
+  --- a/root-file.json	Thu Jan 01 00:00:00 1970 +0000
+  +++ b/root-file.json	* (glob)
+  @@ -9,6 +9,7 @@
+       "key3": [
+           "arthur",
+           "babar",
+  -        "celeste"
+  +        "celeste",
+  +        "pomme"
+       ]
+   }
+  $ hg diff -r 'p2()'
+  diff -r 86b2ca2ae552 .hg-format-source
+  --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+  +++ b/.hg-format-source	* (glob)
+  @@ -0,0 +1,1 @@
+  +{"pattern": "glob:root-file.json", "tool": "json"}
+  diff -r 86b2ca2ae552 root-file.json
+  --- a/root-file.json	Thu Jan 01 00:00:00 1970 +0000
+  +++ b/root-file.json	* (glob)
+  @@ -1,1 +1,15 @@
+  -{"key1": 1, "key2": [5,6,7,8], "key3": ["arthur", "babar", "celeste", "pomme"]}
+  +{
+  +    "key1": 4,
+  +    "key2": [
+  +        5,
+  +        6,
+  +        7,
+  +        8
+  +    ],
+  +    "key3": [
+  +        "arthur",
+  +        "babar",
+  +        "celeste",
+  +        "pomme"
+  +    ]
+  +}
+
+Merging with conflict
+
+  $ hg up -C B_B
+  1 files updated, 0 files merged, 0 files removed, 0 files unresolved
+
+  $ echo '{"key1": 4, "key2": [5,6,7,8], "key3": ["arthur", "babar", "celeste", "Flore"]}' | $PYTHON -m json.tool > root-file.json
+  $ hg ci -m 'conflicting update'
+  $ hg merge B_A
+  merging root-file.json
+  warning: conflicts while merging root-file.json! (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 merge --abort' to abandon
+  [1]
+  $ hg resolve -l
+  U root-file.json
+  $ hg diff -r 'p1()'
+  diff -r 654f4c32c118 root-file.json
+  --- a/root-file.json	Thu Jan 01 00:00:00 1970 +0000
+  +++ b/root-file.json	* (glob)
+  @@ -9,7 +9,14 @@
+       "key3": [
+           "arthur",
+           "babar",
+  +<<<<<<< working copy: 654f4c32c118 B_B - test: conflicting update
+           "celeste",
+           "Flore"
+  +||||||| base
+  +        "celeste"
+  +=======
+  +        "celeste",
+  +        "pomme"
+  +>>>>>>> merge rev:    86b2ca2ae552 B_A - test: update root-file
+       ]
+   }
+  $ hg diff -r 'p2()'
+  diff -r 86b2ca2ae552 .hg-format-source
+  --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+  +++ b/.hg-format-source	* (glob)
+  @@ -0,0 +1,1 @@
+  +{"pattern": "glob:root-file.json", "tool": "json"}
+  diff -r 86b2ca2ae552 root-file.json
+  --- a/root-file.json	Thu Jan 01 00:00:00 1970 +0000
+  +++ b/root-file.json	* (glob)
+  @@ -1,1 +1,22 @@
+  -{"key1": 1, "key2": [5,6,7,8], "key3": ["arthur", "babar", "celeste", "pomme"]}
+  +{
+  +    "key1": 4,
+  +    "key2": [
+  +        5,
+  +        6,
+  +        7,
+  +        8
+  +    ],
+  +    "key3": [
+  +        "arthur",
+  +        "babar",
+  +<<<<<<< working copy: 654f4c32c118 B_B - test: conflicting update
+  +        "celeste",
+  +        "Flore"
+  +||||||| base
+  +        "celeste"
+  +=======
+  +        "celeste",
+  +        "pomme"
+  +>>>>>>> merge rev:    86b2ca2ae552 B_A - test: update root-file
+  +    ]
+  +}
+
+Test merge with no reformating needed
+-------------------------------------
+
+  $ hg up -C B_B
+  1 files updated, 0 files merged, 0 files removed, 0 files unresolved
+  $ hg merge default
+  5 files updated, 0 files merged, 0 files removed, 0 files unresolved
+  (branch merge, don't forget to commit)
+  $ hg diff -r 'p2()'
+  diff -r d29e366c2a4b root-file.json
+  --- a/root-file.json	Thu Jan 01 00:00:00 1970 +0000
+  +++ b/root-file.json	* (glob)
+  @@ -1,5 +1,5 @@
+   {
+  -    "key1": 1,
+  +    "key1": 4,
+       "key2": [
+           5,
+           6,
+  @@ -9,6 +9,7 @@
+       "key3": [
+           "arthur",
+           "babar",
+  -        "celeste"
+  +        "celeste",
+  +        "Flore"
+       ]
+   }
+
+Test rebase
+-----------
+
+  $ hg up -C B_A
+  5 files updated, 0 files merged, 1 files removed, 0 files unresolved
+  $ hg rebase -d default
+  rebasing 4:86b2ca2ae552 "update root-file"
+  merging root-file.json
+  saved backup bundle to $TESTTMP/test_repo/.hg/strip-backup/86b2ca2ae552-2a92ee50-*.hg (glob)
+  $ hg export
+  # HG changeset patch
+  # User test
+  # Date 0 0
+  #      Thu Jan 01 00:00:00 1970 +0000
+  # Node ID 180a5b3a41142f9e2f40e773ed299bc996442266
+  # Parent  d29e366c2a4b03cc2c09c0581fed00147b5bbebc
+  update root-file
+  
+  diff -r d29e366c2a4b -r 180a5b3a4114 root-file.json
+  --- a/root-file.json	Thu Jan 01 00:00:00 1970 +0000
+  +++ b/root-file.json	Thu Jan 01 00:00:00 1970 +0000
+  @@ -9,6 +9,7 @@
+       "key3": [
+           "arthur",
+           "babar",
+  -        "celeste"
+  +        "celeste",
+  +        "pomme"
+       ]
+   }
+  $ hg log -G
+  @  changeset:   6:180a5b3a4114
+  |  tag:         tip
+  |  parent:      3:d29e366c2a4b
+  |  user:        test
+  |  date:        Thu Jan 01 00:00:00 1970 +0000
+  |  summary:     update root-file
+  |
+  | o  changeset:   5:654f4c32c118
+  | |  branch:      B_B
+  | |  user:        test
+  | |  date:        Thu Jan 01 00:00:00 1970 +0000
+  | |  summary:     conflicting update
+  | |
+  | o  changeset:   4:217d6c23789b
+  | |  branch:      B_B
+  | |  parent:      1:4d5d2129291f
+  | |  user:        test
+  | |  date:        Thu Jan 01 00:00:00 1970 +0000
+  | |  summary:     update key 1
+  | |
+  o |  changeset:   3:d29e366c2a4b
+  | |  user:        test
+  | |  date:        Thu Jan 01 00:00:00 1970 +0000
+  | |  summary:     format some dir2 with explicite pattern
+  | |
+  o |  changeset:   2:279459a606de
+  |/   user:        test
+  |    date:        Thu Jan 01 00:00:00 1970 +0000
+  |    summary:     format dir1 as a whole
+  |
+  o  changeset:   1:4d5d2129291f
+  |  user:        test
+  |  date:        Thu Jan 01 00:00:00 1970 +0000
+  |  summary:     format the root-file
+  |
+  o  changeset:   0:f168d21ce765
+     user:        test
+     date:        Thu Jan 01 00:00:00 1970 +0000
+     summary:     initial commit
+  
+
+Test update
+-----------
+
+  $ hg up -C 0
+  5 files updated, 0 files merged, 1 files removed, 0 files unresolved
+  $ cat << EOF > root-file.json
+  > {"key1": 1, "key2": [4,6,7,8], "key3": ["arthur", "babar", "celeste"]}
+  > EOF
+  $ hg diff
+  diff -r f168d21ce765 root-file.json
+  --- a/root-file.json	Thu Jan 01 00:00:00 1970 +0000
+  +++ b/root-file.json	* (glob)
+  @@ -1,1 +1,1 @@
+  -{"key1": 1, "key2": [5,6,7,8], "key3": ["arthur", "babar", "celeste"]}
+  +{"key1": 1, "key2": [4,6,7,8], "key3": ["arthur", "babar", "celeste"]}
+  $ hg up --merge
+  merging root-file.json
+  5 files updated, 1 files merged, 0 files removed, 0 files unresolved
+  $ hg diff
+  diff -r 180a5b3a4114 root-file.json
+  --- a/root-file.json	Thu Jan 01 00:00:00 1970 +0000
+  +++ b/root-file.json	* (glob)
+  @@ -1,7 +1,7 @@
+   {
+       "key1": 1,
+       "key2": [
+  -        5,
+  +        4,
+           6,
+           7,
+           8
+
+
+Test multiple change to multiple file accross multiple revisions
+----------------------------------------------------------------
+
+modify one side
+
+  $ hg up -C default
+  1 files updated, 0 files merged, 0 files removed, 0 files unresolved
+  $ echo '{"key1": [42,53,76], "key2": [9,3,8,1], "key3": ["London", "Paris", "Tokyo"]}' | $PYTHON -m json.tool > dir-1/file-1.json
+  $ hg diff
+  diff -r 180a5b3a4114 dir-1/file-1.json
+  --- a/dir-1/file-1.json	Thu Jan 01 00:00:00 1970 +0000
+  +++ b/dir-1/file-1.json	* (glob)
+  @@ -2,7 +2,7 @@
+       "key1": [
+           42,
+           53,
+  -        78
+  +        76
+       ],
+       "key2": [
+           9,
+  $ hg ci -m 'update d1f1'
+  $ echo '{"key1": 4, "key2": [0, 1, 2, 3, 4, 5, 6, 7, 8, 9], "key3": [54]}' | $PYTHON -m json.tool > dir-1/file-2.json
+  $ echo '{"key1": [44,50,86], "key2": [4, 8, -4, 5], "key3": ["Asia", "Europe", "Oceania"]}' | $PYTHON -m json.tool > dir-2/file-1.json
+  $ hg diff
+  diff -r 0daf45f1c8ad dir-1/file-2.json
+  --- a/dir-1/file-2.json	Thu Jan 01 00:00:00 1970 +0000
+  +++ b/dir-1/file-2.json	* (glob)
+  @@ -1,5 +1,5 @@
+   {
+  -    "key1": 1,
+  +    "key1": 4,
+       "key2": [
+           0,
+           1,
+  diff -r 0daf45f1c8ad dir-2/file-1.json
+  --- a/dir-2/file-1.json	Thu Jan 01 00:00:00 1970 +0000
+  +++ b/dir-2/file-1.json	* (glob)
+  @@ -2,7 +2,7 @@
+       "key1": [
+           44,
+           50,
+  -        82
+  +        86
+       ],
+       "key2": [
+           4,
+  @@ -12,6 +12,7 @@
+       ],
+       "key3": [
+           "Asia",
+  -        "Europe"
+  +        "Europe",
+  +        "Oceania"
+       ]
+   }
+  $ hg ci -m 'update d1f2 and d2f1'
+
+modify the other side
+
+  $ hg up -C B_B
+  6 files updated, 0 files merged, 0 files removed, 0 files unresolved
+  $ echo '{"key1": 1337, "key2": [5,6,7,8], "key3": ["arthur", "babar", "celeste"]}' | $PYTHON -m json.tool > root-file.json
+  $ echo '{"key1": 4, "key2": [0, 1, 3, 3, 7, 5, 9, 7, 8, 9], "key3": [54]}' > dir-1/file-2.json
+  $ hg diff
+  diff -r 654f4c32c118 dir-1/file-2.json
+  --- a/dir-1/file-2.json	Thu Jan 01 00:00:00 1970 +0000
+  +++ b/dir-1/file-2.json	* (glob)
+  @@ -1,1 +1,1 @@
+  -{"key1": 1, "key2": [0, 1, 2, 3, 4, 5, 6, 7, 8, 9], "key3": [54]}
+  +{"key1": 4, "key2": [0, 1, 3, 3, 7, 5, 9, 7, 8, 9], "key3": [54]}
+  diff -r 654f4c32c118 root-file.json
+  --- a/root-file.json	Thu Jan 01 00:00:00 1970 +0000
+  +++ b/root-file.json	* (glob)
+  @@ -1,5 +1,5 @@
+   {
+  -    "key1": 4,
+  +    "key1": 1337,
+       "key2": [
+           5,
+           6,
+  @@ -9,7 +9,6 @@
+       "key3": [
+           "arthur",
+           "babar",
+  -        "celeste",
+  -        "Flore"
+  +        "celeste"
+       ]
+   }
+  $ hg ci -m 'update root-file and d1f2'
+  $ cat << EOF > dir-2/file-2.json
+  > {"key1": 6,
+  > "key2": [3, 5, -2, 3, 4, 11, 10, -4, 8, 9],
+  > "key3": [898, 32543, 2336]}
+  > EOF
+  $ echo '{"key1": "hello", "key2": [6, 1, 8, -1, 19, 2], "key3": "Babar"}' > dir-2/file-3.json
+  $ hg diff
+  diff -r b3816cb249a4 dir-2/file-2.json
+  --- a/dir-2/file-2.json	Thu Jan 01 00:00:00 1970 +0000
+  +++ b/dir-2/file-2.json	* (glob)
+  @@ -1,3 +1,3 @@
+   {"key1": 6,
+   "key2": [3, 5, -2, 3, 4, 11, 10, -4, 8, 9],
+  -"key3": [898, 32543, 2342]}
+  +"key3": [898, 32543, 2336]}
+  diff -r b3816cb249a4 dir-2/file-3.json
+  --- a/dir-2/file-3.json	Thu Jan 01 00:00:00 1970 +0000
+  +++ b/dir-2/file-3.json	* (glob)
+  @@ -1,1 +1,1 @@
+  -{"key1": "hello", "key2": [3, 1, 8, -1, 19, 2], "key3": "babar"}
+  +{"key1": "hello", "key2": [6, 1, 8, -1, 19, 2], "key3": "Babar"}
+  $ hg ci -m 'update d2f2 and d2f3'
+
+Check output before leaping
+
+  $ hg log -Gp -r 'sort(all(), "topo")'
+  @  changeset:   10:db645e78c8a3
+  |  branch:      B_B
+  |  tag:         tip
+  |  user:        test
+  |  date:        Thu Jan 01 00:00:00 1970 +0000
+  |  summary:     update d2f2 and d2f3
+  |
+  |  diff -r b3816cb249a4 -r db645e78c8a3 dir-2/file-2.json
+  |  --- a/dir-2/file-2.json	Thu Jan 01 00:00:00 1970 +0000
+  |  +++ b/dir-2/file-2.json	Thu Jan 01 00:00:00 1970 +0000
+  |  @@ -1,3 +1,3 @@
+  |   {"key1": 6,
+  |   "key2": [3, 5, -2, 3, 4, 11, 10, -4, 8, 9],
+  |  -"key3": [898, 32543, 2342]}
+  |  +"key3": [898, 32543, 2336]}
+  |  diff -r b3816cb249a4 -r db645e78c8a3 dir-2/file-3.json
+  |  --- a/dir-2/file-3.json	Thu Jan 01 00:00:00 1970 +0000
+  |  +++ b/dir-2/file-3.json	Thu Jan 01 00:00:00 1970 +0000
+  |  @@ -1,1 +1,1 @@
+  |  -{"key1": "hello", "key2": [3, 1, 8, -1, 19, 2], "key3": "babar"}
+  |  +{"key1": "hello", "key2": [6, 1, 8, -1, 19, 2], "key3": "Babar"}
+  |
+  o  changeset:   9:b3816cb249a4
+  |  branch:      B_B
+  |  parent:      5:654f4c32c118
+  |  user:        test
+  |  date:        Thu Jan 01 00:00:00 1970 +0000
+  |  summary:     update root-file and d1f2
+  |
+  |  diff -r 654f4c32c118 -r b3816cb249a4 dir-1/file-2.json
+  |  --- a/dir-1/file-2.json	Thu Jan 01 00:00:00 1970 +0000
+  |  +++ b/dir-1/file-2.json	Thu Jan 01 00:00:00 1970 +0000
+  |  @@ -1,1 +1,1 @@
+  |  -{"key1": 1, "key2": [0, 1, 2, 3, 4, 5, 6, 7, 8, 9], "key3": [54]}
+  |  +{"key1": 4, "key2": [0, 1, 3, 3, 7, 5, 9, 7, 8, 9], "key3": [54]}
+  |  diff -r 654f4c32c118 -r b3816cb249a4 root-file.json
+  |  --- a/root-file.json	Thu Jan 01 00:00:00 1970 +0000
+  |  +++ b/root-file.json	Thu Jan 01 00:00:00 1970 +0000
+  |  @@ -1,5 +1,5 @@
+  |   {
+  |  -    "key1": 4,
+  |  +    "key1": 1337,
+  |       "key2": [
+  |           5,
+  |           6,
+  |  @@ -9,7 +9,6 @@
+  |       "key3": [
+  |           "arthur",
+  |           "babar",
+  |  -        "celeste",
+  |  -        "Flore"
+  |  +        "celeste"
+  |       ]
+  |   }
+  |
+  o  changeset:   5:654f4c32c118
+  |  branch:      B_B
+  |  user:        test
+  |  date:        Thu Jan 01 00:00:00 1970 +0000
+  |  summary:     conflicting update
+  |
+  |  diff -r 217d6c23789b -r 654f4c32c118 root-file.json
+  |  --- a/root-file.json	Thu Jan 01 00:00:00 1970 +0000
+  |  +++ b/root-file.json	Thu Jan 01 00:00:00 1970 +0000
+  |  @@ -9,6 +9,7 @@
+  |       "key3": [
+  |           "arthur",
+  |           "babar",
+  |  -        "celeste"
+  |  +        "celeste",
+  |  +        "Flore"
+  |       ]
+  |   }
+  |
+  o  changeset:   4:217d6c23789b
+  |  branch:      B_B
+  |  parent:      1:4d5d2129291f
+  |  user:        test
+  |  date:        Thu Jan 01 00:00:00 1970 +0000
+  |  summary:     update key 1
+  |
+  |  diff -r 4d5d2129291f -r 217d6c23789b root-file.json
+  |  --- a/root-file.json	Thu Jan 01 00:00:00 1970 +0000
+  |  +++ b/root-file.json	Thu Jan 01 00:00:00 1970 +0000
+  |  @@ -1,5 +1,5 @@
+  |   {
+  |  -    "key1": 1,
+  |  +    "key1": 4,
+  |       "key2": [
+  |           5,
+  |           6,
+  |
+  | o  changeset:   8:73175e0546e6
+  | |  user:        test
+  | |  date:        Thu Jan 01 00:00:00 1970 +0000
+  | |  summary:     update d1f2 and d2f1
+  | |
+  | |  diff -r 0daf45f1c8ad -r 73175e0546e6 dir-1/file-2.json
+  | |  --- a/dir-1/file-2.json	Thu Jan 01 00:00:00 1970 +0000
+  | |  +++ b/dir-1/file-2.json	Thu Jan 01 00:00:00 1970 +0000
+  | |  @@ -1,5 +1,5 @@
+  | |   {
+  | |  -    "key1": 1,
+  | |  +    "key1": 4,
+  | |       "key2": [
+  | |           0,
+  | |           1,
+  | |  diff -r 0daf45f1c8ad -r 73175e0546e6 dir-2/file-1.json
+  | |  --- a/dir-2/file-1.json	Thu Jan 01 00:00:00 1970 +0000
+  | |  +++ b/dir-2/file-1.json	Thu Jan 01 00:00:00 1970 +0000
+  | |  @@ -2,7 +2,7 @@
+  | |       "key1": [
+  | |           44,
+  | |           50,
+  | |  -        82
+  | |  +        86
+  | |       ],
+  | |       "key2": [
+  | |           4,
+  | |  @@ -12,6 +12,7 @@
+  | |       ],
+  | |       "key3": [
+  | |           "Asia",
+  | |  -        "Europe"
+  | |  +        "Europe",
+  | |  +        "Oceania"
+  | |       ]
+  | |   }
+  | |
+  | o  changeset:   7:0daf45f1c8ad
+  | |  user:        test
+  | |  date:        Thu Jan 01 00:00:00 1970 +0000
+  | |  summary:     update d1f1
+  | |
+  | |  diff -r 180a5b3a4114 -r 0daf45f1c8ad dir-1/file-1.json
+  | |  --- a/dir-1/file-1.json	Thu Jan 01 00:00:00 1970 +0000
+  | |  +++ b/dir-1/file-1.json	Thu Jan 01 00:00:00 1970 +0000
+  | |  @@ -2,7 +2,7 @@
+  | |       "key1": [
+  | |           42,
+  | |           53,
+  | |  -        78
+  | |  +        76
+  | |       ],
+  | |       "key2": [
+  | |           9,
+  | |
+  | o  changeset:   6:180a5b3a4114
+  | |  parent:      3:d29e366c2a4b
+  | |  user:        test
+  | |  date:        Thu Jan 01 00:00:00 1970 +0000
+  | |  summary:     update root-file
+  | |
+  | |  diff -r d29e366c2a4b -r 180a5b3a4114 root-file.json
+  | |  --- a/root-file.json	Thu Jan 01 00:00:00 1970 +0000
+  | |  +++ b/root-file.json	Thu Jan 01 00:00:00 1970 +0000
+  | |  @@ -9,6 +9,7 @@
+  | |       "key3": [
+  | |           "arthur",
+  | |           "babar",
+  | |  -        "celeste"
+  | |  +        "celeste",
+  | |  +        "pomme"
+  | |       ]
+  | |   }
+  | |
+  | o  changeset:   3:d29e366c2a4b
+  | |  user:        test
+  | |  date:        Thu Jan 01 00:00:00 1970 +0000
+  | |  summary:     format some dir2 with explicite pattern
+  | |
+  | |  diff -r 279459a606de -r d29e366c2a4b .hg-format-source
+  | |  --- a/.hg-format-source	Thu Jan 01 00:00:00 1970 +0000
+  | |  +++ b/.hg-format-source	Thu Jan 01 00:00:00 1970 +0000
+  | |  @@ -1,2 +1,4 @@
+  | |   {"pattern": "glob:root-file.json", "tool": "json"}
+  | |   {"pattern": "glob:dir-1/**", "tool": "json"}
+  | |  +{"pattern": "glob:dir-2/file-1.json", "tool": "json"}
+  | |  +{"pattern": "glob:dir-2/file-3.json", "tool": "json"}
+  | |  diff -r 279459a606de -r d29e366c2a4b dir-2/file-1.json
+  | |  --- a/dir-2/file-1.json	Thu Jan 01 00:00:00 1970 +0000
+  | |  +++ b/dir-2/file-1.json	Thu Jan 01 00:00:00 1970 +0000
+  | |  @@ -1,1 +1,17 @@
+  | |  -{"key1": [44,50,82], "key2": [4, 8, -4, 5], "key3": ["Asia", "Europe"]}
+  | |  +{
+  | |  +    "key1": [
+  | |  +        44,
+  | |  +        50,
+  | |  +        82
+  | |  +    ],
+  | |  +    "key2": [
+  | |  +        4,
+  | |  +        8,
+  | |  +        -4,
+  | |  +        5
+  | |  +    ],
+  | |  +    "key3": [
+  | |  +        "Asia",
+  | |  +        "Europe"
+  | |  +    ]
+  | |  +}
+  | |  diff -r 279459a606de -r d29e366c2a4b dir-2/file-3.json
+  | |  --- a/dir-2/file-3.json	Thu Jan 01 00:00:00 1970 +0000
+  | |  +++ b/dir-2/file-3.json	Thu Jan 01 00:00:00 1970 +0000
+  | |  @@ -1,1 +1,12 @@
+  | |  -{"key1": "hello", "key2": [3, 1, 8, -1, 19, 2], "key3": "babar"}
+  | |  +{
+  | |  +    "key1": "hello",
+  | |  +    "key2": [
+  | |  +        3,
+  | |  +        1,
+  | |  +        8,
+  | |  +        -1,
+  | |  +        19,
+  | |  +        2
+  | |  +    ],
+  | |  +    "key3": "babar"
+  | |  +}
+  | |
+  | o  changeset:   2:279459a606de
+  |/   user:        test
+  |    date:        Thu Jan 01 00:00:00 1970 +0000
+  |    summary:     format dir1 as a whole
+  |
+  |    diff -r 4d5d2129291f -r 279459a606de .hg-format-source
+  |    --- a/.hg-format-source	Thu Jan 01 00:00:00 1970 +0000
+  |    +++ b/.hg-format-source	Thu Jan 01 00:00:00 1970 +0000
+  |    @@ -1,1 +1,2 @@
+  |     {"pattern": "glob:root-file.json", "tool": "json"}
+  |    +{"pattern": "glob:dir-1/**", "tool": "json"}
+  |    diff -r 4d5d2129291f -r 279459a606de dir-1/file-1.json
+  |    --- a/dir-1/file-1.json	Thu Jan 01 00:00:00 1970 +0000
+  |    +++ b/dir-1/file-1.json	Thu Jan 01 00:00:00 1970 +0000
+  |    @@ -1,1 +1,18 @@
+  |    -{"key1": [42,53,78], "key2": [9,3,8,1], "key3": ["London", "Paris", "Tokyo"]}
+  |    +{
+  |    +    "key1": [
+  |    +        42,
+  |    +        53,
+  |    +        78
+  |    +    ],
+  |    +    "key2": [
+  |    +        9,
+  |    +        3,
+  |    +        8,
+  |    +        1
+  |    +    ],
+  |    +    "key3": [
+  |    +        "London",
+  |    +        "Paris",
+  |    +        "Tokyo"
+  |    +    ]
+  |    +}
+  |    diff -r 4d5d2129291f -r 279459a606de dir-1/file-2.json
+  |    --- a/dir-1/file-2.json	Thu Jan 01 00:00:00 1970 +0000
+  |    +++ b/dir-1/file-2.json	Thu Jan 01 00:00:00 1970 +0000
+  |    @@ -1,1 +1,18 @@
+  |    -{"key1": 1, "key2": [0, 1, 2, 3, 4, 5, 6, 7, 8, 9], "key3": [54]}
+  |    +{
+  |    +    "key1": 1,
+  |    +    "key2": [
+  |    +        0,
+  |    +        1,
+  |    +        2,
+  |    +        3,
+  |    +        4,
+  |    +        5,
+  |    +        6,
+  |    +        7,
+  |    +        8,
+  |    +        9
+  |    +    ],
+  |    +    "key3": [
+  |    +        54
+  |    +    ]
+  |    +}
+  |
+  o  changeset:   1:4d5d2129291f
+  |  user:        test
+  |  date:        Thu Jan 01 00:00:00 1970 +0000
+  |  summary:     format the root-file
+  |
+  |  diff -r f168d21ce765 -r 4d5d2129291f .hg-format-source
+  |  --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+  |  +++ b/.hg-format-source	Thu Jan 01 00:00:00 1970 +0000
+  |  @@ -0,0 +1,1 @@
+  |  +{"pattern": "glob:root-file.json", "tool": "json"}
+  |  diff -r f168d21ce765 -r 4d5d2129291f root-file.json
+  |  --- a/root-file.json	Thu Jan 01 00:00:00 1970 +0000
+  |  +++ b/root-file.json	Thu Jan 01 00:00:00 1970 +0000
+  |  @@ -1,1 +1,14 @@
+  |  -{"key1": 1, "key2": [5,6,7,8], "key3": ["arthur", "babar", "celeste"]}
+  |  +{
+  |  +    "key1": 1,
+  |  +    "key2": [
+  |  +        5,
+  |  +        6,
+  |  +        7,
+  |  +        8
+  |  +    ],
+  |  +    "key3": [
+  |  +        "arthur",
+  |  +        "babar",
+  |  +        "celeste"
+  |  +    ]
+  |  +}
+  |
+  o  changeset:   0:f168d21ce765
+     user:        test
+     date:        Thu Jan 01 00:00:00 1970 +0000
+     summary:     initial commit
+  
+     diff -r 000000000000 -r f168d21ce765 dir-1/file-1.json
+     --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+     +++ b/dir-1/file-1.json	Thu Jan 01 00:00:00 1970 +0000
+     @@ -0,0 +1,1 @@
+     +{"key1": [42,53,78], "key2": [9,3,8,1], "key3": ["London", "Paris", "Tokyo"]}
+     diff -r 000000000000 -r f168d21ce765 dir-1/file-2.json
+     --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+     +++ b/dir-1/file-2.json	Thu Jan 01 00:00:00 1970 +0000
+     @@ -0,0 +1,1 @@
+     +{"key1": 1, "key2": [0, 1, 2, 3, 4, 5, 6, 7, 8, 9], "key3": [54]}
+     diff -r 000000000000 -r f168d21ce765 dir-2/file-1.json
+     --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+     +++ b/dir-2/file-1.json	Thu Jan 01 00:00:00 1970 +0000
+     @@ -0,0 +1,1 @@
+     +{"key1": [44,50,82], "key2": [4, 8, -4, 5], "key3": ["Asia", "Europe"]}
+     diff -r 000000000000 -r f168d21ce765 dir-2/file-2.json
+     --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+     +++ b/dir-2/file-2.json	Thu Jan 01 00:00:00 1970 +0000
+     @@ -0,0 +1,3 @@
+     +{"key1": 6,
+     +"key2": [3, 5, -2, 3, 4, 11, 10, -4, 8, 9],
+     +"key3": [898, 32543, 2342]}
+     diff -r 000000000000 -r f168d21ce765 dir-2/file-3.json
+     --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+     +++ b/dir-2/file-3.json	Thu Jan 01 00:00:00 1970 +0000
+     @@ -0,0 +1,1 @@
+     +{"key1": "hello", "key2": [3, 1, 8, -1, 19, 2], "key3": "babar"}
+     diff -r 000000000000 -r f168d21ce765 root-file.json
+     --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+     +++ b/root-file.json	Thu Jan 01 00:00:00 1970 +0000
+     @@ -0,0 +1,1 @@
+     +{"key1": 1, "key2": [5,6,7,8], "key3": ["arthur", "babar", "celeste"]}
+  
+
+Actual merge
+
+  $ hg diff -r 1 -r B_B .hg-format-source
+  $ hg diff -r 1 -r default .hg-format-source
+  diff -r 4d5d2129291f -r 73175e0546e6 .hg-format-source
+  --- a/.hg-format-source	Thu Jan 01 00:00:00 1970 +0000
+  +++ b/.hg-format-source	Thu Jan 01 00:00:00 1970 +0000* (glob)
+  @@ -1,1 +1,4 @@
+   {"pattern": "glob:root-file.json", "tool": "json"}
+  +{"pattern": "glob:dir-1/**", "tool": "json"}
+  +{"pattern": "glob:dir-2/file-1.json", "tool": "json"}
+  +{"pattern": "glob:dir-2/file-3.json", "tool": "json"}
+  $ hg status
+  $ hg up -C B_B
+  0 files updated, 0 files merged, 0 files removed, 0 files unresolved
+  $ hg merge default
+  merging dir-1/file-2.json
+  merging dir-2/file-3.json
+  merging root-file.json
+  3 files updated, 3 files merged, 0 files removed, 0 files unresolved
+  (branch merge, don't forget to commit)
+  $ hg log -GT compact
+  @  10[tip]   db645e78c8a3   1970-01-01 00:00 +0000   test
+  |    update d2f2 and d2f3
+  |
+  o  9:5   b3816cb249a4   1970-01-01 00:00 +0000   test
+  |    update root-file and d1f2
+  |
+  | @  8   73175e0546e6   1970-01-01 00:00 +0000   test
+  | |    update d1f2 and d2f1
+  | |
+  | o  7   0daf45f1c8ad   1970-01-01 00:00 +0000   test
+  | |    update d1f1
+  | |
+  | o  6:3   180a5b3a4114   1970-01-01 00:00 +0000   test
+  | |    update root-file
+  | |
+  o |  5   654f4c32c118   1970-01-01 00:00 +0000   test
+  | |    conflicting update
+  | |
+  o |  4:1   217d6c23789b   1970-01-01 00:00 +0000   test
+  | |    update key 1
+  | |
+  | o  3   d29e366c2a4b   1970-01-01 00:00 +0000   test
+  | |    format some dir2 with explicite pattern
+  | |
+  | o  2   279459a606de   1970-01-01 00:00 +0000   test
+  |/     format dir1 as a whole
+  |
+  o  1   4d5d2129291f   1970-01-01 00:00 +0000   test
+  |    format the root-file
+  |
+  o  0   f168d21ce765   1970-01-01 00:00 +0000   test
+       initial commit
+  
+  $ hg diff -r 'p1()'
+  diff -r db645e78c8a3 .hg-format-source
+  --- a/.hg-format-source	Thu Jan 01 00:00:00 1970 +0000
+  +++ b/.hg-format-source	* (glob)
+  @@ -1,1 +1,4 @@
+   {"pattern": "glob:root-file.json", "tool": "json"}
+  +{"pattern": "glob:dir-1/**", "tool": "json"}
+  +{"pattern": "glob:dir-2/file-1.json", "tool": "json"}
+  +{"pattern": "glob:dir-2/file-3.json", "tool": "json"}
+  diff -r db645e78c8a3 dir-1/file-1.json
+  --- a/dir-1/file-1.json	Thu Jan 01 00:00:00 1970 +0000
+  +++ b/dir-1/file-1.json	* (glob)
+  @@ -1,1 +1,18 @@
+  -{"key1": [42,53,78], "key2": [9,3,8,1], "key3": ["London", "Paris", "Tokyo"]}
+  +{
+  +    "key1": [
+  +        42,
+  +        53,
+  +        76
+  +    ],
+  +    "key2": [
+  +        9,
+  +        3,
+  +        8,
+  +        1
+  +    ],
+  +    "key3": [
+  +        "London",
+  +        "Paris",
+  +        "Tokyo"
+  +    ]
+  +}
+  diff -r db645e78c8a3 dir-1/file-2.json
+  --- a/dir-1/file-2.json	Thu Jan 01 00:00:00 1970 +0000
+  +++ b/dir-1/file-2.json	* (glob)
+  @@ -1,1 +1,18 @@
+  -{"key1": 4, "key2": [0, 1, 3, 3, 7, 5, 9, 7, 8, 9], "key3": [54]}
+  +{
+  +    "key1": 4,
+  +    "key2": [
+  +        0,
+  +        1,
+  +        3,
+  +        3,
+  +        7,
+  +        5,
+  +        9,
+  +        7,
+  +        8,
+  +        9
+  +    ],
+  +    "key3": [
+  +        54
+  +    ]
+  +}
+  diff -r db645e78c8a3 dir-2/file-1.json
+  --- a/dir-2/file-1.json	Thu Jan 01 00:00:00 1970 +0000
+  +++ b/dir-2/file-1.json	* (glob)
+  @@ -1,1 +1,18 @@
+  -{"key1": [44,50,82], "key2": [4, 8, -4, 5], "key3": ["Asia", "Europe"]}
+  +{
+  +    "key1": [
+  +        44,
+  +        50,
+  +        86
+  +    ],
+  +    "key2": [
+  +        4,
+  +        8,
+  +        -4,
+  +        5
+  +    ],
+  +    "key3": [
+  +        "Asia",
+  +        "Europe",
+  +        "Oceania"
+  +    ]
+  +}
+  diff -r db645e78c8a3 dir-2/file-3.json
+  --- a/dir-2/file-3.json	Thu Jan 01 00:00:00 1970 +0000
+  +++ b/dir-2/file-3.json	* (glob)
+  @@ -1,1 +1,12 @@
+  -{"key1": "hello", "key2": [6, 1, 8, -1, 19, 2], "key3": "Babar"}
+  +{
+  +    "key1": "hello",
+  +    "key2": [
+  +        6,
+  +        1,
+  +        8,
+  +        -1,
+  +        19,
+  +        2
+  +    ],
+  +    "key3": "Babar"
+  +}
+  diff -r db645e78c8a3 root-file.json
+  --- a/root-file.json	Thu Jan 01 00:00:00 1970 +0000
+  +++ b/root-file.json	* (glob)
+  @@ -9,6 +9,7 @@
+       "key3": [
+           "arthur",
+           "babar",
+  -        "celeste"
+  +        "celeste",
+  +        "pomme"
+       ]
+   }
+  $ hg diff -r 'p2()'
+  diff -r 73175e0546e6 dir-1/file-2.json
+  --- a/dir-1/file-2.json	Thu Jan 01 00:00:00 1970 +0000
+  +++ b/dir-1/file-2.json	* (glob)
+  @@ -3,11 +3,11 @@
+       "key2": [
+           0,
+           1,
+  -        2,
+  +        3,
+           3,
+  -        4,
+  +        7,
+           5,
+  -        6,
+  +        9,
+           7,
+           8,
+           9
+  diff -r 73175e0546e6 dir-2/file-2.json
+  --- a/dir-2/file-2.json	Thu Jan 01 00:00:00 1970 +0000
+  +++ b/dir-2/file-2.json	* (glob)
+  @@ -1,3 +1,3 @@
+   {"key1": 6,
+   "key2": [3, 5, -2, 3, 4, 11, 10, -4, 8, 9],
+  -"key3": [898, 32543, 2342]}
+  +"key3": [898, 32543, 2336]}
+  diff -r 73175e0546e6 dir-2/file-3.json
+  --- a/dir-2/file-3.json	Thu Jan 01 00:00:00 1970 +0000
+  +++ b/dir-2/file-3.json	* (glob)
+  @@ -1,12 +1,12 @@
+   {
+       "key1": "hello",
+       "key2": [
+  -        3,
+  +        6,
+           1,
+           8,
+           -1,
+           19,
+           2
+       ],
+  -    "key3": "babar"
+  +    "key3": "Babar"
+   }
+  diff -r 73175e0546e6 root-file.json
+  --- a/root-file.json	Thu Jan 01 00:00:00 1970 +0000
+  +++ b/root-file.json	* (glob)
+  @@ -1,5 +1,5 @@
+   {
+  -    "key1": 1,
+  +    "key1": 1337,
+       "key2": [
+           5,
+           6,
+
+Merge from a sub directory
+==========================
+
+  $ hg up -C .
+  6 files updated, 0 files merged, 0 files removed, 0 files unresolved
+  $ cd dir-1
+  $ hg merge default
+  merging dir-1/file-2.json
+  merging dir-2/file-3.json
+  merging root-file.json
+  3 files updated, 3 files merged, 0 files removed, 0 files unresolved
+  (branch merge, don't forget to commit)
+  $ hg commit -qm 'Merge commit'
+  $ cd ..
+
+Preserve flags
+==============
+
+  $ mkdir flags
+  $ cat << EOF > flags/executable.json
+  > {}
+  > EOF
+  $ chmod +x flags/executable.json
+  $ hg add -q flags/*
+  $ hg ci -qm "Add flags subdir"
+  $ hg format-source json glob:flags/* -m 'Executable file formatted'
+  $ ls -l flags/* | cut -b 1-10
+  -rwxr-xr-x
+
+Test error case
+===============
+
+Current limitation
+------------------
+
+not at repository root
+
+  $ cd dir-1
+  $ hg format-source json glob:file-2.json
+  abort: format-source must be run from repository root
+  (cd $TESTTMP/test_repo)
+  [255]
+
+Not a glob:
+
+  $ cd ..
+  $ hg format-source json dir-1:file-2.json
+  abort: format-source only supports explicit 'glob' patterns for now ('dir-1:file-2.json')
+  (maybe try with "glob:dir-1:file-2.json")
+  [255]
+
+legitimate error
+----------------
+
+no pattern
+
+  $ hg format-source json
+  abort: no files specified
+  [255]
+
+unknown tool
+
+  $ hg format-source babar-tool glob:file-2.json
+  abort: unknow format tool: babar-tool (no 'format-source.babar-tool' config)
+  [255]
+
+space in tool
+
+  $ hg format-source 'babar tooling' 'glob:**'
+  abort: tool name cannot contains space: 'babar tooling'
+  [255]
+
+Uncommited changes
+
+  $ echo a >> root-file.json
+  $ hg format-source json 'glob:root-file.json'
+  abort: uncommitted changes
+  [255]
+
+Merge with an undefined tool
+
+  $ hg up -qC 0
+  $ cat << EOF > root-file.json
+  > {"key1": 1, "key2": [5,6,7,8], "key3": ["Arthur", "babar", "celeste"]}
+  > EOF
+  $ hg diff
+  diff -r f168d21ce765 root-file.json
+  --- a/root-file.json	Thu Jan 01 00:00:00 1970 +0000
+  +++ b/root-file.json	* (glob)
+  @@ -1,1 +1,1 @@
+  -{"key1": 1, "key2": [5,6,7,8], "key3": ["arthur", "babar", "celeste"]}
+  +{"key1": 1, "key2": [5,6,7,8], "key3": ["Arthur", "babar", "celeste"]}
+  $ hg up --merge default --config 'format-source.json='
+  format-source, no command defined for 'json', skipping formating: 'root-file.json'
+  format-source, no command defined for 'json', skipping formating: 'root-file.json'
+  merging root-file.json
+  format-source, no command defined for 'json', skipping formating: 'root-file.json'
+  format-source, no command defined for 'json', skipping formating: 'root-file.json'
+  warning: conflicts while merging root-file.json! (edit, then use 'hg resolve --mark')
+  5 files updated, 0 files merged, 0 files removed, 1 files unresolved
+  use 'hg resolve' to retry unresolved file merges
+  [1]
diff --git a/tests/test-format-config.t b/tests/test-format-config.t
new file mode 100644
--- /dev/null
+++ b/tests/test-format-config.t
@@ -0,0 +1,288 @@
+
+Basic init
+
+  $ code_root=`dirname $TESTDIR`
+
+  $ cat << EOF >> $HGRCPATH
+  > [extensions]
+  > formatsource=
+  > rebase =
+  > strip =
+  > [format-source]
+  > json = $PYTHON $TESTDIR/testlib/json-pretty.py
+  > json:configpaths = .json-indent
+  > [default]
+  > format-source=--date '0 0'
+  > EOF
+  $ HGMERGE=:merge3
+
+  $ hg init test_repo
+  $ cd test_repo
+
+Commit various json file
+
+  $ mkdir dir-1
+  $ cat << EOF > dir-1/file-1.json
+  > {"key1": [42,53,78], "key2": [9,3,8,1], "key3": ["London", "Paris", "Tokyo"]}
+  > EOF
+  $ cat << EOF > dir-1/file-2.json
+  > {"key1": 1, "key2": [0, 1, 2, 3, 4, 5, 6, 7, 8, 9], "key3": [54]}
+  > EOF
+  $ hg add .
+  adding dir-1/file-1.json
+  adding dir-1/file-2.json
+  $ hg commit --message 'initial commit'
+
+format them (in multiple steps)
+
+  $ hg format-source --date '0 0' json glob:*/file-1.json -m 'format without config'
+  $ hg export
+  # HG changeset patch
+  # User test
+  # Date 0 0
+  #      Thu Jan 01 00:00:00 1970 +0000
+  # Node ID fb63bdd6edbf60d86f7aeaf0d806ecf6555c02eb
+  # Parent  103bbf4a41e9e9010c27ab49c158f99b176d4f3e
+  format without config
+  
+  diff -r 103bbf4a41e9 -r fb63bdd6edbf .hg-format-source
+  --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+  +++ b/.hg-format-source	Thu Jan 01 00:00:00 1970 +0000
+  @@ -0,0 +1,1 @@
+  +{"configpaths": [".json-indent"], "pattern": "glob:*/file-1.json", "tool": "json"}
+  diff -r 103bbf4a41e9 -r fb63bdd6edbf dir-1/file-1.json
+  --- a/dir-1/file-1.json	Thu Jan 01 00:00:00 1970 +0000
+  +++ b/dir-1/file-1.json	Thu Jan 01 00:00:00 1970 +0000
+  @@ -1,1 +1,18 @@
+  -{"key1": [42,53,78], "key2": [9,3,8,1], "key3": ["London", "Paris", "Tokyo"]}
+  +{
+  +    "key1": [
+  +        42,
+  +        53,
+  +        78
+  +    ],
+  +    "key2": [
+  +        9,
+  +        3,
+  +        8,
+  +        1
+  +    ],
+  +    "key3": [
+  +        "London",
+  +        "Paris",
+  +        "Tokyo"
+  +    ]
+  +}
+  $ echo 2 > .json-indent
+  $ hg add .json-indent
+  $ $PYTHON $TESTDIR/testlib/json-pretty.py < dir-1/file-1.json > tmp
+  $ mv tmp dir-1/file-1.json
+  $ hg diff
+  diff -r fb63bdd6edbf .json-indent
+  --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+  +++ b/.json-indent	Thu Jan 01 00:00:00 1970 +0000
+  @@ -0,0 +1,1 @@
+  +2
+  diff -r fb63bdd6edbf dir-1/file-1.json
+  --- a/dir-1/file-1.json	Thu Jan 01 00:00:00 1970 +0000
+  +++ b/dir-1/file-1.json	Thu Jan 01 00:00:00 1970 +0000
+  @@ -1,18 +1,18 @@
+   {
+  -    "key1": [
+  -        42,
+  -        53,
+  -        78
+  -    ],
+  -    "key2": [
+  -        9,
+  -        3,
+  -        8,
+  -        1
+  -    ],
+  -    "key3": [
+  -        "London",
+  -        "Paris",
+  -        "Tokyo"
+  -    ]
+  +  "key1": [
+  +    42,
+  +    53,
+  +    78
+  +  ],
+  +  "key2": [
+  +    9,
+  +    3,
+  +    8,
+  +    1
+  +  ],
+  +  "key3": [
+  +    "London",
+  +    "Paris",
+  +    "Tokyo"
+  +  ]
+   }
+  $ hg commit -m 'reformat with indent=2'
+  $ echo 1 > .json-indent
+  $ $PYTHON $TESTDIR/testlib/json-pretty.py < dir-1/file-1.json > tmp
+  $ mv tmp dir-1/file-1.json
+  $ hg diff
+  diff -r bacb7be97453 .json-indent
+  --- a/.json-indent	Thu Jan 01 00:00:00 1970 +0000
+  +++ b/.json-indent	Thu Jan 01 00:00:00 1970 +0000
+  @@ -1,1 +1,1 @@
+  -2
+  +1
+  diff -r bacb7be97453 dir-1/file-1.json
+  --- a/dir-1/file-1.json	Thu Jan 01 00:00:00 1970 +0000
+  +++ b/dir-1/file-1.json	Thu Jan 01 00:00:00 1970 +0000
+  @@ -1,18 +1,18 @@
+   {
+  -  "key1": [
+  -    42,
+  -    53,
+  -    78
+  -  ],
+  -  "key2": [
+  -    9,
+  -    3,
+  -    8,
+  -    1
+  -  ],
+  -  "key3": [
+  -    "London",
+  -    "Paris",
+  -    "Tokyo"
+  -  ]
+  + "key1": [
+  +  42,
+  +  53,
+  +  78
+  + ],
+  + "key2": [
+  +  9,
+  +  3,
+  +  8,
+  +  1
+  + ],
+  + "key3": [
+  +  "London",
+  +  "Paris",
+  +  "Tokyo"
+  + ]
+   }
+  $ hg commit -m 'reformat with indent=1'
+
+Add changes on another branch
+
+  $ hg up 0
+  1 files updated, 0 files merged, 2 files removed, 0 files unresolved
+  $ cat << EOF > dir-1/file-1.json
+  > {"key1": [42,53,78,66], "key2": [9,3,8,1], "key3": ["London", "Paris", "Tokyo"]}
+  > EOF
+  $ cat << EOF > dir-1/file-2.json
+  > {"key1": 1, "key2": [0, 1, 2, 3, 4, 5, 6, 7, 8, 9], "key3": [54, 55]}
+  > EOF
+  $ hg commit -m 'some editions'
+  created new head
+
+Merge with "format without config"
+
+  $ hg log -G
+  @  changeset:   4:360af76de133
+  |  tag:         tip
+  |  parent:      0:103bbf4a41e9
+  |  user:        test
+  |  date:        Thu Jan 01 00:00:00 1970 +0000
+  |  summary:     some editions
+  |
+  | o  changeset:   3:fa670ec0f89c
+  | |  user:        test
+  | |  date:        Thu Jan 01 00:00:00 1970 +0000
+  | |  summary:     reformat with indent=1
+  | |
+  | o  changeset:   2:bacb7be97453
+  | |  user:        test
+  | |  date:        Thu Jan 01 00:00:00 1970 +0000
+  | |  summary:     reformat with indent=2
+  | |
+  | o  changeset:   1:fb63bdd6edbf
+  |/   user:        test
+  |    date:        Thu Jan 01 00:00:00 1970 +0000
+  |    summary:     format without config
+  |
+  o  changeset:   0:103bbf4a41e9
+     user:        test
+     date:        Thu Jan 01 00:00:00 1970 +0000
+     summary:     initial commit
+  
+  $ f --md5 dir-1/file-2.json
+  dir-1/file-2.json: md5=19be5044f37a12515b1bf92e2becf702
+  $ hg merge 1
+  merging dir-1/file-1.json
+  1 files updated, 1 files merged, 0 files removed, 0 files unresolved
+  (branch merge, don't forget to commit)
+  $ f --md5 dir-1/file-2.json
+  dir-1/file-2.json: md5=19be5044f37a12515b1bf92e2becf702
+  $ hg diff -r 'p2()' dir-1/file-1.json
+  diff -r fb63bdd6edbf dir-1/file-1.json
+  --- a/dir-1/file-1.json	Thu Jan 01 00:00:00 1970 +0000
+  +++ b/dir-1/file-1.json	Thu Jan 01 00:00:00 1970 +0000
+  @@ -2,7 +2,8 @@
+       "key1": [
+           42,
+           53,
+  -        78
+  +        78,
+  +        66
+       ],
+       "key2": [
+           9,
+  $ hg commit -m 'merge #1'
+
+Merge with "format with indent=2"
+
+  $ hg merge 2
+  merging dir-1/file-1.json
+  1 files updated, 1 files merged, 0 files removed, 0 files unresolved
+  (branch merge, don't forget to commit)
+  $ f --md5 dir-1/file-2.json
+  dir-1/file-2.json: md5=19be5044f37a12515b1bf92e2becf702
+  $ hg diff -r 'p2()' dir-1/file-1.json
+  diff -r bacb7be97453 dir-1/file-1.json
+  --- a/dir-1/file-1.json	Thu Jan 01 00:00:00 1970 +0000
+  +++ b/dir-1/file-1.json	Thu Jan 01 00:00:00 1970 +0000
+  @@ -2,7 +2,8 @@
+     "key1": [
+       42,
+       53,
+  -    78
+  +    78,
+  +    66
+     ],
+     "key2": [
+       9,
+  $ hg commit -m 'merge #2'
+
+Merge with indent=1
+
+  $ hg merge 3
+  merging dir-1/file-1.json
+  1 files updated, 1 files merged, 0 files removed, 0 files unresolved
+  (branch merge, don't forget to commit)
+  $ f --md5 dir-1/file-2.json
+  dir-1/file-2.json: md5=19be5044f37a12515b1bf92e2becf702
+  $ hg diff -r 'p2()' dir-1/file-1.json
+  diff -r fa670ec0f89c dir-1/file-1.json
+  --- a/dir-1/file-1.json	Thu Jan 01 00:00:00 1970 +0000
+  +++ b/dir-1/file-1.json	Thu Jan 01 00:00:00 1970 +0000
+  @@ -2,7 +2,8 @@
+    "key1": [
+     42,
+     53,
+  -  78
+  +  78,
+  +  66
+    ],
+    "key2": [
+     9,
+  $ hg commit -m 'merge #3'
diff --git a/hgext/formatsource.py b/hgext/formatsource.py
new file mode 100644
--- /dev/null
+++ b/hgext/formatsource.py
@@ -0,0 +1,331 @@
+# Copyright 2017 Octobus <contact at octobus.net>
+#
+# This software may be used and distributed according to the terms of the
+# GNU General Public License version 2 or any later version.
+"""help dealing with code source reformating
+
+The extension provides a way to run code-formatting tools in a way that avoids
+conflicts related to this formatting when merging/rebasing code across the
+reformatting.
+
+A new `format-source` command is provided, to apply code formatting tool on
+some specific files. This information is recorded into the repository and
+reused when merging. The client doing the merge needs the extension for this
+logic to kick in.
+
+Code formatting tools have to be registered in the configuration. The tool
+"name" will be used to identify a specific command accross all repositories.
+It is mapped to a command line that must output the formatted content on its
+standard output.
+
+For each tool a list of files affecting the result of the formatting can be
+configured with the "configpaths" suboption, which is read and registered at
+"hg format-source" time.  Any change in those files should trigger
+reformatting.
+
+Example::
+
+    [format-source]
+    json = python -m json.tool
+    clang = clang-format -style=Mozilla
+    clang:configpaths = .clang-format, .clang-format-ignore
+
+We do not support specifying the mapping of tool name to tool command in the
+repository itself for security reasons.
+
+The code formatting information is tracked in a .hg-format-source file at the
+root of the repository.
+
+Warning: There is no special logic handling renames so moving files to a
+directory not covered by the patterns used for the initial formatting will
+likely fail.
+"""
+
+from __future__ import absolute_import
+
+import json
+import tempfile
+
+from mercurial.i18n import _
+
+from mercurial import (
+    cmdutil,
+    commands,
+    encoding,
+    error,
+    extensions,
+    filemerge,
+    match,
+    merge,
+    registrar,
+    scmutil,
+    util,
+)
+
+cmdtable = {}
+command = registrar.command(cmdtable)
+
+configtable = {}
+configitem = registrar.configitem(configtable)
+configitem('format-source', '.*', default=None, generic=True)
+
+file_storage_path = '.hg-format-source'
+
+# Note for extension authors: ONLY specify testedwith = 'ships-with-hg-core' for
+# extensions which SHIP WITH MERCURIAL. Non-mainline extensions should
+# be specifying the version(s) of Mercurial they are tested with, or
+# leave the attribute unspecified.
+testedwith = 'ships-with-hg-core'
+
+ at command('format-source',
+        [] + commands.walkopts + commands.commitopts + commands.commitopts2,
+        _('TOOL FILES+'))
+def cmd_format_source(ui, repo, tool, *pats, **opts):
+    """format source file using a registered tools
+
+    This command run TOOL on FILES and record this information in a commit to
+    help with future merge.
+
+    The actual command run for TOOL needs to be registered in the config. See
+    :hg:`help -e formatsource` for details.
+    """
+    if repo.getcwd():
+        msg = _("format-source must be run from repository root")
+        hint = _("cd %s") % repo.root
+        raise error.Abort(msg, hint=hint)
+
+    if not pats:
+        raise error.Abort(_('no files specified'))
+
+    # XXX We support glob pattern only for now, the recursive behavior of
+    # various others is a bit wonky.
+    for pattern in pats:
+        if not pattern.startswith('glob:'):
+            msg = _("format-source only supports explicit 'glob' patterns "
+                    "for now ('%s')")
+            msg %= pattern
+            hint = _('maybe try with "glob:%s"') % pattern
+            raise error.Abort(msg, hint=hint)
+
+    # lock the repo to make sure no content is changed
+    with repo.wlock():
+        # formating tool
+        if ' ' in tool:
+            raise error.Abort(_("tool name cannot contains space:"
+                                " '%s'") % tool)
+        shell_tool = repo.ui.config('format-source', tool)
+        tool_config_files = repo.ui.configlist('format-source',
+                                               '%s:configpaths' % tool)
+        if not shell_tool:
+            msg = _("unknow format tool: %s (no 'format-source.%s' config)")
+            raise error.Abort(msg % (tool, tool))
+        cmdutil.bailifchanged(repo)
+        cmdutil.checkunfinished(repo, commit=True)
+        wctx = repo[None]
+        # files to be formatted
+        matcher = scmutil.match(wctx, pats, opts)
+        # perform actual formatting
+        for filepath in wctx.matches(matcher):
+            flags = wctx.flags(filepath)
+            newcontent = run_tools(ui, repo.root, tool, shell_tool,
+                                   filepath, filepath)
+            # XXX we could do the whole commit in memory
+            with repo.wvfs(filepath, 'wb') as formatted_file:
+                formatted_file.write(newcontent)
+            wctx.filectx(filepath).setflags('l' in flags, 'x' in flags)
+
+        # update the storage to mark formated file as formatted
+        with repo.wvfs(file_storage_path, mode='ab') as storage:
+            for pattern in pats:
+                # XXX if pattern was relative, we need to reroot it from the
+                # repository root. For now we constrainted the command to run
+                # at the root of the repository.
+                data = {'tool': encoding.unifromlocal(tool),
+                        'pattern': encoding.unifromlocal(pattern)}
+                if tool_config_files:
+                    data['configpaths'] = [encoding.unifromlocal(path)
+                                           for path in tool_config_files]
+                entry = json.dumps(data, sort_keys=True)
+                assert '\n' not in entry
+                storage.write('%s\n' % entry)
+
+        if file_storage_path not in wctx:
+            storage_matcher = scmutil.match(wctx, ['path:' + file_storage_path])
+            cmdutil.add(ui, repo, storage_matcher, '', True)
+
+        # commit the whole
+        with repo.lock():
+            commit_patterns = ['path:' + file_storage_path]
+            commit_patterns.extend(pats)
+            return commands._docommit(ui, repo, *commit_patterns, **opts)
+
+def run_tools(ui, root, tool, cmd, filepath, filename):
+    """Run the a formatter tool on a specific file"""
+    env = encoding.environ.copy()
+    env['HG_FILENAME'] = filename
+    # XXX escape special character in filepath
+    format_cmd = "%s %s" % (cmd, filepath)
+    ui.debug('running %s\n' % format_cmd)
+    ui.pushbuffer(subproc=True)
+    try:
+        ui.system(format_cmd,
+                  environ=env,
+                  cwd=root,
+                  onerr=error.Abort,
+                  errprefix=tool)
+    finally:
+        newcontent = ui.popbuffer()
+    return newcontent
+
+def touched(repo, old_ctx, new_ctx, paths):
+    matcher = rootedmatch(repo, new_ctx, paths)
+    if any(path in new_ctx for path in paths):
+        status = old_ctx.status(other=new_ctx, match=matcher)
+        return bool(status.modified or status.added)
+    return False
+
+def formatted(repo, old_ctx, new_ctx):
+    """retrieve the list of formatted patterns between <old> and <new>
+
+    return a {'tool': [patterns]} mapping
+    """
+    new_formatting = {}
+    if touched(repo, old_ctx, new_ctx, [file_storage_path]):
+        # quick and dirty line diffing
+        # (the file is append only by contract)
+
+        new_lines = set(new_ctx[file_storage_path].data().splitlines())
+        old_lines = set()
+        if file_storage_path in old_ctx:
+            old_lines = set(old_ctx[file_storage_path].data().splitlines())
+        new_lines -= old_lines
+        for line in new_lines:
+            entry = json.loads(line)
+            def getkey(key):
+                return encoding.unitolocal(entry[key])
+            new_formatting.setdefault(getkey('tool'),
+                                      set()).add(getkey('pattern'))
+    if file_storage_path in old_ctx:
+        for line in old_ctx[file_storage_path].data().splitlines():
+            entry = json.loads(line)
+            if not entry.get('configpaths'):
+                continue
+            configpaths = [encoding.unitolocal(path) \
+                           for path in entry['configpaths']]
+            def getkey(key):
+                return encoding.unitolocal(entry[key])
+            if touched(repo, old_ctx, new_ctx, configpaths):
+                new_formatting.setdefault(getkey('tool'),
+                                          set()).add(getkey('pattern'))
+    return new_formatting
+
+def allformatted(repo, local, other, ancestor):
+    """return a mapping of formatting needed for all involved changeset
+    """
+
+    cachekey = (local.node, other.node(), ancestor.node())
+    cached = getattr(repo, '_formatting_cache', {}).get(cachekey)
+
+    if cached is not None:
+        return cached
+
+    local_formating = formatted(repo, ancestor, local)
+    other_formating = formatted(repo, ancestor, other)
+    full_formating = local_formating.copy()
+    for key, value in other_formating.iteritems():
+        if key in local_formating:
+            value = value | local_formating[key]
+        full_formating[key] = value
+
+    all = [
+        (local, local_formating),
+        (other, other_formating),
+        (ancestor, full_formating)
+    ]
+    for ctx, formatting in all:
+        for tool, patterns in formatting.iteritems():
+            formatting[tool] = rootedmatch(repo, ctx, patterns)
+
+    final = tuple(formatting for __, formatting in all)
+    getattr(repo, '_formatting_cache', {})[cachekey] = cached
+
+    return final
+
+def rootedmatch(repo, ctx, patterns):
+    """match patterns agains the root of a repository"""
+    # rework of basectx.match to ignore current working directory
+
+    # Only a case insensitive filesystem needs magic to translate user input
+    # to actual case in the filesystem.
+    icasefs = not util.fscasesensitive(repo.root)
+    if util.safehasattr(match, 'icasefsmatcher'): #< hg 4.3
+        if icasefs:
+            return match.icasefsmatcher(repo.root, repo.root, patterns,
+                                        default='glob', auditor=repo.auditor,
+                                        ctx=ctx)
+        else:
+            return match.match(repo.root, repo.root, patterns, default='glob',
+                               auditor=repo.auditor, ctx=ctx)
+    else:
+        return match.match(repo.root, repo.root, patterns, default='glob',
+                           auditor=repo.auditor, ctx=ctx, icasefs=icasefs)
+
+def apply_formating(repo, formatting, fctx):
+    """apply formatting to a file context (if applicable)"""
+    data = None
+    for tool, matcher in sorted(formatting.items()):
+        # matches?
+        if matcher(fctx.path()):
+            if data is None:
+                data = fctx.data()
+            shell_tool = repo.ui.config('format-source', tool)
+            if not shell_tool:
+                msg = _("format-source, no command defined for '%s',"
+                        " skipping formating: '%s'\n")
+                msg %= (tool, fctx.path())
+                repo.ui.warn(msg)
+                continue
+            with tempfile.NamedTemporaryFile(mode='wb') as f:
+                f.write(data)
+                f.flush()
+                data = run_tools(repo.ui, repo.root, tool, shell_tool,
+                                 f.name, fctx.path())
+    if data is not None:
+        fctx.data = lambda: data
+
+
+def wrap_filemerge(origfunc, premerge, repo, wctx, mynode, orig, fcd, fco,
+                     fca, *args, **kwargs):
+    """wrap the file merge logic to apply formatting on files that needs them"""
+    _update_filemerge_content(repo, fcd, fco, fca)
+    return origfunc(premerge, repo, wctx, mynode, orig, fcd, fco, fca,
+                    *args, **kwargs)
+
+def _update_filemerge_content(repo, fcd, fco, fca):
+    if fcd.isabsent() or fco.isabsent() or fca.isabsent():
+        return
+    local = fcd._changectx
+    other = fco._changectx
+    ances = fca._changectx
+    all = allformatted(repo, local, other, ances)
+    local_formating, other_formating, full_formating = all
+    apply_formating(repo, local_formating, fco)
+    apply_formating(repo, other_formating, fcd)
+    apply_formating(repo, full_formating, fca)
+
+    if 'data' in vars(fcd): # XXX hacky way to check if data overwritten
+        file_path = repo.wvfs.join(fcd.path())
+        with open(file_path, 'wb') as local_file:
+            local_file.write(fcd.data())
+
+def wrap_update(orig, repo, *args, **kwargs):
+    """install the formatting cache"""
+    repo._formatting_cache = {}
+    try:
+        return orig(repo, *args, **kwargs)
+    finally:
+        del repo._formatting_cache
+
+def uisetup(self):
+    extensions.wrapfunction(filemerge, '_filemerge', wrap_filemerge)
+    extensions.wrapfunction(merge, 'update', wrap_update)



To: pulkit, #hg-reviewers
Cc: mercurial-devel


More information about the Mercurial-devel mailing list