[RFC/PATCH] link: Add new command

Ali Polatel polatel at gmail.com
Sat Oct 1 01:45:46 PDT 2011


From: Ali Polatel <alip at exherbo.org>

'link' is a new command to create links to specified target maildirs.
This is especially useful for integration with other mail agents.
---
 Makefile.local   |    2 +
 maildir.c        |  262 +++++++++++++++++++++++++++++++++++++++++
 maildir.h        |   53 +++++++++
 notmuch-client.h |    4 +
 notmuch-link.c   |  339 ++++++++++++++++++++++++++++++++++++++++++++++++++++++
 notmuch.c        |   44 +++++++
 6 files changed, 704 insertions(+), 0 deletions(-)
 create mode 100644 maildir.c
 create mode 100644 maildir.h
 create mode 100644 notmuch-link.c

diff --git a/Makefile.local b/Makefile.local
index 38f6c17..a613a4b 100644
--- a/Makefile.local
+++ b/Makefile.local
@@ -278,6 +278,7 @@ notmuch_client_srcs =		\
 	notmuch-config.c	\
 	notmuch-count.c		\
 	notmuch-dump.c		\
+	notmuch-link.c		\
 	notmuch-new.c		\
 	notmuch-reply.c		\
 	notmuch-restore.c	\
@@ -289,6 +290,7 @@ notmuch_client_srcs =		\
 	query-string.c		\
 	show-message.c		\
 	json.c			\
+	maildir.c		\
 	xutil.c
 
 notmuch_client_modules = $(notmuch_client_srcs:.c=.o)
