More locking improvements.
authorJelmer Vernooij <jelmer@jelmer.uk>
Sat, 26 May 2018 12:55:45 +0000 (13:55 +0100)
committerJelmer Vernooij <jelmer@jelmer.uk>
Sat, 26 May 2018 12:55:45 +0000 (13:55 +0100)
subvertpy/tests/test_wc.py
subvertpy/wc.c
subvertpy/wc.h
subvertpy/wc_adm.c

index 69eeb0595651c7c3cd1207445ca192a2344e7f53..394c6082af7e4a2100436246fc04476927457faf 100644 (file)
@@ -117,14 +117,14 @@ class AdmObjTests(SubversionTestCase):
         adm.close()
 
     def test_add_repos_file(self):
-        if wc.api_version() >= (1, 7):
-            self.skipTest("TODO: doesn't yet work with svn >= 1.7")
         if wc.api_version() < (1, 6):
             self.skipTest("doesn't work with svn < 1.6")
         self.make_client("repos", "checkout")
         adm = wc.Adm(None, "checkout", True)
         adm.add_repos_file("checkout/bar", BytesIO(b"basecontents"),
                            BytesIO(b"contents"), {}, {})
+        if wc.api_version() >= (1, 7):
+            self.skipTest("TODO: doesn't yet work with svn >= 1.7")
         self.assertEqual(b"basecontents",
                          wc.get_pristine_contents("checkout/bar").read())
 
@@ -265,6 +265,10 @@ class AdmObjTests(SubversionTestCase):
 
             def __init__(self):
                 self._windows = []
+                self._prop = {}
+
+            def change_prop(self, name, value):
+                self._prop[name] = value
 
             def apply_textdelta(self, checksum):
                 def window_handler(window):
@@ -274,18 +278,21 @@ class AdmObjTests(SubversionTestCase):
             def close(self):
                 pass
         editor = Editor()
-        (tmpfile, digest) = adm.transmit_text_deltas("bar", True, editor)
+        (tmpfile, md5_digest) = adm.transmit_text_deltas("bar", True, editor)
         self.assertEqual(editor._windows,
                          [(0, 0, 5, 0, [(2, 0, 5)], b'blala'), None])
         self.assertIsInstance(tmpfile, str)
-        self.assertEqual(16, len(digest))
+        self.assertEqual(16, len(md5_digest))
+        self.assertEqual(hashlib.md5(b'blala').digest(), md5_digest)
 
         bar = adm.entry("bar")
         self.assertEqual(-1, bar.cmt_rev)
         self.assertEqual(0, bar.revision)
+        self.assertIn(bar.checksum, (None, hashlib.md5(b'blala').hexdigest()))
 
         cq = wc.CommittedQueue()
-        cq.queue("bar", adm)
+        cq.queue("bar", adm, wcprop_changes=editor._prop,
+                 md5_digest=md5_digest)
         adm.process_committed_queue(cq, 1, "2010-05-31T08:49:22.430000Z",
                                     "jelmer")
         bar = adm.entry("bar")
@@ -309,6 +316,10 @@ class AdmObjTests(SubversionTestCase):
 
             def __init__(self):
                 self._windows = []
+                self._prop = {}
+
+            def change_prop(self, name, value):
+                self._prop[name] = value
 
             def apply_textdelta(self, checksum):
                 def window_handler(window):
@@ -319,15 +330,17 @@ class AdmObjTests(SubversionTestCase):
                 pass
 
         editor = Editor()
-        (tmpfile, digest) = adm.transmit_text_deltas(
+        (tmpfile, md5_digest) = adm.transmit_text_deltas(
                 "checkout/bar", True, editor)
         self.assertEqual(editor._windows,
                          [(0, 0, 5, 0, [(2, 0, 5)], b'blala'), None])
         self.assertIsInstance(tmpfile, str)
-        self.assertEqual(16, len(digest))
+        self.assertEqual(16, len(md5_digest))
+        self.assertEqual(hashlib.md5(b'blala').digest(), md5_digest)
 
         cq = wc.CommittedQueue()
-        cq.queue("checkout/bar", adm)
+        cq.queue("checkout/bar", adm, wcprop_changes=editor._prop,
+                 md5_digest=md5_digest)
         adm.process_committed_queue(cq, 1, "2010-05-31T08:49:22.430000Z",
                                     "jelmer")
         bar = adm.entry("checkout/bar")
@@ -362,6 +375,7 @@ class AdmObjTests(SubversionTestCase):
                          [(0, 0, 2, 0, [(2, 0, 2)], b'la'), None])
         self.assertIsInstance(tmpfile, str)
         self.assertEqual(16, len(digest))
