浏览代码

Add a RefsContainer.watch method.

Jelmer Vernooij 4 年之前
父节点
当前提交
f189faa19a
共有 3 个文件被更改,包括 74 次插入1 次删除
  1. 5 0
      NEWS
  2. 43 1
      dulwich/refs.py
  3. 26 0
      dulwich/tests/test_refs.py

+ 5 - 0
NEWS

@@ -1,3 +1,8 @@
+0.20.6	UNRELEASED
+
+ * Add a ``RefsContainer.watch`` interface.
+   (Jelmer Vernooij, #751)
+
 0.20.5	2020-06-22
 
  * Print a clearer exception when setup.py is executed on Python < 3.5.

+ 43 - 1
dulwich/refs.py

@@ -390,6 +390,36 @@ class RefsContainer(object):
                 ret[src] = dst
         return ret
 
+    def watch(self):
+        """Watch for changes to the refs in this container.
+
+        Returns a context manager that yields tuples with (refname, old_sha,
+        new_sha)
+        """
+        raise NotImplementedError(self.watch)
+
+
+class _DictRefsWatcher(object):
+
+    def __init__(self, refs):
+        self._refs = refs
+
+    def __enter__(self):
+        from queue import Queue
+        self.queue = Queue()
+        self._refs._watchers.add(self)
+        return self
+
+    def __next__(self):
+        return self.queue.get()
+
+    def _notify(self, entry):
+        self.queue.put_nowait(entry)
+
+    def __exit__(self, exc_type, exc_val, exc_tb):
+        self._refs._watchers.remove(self)
+        return False
+
 
 class DictRefsContainer(RefsContainer):
     """RefsContainer backed by a simple dict.
@@ -402,6 +432,7 @@ class DictRefsContainer(RefsContainer):
         super(DictRefsContainer, self).__init__(logger=logger)
         self._refs = refs
         self._peeled = {}
+        self._watchers = set()
 
     def allkeys(self):
         return self._refs.keys()
@@ -412,11 +443,19 @@ class DictRefsContainer(RefsContainer):
     def get_packed_refs(self):
         return {}
 
+    def _notify(self, ref, oldsha, newsha):
+        for watcher in self._watchers:
+            watcher._notify((ref, oldsha, newsha))
+
+    def watch(self):
+        return _DictRefsWatcher(self)
+
     def set_symbolic_ref(self, name, other, committer=None,
                          timestamp=None, timezone=None, message=None):
         old = self.follow(name)[-1]
         new = SYMREF + other
         self._refs[name] = new
+        self._notify(name, old, new)
         self._log(name, old, new, committer=committer, timestamp=timestamp,
                   timezone=timezone, message=message)
 
@@ -429,6 +468,7 @@ class DictRefsContainer(RefsContainer):
             self._check_refname(realname)
             old = self._refs.get(realname)
             self._refs[realname] = new_ref
+            self._notify(realname, old, new_ref)
             self._log(realname, old, new_ref, committer=committer,
                       timestamp=timestamp, timezone=timezone, message=message)
         return True
@@ -438,6 +478,7 @@ class DictRefsContainer(RefsContainer):
         if name in self._refs:
             return False
         self._refs[name] = ref
+        self._notify(name, None, ref)
         self._log(name, None, ref, committer=committer, timestamp=timestamp,
                   timezone=timezone, message=message)
         return True
@@ -451,6 +492,7 @@ class DictRefsContainer(RefsContainer):
         except KeyError:
             pass
         else:
+            self._notify(name, old, None)
             self._log(name, old, None, committer=committer,
                       timestamp=timestamp, timezone=timezone, message=message)
         return True
@@ -463,7 +505,7 @@ class DictRefsContainer(RefsContainer):
         # TODO(dborowitz): replace this with a public function that uses
         # set_if_equal.
         for ref, sha in refs.items():
-            self._refs[ref] = sha
+            self.set_if_equal(ref, None, sha)
 
     def _update_peeled(self, peeled):
         """Update cached peeled refs; intended only for testing."""

+ 26 - 0
dulwich/tests/test_refs.py

@@ -323,6 +323,32 @@ class RefsContainerTests(object):
         self.assertNotIn(
             b'refs/remotes/origin/other', self._refs)
 
+    def test_watch(self):
+        try:
+            watcher = self._refs.watch()
+        except NotImplementedError:
+            self.skipTest('watching not supported')
+        with watcher:
+            self._refs[b'refs/remotes/origin/other'] = (
+                b'48d01bd4b77fed026b154d16493e5deab78f02ec')
+            change = next(watcher)
+            self.assertEqual(
+                (b'refs/remotes/origin/other', None,
+                 b'48d01bd4b77fed026b154d16493e5deab78f02ec'), change)
+            self._refs[b'refs/remotes/origin/other'] = (
+                b'48d01bd4b77fed026b154d16493e5deab78f02ed')
+            change = next(watcher)
+            self.assertEqual(
+                (b'refs/remotes/origin/other',
+                 b'48d01bd4b77fed026b154d16493e5deab78f02ec',
+                 b'48d01bd4b77fed026b154d16493e5deab78f02ed'), change)
+            del self._refs[b'refs/remotes/origin/other']
+            change = next(watcher)
+            self.assertEqual(
+                (b'refs/remotes/origin/other',
+                 b'48d01bd4b77fed026b154d16493e5deab78f02ed',
+                 None), change)
+
 
 class DictRefsContainerTests(RefsContainerTests, TestCase):