]> git.notmuchmail.org Git - notmuch/commitdiff
Merge branch 'release'
authorDavid Bremner <bremner@debian.org>
Thu, 24 May 2012 01:48:16 +0000 (22:48 -0300)
committerDavid Bremner <bremner@debian.org>
Thu, 24 May 2012 01:48:16 +0000 (22:48 -0300)
merge 0.13.1 bugfix patches back to master, fixes for emacs reply and
spurious directory document creation.

NEWS
bindings/python/notmuch/database.py
emacs/notmuch-lib.el
lib/database.cc
lib/directory.cc
lib/message.cc
lib/notmuch-private.h
lib/notmuch.h
notmuch-new.c
test/python

diff --git a/NEWS b/NEWS
index 69a72030d53256e58cc2677b1e927421e6c641b6..668281c55008a72dc8531c754401fa77f9f5332d 100644 (file)
--- a/NEWS
+++ b/NEWS
@@ -1,24 +1,17 @@
-Notmuch 0.13 (2012-xx-xx)
+Notmuch 0.13 (2012-05-15)
 =========================
 
 Command-Line Interface
 ----------------------
 
-Reply to sender
-
-  "notmuch reply" has gained the ability to create a reply template
-  for replying just to the sender of the message, in addition to reply
-  to all. The feature is available through the new command line option
-  --reply-to=(all|sender).
-
 JSON reply format
 
-  "notmuch reply" can now produce JSON output that contains the headers
+  `notmuch reply` can now produce JSON output that contains the headers
   for a reply message and full information about the original message
   begin replied to. This allows MUAs to create replies intelligently.
   For example, an MUA that can parse HTML might quote HTML parts.
 
-  Calling notmuch reply with --format=json imposes the restriction that
+  Calling notmuch reply with `--format=json` imposes the restriction that
   only a single message is returned by the search, as replying to
   multiple messages does not have a well-defined behavior. The default
   retains its current behavior for multiple message replies.
@@ -26,24 +19,24 @@ JSON reply format
 Tag exclusion
 
   Tags can be automatically excluded from search results by adding them
-  to the new 'search.exclude_tags' option in the Notmuch config file.
+  to the new `search.exclude_tags` option in the Notmuch config file.
 
   This behaviour can be overridden by explicitly including an excluded
   tag in your query, for example:
 
-    notmuch search $your_query and tag:$excluded_tag
+        notmuch search $your_query and tag:$excluded_tag
 
-  Existing users will probably want to run "notmuch setup" again to add
+  Existing users will probably want to run `notmuch setup` again to add
   the new well-commented [search] section to the configuration file.
 
   For new configurations, accepting the default setting will cause the
   tags "deleted" and "spam" to be excluded, equivalent to running:
 
-    notmuch config set search.exclude_tags deleted spam
+        notmuch config set search.exclude_tags deleted spam
 
 Raw show format changes
 