+        self.assertEqual(hashlib.md5(b'blala').digest(), digest)
         bar = adm.entry("bar")
         self.assertEqual(-1, bar.cmt_rev)
         self.assertEqual(0, bar.revision)
@@ -388,6 +402,16 @@ class AdmObjTests(SubversionTestCase):
             os.path.abspath("checkout"),
             adm.probe_try(os.path.join("checkout", "bar")).access_path())
 
+    def test_lock(self):
+        self.make_client("repos", "checkout")
+        self.build_tree({"checkout/bar": b"la"})
+        self.client_add('checkout/bar')
+        adm = wc.Adm(None, "checkout", True)
+        lock = wc.Lock()
+        lock.token = b"blah"
+        adm.add_lock("checkout", lock)
+        adm.remove_lock("checkout")
+
 
 class ContextTests(SubversionTestCase):
 
@@ -480,28 +504,31 @@ class ContextTests(SubversionTestCase):
                  os.path.abspath("checkout/bla.txt")})
 
     def test_locking(self):
-        self.skipTest('TODO: locking does not work yet')
+        if wc.api_version() >= (1, 7):
+            self.skipTest("TODO: doesn't yet work with svn >= 1.7")
         self.make_client("repos", "checkout")
         with open('checkout/bla.txt', 'w') as f:
             f.write("modified")
         self.client_add("checkout/bla.txt")
         context = wc.Context()
-        lock = wc.Lock()
+        lock = wc.Lock(token=b'foo')
         self.assertEqual((False, False), context.locked("checkout"))
         context.add_lock("checkout/", lock)
         self.assertEqual((True, True), context.locked("checkout"))
         context.remove_lock("checkout/", lock)
 
     def test_add_from_disk(self):
-        self.skipTest('TODO: locking does not work yet')
+        if wc.api_version() >= (1, 7):
+            self.skipTest("TODO: doesn't yet work with svn >= 1.7")
         self.make_client("repos", "checkout")
         with open('checkout/bla.txt', 'w') as f:
             f.write("modified")
         context = wc.Context()
-        lock = wc.Lock()
-        context.add_lock("checkout/", lock)
+        lock = wc.Lock(token=b'foo')
+        lock.path = os.path.abspath('checkout')+"/"
+        context.add_lock("checkout", lock)
         context.add_from_disk('checkout/bla.txt')
-        context.remove_lock("checkout/", lock)
+        context.remove_lock("checkout", lock)
 
     def test_get_prop_diffs(self):
         self.make_client("repos", "checkout")
@@ -509,3 +536,16 @@ class ContextTests(SubversionTestCase):
         (orig_props, propdelta) = context.get_prop_diffs("checkout")
         self.assertEqual({}, orig_props)
         self.assertEqual([], propdelta)
+
+    def test_process_committed_queue(self):
+        if wc.api_version() >= (1, 7):
+            self.skipTest("TODO: doesn't yet work with svn >= 1.7")
+        self.make_client("repos", "checkout")
+        adm = wc.Context()
+        self.build_tree({"checkout/bar": b"blala"})
+        self.client_add('checkout/bar')
+        adm.add_lock("checkout", wc.Lock(token=b'foo'))
+        cq = wc.CommittedQueue()
+        cq.queue("checkout/bar", adm)
+        adm.process_committed_queue(cq, 1, "2010-05-31T08:49:22.430000Z",
+                                    "jelmer")
index 02add9be632593a149282b43d7e5f3a6e0cc70f7..4fb3b0c9cff293819ae5495d12003c2221b08c75 100644 (file)
 
 typedef struct {
     PyObject_HEAD
-    svn_lock_t *lock;
+    svn_lock_t lock;
     apr_pool_t *pool;
 } LockObject;
-extern PyTypeObject Lock_Type;
+
+#if ONLY_SINCE_SVN(1, 7)
+typedef struct {
+    PyObject_VAR_HEAD
+    apr_pool_t *pool;
+    svn_wc_context_t *context;
+} ContextObject;
+static PyTypeObject Context_Type;
+#endif
 
 #if ONLY_BEFORE_SVN(1, 5)
 struct svn_wc_committed_queue_t