diff --git a/maildir.c b/maildir.c
new file mode 100644
index 0000000..b8c48b3
--- /dev/null
+++ b/maildir.c
@@ -0,0 +1,262 @@
+/* Maildir utilities for the notmuch mail library
+ *
+ * Copyright © 2011 Ali Polatel
+ * Based in part upon mu which is:
+ *   Copyright © 2008-2011 Dirk-Jan C. Binnema <djcb at djcbsoftware.nl>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see http://www.gnu.org/licenses/ .
+ *
+ * Author: Ali Polatel <polatel at gmail.com>
+ */
+
+#include "maildir.h"
+
+static const char *const maildir_subdirs_array[] = {"new", "cur", "tmp"};
+
+/* FIXME: The two functions below, dirent_sort_inode and get_dtype duplicate
+ * code from notmuch-new.c
+ */
+static int
+dirent_sort_inode (const struct dirent **a, const struct dirent **b)
+{
+    return ((*a)->d_ino < (*b)->d_ino) ? -1 : 1;
+}
+
+static unsigned char
+get_dtype(const char *fullpath, struct dirent *entry)
+{
+    struct stat buf;
+
+    if (entry->d_type != DT_UNKNOWN)
+	return entry->d_type;
+
+    if (lstat(fullpath, &buf) == -1) {
+	fprintf (stderr, "Warning: stat failed on `%s': %s\n",
+		 fullpath, strerror(errno));
+	return DT_UNKNOWN;
+    }
+
+    if (S_ISREG (buf.st_mode)) {
+	return DT_REG;
+    } else if (S_ISDIR (buf.st_mode)) {
+	return DT_DIR;
+    } else if (S_ISLNK (buf.st_mode)) {
+	return DT_LNK;
+    }
+
+    return DT_UNKNOWN;
+}
+
+static notmuch_bool_t
+maildir_access (const char *path)
+{
+    struct stat buf;
+
+    if (access (path, R_OK | W_OK | X_OK) == -1) {
+	fprintf (stderr, "Failed to access path `%s': %s\n",
+		 path, strerror(errno));
+	return FALSE;
+    }
+
+    if (lstat(path, &buf) == -1) {
+	fprintf (stderr, "Failed to access path `%s': %s\n",
+		 path, strerror(errno));
+	return FALSE;
+    }
+
+    if (!S_ISDIR(buf.st_mode)) {
+	fprintf (stderr, "Path `%s' is not a directory\n", path);
+	return FALSE;
+    }
+
+    return TRUE;
+}
+
+/* Determine whether the source message is in 'new' or in 'cur';
+ * we ignore messages in 'tmp' for obvious reasons
+ */
+static notmuch_bool_t
+maildir_subdir (const char *src, notmuch_bool_t *in_cur)
+{
+    char *srcpath;
+
+    srcpath = g_path_get_dirname (src);
+
+    if (g_str_has_suffix (srcpath, "new"))
+	*in_cur = FALSE;
+    else if (g_str_has_suffix (srcpath, "cur"))
+	*in_cur = TRUE;
+    else {
+	g_free (srcpath);
+	errno = EINVAL;
+	return FALSE;
+    }
+
+    g_free(srcpath);
+    return TRUE;
+}
+
+static char *
+maildir_transform_path (const char *src, const char *targetpath)
+{
+    char *targetfullpath, *srcfile;
+    notmuch_bool_t in_cur;
+
+    if (!maildir_subdir (src, &in_cur)) {
+	fprintf (stderr, "Invalid maildir subdirectory `%s': %s\n",
+		 src, strerror(errno));
+	return NULL;
+    }
+
+    srcfile = g_path_get_basename (src);
+    targetfullpath = g_strdup_printf ("%s%c%s%c%s",
+				      targetpath,
+				      G_DIR_SEPARATOR,
+				      in_cur ? "cur" : "new",
+				      G_DIR_SEPARATOR,
+				      srcfile);
+    g_free (srcfile);
+
+    return targetfullpath;
+}
+
+notmuch_bool_t
+maildir_check (const char *path, notmuch_bool_t makedir, mode_t mode)
+{
+    int i;
+    char *fullpath = NULL;
+
+    for (i = 0; i != G_N_ELEMENTS (maildir_subdirs_array); i++) {
+	    fullpath = g_build_filename (path, maildir_subdirs_array[i], NULL);
+	    if (!makedir && !maildir_access(fullpath))
+		goto FAIL;
+	    if (makedir && g_mkdir_with_parents (fullpath, (int)mode) != 0) {
+		fprintf (stderr, "Error creating %s: %s\n",
+			 fullpath, strerror(errno));
+		goto FAIL;
+	    }
+	    g_free (fullpath);
+    }
+
+    return TRUE;
+
+  FAIL:
+    g_free (fullpath);
+    return FALSE;
+}
+
+notmuch_bool_t
+maildir_rename (const char *src, const char *targetpath, rename_method_t methr)
+{
+    int ret;
+    char *targetfullpath;
+
+    targetfullpath = maildir_transform_path (src, targetpath);
+    if (!targetfullpath)
+	return FALSE;
+
+    switch (methr) {
+    case RENAME_SYMLINK:
+	ret = symlink (src, targetfullpath);
+	break;
+    case RENAME_HARDLINK:
+	ret = link (src, targetfullpath);
+	break;
+    default:
+	return FALSE;
+    }
+
+    if (ret == -1) {
+	if (errno != EEXIST) {
+	    fprintf (stderr, "Failed to link %s to %s: %s\n",
+		     src, targetfullpath, strerror(errno));
+	}
+	g_free (targetfullpath);
+	return FALSE;
+    }
+
+    g_free (targetfullpath);
+    return TRUE;
+}
+
+int
+maildir_clean_recursive (const char *path, clean_method_t methc)
+{
+    int i, count, ret, num_fs_entries;
+    notmuch_bool_t delete;
+    char *fullpath = NULL;
+    struct dirent *entry = NULL;
+    struct dirent **fs_entries = NULL;
+    struct stat buf;
+
+    if (methc == CLEAN_NONE)
+	return 0;
+
+    num_fs_entries = scandir(path, &fs_entries, NULL, dirent_sort_inode);
+    if (num_fs_entries == -1) {
+	fprintf (stderr, "Error opening directory %s: %s\n",
+		 path, strerror(errno));
+	return -1;
+    }
+
+    count = 0;
+    for (i = 0; i < num_fs_entries; i++) {
+	entry = fs_entries[i];
+
+	if (!entry->d_name ||
+	    strcmp (entry->d_name, ".") == 0 ||
+	    strcmp (entry->d_name, "..") == 0 ||
+	    strcmp (entry->d_name, "tmp") == 0)
+	{
+	    continue;
+	}
+
+	delete = FALSE;
+	fullpath = g_build_filename (path, entry->d_name, NULL);
+	switch (get_dtype(fullpath, entry)) {
+	case DT_REG:
+	    if (methc == CLEAN_ALL)
+		delete = TRUE;
+	    break;
+	case DT_LNK:
+	    if (methc == CLEAN_ALL ||
+		methc == CLEAN_SYMLINK ||
+		(methc == CLEAN_DANGLING &&
+		 (stat(fullpath, &buf) == -1 &&
+		  (errno == ENOENT || errno == ELOOP))))
+		delete = TRUE;
+	    break;
+	case DT_DIR:
+	    ret = maildir_clean_recursive (fullpath, methc);
+	    if (ret != -1)
+		count += ret;
+	    break;
+	default:
+	    break; /* skip the rest */
+	}
+
+	if (delete) {
+	    if (unlink (fullpath) == -1) {
+		fprintf (stderr, "Warning: error unlinking `%s': %s",
+			 fullpath, strerror(errno));
+	    } else {
+		count++;
+	    }
+	}
+
+	g_free (fullpath);
+    }
+
+    return count;
+}
diff --git a/maildir.h b/maildir.h
new file mode 100644
index 0000000..8b50ddf
--- /dev/null
+++ b/maildir.h
@@ -0,0 +1,53 @@
+/* Maildir utilities for the notmuch mail library
+ *
+ * Copyright © 2011 Ali Polatel
+ * Based in part upon mu which is:
+ *   Copyright © 2008-2011 Dirk-Jan C. Binnema <djcb at djcbsoftware.nl>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see http://www.gnu.org/licenses/ .
+ *
+ * Author: Ali Polatel <polatel at gmail.com>
+ */
+
+#ifndef NOTMUCH_MAILDIR_H
+#define NOTMUCH_MAILDIR_H
+
+#include "notmuch-client.h"
+
+typedef enum {
+    RENAME_SYMLINK,
+    RENAME_HARDLINK,
+    /* TODO:
+     * RENAME_COPY,
+     * RENAME_MOVE,
+     */
+} rename_method_t;
+
+typedef enum {
+    CLEAN_DANGLING,
+    CLEAN_SYMLINK,
+    CLEAN_ALL,
+    CLEAN_NONE
+} clean_method_t;
+
+notmuch_bool_t
+maildir_check (const char *path, notmuch_bool_t makedir, mode_t mode);
+
+notmuch_bool_t
+maildir_rename (const char *src, const char *targetpath, rename_method_t methr);
+
+int
+maildir_clean_recursive(const char *path, clean_method_t methc);
+
+#endif
diff --git a/notmuch-client.h b/notmuch-client.h
index b50cb38..979eafd 100644
--- a/notmuch-client.h
+++ b/notmuch-client.h
@@ -36,6 +36,7 @@
  * keep notmuch.c from looking into any internals, (which helps us
  * develop notmuch.h into a plausible library interface).
  */