-  The output of show --format=raw has changed for multipart and
+  The output of show `--format=raw` has changed for multipart and
   message parts.  Previously, the output was a mash of somewhat-parsed
   headers and transfer-decoded bodies.  Now, such parts are reproduced
   faithfully from the original source.  Message parts (which includes
@@ -54,7 +47,7 @@ Raw show format changes
 
 Listing configuration items
 
-  The new "config list" command prints out all configuration items and
+  The new `config list` command prints out all configuration items and
   their values.
 
 Emacs Interface
@@ -64,7 +57,7 @@ Changes to tagging interface
 
   The user-facing tagging functions in the Emacs interface have been
   normalized across all notmuch modes.  The tagging functions are now
-  'notmuch-search-tag' in search-mode, and 'notmuch-show-tag' in
+  notmuch-search-tag in search-mode, and notmuch-show-tag in
   show-mode.  They accept a string representing a single tag change,
   or a list of tag changes.  See 'M-x describe-function notmuch-tag'
   for more information.
@@ -83,11 +76,11 @@ Reply improvement using the JSON format
 New add-on tool: notmuch-mutt
 -----------------------------
 
-The new contrib/ tool "notmuch-mutt" provides Notmuch integration for
+The new contrib/ tool `notmuch-mutt` provides Notmuch integration for
 the Mutt mail user agent. Using it, Mutt users can perform mail
 search, thread reconstruction, and mail tagging/untagging without
 leaving Mutt.  notmuch-mutt, formerly distributed under the name
-"mutt-notmuch" by Stefano Zacchiroli, will be maintained as a notmuch
+`mutt-notmuch` by Stefano Zacchiroli, will be maintained as a notmuch
 contrib/ from now on.
 
 Library changes
@@ -96,26 +89,26 @@ Library changes
 The API changes detailed below break binary and source compatibility,
 so libnotmuch has been bumped to version 3.0.0.
 
-The function notmuch_database_close has been split into
-notmuch_database_close and notmuch_database_destroy
+The function `notmuch_database_close` has been split into
+`notmuch_database_close` and `notmuch_database_destroy`
 
   This makes it possible for long running programs to close the xapian
   database and thus release the lock associated with it without
   destroying the data structures obtained from it.
 
-notmuch_database_open, notmuch_database_create, and
-notmuch_database_get_directory now return errors
+`notmuch_database_open`, `notmuch_database_create`, and
+`notmuch_database_get_directory` now return errors
 
   The type signatures of these functions have changed so that the
-  functions now return a notmuch_status_t and take an out-argument for
+  functions now return a `notmuch_status_t` and take an out-argument for
   returning the new database object or directory object.
 
-go bindings changes
+Go bindings changes
 -------------------
 
 Go 1 compatibility
 
-  The go bindings and the notmuch-addrlookup utility are now
+  The go bindings and the `notmuch-addrlookup` utility are now
   compatible with go 1.
 
 Notmuch 0.12 (2012-03-20)
index ee0366cd0c846ce848be42c1a349e38be68d7b81..5931f41bc26c28d17ca1b5b306beb9abef313674 100644 (file)
@@ -347,7 +347,6 @@ class Database(object):
 
     def get_directory(self, path):
         """Returns a :class:`Directory` of path,
-        (creating it if it does not exist(?))
 
         :param path: An unicode string containing the path relative to the path
               of database (see :meth:`get_path`), or else should be an absolute
@@ -355,8 +354,6 @@ class Database(object):
         :returns: :class:`Directory` or raises an exception.
         :raises: :exc:`FileError` if path is not relative database or absolute
                  with initial components same as database.
-        :raises: :exc:`ReadOnlyDatabaseError` if the database has not been
-                 opened in read-write mode
         """
         self._assert_db_is_initialized()
 
@@ -530,19 +527,10 @@ class Database(object):
                  retry.
         :raises: :exc:`NotInitializedError` if the database was not
                  intitialized.
-        :raises: :exc:`ReadOnlyDatabaseError` if the database has not been
-                 opened in read-write mode
 
         *Added in notmuch 0.9*"""
         self._assert_db_is_initialized()
 
-        # work around libnotmuch calling exit(3), see
-        # id:20120221002921.8534.57091@thinkbox.jade-hamburg.de
-        # TODO: remove once this issue is resolved
-        if self.mode != Database.MODE.READ_WRITE:
-            raise ReadOnlyDatabaseError('The database has to be opened in '
-                                        'read-write mode for get_directory')
-
         msg_p = NotmuchMessageP()
         status = Database._find_message_by_filename(self._db, _str(filename),
                                                     byref(msg_p))
index 7fa441af30a5ba17ca6881689febe5cfc9ec30f2..e99b48d107e164b1ab56c92dc9da654f75d862e4 100644 (file)
@@ -244,7 +244,12 @@ the given type."
 current buffer, if possible."
   (let ((display-buffer (current-buffer)))
     (with-temp-buffer
-      (let* ((charset (plist-get part :content-charset))
+      ;; In case there is :content, the content string is already converted
+      ;; into emacs internal format. `gnus-decoded' is a fake charset,
+      ;; which means no further decoding (to be done by mm- functions).
+      (let* ((charset (if (plist-member part :content)
+                         'gnus-decoded
+                       (plist-get part :content-charset)))
             (handle (mm-make-handle (current-buffer) `(,content-type (charset . ,charset)))))
        ;; If the user wants the part inlined, insert the content and
        ;; test whether we are able to inline it (which includes both
index f8c4a7d121d0bdbcd2a631ee65ed9303ab9cd9fa..761dc1a24c3a26247dcfa7734ceb7141f02df36a 100644 (file)
@@ -956,7 +956,7 @@ notmuch_database_upgrade (notmuch_database_t *notmuch,
                    document.get_value (NOTMUCH_VALUE_TIMESTAMP));
 
                directory = _notmuch_directory_create (notmuch, term.c_str() + 10,
-                                                      &status);
+                                                      NOTMUCH_FIND_CREATE, &status);
                notmuch_directory_set_mtime (directory, mtime);
                notmuch_directory_destroy (directory);
            }
@@ -1197,9 +1197,17 @@ _notmuch_database_split_path (void *ctx,
     return NOTMUCH_STATUS_SUCCESS;
 }
 
+/* Find the document ID of the specified directory.
+ *
+ * If (flags & NOTMUCH_FIND_CREATE), a new directory document will be
+ * created if one does not exist for 'path'.  Otherwise, if the
+ * directory document does not exist, this sets *directory_id to
+ * ((unsigned int)-1) and returns NOTMUCH_STATUS_SUCCESS.
+ */
 notmuch_status_t
 _notmuch_database_find_directory_id (notmuch_database_t *notmuch,
                                     const char *path,
+                                    notmuch_find_flags_t flags,
                                     unsigned int *directory_id)
 {
     notmuch_directory_t *directory;
@@ -1210,8 +1218,8 @@ _notmuch_database_find_directory_id (notmuch_database_t *notmuch,
        return NOTMUCH_STATUS_SUCCESS;
     }
 
-    directory = _notmuch_directory_create (notmuch, path, &status);
-    if (status) {
+    directory = _notmuch_directory_create (notmuch, path, flags, &status);
+    if (status || !directory) {
        *directory_id = -1;
        return status;
     }
@@ -1240,13 +1248,16 @@ _notmuch_database_get_directory_path (void *ctx,
  * database path), return a new string (with 'ctx' as the talloc
  * owner) suitable for use as a direntry term value.
  *
- * The necessary directory documents will be created in the database
- * as needed.
+ * If (flags & NOTMUCH_FIND_CREATE), the necessary directory documents
+ * will be created in the database as needed.  Otherwise, if the
+ * necessary directory documents do not exist, this sets
+ * *direntry to NULL and returns NOTMUCH_STATUS_SUCCESS.
  */
 notmuch_status_t
 _notmuch_database_filename_to_direntry (void *ctx,
                                        notmuch_database_t *notmuch,
                                        const char *filename,
+                                       notmuch_find_flags_t flags,
                                        char **direntry)
 {
     const char *relative, *directory, *basename;
@@ -1260,10 +1271,12 @@ _notmuch_database_filename_to_direntry (void *ctx,
     if (status)
        return status;
 
-    status = _notmuch_database_find_directory_id (notmuch, directory,
+    status = _notmuch_database_find_directory_id (notmuch, directory, flags,
                                                  &directory_id);
-    if (status)
+    if (status || directory_id == (unsigned int)-1) {
+       *direntry = NULL;
        return status;
+    }
 
     *direntry = talloc_asprintf (ctx, "%u:%s", directory_id, basename);
 
@@ -1315,12 +1328,9 @@ notmuch_database_get_directory (notmuch_database_t *notmuch,
        return NOTMUCH_STATUS_NULL_POINTER;
     *directory = NULL;
 
-    /* XXX Handle read-only databases properly */
-    if (notmuch->mode == NOTMUCH_DATABASE_MODE_READ_ONLY)
-       return NOTMUCH_STATUS_READ_ONLY_DATABASE;
-
     try {
-       *directory = _notmuch_directory_create (notmuch, path, &status);
+       *directory = _notmuch_directory_create (notmuch, path,
+                                               NOTMUCH_FIND_LOOKUP, &status);
     } catch (const Xapian::Error &error) {
        fprintf (stderr, "A Xapian exception occurred getting directory: %s.\n",
                 error.get_msg().c_str());
@@ -1884,9 +1894,9 @@ notmuch_database_find_message_by_filename (notmuch_database_t *notmuch,
     local = talloc_new (notmuch);
 
     try {
-       status = _notmuch_database_filename_to_direntry (local, notmuch,
-                                                        filename, &direntry);
-       if (status)
+       status = _notmuch_database_filename_to_direntry (
+           local, notmuch, filename, NOTMUCH_FIND_LOOKUP, &direntry);
+       if (status || !direntry)
            goto DONE;
 
        term = talloc_asprintf (local, "%s%s", prefix, direntry);
index 70e1693ea54eaa4f3bfe5e5312610318d803621f..6a3ffed73bb6031d36cf579a700d4b67b6ba5ec6 100644 (file)
@@ -82,28 +82,41 @@ find_directory_document (notmuch_database_t *notmuch,
     return NOTMUCH_PRIVATE_STATUS_SUCCESS;
 }
 
+/* Find or create a directory document.
+ *
+ * 'path' should be a path relative to the path of 'database', or else
+ * should be an absolute path with initial components that match the
+ * path of 'database'.
+ *
+ * If (flags & NOTMUCH_FIND_CREATE), then the directory document will
+ * be created if it does not exist.  Otherwise, if the directory
+ * document does not exist, *status_ret is set to
+ * NOTMUCH_STATUS_SUCCESS and this returns NULL.
+ */
 notmuch_directory_t *
 _notmuch_directory_create (notmuch_database_t *notmuch,
                           const char *path,
+                          notmuch_find_flags_t flags,
                           notmuch_status_t *status_ret)
 {
     Xapian::WritableDatabase *db;
     notmuch_directory_t *directory;
     notmuch_private_status_t private_status;
     const char *db_path;
+    notmuch_bool_t create = (flags & NOTMUCH_FIND_CREATE);
 
     *status_ret = NOTMUCH_STATUS_SUCCESS;
 
     path = _notmuch_database_relative_path (notmuch, path);
 
-    if (notmuch->mode == NOTMUCH_DATABASE_MODE_READ_ONLY)
+    if (create && notmuch->mode == NOTMUCH_DATABASE_MODE_READ_ONLY)
        INTERNAL_ERROR ("Failure to ensure database is writable");
 
-    db = static_cast <Xapian::WritableDatabase *> (notmuch->xapian_db);
-
     directory = talloc (notmuch, notmuch_directory_t);
-    if (unlikely (directory == NULL))
+    if (unlikely (directory == NULL)) {
+       *status_ret = NOTMUCH_STATUS_OUT_OF_MEMORY;
        return NULL;
+    }
 
     directory->notmuch = notmuch;
 
@@ -122,6 +135,13 @@ _notmuch_directory_create (notmuch_database_t *notmuch,
        directory->document_id = directory->doc.get_docid ();
 
        if (private_status == NOTMUCH_PRIVATE_STATUS_NO_DOCUMENT_FOUND) {
+           if (!create) {
+               notmuch_directory_destroy (directory);
+               directory = NULL;
+               *status_ret = NOTMUCH_STATUS_SUCCESS;
+               goto DONE;
+           }
+
            void *local = talloc_new (directory);
            const char *parent, *basename;
            Xapian::docid parent_id;
@@ -133,7 +153,13 @@ _notmuch_directory_create (notmuch_database_t *notmuch,
 
            _notmuch_database_split_path (local, path, &parent, &basename);
 
-           _notmuch_database_find_directory_id (notmuch, parent, &parent_id);
+           *status_ret = _notmuch_database_find_directory_id (
+               notmuch, parent, NOTMUCH_FIND_CREATE, &parent_id);
+           if (*status_ret) {
+               notmuch_directory_destroy (directory);
+               directory = NULL;
+               goto DONE;
+           }
 
            if (basename) {
                term = talloc_asprintf (local, "%s%u:%s",
@@ -145,6 +171,8 @@ _notmuch_directory_create (notmuch_database_t *notmuch,
            directory->doc.add_value (NOTMUCH_VALUE_TIMESTAMP,
                                      Xapian::sortable_serialise (0));
 
+           db = static_cast <Xapian::WritableDatabase *> (notmuch->xapian_db);
+
            directory->document_id = _notmuch_database_generate_doc_id (notmuch);
            db->replace_document (directory->document_id, directory->doc);
            talloc_free (local);
@@ -158,10 +186,11 @@ _notmuch_directory_create (notmuch_database_t *notmuch,
                 error.get_msg().c_str());
        notmuch->exception_reported = TRUE;
        notmuch_directory_destroy (directory);
+       directory = NULL;
        *status_ret = NOTMUCH_STATUS_XAPIAN_EXCEPTION;
-       return NULL;
     }
 
+  DONE:
     if (db_path != path)
        free ((char *) db_path);
 
index 00754254b87bc387420529a66082a3887ec69a4e..67875065f5c93f01edbad2387172d3767be59364 100644 (file)
@@ -495,9 +495,8 @@ _notmuch_message_add_filename (notmuch_message_t *message,
     if (status)
        return status;
 
-    status = _notmuch_database_filename_to_direntry (local,
-                                                    message->notmuch,
-                                                    filename, &direntry);
+    status = _notmuch_database_filename_to_direntry (
+       local, message->notmuch, filename, NOTMUCH_FIND_CREATE, &direntry);
     if (status)
        return status;
 
@@ -541,9 +540,9 @@ _notmuch_message_remove_filename (notmuch_message_t *message,
     notmuch_status_t status;
     Xapian::TermIterator i, last;
 
-    status = _notmuch_database_filename_to_direntry (local, message->notmuch,
-                                                    filename, &direntry);
-    if (status)
+    status = _notmuch_database_filename_to_direntry (
+       local, message->notmuch, filename, NOTMUCH_FIND_LOOKUP, &direntry);
+    if (status || !direntry)
        return status;
 
     /* Unlink this file from its parent directory. */
index 3886e0ca0c86475e242ca6a7adf61b6b91c2d74e..bfb41116adcff104edadf639cd95513b225289bb 100644 (file)
@@ -146,6 +146,16 @@ typedef enum _notmuch_private_status {
      :                                                                 \
      (notmuch_status_t) private_status)
 
+/* Flags shared by various lookup functions. */
+typedef enum _notmuch_find_flags {
+    /* Lookup without creating any documents.  This is the default
+     * behavior. */
+    NOTMUCH_FIND_LOOKUP = 0,
+    /* If set, create the necessary document (or documents) if they
+     * are missing.  Requires a read/write database. */
+    NOTMUCH_FIND_CREATE = 1<<0,
+} notmuch_find_flags_t;
+
 typedef struct _notmuch_doc_id_set notmuch_doc_id_set_t;
 
 typedef struct _notmuch_string_list notmuch_string_list_t;
@@ -188,6 +198,7 @@ _notmuch_database_find_unique_doc_id (notmuch_database_t *notmuch,
 notmuch_status_t
 _notmuch_database_find_directory_id (notmuch_database_t *database,
                                     const char *path,
+                                    notmuch_find_flags_t flags,
                                     unsigned int *directory_id);
 
 const char *
@@ -199,6 +210,7 @@ notmuch_status_t
 _notmuch_database_filename_to_direntry (void *ctx,
                                        notmuch_database_t *notmuch,
                                        const char *filename,
+                                       notmuch_find_flags_t flags,
                                        char **direntry);
 
 /* directory.cc */
@@ -206,6 +218,7 @@ _notmuch_database_filename_to_direntry (void *ctx,
 notmuch_directory_t *
 _notmuch_directory_create (notmuch_database_t *notmuch,
                           const char *path,
+                          notmuch_find_flags_t flags,
                           notmuch_status_t *status_ret);
 
 unsigned int
index bbb17e4a66d5c3f5e6c7a9ba4c6990be0605883b..3633bedde20acafc064b62a52f319dcdde153111 100644 (file)
@@ -300,10 +300,8 @@ notmuch_database_end_atomic (notmuch_database_t *notmuch);
  * (see notmuch_database_get_path), or else should be an absolute path
  * with initial components that match the path of 'database'.
  *
- * Note: Currently this will create the directory object if it doesn't
- * exist.  In the future, when a directory object does not exist this
- * will return NOTMUCH_STATUS_SUCCESS and set *directory to NULL.
- * Callers should be prepared for this.
+ * If this directory object does not exist in the database, this
+ * returns NOTMUCH_STATUS_SUCCESS and sets *directory to NULL.
  *
  * Return value:
  *
@@ -313,10 +311,6 @@ notmuch_database_end_atomic (notmuch_database_t *notmuch);
  *
  * NOTMUCH_STATUS_XAPIAN_EXCEPTION: A Xapian exception occurred;
  *     directory not retrieved.
- *
- * NOTMUCH_STATUS_READ_ONLY_DATABASE: Database was opened in read-only
- *     mode so the directory cannot be created (this case will be
- *     removed in the future).
  */
 notmuch_status_t
 notmuch_database_get_directory (notmuch_database_t *database,
index a3a8bece2d65432f567bd3a4d79359e7c0a3e437..72dd558d0fa150288eb15a14ea6aee5f6b00f135 100644 (file)
@@ -256,7 +256,7 @@ add_files_recursive (notmuch_database_t *notmuch,
     notmuch_filenames_t *db_subdirs = NULL;
     time_t stat_time;
     struct stat st;
-    notmuch_bool_t is_maildir, new_directory;
+    notmuch_bool_t is_maildir;
     const char **tag;
 
     if (stat (path, &st)) {
@@ -281,33 +281,12 @@ add_files_recursive (notmuch_database_t *notmuch,
     }
     db_mtime = directory ? notmuch_directory_get_mtime (directory) : 0;
 
-    new_directory = db_mtime ? FALSE : TRUE;
-
-    /* XXX This is a temporary workaround.  If we don't update the
-     * database mtime until after processing messages in this
-     * directory, then a 0 mtime is *not* sufficient to indicate that
-     * this directory has no messages or subdirs in the database (for
-     * example, if an earlier run skipped the mtime update because
-     * fs_mtime == stat_time, or was interrupted before updating the
-     * mtime at the end).  To address this, we record a (bogus)
-     * non-zero value before processing any child messages so that a
-     * later run won't mistake this for a new directory (and, for
-     * example, fail to detect removed files and subdirs).
-     *
-     * A better solution would be for notmuch_database_get_directory
-     * to indicate if it really created a new directory or not, either
-     * by a new out-argument, or by recording this information and
-     * providing an accessor.
-     */
-    if (new_directory && directory)
-       notmuch_directory_set_mtime (directory, -1);
-
     /* If the database knows about this directory, then we sort based
      * on strcmp to match the database sorting. Otherwise, we can do
      * inode-based sorting for faster filesystem operation. */
     num_fs_entries = scandir (path, &fs_entries, 0,
-                             new_directory ?
-                             dirent_sort_inode : dirent_sort_strcmp_name);
+                             directory ?
+                             dirent_sort_strcmp_name : dirent_sort_inode);
 
     if (num_fs_entries == -1) {
        fprintf (stderr, "Error opening directory %s: %s\n",
@@ -376,13 +355,12 @@ add_files_recursive (notmuch_database_t *notmuch,
      * being discovered until the clock catches up and the directory
      * is modified again).
      */
-    if (fs_mtime == db_mtime)
+    if (directory && fs_mtime == db_mtime)
        goto DONE;
 
-    /* new_directory means a directory that the database has never
-     * seen before. In that case, we can simply leave db_files and
-     * db_subdirs NULL. */
-    if (!new_directory) {
+    /* If the database has never seen this directory before, we can
+     * simply leave db_files and db_subdirs NULL. */
+    if (directory) {
        db_files = notmuch_directory_get_child_files (directory);
        db_subdirs = notmuch_directory_get_child_directories (directory);
     }
index 6018c2d0ad134e1b1bdad765e62b235e48afa618..3f03a2e3a966018a273bdb75dece7fcc1b224ddd 100755 (executable)
@@ -28,4 +28,12 @@ EOF
 notmuch search --sort=oldest-first --output=messages tag:inbox | sed s/^id:// > EXPECTED
 test_expect_equal_file OUTPUT EXPECTED
 
+test_begin_subtest "get non-existent file"
+test_python <<EOF
+import notmuch
+db = notmuch.Database(mode=notmuch.Database.MODE.READ_ONLY)
+print db.find_message_by_filename("i-dont-exist")
+EOF
+test_expect_equal "$(cat OUTPUT)" "None"
+
 test_done