[PATCH] auto rename: best matches and speed improvement UPDATE5

Herbert Griebel herbertg at gmx.at
Fri Aug 22 07:47:19 CDT 2008


# HG changeset patch
# User Herbert Griebel <herbertg at gmx.at>
# Date 1219406896 -7200
# Node ID b1173917f68067703012f27c45fd4453926ebd8c
# Parent  6dcbe191a9b51799a4cd40bd398e826bcd9b4dac
auto rename: best matches and speed improvements

The auto rename feature of addremove did not rename to the best match
and was very slow. The new implementation fixes this. The current
checked file is displayed to give the user feedback about the
progress. The result is displayed as before but renamed files are
listed with descending similarity and are no more listed in the added
and removed list.

Added files with the same similarity for a removed file are also
matched against the pathname so directory structure can be preserved
with regard to renaming identical files in multiple directories.

The options --filters and --sizechange have been added to give the user
more control over renaming and/or to make it faster.

diff -r 6dcbe191a9b5 -r b1173917f680 mercurial/cmdutil.py
--- a/mercurial/cmdutil.py	Mon Aug 18 16:50:36 2008 -0500
+++ b/mercurial/cmdutil.py	Fri Aug 22 14:08:16 2008 +0200
@@ -7,7 +7,7 @@

 from node import hex, nullid, nullrev, short
 from i18n import _
-import os, sys, bisect, stat
+import os, sys, bisect, stat, operator, time
 import mdiff, bdiff, util, templater, templatefilters, patch, errno
 import match as _match

@@ -241,40 +241,352 @@
 def matchfiles(repo, files):
     return _match.exact(repo.root, repo.getcwd(), files)

-def findrenames(repo, added=None, removed=None, threshold=0.5):
-    '''find renamed files -- yields (before, after, score) tuples'''
+def matchending(s1, s2):
+    imax = min([len(s1), len(s2)])
+    i = 1
+    while i <= imax and s1[-i] == s2[-i]:
+        i += 1
+    return i - 1
+
+def parsegroups(s, sep = '|', grouper = "'"):
+    ssplit = s.split(sep)
+    i = 0
+    while i < len(ssplit)-1:
+        if ssplit[i][0] == grouper and ssplit[i][-1] != grouper:
+            ssplit[i] = ssplit[i] + sep + ssplit[i+1]
+            del ssplit[i+1]
+            if ssplit[i][-1] == grouper:
+                ssplit[i] = ssplit[i][1:-1] # remove grouper
+        i += 1
+    return ssplit
+
+def findrenames(repo, added=None, removed=None, similarity=50,
+                filters='', sizechange=-1, mapping={}):
+    '''find renamed files --
+       returns [renameold, ...], [renamenew, ...], [renamescore, ...]'''
     if added is None or removed is None:
         added, removed = repo.status()[1:3]
+    Na = len(added)
+    Nr = len(removed)
+    if not added or not removed:
+        return [], [], []
     ctx = repo['.']
-    for a in added:
-        aa = repo.wread(a)
-        bestname, bestscore = None, threshold
-        for r in removed:
-            rr = ctx.filectx(r).data()

-            # bdiff.blocks() returns blocks of matching lines
-            # count the number of bytes in each
-            equal = 0
-            alines = mdiff.splitnewlines(aa)
-            matches = bdiff.blocks(aa, rr)
-            for x1,x2,y1,y2 in matches:
-                for line in alines[x1:x2]:
-                    equal += len(line)
+    # check rename filters
+    checkext, norenames = False, False
+    transpairs, blockpairs = [], []
+    if filters:
+        # construct filters with regard to removed file
+        for t in parsegroups(filters, '|'):
+            if t == ':':
+                norenames = True
+                continue
+            if t == 'e':
+                checkext = True
+                continue
+            ext = parsegroups(t, '>')
+            if len(ext) == 2:
+                mf1 = _match.match(repo.root, '', [ext[0]], [], [], 'glob')
+                mf2 = _match.match(repo.root, '', [ext[1]], [], [], 'glob')
+                transpairs.append((mf1, mf2))
+                continue
+            ext = parsegroups(t, ':')
+            if len(ext) == 2:
+                mf1 = _match.match(repo.root, '', [ext[0]], [], [], 'glob')
+                mf2 = _match.match(repo.root, '', [ext[1]], [], [], 'glob')
+                blockpairs.append((mf1, mf2))
+                continue
+            raise util.Abort(_('could not parse rename filter: %s') % t)
+    checkpairs  = transpairs or blockpairs and not norenames
+    checkpairs2 = checkpairs

