# HG changeset patch
# User Gregory Szorc <gregory.szorc@gmail.com>
# Date 2018-10-03 21:57:29
# Node ID f1a39128da95fa2abeca2aade650aca49c1dea55
# Parent  4a81d82474e9094d3b9d5f8d89743d4dc7da1291

filelog: add a hasnode() method (API)

Missing in the file storage interface is the ability to query whether
a specified value is a known node.

This commit defines that interface member and implements it on the
revlog and sqlite file stores.

Storage unit tests have been added.

The revlog implementation is a bit more complicated because index lookups
don't consistently raise the same exception. For SQLite, we can simply look
for a key in a dict.

Differential Revision: https://phab.mercurial-scm.org/D5163

diff --git a/hgext/sqlitestore.py b/hgext/sqlitestore.py
--- a/hgext/sqlitestore.py
+++ b/hgext/sqlitestore.py
@@ -381,6 +381,12 @@ class sqlitefilestore(object):
     def __iter__(self):
         return iter(pycompat.xrange(len(self._revisions)))
 
+    def hasnode(self, node):
+        if node == nullid:
+            return False
+
+        return node in self._nodetorev
+
     def revs(self, start=0, stop=None):
         return storageutil.iterrevs(len(self._revisions), start=start,
                                     stop=stop)
diff --git a/mercurial/filelog.py b/mercurial/filelog.py
--- a/mercurial/filelog.py
+++ b/mercurial/filelog.py
@@ -7,6 +7,10 @@
 
 from __future__ import absolute_import
 
+from .node import (
+    nullid,
+    nullrev,
+)
 from . import (
     error,
     repository,
@@ -33,6 +37,16 @@ class filelog(object):
     def __iter__(self):
         return self._revlog.__iter__()
 
+    def hasnode(self, node):
+        if node in (nullid, nullrev):
+            return False
+
+        try:
+            self._revlog.rev(node)
+            return True
+        except (TypeError, ValueError, IndexError, error.LookupError):
+            return False
+
     def revs(self, start=0, stop=None):
         return self._revlog.revs(start=start, stop=stop)
 
diff --git a/mercurial/repository.py b/mercurial/repository.py
--- a/mercurial/repository.py
+++ b/mercurial/repository.py
@@ -484,6 +484,16 @@ class ifileindex(interfaceutil.Interface
     def __iter__():
         """Iterate over revision numbers for this file."""
 
+    def hasnode(node):
+        """Returns a bool indicating if a node is known to this store.
+
+        Implementations must only return True for full, binary node values:
+        hex nodes, revision numbers, and partial node matches must be
+        rejected.
+
+        The null node is never present.
+        """
+
     def revs(start=0, stop=None):
         """Iterate over revision numbers for this file, with control."""
 
diff --git a/mercurial/testing/storage.py b/mercurial/testing/storage.py
--- a/mercurial/testing/storage.py
+++ b/mercurial/testing/storage.py
@@ -45,6 +45,13 @@ class ifileindextests(basetestcase):
         with self.assertRaises(StopIteration):
             next(gen)
 
+        self.assertFalse(f.hasnode(None))
+        self.assertFalse(f.hasnode(0))
+        self.assertFalse(f.hasnode(nullrev))
+        self.assertFalse(f.hasnode(nullid))
+        self.assertFalse(f.hasnode(b'0'))
+        self.assertFalse(f.hasnode(b'a' * 20))
+
         # revs() should evaluate to an empty list.
         self.assertEqual(list(f.revs()), [])
 
@@ -161,6 +168,13 @@ class ifileindextests(basetestcase):
         with self.assertRaises(StopIteration):
             next(gen)
 
+        self.assertTrue(f.hasnode(node))
+        self.assertFalse(f.hasnode(hex(node)))
+        self.assertFalse(f.hasnode(nullrev))
+        self.assertFalse(f.hasnode(nullid))
+        self.assertFalse(f.hasnode(node[0:12]))
+        self.assertFalse(f.hasnode(hex(node)[0:20]))
+
         self.assertEqual(list(f.revs()), [0])
         self.assertEqual(list(f.revs(start=1)), [])
         self.assertEqual(list(f.revs(start=0)), [0])