Patchwork [3,of,3] log: make -frREV PATH detect missing files before falling back to slow path

login
register
mail settings
Submitter Yuya Nishihara
Date Sept. 14, 2020, 1:25 p.m.
Message ID <78f4530ec2ed09dce014.1600089928@mimosa>
Download mbox | patch
Permalink /patch/47166/
State Accepted
Headers show

Comments

Yuya Nishihara - Sept. 14, 2020, 1:25 p.m.
# HG changeset patch
# User Yuya Nishihara <yuya@tcha.org>
# Date 1599863027 -32400
#      Sat Sep 12 07:23:47 2020 +0900
# Node ID 78f4530ec2ed09dce0146ab2dfb6f42b323abe76
# Parent  21487b54666e457374fa669ffe3005f3840353c6
log: make -frREV PATH detect missing files before falling back to slow path

If -rREV isn't specified, "log --follow" would abort on nonexistent paths.
Let's implement this behavior for "-frREV" case as we have ctx.hasdir() now.
Otherwise "log -frREV PATH" would silently fall back to slow path and files
wouldn't be followed across renames.

The loop is quadratic (as before), but the size of the startctxs and
match.files() should be small in general.

Some tests are marked as BROKEN since file renames aren't tracked in the
slow path. This is a known limitation of the current history traversal
function.

Patch

diff --git a/mercurial/logcmdutil.py b/mercurial/logcmdutil.py
--- a/mercurial/logcmdutil.py
+++ b/mercurial/logcmdutil.py
@@ -692,13 +692,27 @@  def _makematcher(repo, revs, pats, opts)
     if not slowpath:
         follow = opts.get(b'follow') or opts.get(b'follow_first')
         if follow and opts.get(b'rev'):
+            # There may be the case that a path doesn't exist in some (but
+            # not all) of the specified start revisions, but let's consider
+            # the path is valid. Missing files will be warned by the matcher.
             startctxs = [repo[r] for r in revs]
             for f in match.files():
-                # No idea if the path was a directory at that revision, so
-                # take the slow path.
-                if any(f not in c for c in startctxs):
-                    slowpath = True
-                    break
+                found = False
+                for c in startctxs:
+                    if f in c:
+                        found = True
+                    elif c.hasdir(f):
+                        # If a directory exists in any of the start revisions,
+                        # take the slow path.
+                        found = slowpath = True
+                if not found:
+                    raise error.Abort(
+                        _(
+                            b'cannot follow file not in any of the specified '
+                            b'revisions: "%s"'
+                        )
+                        % f
+                    )
         elif follow:
             for f in match.files():
                 if f not in wctx:
diff --git a/tests/test-log.t b/tests/test-log.t
--- a/tests/test-log.t
+++ b/tests/test-log.t
@@ -504,14 +504,50 @@  follow files from the specified revision
   0 (false !)
 
 follow files from the specified revisions with missing patterns
-(BROKEN: should follow copies from e@4)
 
   $ hg log -T '{rev}\n' -fr4 e x
-  4
-  2 (false !)
+  abort: cannot follow file not in any of the specified revisions: "x"
+  [255]
+
+follow files from the specified revisions with directory patterns
+(BROKEN: should follow copies from dir/b@2)
+
+  $ hg log -T '{rev}\n' -fr2 dir/b dir
+  2
   1 (false !)
   0 (false !)
 
+follow files from multiple revisions, but the pattern is missing in
+one of the specified revisions
+
+  $ hg log -T '{rev}\n' -fr'2+4' dir/b e
+  e: no such file in rev f8954cd4dc1f
+  dir/b: no such file in rev 7e4639b4691b
+  4
+  2
+  1
+  0
+
+follow files from multiple revisions, and the pattern matches a file in
+one revision but matches a directory in another:
+(BROKEN: should follow copies from dir/b@2 and dir/b/g@5)
+(BROKEN: the revision 4 should not be included since dir/b/g@5 is unchanged)
+
+  $ mkdir -p dir/b
+  $ hg mv g dir/b
+  $ hg ci -m 'make dir/b a directory'
+
+  $ hg log -T '{rev}\n' -fr'2+5' dir/b
+  5
+  4
+  3 (false !)
+  2
+  1 (false !)
+  0 (false !)
+
+  $ hg --config extensions.strip= strip -r. --no-backup
+  1 files updated, 0 files merged, 1 files removed, 0 files unresolved
+
 follow files from the specified revisions across copies with -p/--patch
 
   $ hg log -T '== rev: {rev},{file_copies % " {source}->{name}"} ==\n' -fpr 4 e g
@@ -2312,7 +2348,15 @@  follow files from wdir
   
 
   $ hg log -T '== {rev} ==\n' -fr'wdir()' --git --stat notfound
-  notfound: $ENOENT$
+  abort: cannot follow file not in any of the specified revisions: "notfound"
+  [255]
+
+follow files from wdir and non-wdir revision:
+
+  $ hg log -T '{rev}\n' -fr'wdir()+.' f1-copy
+  f1-copy: no such file in rev 65624cd9070a
+  2147483647
+  0
 
 follow added/removed files from wdir parent