-            lengths = len(aa) + len(rr)
-            if lengths:
-                myscore = equal*2.0 / lengths
-                if myscore >= bestscore:
-                    bestname, bestscore = r, myscore
-        if bestname:
-            yield bestname, a, bestscore
+    # preload attributes
+    rfiles = [()] * Nr
+    afiles = [()] * Na
+    for i,r in enumerate(removed):
+        rfiles[i] = (ctx.filectx(r).size(), os.path.split(r)[1], r,
+                     os.path.splitext(r)[1], i)
+    for i,a in enumerate(added):
+        afiles[i] = (repo.wfilesize(a), os.path.split(a)[1], a,
+                     os.path.splitext(a)[1], i)

-def addremove(repo, pats=[], opts={}, dry_run=None, similarity=None):
+    # sort files by size
+    rfiles.sort()
+    afiles.sort()
+
+    # try to find moves
+    rins, ains = 0, 0
+    na = len(afiles)
+    for ir in range(len(rfiles)):
+        rname, rsize = rfiles[ir][1], rfiles[ir][0]
+        rmove = False
+        ia = ains
+        while ia < na:
+            if rsize == afiles[ia][0] and rname == afiles[ia][1]:
+                t = afiles[ia]
+                del afiles[ia]
+                afiles.insert(ains, t)
+                ains += 1
+                rmove = True
+            ia += 1
+        if rmove:
+            t = rfiles[ir]
+            del rfiles[ir]
+            rfiles.insert(rins, t)
+            rins += 1
+
+    # check loop order
+    if Na == 1:
+        addedfirst = True
+    elif Nr == 1:
+        addedfirst = False
+    else:
+        # measure times to decide loops
+        measure = 0
+        if measure:
+            # this code is only for getting the estimator
+            S1a = 0
+            S1r = 0
+            added1 = added[:Na/3]
+            removed1 = removed[:Nr/3]
+            N1a = len(added1)
+            N1r = len(removed1)
+            t0 = time.clock()
+            for a in added1:
+                S1a += len(repo.wread(a))
+            t1 = time.clock()
+            for r in removed1:
+                S1r += len(ctx.filectx(r).data())
+            t2 = time.clock()
+            d1a = t1 - t0
+            d1r = t2 - t1
+
+            S2a = 0
+            S2r = 0
+            added2 = added[Na/3:]
+            removed2 = removed[Nr/3:]
+            N2a = len(added2)
+            N2r = len(removed2)
+            t0 = time.clock()
+            for a in added2:
+                S2a += len(repo.wread(a))
+            t1 = time.clock()
+            for r in removed2:
+                S2r += len(ctx.filectx(r).data())
+            t2 = time.clock()
+            d2a = t1 - t0
+            d2r = t2 - t1
+
+            # calc estimator
+
+            def solve2d(a1,a2,b1,b2,e1,e2):
+                """a1*x + b1*y = e1
+                   a2*x + b2*y = e2"""
+                def det(a1,a2,b1,b2):
+                    return a1*b2 - b1*a2
+                d = det(a1,a2,b1,b2)
+                x = det(e1,e2,b1,b2) / d
+                y = det(a1,a2,e1,e2) / d
+                return x, y
+
+            try:
+                # S*c1 + N*c2 = duration
+                print S1a, S2a, N1a, N2a, d1a, d2a
+                xc1a, xc2a = solve2d(S1a, S2a, N1a, N2a, d1a, d2a)
+                xc1r, xc2r = solve2d(S1r, S2r, N1r, N2r, d1r, d2r)
+                print 'c1r, c2r, c1a, c2a = %g, %g, %g, %g' \
+                        % (xc1r, xc2r, xc1a, xc2a)
+            except:
+                print 'calc error'
+
+        # estimate times to decide loops
+        c1r, c2r, c1a, c2a = 1.60882e-008, 0.00145002, 9.03138e-009, 0.00110696
+        Sr = sum([f[0] for f in rfiles])
+        Sa = sum([f[0] for f in afiles])
+        dr = Sr*c1r + Nr*c2r
+        da = Sa*c1a + Na*c2a
+        d1 = da + dr*Na
+        d2 = dr + da*Nr
+        addedfirst = d1 <= d2
+
+    if addedfirst:
+        filelist1, filelist2 = afiles, rfiles
+        transpairs = [(f2, f1) for f1, f2 in transpairs]
+        blockpairs = [(f2, f1) for f1, f2 in blockpairs]
+    else:
+        filelist1, filelist2 = rfiles, afiles
+
+    # init
+    numbered = len(filelist1) >= 20
+    if numbered:
+        s = '%d' % len(filelist1)
+        fmt = '%%%dd/%s %s %%s\n' % (len(s), s, _('checking rename with'))
+    else:
+        fmt = '%s %%s\n' % _('checking rename with')
+    minscore = similarity/100
+    minscoresize = minscore
+    if sizechange >= 0:
+        minscoresize = max([1/(sizechange/100 + 1), minscore])
+    bestmatches = []
+    rbestscores = [0] * Nr
+
+    def getscore():
+        # bdiff.blocks() returns blocks of matching lines
+        # count the number of bytes in each
+        l = len(aa) + len(rr)
+        if not l:
+            return 1
+        equal = 0
+        matches = bdiff.blocks(aa, rr)
+        for x1,x2,y1,y2 in matches:
+            for line in alines[x1:x2]:
+                equal += len(line)
+        score = equal*2.0 / l
+        return score
+
+    # loop 1
+    for ifile1, f1 in enumerate(filelist1):
+        size1, name1, path1, ext1, ref1 = f1
+        rel, exact = mapping[path1]
+        repo.ui.status(fmt % ((ifile1+1, rel) if numbered else rel))
+        if addedfirst:
+            a = path1
+        else:
+            r = path1
+            ir = ref1
+        loadfile1 = True
+        if checkpairs:
+            transpairs2 = []
+            for mf1, mf2 in transpairs:
+                if mf1(path1):
+                    transpairs2.append(mf2)
+            if transpairs and not transpairs2:
+                continue
+            blockpairs2 = []
+            for mf1, mf2 in blockpairs:
+                if mf1(path1):
+                    blockpairs2.append(mf2)
+            checkpairs2 = transpairs2 or blockpairs2
+
+        # loop 2
+        for ifile2, f2 in enumerate(filelist2):
+            size2, name2, path2, ext2, ref2 = f2
+            if norenames:
+                if name1 != name2:
+                    continue
+            elif checkpairs2:
+                if transpairs2:
+                    block = True
+                    for mf2 in transpairs2:
+                        if mf2(path2):
+                            block = False
+                            break
+                    if block:
+                        if not checkext or ext1 != ext2:
+                            continue
+                block = False
+                for mf2 in blockpairs2:
+                    if mf2(path2):
+                        block = True
+                        break
+                if block:
+                    continue
+            elif checkext and ext1 != ext2:
+                continue
+            if addedfirst:
+                r = path2
+                ir = ref2
+            else:
+                a = path2
+
+            # special case: empty files
+            if not size1 or not size2:
+                if size1 == size2:
+                    bestmatches.append((r, a, 1, False))
+                    rbestscores[ir] = 1
+                continue
+
+            # check for maximum score that could be reached because of sizes
+            maxscore = float(size1)/size2 if size2>size1 else float(size2)/size1
+            if maxscore < minscoresize:
+                continue
+
+            # deferred compares
+            if maxscore < rbestscores[ir]:
+                bestmatches.append((r, a, maxscore, True))
+                continue
+
+            # load file 1
+            if loadfile1:
+                if addedfirst:
+                    aa = repo.wread(a)
+                    alines = mdiff.splitnewlines(aa)
+                else:
+                    rr = ctx.filectx(r).data()
+                loadfile1 = False
+
+            # load file 2
+            if addedfirst:
+                rr = ctx.filectx(r).data()
+            else:
+                aa = repo.wread(a)
+                alines = mdiff.splitnewlines(aa)
+
+            # score it
+            score = getscore()
+            if score >= minscore:
+                bestmatches.append((r, a, score, False))
+                if rbestscores[ir] < score:
+                    rbestscores[ir] = score
+
+    # assign best matches
+    bestmatches.sort(key = operator.itemgetter(2), reverse = True)
+    renamenew, renameold, renamescore = [], [], []
+    imatch, nmatches = 0, len(bestmatches)
+    while imatch < nmatches:
+        r, a, bestscore, deferred = bestmatches[imatch]
+        imatch += 1
+        if a not in renamenew and r not in renameold:
+            if deferred:
+                rr = ctx.filectx(r).data()
+                aa = repo.wread(a)
+                alines = mdiff.splitnewlines(aa)
+                score = getscore()
+                if score >= minscore:
+                    # insert sorted in list
+                    iins = imatch
+                    while iins < nmatches and score < bestmatches[iins][2]:
+                        iins += 1
+                    bestmatches.insert(iins, (r, a, score, False))
+                    nmatches += 1
+                continue
+            # check pathname for other matches with equal score
+            imatch2 = imatch
+            maxscore = matchending(r, a)
+            while imatch2 < nmatches and bestmatches[imatch2][2] == bestscore:
+                if r == bestmatches[imatch2][0]:
+                    score = matchending(r, bestmatches[imatch2][1])
+                    if maxscore < score:
+                        maxscore = score
+                        a = bestmatches[imatch2][1]
+                imatch2 += 1
+            # add the best match
+            renameold.append(r)
+            renamenew.append(a)
+            renamescore.append(bestscore)
+    return renameold, renamenew, renamescore
+
+def addremove(repo, pats=[], opts={}, dry_run=None, similarity=None,
+              filters=None, sizechange=None):
     if dry_run is None:
         dry_run = opts.get('dry_run')
     if similarity is None:
         similarity = float(opts.get('similarity') or 0)