@@ -877,46 +885,36 @@ static PyObject *committed_queue_queue(CommittedQueueObject *self, PyObject *arg
        const char *path;
        PyObject *admobj;
        PyObject *py_wcprop_changes = Py_None, *py_path;
-    svn_wc_adm_access_t *adm;
+    svn_wc_adm_access_t *adm = NULL;
        bool remove_lock = false, remove_changelist = false;
        char *md5_digest = NULL, *sha1_digest = NULL;
        bool recurse = false;
-       apr_pool_t *temp_pool;
        apr_array_header_t *wcprop_changes;
        int md5_digest_len, sha1_digest_len;
+#if ONLY_SINCE_SVN(1, 7)
+       svn_wc_context_t *context = NULL;
+#endif
        char *kwnames[] = { "path", "adm", "recurse", "wcprop_changes", "remove_lock", "remove_changelist", "md5_digest", "sha1_digest", NULL };
 
-       if (!PyArg_ParseTupleAndKeywords(args, kwargs, "OO!|bObbz#z#", kwnames,
-                                                                        &py_path, &Adm_Type, &admobj,
+       if (!PyArg_ParseTupleAndKeywords(args, kwargs, "OO|bObbz#z#", kwnames,
+                                                                        &py_path, &admobj,
                                                  &recurse, &py_wcprop_changes, &remove_lock,
                                                  &remove_changelist, &md5_digest, &md5_digest_len,
                                                  &sha1_digest, &sha1_digest_len))
                return NULL;
 
-       temp_pool = Pool(NULL);
-       if (temp_pool == NULL)
-               return NULL;
-
        if (!py_dict_to_wcprop_changes(py_wcprop_changes, self->pool, &wcprop_changes)) {
-               apr_pool_destroy(temp_pool);
                return NULL;
        }
 
        path = py_object_to_svn_abspath(py_path, self->pool);
        if (path == NULL) {
-               apr_pool_destroy(temp_pool);
                return NULL;
        }
 
        if (md5_digest != NULL) {
                if (md5_digest_len != APR_MD5_DIGESTSIZE) {
                        PyErr_SetString(PyExc_ValueError, "Invalid size for md5 digest");
-                       apr_pool_destroy(temp_pool);
-                       return NULL;
-               }
-               md5_digest = apr_pstrdup(temp_pool, md5_digest);
-               if (md5_digest == NULL) {
-                       PyErr_NoMemory();
                        return NULL;
                }
        }
@@ -924,44 +922,65 @@ static PyObject *committed_queue_queue(CommittedQueueObject *self, PyObject *arg
        if (sha1_digest != NULL) {
                if (sha1_digest_len != APR_SHA1_DIGESTSIZE) {
                        PyErr_SetString(PyExc_ValueError, "Invalid size for sha1 digest");
-                       apr_pool_destroy(temp_pool);
-                       return NULL;
-               }
-               sha1_digest = apr_pstrdup(temp_pool, sha1_digest);
-               if (sha1_digest == NULL) {
-                       PyErr_NoMemory();
                        return NULL;
                }
        }
 
-    adm = PyObject_GetAdmAccess(admobj);
+       if (PyObject_IsInstance(admobj, (PyObject *)&Adm_Type)) {
+               adm = PyObject_GetAdmAccess(admobj);
+#if ONLY_SINCE_SVN(1, 7)
+       } else if (PyObject_IsInstance(admobj, (PyObject *)&Context_Type)) {
+               context = ((ContextObject*)admobj)->context;
+#endif
+       } else {
+               PyErr_SetString(PyExc_TypeError, "Second arguments needs to be Adm or Context");
+               return NULL;
+       }
 
+#if ONLY_SINCE_SVN(1, 7)
+       if (adm != NULL) {
+#endif
 #if ONLY_SINCE_SVN(1, 6)
        {
-       svn_checksum_t svn_checksum, *svn_checksum_p = &svn_checksum;
+       svn_checksum_t *svn_checksum_p;
 
-       if (sha1_digest != NULL) {
-               svn_checksum.digest = (unsigned char *)sha1_digest;
-               svn_checksum.kind = svn_checksum_sha1;
-       } else if (md5_digest != NULL) {
-               svn_checksum.digest = (unsigned char *)md5_digest;
-               svn_checksum.kind = svn_checksum_md5;
+       if (md5_digest != NULL) {
+               svn_checksum_p  = apr_palloc(self->pool, sizeof(svn_checksum_t));
+        svn_checksum_p->digest = apr_pmemdup(
+            self->pool, (unsigned char *)md5_digest, APR_MD5_DIGESTSIZE);
+               svn_checksum_p->kind = svn_checksum_md5;
        } else {
                svn_checksum_p = NULL;
        }
-       RUN_SVN_WITH_POOL(temp_pool,
+       RUN_SVN(
                svn_wc_queue_committed2(self->queue, path, adm, recurse?TRUE:FALSE,
                                                           wcprop_changes, remove_lock?TRUE:FALSE, remove_changelist?TRUE:FALSE,
-                                                          svn_checksum_p, temp_pool));
+                                                          svn_checksum_p, self->pool));
        }
 #else
-       RUN_SVN_WITH_POOL(temp_pool,
+       RUN_SVN(
                svn_wc_queue_committed(&self->queue, path, adm, recurse?TRUE:FALSE,
                                                           wcprop_changes, remove_lock?TRUE:FALSE, remove_changelist?TRUE:FALSE,
-                                                          (unsigned char *)md5_digest, temp_pool));
+                                                          (unsigned char *)md5_digest, self->pool));
 #endif
+#if ONLY_SINCE_SVN(1, 7)
+       } else {
+               svn_checksum_t *svn_checksum_p;
 
-       apr_pool_destroy(temp_pool);
+               if (sha1_digest != NULL) {
+                       svn_checksum_p  = apr_palloc(self->pool, sizeof(svn_checksum_t));
+                       svn_checksum_p->digest = apr_pmemdup(
+                               self->pool, (unsigned char *)sha1_digest, APR_SHA1_DIGESTSIZE);
+                       svn_checksum_p->kind = svn_checksum_sha1;
+               } else {
+                       svn_checksum_p = NULL;
+               }
+               RUN_SVN(
+                       svn_wc_queue_committed3(self->queue, context, path, recurse?TRUE:FALSE,
+                                                                  wcprop_changes, remove_lock?TRUE:FALSE, remove_changelist?TRUE:FALSE,
+                                                                  svn_checksum_p, self->pool));
+       }
+#endif
 
        Py_RETURN_NONE;
 }
@@ -1045,12 +1064,6 @@ PyTypeObject CommittedQueue_Type = {
 #if ONLY_SINCE_SVN(1, 7)
 static PyTypeObject Context_Type;
 
-typedef struct {
-    PyObject_VAR_HEAD
-    apr_pool_t *pool;
-    svn_wc_context_t *context;
-} ContextObject;
-
 static PyObject *py_wc_context_locked(PyObject *self, PyObject *args)
 {
     PyObject* py_path;
@@ -1605,10 +1618,14 @@ static PyObject *py_wc_walk_status(PyObject *self, PyObject *args, PyObject *kwa
     Py_RETURN_NONE;
 }
 
-static svn_lock_t *py_object_to_svn_lock(PyObject *py_lock, apr_pool_t *pool)
+svn_lock_t *py_object_to_svn_lock(PyObject *py_lock, apr_pool_t *pool)
 {
        LockObject* lockobj = (LockObject *)py_lock;
-       return lockobj->lock;
+    if (!PyObject_IsInstance(py_lock, (PyObject *)&Lock_Type)) {
+        PyErr_SetString(PyExc_TypeError, "Expected Lock object");
+        return NULL;
+    }
+       return &lockobj->lock;
 }
 
 static PyObject *py_wc_add_lock(PyObject *self, PyObject *args, PyObject *kwargs)
@@ -1626,6 +1643,9 @@ static PyObject *py_wc_add_lock(PyObject *self, PyObject *args, PyObject *kwargs
     }
 
     scratch_pool = Pool(NULL);
+    if (scratch_pool == NULL) {
+        return NULL;
+    }
 
     path = py_object_to_svn_abspath(py_path, scratch_pool);
     if (path == NULL) {
@@ -1790,6 +1810,38 @@ static PyObject *py_wc_get_prop_diffs(PyObject *self, PyObject *args, PyObject *
     return Py_BuildValue("NN", py_orig_props, py_propchanges);
 }
 
+static PyObject *py_wc_context_process_committed_queue(PyObject *self, PyObject *args)
+{
+    apr_pool_t *temp_pool;
+    ContextObject *contextobj = (ContextObject *)self;
+    svn_revnum_t revnum;
+    char *date, *author;
+    PyObject *py_queue;
+
+    if (!PyArg_ParseTuple(args, "O!lss", &CommittedQueue_Type, &py_queue,
+                          &revnum, &date, &author))
+        return NULL;
+
+    temp_pool = Pool(NULL);
+    if (temp_pool == NULL)
+        return NULL;
+
+    svn_wc_committed_queue_t *committed_queue = PyObject_GetCommittedQueue(py_queue);
+
+    RUN_SVN_WITH_POOL(temp_pool,
+                      svn_wc_process_committed_queue2(committed_queue,
+                                                      contextobj->context,
+                                                      revnum, date, author,
+                                                      py_cancel_check, NULL,
+                                                      temp_pool));
+
+    apr_pool_destroy(temp_pool);
+
+    Py_RETURN_NONE;
+}
+
+
+
 static PyMethodDef context_methods[] = {
     { "locked", py_wc_context_locked, METH_VARARGS,
         "locked(path) -> (locked_here, locked)\n"
@@ -1825,6 +1877,10 @@ static PyMethodDef context_methods[] = {
         (PyCFunction)py_wc_context_ensure_adm,
         METH_VARARGS|METH_KEYWORDS,
         "ensure_adm(local_abspath, url, repos_root_url, repos_uuid, revnum, depth)" },
+    { "process_committed_queue",
+        (PyCFunction)py_wc_context_process_committed_queue,
+        METH_VARARGS|METH_KEYWORDS,
+        "" },
     { "status",
         (PyCFunction)py_wc_status,
         METH_VARARGS|METH_KEYWORDS,
@@ -1967,10 +2023,11 @@ static void lock_dealloc(PyObject *self)
 
 static PyObject *lock_init(PyTypeObject *type, PyObject *args, PyObject *kwargs)
 {
-       char *kwnames[] = { NULL };
+       char *kwnames[] = { "token", NULL };
        LockObject *ret;
+    char *token = NULL;
 
-       if (!PyArg_ParseTupleAndKeywords(args, kwargs, "", kwnames))
+       if (!PyArg_ParseTupleAndKeywords(args, kwargs, "|z", kwnames, &token))
                return NULL;
 
        ret = PyObject_New(LockObject, &Lock_Type);
@@ -1980,11 +2037,69 @@ static PyObject *lock_init(PyTypeObject *type, PyObject *args, PyObject *kwargs)
        ret->pool = Pool(NULL);
        if (ret->pool == NULL)
                return NULL;
-       ret->lock = svn_lock_create(ret->pool);
+       ret->lock = *svn_lock_create(ret->pool);
+    if (token != NULL) {
+        ret->lock.token = apr_pstrdup(ret->pool, token);
+    }
 
        return (PyObject *)ret;
 }
 
+static PyObject *lock_get_path(PyObject *self, void *closure) {
+    LockObject *lock_obj = (LockObject *)self;
+
+    if (lock_obj->lock.path == NULL) {
+        Py_RETURN_NONE;
+    }
+
+    return PyUnicode_FromString(lock_obj->lock.path);
+}
+
+static int lock_set_path(PyObject *self, PyObject *value, void *closure) {
+    LockObject *lock_obj = (LockObject *)self;
+    char *path;
+
+    path = PyBytes_AsString(value);
+    if (path == NULL) {
+        return -1;
+    }
+
+    lock_obj->lock.path = py_object_to_svn_string(value, lock_obj->pool);
+    return 0;
+}
+
+static PyObject *lock_get_token(PyObject *self, void *closure) {
+    LockObject *lock_obj = (LockObject *)self;
+
+    if (lock_obj->lock.token == NULL) {
+        Py_RETURN_NONE;
+    }
+
+    return PyBytes_FromString(lock_obj->lock.token);
+}
+
+static int lock_set_token(PyObject *self, PyObject *value, void *closure) {
+    LockObject *lock_obj = (LockObject *)self;
+    char *token;
+
+    token = PyBytes_AsString(value);
+    if (token == NULL) {
+        PyErr_SetNone(PyExc_TypeError);
+        return -1;
+    }
+
+    lock_obj->lock.token = apr_pstrdup(lock_obj->pool, PyBytes_AsString(value));
+    return 0;
+}
+
+static PyGetSetDef lock_getsetters[] = {
+    { "path", lock_get_path, lock_set_path,
+        "the path this lock applies to"},
+    { "token", lock_get_token, lock_set_token,
+        "unique URI representing lock"},
+    { NULL },
+};
+
 PyTypeObject Lock_Type = {
        PyVarObject_HEAD_INIT(NULL, 0)
        "wc.Lock", /*   const char *tp_name;  For printing, in format "<module>.<name>" */
@@ -2000,6 +2115,8 @@ PyTypeObject Lock_Type = {
        .tp_methods = NULL, /*  struct PyMethodDef *tp_methods; */
 
        .tp_new = lock_init, /* tp_new tp_new */
+
+    .tp_getset = lock_getsetters,
 };
 
 static PyObject *
index 558c38dddb76e0b7700365a20a90ab3fd815db73..ccd50d481081a540ad31dc015afc3782b3b5ab52 100644 (file)
@@ -38,12 +38,14 @@ svn_error_t *wc_validator3(void *baton, const char *uuid, const char *url, const
 svn_error_t *wc_validator2(void *baton, const char *uuid, const char *url, svn_boolean_t root, apr_pool_t *pool);
 svn_wc_committed_queue_t *PyObject_GetCommittedQueue(PyObject *obj);
 extern PyTypeObject CommittedQueue_Type;
+svn_lock_t *py_object_to_svn_lock(PyObject *py_lock, apr_pool_t *pool);
 
 /* Provided by wc_adm.h */
 extern PyTypeObject Adm_Type;
 extern PyTypeObject Entry_Type;
 extern PyTypeObject Status2_Type;
 svn_wc_adm_access_t *PyObject_GetAdmAccess(PyObject *obj);
+extern PyTypeObject Lock_Type;
 
 #ifdef __GNUC__
 #pragma GCC visibility pop
index 6ced3c1b21224222eadc98664af6ce55a639b4df..7cdce98c4338a47ba9e727aaa5d4eab6d62a2649 100644 (file)
@@ -1744,6 +1744,44 @@ static PyObject *wc_status(PyObject *self, PyObject *args)
     return (PyObject*)ret;
 }
 
+static PyObject *wc_add_lock(PyObject *self, PyObject *args)
+{
+    const char *path;
+    apr_pool_t *temp_pool;
+    AdmObject *admobj = (AdmObject *)self;
+    svn_lock_t *lock;
+    PyObject *py_path, *py_lock;
+
+    if (!PyArg_ParseTuple(args, "OO!", &py_path, &Lock_Type, &py_lock))
+        return NULL;
+
+    ADM_CHECK_CLOSED(admobj);
+
+    temp_pool = Pool(NULL);
+    if (temp_pool == NULL) {
+        return NULL;
+    }
+
+    path = py_object_to_svn_abspath(py_path, temp_pool);
+    if (path == NULL) {
+        apr_pool_destroy(temp_pool);
+        return NULL;
+    }
+
+    lock = py_object_to_svn_lock(py_lock, temp_pool);
+    if (lock == NULL) {
+        apr_pool_destroy(temp_pool);
+        return NULL;
+    }
+
+    RUN_SVN_WITH_POOL(temp_pool,
+                      svn_wc_add_lock(path, lock, admobj->adm, temp_pool));
+
+    apr_pool_destroy(temp_pool);
+
+    Py_RETURN_NONE;
+}
+
 static PyMethodDef adm_methods[] = {
     { "prop_set", adm_prop_set, METH_VARARGS, "S.prop_set(name, value, path, skip_checks=False)" },
     { "access_path", (PyCFunction)adm_access_path, METH_NOARGS,
@@ -1809,6 +1847,7 @@ static PyMethodDef adm_methods[] = {
     { "resolved_conflict", (PyCFunction)resolved_conflict, METH_VARARGS,
         "S.resolved_conflict(path, resolve_text, resolve_props, resolve_tree, depth, conflict_choice, notify_func=None, cancel=None)" },
     { "status", (PyCFunction)wc_status, METH_VARARGS, "status(wc_path) -> Status" },
+    { "add_lock", (PyCFunction)wc_add_lock, METH_VARARGS, "add_lock(path, lock)" },
     { NULL, }
 };