+#include "maildir.h"
 #include "xutil.h"
 
 #include <stddef.h>
@@ -120,6 +121,9 @@ int
 notmuch_dump_command (void *ctx, int argc, char *argv[]);
 
 int
+notmuch_link_command (void *ctx, int argc, char *argv[]);
+
+int
 notmuch_new_command (void *ctx, int argc, char *argv[]);
 
 int
diff --git a/notmuch-link.c b/notmuch-link.c
new file mode 100644
index 0000000..a147206
--- /dev/null
+++ b/notmuch-link.c
@@ -0,0 +1,339 @@
+/* notmuch - Not much of an email program, (just index and search)
+ *
+ * Copyright © 2011 Ali Polatel
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see http://www.gnu.org/licenses/ .
+ *
+ * Author: Ali Polatel <alip at exherbo.org>
+ */
+
+#include "notmuch-client.h"
+
+typedef struct {
+    const char *maildir;
+
+    notmuch_bool_t cleandir;
+    notmuch_bool_t makedir;
+    notmuch_bool_t entire_thread;
+    mode_t mode;
+
+    clean_method_t clean_method;
+    rename_method_t rename_method;
+} link_options_t;
+
+static inline void
+init_link_options(link_options_t *options)
+{
+    options->maildir = NULL;
+
+    options->makedir = FALSE;
+    options->entire_thread = FALSE;
+    options->mode = 0700;
+
+    options->clean_method = CLEAN_NONE;
+    options->rename_method = RENAME_SYMLINK;
+}
+
+static inline const char *
+rename_method_abbr(rename_method_t method)
+{
+    switch (method) {
+    case RENAME_SYMLINK:
+	return "sym";
+    case RENAME_HARDLINK:
+	return "hard";
+    default:
+	return "love";
+    }
+}
+
+static inline const char *
+rename_method_verb(rename_method_t method)
+{
+    switch (method) {
+    case RENAME_SYMLINK:
+	return "symlinked";
+    case RENAME_HARDLINK:
+	return "hardlinked";
+    default:
+	return "made love all day long!";
+    }
+}
+
+static notmuch_bool_t
+prepare_maildir (const char *maildir,
+		 notmuch_bool_t makedir,
+		 mode_t mode,
+		 clean_method_t methc)
+{
+    int ret;
+
+    if (!maildir_check(maildir, makedir, mode))
+	return FALSE;
+
+    ret = maildir_clean_recursive(maildir, methc);
+    if (ret == -1)
+	return FALSE;
+    else if (ret > 0)
+	printf("Unlinked %d entries under %s\n",
+	       ret, maildir);
+
+    return TRUE;
+}
+
+static int
+link_messages (notmuch_messages_t *messages,
+	       rename_method_t rename_method,
+	       const char *maildir)
+{
+    int ret = 0;
+    const char *path;
+    notmuch_message_t *message;
+    notmuch_filenames_t *filenames;
+
+    for (;
+	 notmuch_messages_valid (messages);
+	 notmuch_messages_move_to_next (messages))
+    {
+	message = notmuch_messages_get (messages);
+
+	filenames = notmuch_message_get_filenames (message);
+	for (;
+	     notmuch_filenames_valid (filenames);
+	     notmuch_filenames_move_to_next (filenames))
+	{
+	    path = notmuch_filenames_get (filenames);
+	    if (maildir_rename (path, maildir, rename_method)) {
+		ret++;
+	    }
+	}
+	notmuch_filenames_destroy( filenames );
+
+	notmuch_message_destroy (message);
+    }
+
+    return ret;
+}
+
+static int
+do_link_threads (notmuch_query_t *query,
+		 const link_options_t *lopts)
+{
+    int message_count, thread_count;
+    notmuch_threads_t *threads;
+    notmuch_thread_t *thread;
+    notmuch_messages_t *messages;
+
+    threads = notmuch_query_search_threads (query);
+    if (threads == NULL)
+	return 1;
+
+    if (!prepare_maildir(lopts->maildir,
+			 lopts->makedir, lopts->mode,
+			 lopts->clean_method))
+	return 1;
+
+    message_count = thread_count = 0;
+    for (;
+	 notmuch_threads_valid (threads);
+	 notmuch_threads_move_to_next (threads))
+    {
+	thread = notmuch_threads_get (threads);
+
+	messages = notmuch_thread_get_toplevel_messages (thread);
+
+	if (messages == NULL)
+	    INTERNAL_ERROR ("Thread %s has no toplevel messages.\n",
+			    notmuch_thread_get_thread_id (thread));
+	else
+	    thread_count++;
+
+	message_count += link_messages (messages,
+					lopts->rename_method,
+					lopts->maildir);
+
+	notmuch_messages_destroy (messages);
+	notmuch_thread_destroy (thread);
+    }
+
+    if (message_count > 0) {
+	printf("%d messages in %d threads %s under %s\n",
+	       message_count, thread_count,
+	       rename_method_verb(lopts->rename_method),
+	       lopts->maildir);
+    }
+
+    notmuch_threads_destroy (threads);
+
+    return 0;
+}
+
+static int
+do_link_messages (notmuch_query_t *query,
+		  const link_options_t *lopts)
+{
+    int message_count;
+    notmuch_messages_t *messages;
+
+    messages = notmuch_query_search_messages (query);
+    if (messages == NULL)
+	return 1;
+
+    if (!prepare_maildir(lopts->maildir,
+			 lopts->makedir,
+			 lopts->mode,
+			 lopts->clean_method))
+	return 1;
+
+    message_count = link_messages (messages,
+				   lopts->rename_method,
+				   lopts->maildir);
+    if (message_count > 0) {
+	printf("%d messages %s under %s\n",
+	       message_count,
+	       rename_method_verb(lopts->rename_method),
+	       lopts->maildir);
+    }
+
+    notmuch_messages_destroy (messages);
+
+    return 0;
+}
+
+int
+notmuch_link_command (void *ctx, int argc, char *argv[])
+{
+    int i, ret;
+    char *query_str;
+    char *opt;
+    notmuch_config_t *config;
+    notmuch_database_t *notmuch;
+    notmuch_query_t *query;
+    link_options_t options;
+
+    init_link_options(&options);
+
+    for (i = 0; i < argc && argv[i][0] == '-'; i++) {
+	if (strcmp (argv[i], "--") == 0) {
+	    i++;
+	    break;
+	}
+	if (STRNCMP_LITERAL (argv[i], "--rename=") == 0) {
+	    opt = argv[i] + sizeof ("--rename=") - 1;
+	    if (strcmp (opt, "symlink") == 0) {
+		options.rename_method = RENAME_SYMLINK;
+	    } else if (strcmp (opt, "hardlink") == 0) {
+		options.rename_method = RENAME_HARDLINK;
+#if 0
+#error TODO
+	    } else if (strcmp (opt, "copy") == 0) {
+		options.rename_method = RENAME_COPY;
+	    } else if (strcmp (opt, "move") == 0) {
+		options.rename_method = RENAME_MOVE;
+#endif
+	    } else {
+		fprintf (stderr, "Invalid value for --rename: %s\n", opt);
+		return 1;
+	    }
+	} else if (STRNCMP_LITERAL (argv[i], "--maildir=") == 0) {
+	    opt = argv[i] + sizeof ("--maildir=") - 1;
+	    options.maildir = talloc_strdup (ctx, opt);
+	} else if (STRNCMP_LITERAL (argv[i], "--clean=") == 0) {
+	    opt = argv[i] + sizeof ("--clean=") - 1;
+	    if (strcmp (opt, "dangling") == 0) {
+		options.clean_method = CLEAN_DANGLING;
+	    } else if (strcmp (opt, "symlink") == 0) {
+		options.clean_method = CLEAN_SYMLINK;
+	    } else if (strcmp (opt, "all") == 0) {
+		options.clean_method = CLEAN_ALL;
+	    } else if (strcmp (opt, "none") == 0) {
+		options.clean_method = CLEAN_NONE;
+	    } else {
+		fprintf (stderr, "Invalid value for --clean: %s\n", opt);
+		return 1;
+	    }
+	} else if (STRNCMP_LITERAL (argv[i], "--mkdir") == 0) {
+	    options.makedir = TRUE;
+
+	    opt = argv[i] + sizeof ("--mkdir") - 1;
+
+	    if (opt[0] == '\0') {
+		continue;
+	    } else if (opt[0] != '=') {
+		fprintf (stderr, "Unrecognized option: %s\n", argv[i]);
+		return 1;
+	    } else {
+		opt++; /* skip '=' */
+	    }
+
+	    options.mode = 0;
+	    while (*opt >= '0' && *opt <= '7')
+		options.mode = options.mode * 8 + (*opt++ - '0');
+	    if (*opt) {
+		fprintf (stderr, "Invalid value for --mkdir: %s\n", opt);
+		return 1;
+	    }
+	} else if (STRNCMP_LITERAL (argv[i], "--entire-thread") == 0) {
+	    options.entire_thread = TRUE;
+	} else {
+	    fprintf (stderr, "Unrecognized option: %s\n", argv[i]);
+	    return 1;
+	}
+    }
+
+    if (!options.maildir) {
+	fprintf (stderr, "Target directory must be specified "
+			 "using --maildir option\n");
+	return 1;
+    }
+
+    argc -= i;
+    argv += i;
+
+    config = notmuch_config_open (ctx, NULL, NULL);
+    if (config == NULL)
+	return 1;
+
+    notmuch = notmuch_database_open (notmuch_config_get_database_path (config),
+				     NOTMUCH_DATABASE_MODE_READ_ONLY);
+    if (notmuch == NULL)
+	return 1;
+
+    query_str = query_string_from_args (notmuch, argc, argv);
+    if (query_str == NULL) {
+	fprintf (stderr, "Out of memory.\n");
+	return 1;
+    }
+    if (*query_str == '\0') {
+	fprintf (stderr, "Error: notmuch link requires at least one search term.\n");
+	return 1;
+    }
+
+    query = notmuch_query_create (notmuch, query_str);
+    if (query == NULL) {
+	fprintf (stderr, "Out of memory\n");
+	return 1;
+    }
+
+    if (options.entire_thread) {
+	ret = do_link_threads (query, &options);
+    } else {
+	ret = do_link_messages (query, &options);
+    }
+
+    notmuch_query_destroy (query);
+    notmuch_database_close (notmuch);
+
+    return ret;
+}
diff --git a/notmuch.c b/notmuch.c
index f9d6629..2a8753c 100644
--- a/notmuch.c
+++ b/notmuch.c
@@ -374,6 +374,50 @@ static command_t commands[] = {
       "\n"
       "\tSee \"notmuch help search-terms\" for details of the search\n"
       "\tterms syntax." },