-    add, remove = [], []
+    if filters is None:
+        filters = opts.get('filter') or ''
+    if sizechange is None:
+        sizechange = float(opts.get('sizechange') or -1)
+    add, remove, renamenew, renameold, renamescore = [], [], [], [], []
     mapping = {}
     audit_path = util.path_auditor(repo.root)
     m = match(repo, pats, opts)
@@ -289,26 +601,45 @@
         exact = m.exact(abs)
         if good and abs not in repo.dirstate:
             add.append(abs)
-            mapping[abs] = rel, m.exact(abs)
-            if repo.ui.verbose or not exact:
-                repo.ui.status(_('adding %s\n') % ((pats and rel) or abs))
+            mapping[abs] = (pats and rel) or abs, exact
         if repo.dirstate[abs] != 'r' and (not good or not util.lexists(target)
             or (os.path.isdir(target) and not os.path.islink(target))):
             remove.append(abs)
-            mapping[abs] = rel, exact
+            mapping[abs] = (pats and rel) or abs, exact
+    # find file renames
+    if similarity > 0:
+        renameold, renamenew, renamescore = findrenames( \
+            repo, add, remove, similarity, filters, sizechange, mapping)
+    # output
+    for abs in remove:
+        if abs not in renameold:
+            rel, exact = mapping[abs]
             if repo.ui.verbose or not exact:
-                repo.ui.status(_('removing %s\n') % ((pats and rel) or abs))
-    if not dry_run:
-        repo.remove(remove)
-        repo.add(add)
-    if similarity > 0:
-        for old, new, score in findrenames(repo, add, remove, similarity):
+                repo.ui.status(_('removing %s\n') % rel)
+            if not dry_run:
+                repo.remove([abs])
+    for abs in add:
+        if abs not in renamenew:
+            rel, exact = mapping[abs]
+            if repo.ui.verbose or not exact:
+                repo.ui.status(_('adding %s\n') % rel)
+            if not dry_run:
+                repo.add([abs])
+    if renameold:
+        rmax, amax = 0, 0
+        for old, new, score in zip(renameold, renamenew, renamescore):
             oldrel, oldexact = mapping[old]
             newrel, newexact = mapping[new]
             if repo.ui.verbose or not oldexact or not newexact:
-                repo.ui.status(_('recording removal of %s as rename to %s '
-                                 '(%d%% similar)\n') %
-                               (oldrel, newrel, score * 100))
+                rmax = max([rmax, len(oldrel)])
+                amax = max([amax, len(newrel)])
+        fmt = _('renaming %%-%ds to %%-%ds (%%3d%%%% similar)\n') \
+                % (rmax, amax)
+        for old, new, score in zip(renameold, renamenew, renamescore):
+            oldrel, oldexact = mapping[old]
+            newrel, newexact = mapping[new]
+            if repo.ui.verbose or not oldexact or not newexact:
+                repo.ui.status(fmt % (oldrel, newrel, score * 100))
             if not dry_run:
                 repo.copy(old, new)