+    { "link", notmuch_link_command,
+      "--maildir=TARGET [options...] <search-terms> [...]",
+      "Link messages matching the given search terms.",
+      "\tMake links to the maildir specified with the --maildir option.\n"
+      "\tThis command may be used to create so-called \"virtual\" folders\n"
+      "\tfor integration with different mail agents.\n"
+      "\n"
+      "\tSupported options for link include:\n"
+      "\n"
+      "\t--entire-thread\n"
+      "\n"
+      "\t\tBy default only those messages that match the\n"
+      "\t\tsearch terms will be linked. With this option,\n"
+      "\t\tall messages in the same thread as any matched\n"
+      "\t\tmessage will be linked.\n"
+      "\n"
+      "\t--rename=(hardlink|symlink)\n"
+      "\n"
+      "\t\tSpecify the renaming method, either hardlink or\n"
+      "\t\tsymlink (default)\n"
+      "\n"
+      "\t--maildir=TARGET\n"
+      "\n"
+      "\t\tSpecify the target maildir. This option is mandatory.\n"
+      "\n"
+      "\t--mkdir[=OCTAL_MODE]\n"
+      "\n"
+      "\t\tCreate the target maildir specified with the --maildir option\n"
+      "\t\tand any non-existing parent directories.\n"
+      "\t\tAccepts an optional octal mode argument which may be used to\n"
+      "\t\tspecify permissions. Default is 0700.\n"
+      "\n"
+      "\t--clean=(dangling|symlink|all|none)\n"
+      "\n"
+      "\t\tClean the target maildir specified with --maildir option\n"
+      "\t\tbefore proceeding to create links, using the specified method.\n"
+      "\t\tMethod may be one of:\n"
+      "\t\t- dangling: Clean only dangling symbolic links\n"
+      "\t\t- symlink:  Clean all symbolic links\n"
+      "\t\t- all:      Clean regular files in addition to symbolic links\n"
+      "\t\t- none:     Clean no files (default)\n"
+      "\n"
+      "\tSee \"notmuch help search-terms\" for details of the search\n"
+      "\tterms syntax." },
     { "dump", notmuch_dump_command,
       "[<filename>]",
       "Create a plain-text dump of the tags for each message.",
-- 
1.7.6.1



More information about the notmuch mailing list