diff -r 6dcbe191a9b5 -r b1173917f680 mercurial/commands.py
--- a/mercurial/commands.py	Mon Aug 18 16:50:36 2008 -0500
+++ b/mercurial/commands.py	Fri Aug 22 14:08:16 2008 +0200
@@ -52,22 +52,45 @@

     Add all new files and remove all missing files from the repository.

-    New files are ignored if they match any of the patterns in .hgignore. As
-    with add, these changes take effect at the next commit.
+    New files are ignored if they match any of the patterns in .hgignore.
+    As with add, these changes take effect at the next commit.

     Use the -s option to detect renamed files. With a parameter > 0,
     this compares every removed file with every added file and records
     those similar enough as renames. This option takes a percentage
     between 0 (disabled) and 100 (files must be identical) as its
-    parameter. Detecting renamed files this way can be expensive.
+    parameter.
+    Detecting renamed files with a full search may be expensive. The
+    options -f and -S can reduce the number of file compares and make
+    renaming much faster. The two options also help to avoid unwanted
+    renames: to find moved files with no changes use -s 100 -f ':'.
+    It is recommended to do a dry-run first and check the results.
+
+    Use -f to define renames, for example -f '**.txt>**.doc|**.c>**.cpp'
+    will only find renames from txt to doc files, and from c to cpp files.
+    -f '**.txt>*' will only detect any renames of txt files, -f '**:**.txt'
+    will block any renames to txt files. There are 2 special symbols:
+      - ':' blocks all filename changes, but not path changes.
+      - 'e' will block all changes of file extensions.
+    For pattern grouping use single quotes. The default will allow all
+    renames.
+
+    Use option -S for a maximum percentage in file size change. A value
+    of 100 allows changes between twice and half the original size,
+    0 allows no change. Negativ values will disable this option (default).
     """
     try:
-        sim = float(opts.get('similarity') or 0)
+        similarity = float(opts.get('similarity') or 0)
     except ValueError:
         raise util.Abort(_('similarity must be a number'))
-    if sim < 0 or sim > 100:
+    if similarity < 0 or similarity > 100:
         raise util.Abort(_('similarity must be between 0 and 100'))
-    return cmdutil.addremove(repo, pats, opts, similarity=sim/100.)
+    try:
+        sizechange = float(opts.get('sizechange') or -1)
+    except ValueError:
+        raise util.Abort(_('sizechange must be a number'))
+    return cmdutil.addremove(repo, pats, opts, similarity=similarity,
+                             sizechange=sizechange)

 def annotate(ui, repo, *pats, **opts):
     """show changeset information per file line
@@ -561,14 +584,18 @@

     Commit changes to the given files into the repository.

-    If a list of files is omitted, all changes reported by "hg status"
-    will be committed.
+    If a list of files is committed, all changes of these files
+    as reported by "hg status" will be committed.

-    If you are committing the result of a merge, do not provide any
-    file names or -I/-X filters.
+    If you are committing the result of a merge, do not provide
+    any file names or -I/-X filters.

-    If no commit message is specified, the configured editor is started to
-    enter a message.
+    If no commit message is specified, the configured editor is
+    started to enter a message.
+
+    The addremove option -A will add new files and remove missing
+    files from the repository before committing. For details see
+    the addremove command.

     See 'hg help dates' for a list of formats valid for -d/--date.
     """
@@ -2932,6 +2959,10 @@
         (addremove,
          [('s', 'similarity', '',
            _('guess renamed files by similarity (0<=s<=100)')),
+          ('f', 'filter', '',
+           _('rename filter with patterns (with -s)')),
+          ('S', 'sizechange', '',
+           _('maximum file size change for renames (with -s)')),
          ] + walkopts + dryrunopts,
          _('hg addremove [OPTION]... [FILE]...')),
     "^annotate|blame":
@@ -3015,6 +3046,12 @@
         (commit,
          [('A', 'addremove', None,
            _('mark new/missing files as added/removed before committing')),
+          ('s', 'similarity', '',
+           _('guess renamed files by similarity (with -A)')),
+          ('f', 'filter', '',
+           _('rename filter with patterns (with -s)')),
+          ('S', 'sizechange', '',
+           _('maximum file size change for renames (with -s)')),
          ] + walkopts + commitopts + commitopts2,
          _('hg commit [OPTION]... [FILE]...')),
     "copy|cp":
diff -r 6dcbe191a9b5 -r b1173917f680 mercurial/localrepo.py
--- a/mercurial/localrepo.py	Mon Aug 18 16:50:36 2008 -0500
+++ b/mercurial/localrepo.py	Fri Aug 22 14:08:16 2008 +0200
@@ -528,6 +528,9 @@

     def adddatafilter(self, name, filter):
         self._datafilters[name] = filter
+
+    def wfilesize(self, filename):
+        return os.stat(self.wjoin(filename))[6]

     def wread(self, filename):
         if self._link(filename):
diff -r 6dcbe191a9b5 -r b1173917f680 tests/test-addremove-similar
--- a/tests/test-addremove-similar	Mon Aug 18 16:50:36 2008 -0500
+++ b/tests/test-addremove-similar	Fri Aug 22 14:08:16 2008 +0200
@@ -46,4 +46,111 @@
 hg addremove -s -1
 hg addremove -s 1e6

-true
+
+
+
+echo rename filter test
+
+cd ..
+hg init rep_filtertest
+cd rep_filtertest
+
+touch a
+hg commit -A -m "add"
+
+echo % a to a.x
+mv a a.x
+hg addremove -s50
+hg commit -m "rename"
+
+echo % x
+mv a.x b.z
+hg commit -m "no rename" -A -s50 -f":"
+
+echo % b.z to a.z
+mv b.z a.z
+hg addremove -s50 -f"e"
+hg commit -m "rename"
+
+echo % x
+mv a.z a.y
+hg addremove -s50 -f"e"
+hg commit -m "no rename" -A -s50 -f"e"
+
+echo % a.y to y.gif
+mv a.y y.gif
+hg addremove -s50 -f"*>*"
+hg commit -m "rename"
+
+echo % x
+mv y.gif c.py
+hg commit -m "no rename" -A -s50 -f"*.py>*.py" -S100
+
+echo % c.py to b.py
+mv c.py b.py
+hg addremove -s50 -f"*.py>*.py" -S100
+hg commit -m "rename"
+
+echo % x
+mv b.py y.c
+hg addremove -s50 -f"*.c>*.py"
+hg commit -m "no rename"
+
+echo % y.c to y.cpp
+mv y.c y.cpp
+hg addremove -s50 -f"*.c>*.cpp"
+hg commit -m "rename"
+
+echo % x
+mv y.cpp z.cpp
+hg addremove -s50 -f"y.cpp:*"
+hg commit -m "no rename"
+
+echo % z.cpp to y.cpp
+mv z.cpp y.cpp
+hg addremove -s50 -f"*.c:*.cpp"
+hg commit -m "rename"
+
+echo % x
+mv y.cpp a.c
+hg addremove -s50 -f"*>*.h|*>*.y"
+hg commit -m "no rename"
+
+echo % a.c to a.y
+mv a.c a.y
+hg addremove -s50 -f"*>*.h|*>*.y"
+hg commit -m "rename"
+
+echo % x
+mv a.y b.z
+hg addremove -s50 -f"*.y>*.z|*:*.z"
+hg commit -m "no rename"
+
+echo % b.z to b.y
+mv b.z b.y
+hg addremove -s50 -f"e|*.z>*"
+hg commit -m "rename"
+
+echo % x
+mv b.y a.x
+hg addremove -s50 -f"*:*.x"
+hg commit -m "no rename"
+
+echo % x
+mv a.x a.z
+hg addremove -s50 -f"*.x>*.z|*.x:*.z"
+hg commit -m "no rename"
+
+
+echo % name matching test
+
+touch b.z
+hg addremove -s50
+hg commit -m "adding 2nd file"
+
+echo % a.z to x/a.z
+echo % b.z to x/b.z
+mkdir x
+mv *.* x/
+hg addremove -s50
+hg commit -m "rename 2 moved files"
diff -r 6dcbe191a9b5 -r b1173917f680 tests/test-addremove-similar.out
--- a/tests/test-addremove-similar.out	Mon Aug 18 16:50:36 2008 -0500
+++ b/tests/test-addremove-similar.out	Fri Aug 22 14:08:16 2008 +0200
@@ -1,20 +1,96 @@
 adding empty-file
 adding large-file
-adding another-file
+checking rename with another-file
 removing empty-file
-removing large-file
-recording removal of large-file as rename to another-file (99% similar)
+renaming large-file to another-file ( 99% similar)
 % comparing two empty files caused ZeroDivisionError in the past
 2 files updated, 0 files merged, 1 files removed, 0 files unresolved
-adding another-empty-file
-removing empty-file
+checking rename with another-empty-file
+renaming empty-file to another-empty-file (100% similar)
 adding large-file
 adding tiny-file
+checking rename with small-file
 removing large-file
-adding small-file
-removing tiny-file
-recording removal of tiny-file as rename to small-file (82% similar)
+renaming tiny-file to small-file ( 82% similar)
 % should all fail
 abort: similarity must be a number
 abort: similarity must be between 0 and 100
 abort: similarity must be between 0 and 100
+rename filter test
+adding a
+% a to a.x
+checking rename with a.x
+renaming a to a.x (100% similar)
+% x
+checking rename with b.z
+removing a
+removing a.x
+adding b.z
+% b.z to a.z
+checking rename with a.z
+renaming b.z to a.z (100% similar)
+% x
+checking rename with a.y
+removing a.z
+removing b.z
+adding a.y
+% a.y to y.gif
+checking rename with y.gif
+renaming a.y to y.gif (100% similar)
+% x
+checking rename with c.py
+removing a.y
+removing y.gif
+adding c.py
+% c.py to b.py
+checking rename with b.py
+renaming c.py to b.py (100% similar)
+% x
+checking rename with y.c
+removing b.py
+removing c.py
+adding y.c
+% y.c to y.cpp
+checking rename with y.cpp
+renaming y.c to y.cpp (100% similar)
+% x
+checking rename with z.cpp
+removing y.cpp
+renaming y.c to z.cpp (100% similar)
+% z.cpp to y.cpp
+checking rename with y.cpp
+removing y.c
+renaming z.cpp to y.cpp (100% similar)
+% x
+checking rename with a.c
+removing y.cpp
+removing z.cpp
+adding a.c
+% a.c to a.y
+checking rename with a.y
+renaming a.c to a.y (100% similar)
+% x
+checking rename with b.z
+removing a.c
+removing a.y
+adding b.z
+% b.z to b.y
+checking rename with b.y
+renaming b.z to b.y (100% similar)
+% x
+checking rename with a.x
+removing b.y
+removing b.z
+adding a.x
+% x
+checking rename with a.z
+removing a.x
+adding a.z
+% name matching test
+adding b.z
+% a.z to x/a.z
+% b.z to x/b.z
+checking rename with a.z
+checking rename with b.z
+renaming a.z to x/a.z (100% similar)
+renaming b.z to x/b.z (100% similar)
diff -r 6dcbe191a9b5 -r b1173917f680 tests/test-addremove.out
--- a/tests/test-addremove.out	Mon Aug 18 16:50:36 2008 -0500
+++ b/tests/test-addremove.out	Fri Aug 22 14:08:16 2008 +0200
@@ -8,8 +8,8 @@
 foo_2
 adding a
 adding c
-removing a
-adding b
+checking rename with c
+checking rename with a
 removing c
 adding d
-recording removal of a as rename to b (100% similar)
+renaming a to b (100% similar)
diff -r 6dcbe191a9b5 -r b1173917f680 tests/test-issue660.out
--- a/tests/test-issue660.out	Mon Aug 18 16:50:36 2008 -0500
+++ b/tests/test-issue660.out	Fri Aug 22 14:08:16 2008 +0200
@@ -24,9 +24,9 @@
 undeleting b/b
 % addremove
 removing a
+removing b/b
 adding a/a
 adding b
-removing b/b
 A a/a
 A b
 R a
diff -r 6dcbe191a9b5 -r b1173917f680 tests/test-record.out
--- a/tests/test-record.out	Mon Aug 18 16:50:36 2008 -0500
+++ b/tests/test-record.out	Fri Aug 22 14:08:16 2008 +0200
@@ -27,13 +27,16 @@

 options:

- -A --addremove  mark new/missing files as added/removed before committing
- -I --include    include names matching the given patterns
- -X --exclude    exclude names matching the given patterns
- -m --message    use <text> as commit message
- -l --logfile    read commit message from <file>
- -d --date       record datecode as commit date
- -u --user       record user as committer
+ -A --addremove   mark new/missing files as added/removed before committing
+ -s --similarity  guess renamed files by similarity (with -A)
+ -f --filter      rename filter with patterns (with -s)
+ -S --sizechange  maximum file size change for renames (with -s)
+ -I --include     include names matching the given patterns
+ -X --exclude     exclude names matching the given patterns
+ -m --message     use <text> as commit message
+ -l --logfile     read commit message from <file>
+ -d --date        record datecode as commit date
+ -u --user        record user as committer

 use "hg -v help record" to show global options
 % select no files
diff -r 6dcbe191a9b5 -r b1173917f680 tests/test-symlink-addremove.out
--- a/tests/test-symlink-addremove.out	Mon Aug 18 16:50:36 2008 -0500
+++ b/tests/test-symlink-addremove.out	Fri Aug 22 14:08:16 2008 +0200
@@ -1,6 +1,6 @@
 % directory moved and symlinked
 adding foo/a
 % now addremove should remove old files
+removing foo/a
 adding bar/a
 adding foo
-removing foo/a
diff -r 6dcbe191a9b5 -r b1173917f680 tests/test-transplant.out
--- a/tests/test-transplant.out	Mon Aug 18 16:50:36 2008 -0500
+++ b/tests/test-transplant.out	Fri Aug 22 14:08:16 2008 +0200
@@ -102,8 +102,8 @@
 % transplant --continue
 adding foo
 adding toremove
+removing toremove
 adding added
-removing toremove
 adding bar
 2 files updated, 0 files merged, 2 files removed, 0 files unresolved
 created new head



More information about the Mercurial-devel mailing list