diff -pruN mutt-1.5.1.orig/OPS mutt-1.5.1/OPS --- mutt-1.5.1.orig/OPS Thu Apr 25 15:26:01 2002 +++ mutt-1.5.1/OPS Thu May 2 19:44:28 2002 @@ -94,6 +94,7 @@ OP_LAST_ENTRY "move to the last entry" OP_LIST_REPLY "reply to specified mailing list" OP_MACRO "execute a macro" OP_MAIL "compose a new mail message" +OP_MAIN_BREAK_THREAD "break the thread in two" OP_MAIN_CHANGE_FOLDER "open a different folder" OP_MAIN_CHANGE_FOLDER_READONLY "open a different folder in read only mode" OP_MAIN_CLEAR_FLAG "clear a status flag from a message" @@ -103,6 +104,7 @@ OP_MAIN_FETCH_MAIL "retrieve mail from P OP_MAIN_FIRST_MESSAGE "move to the first message" OP_MAIN_LAST_MESSAGE "move to the last message" OP_MAIN_LIMIT "show only messages matching a pattern" +OP_MAIN_LINK_THREADS "link tagged message to the current one" OP_MAIN_NEXT_NEW "jump to the next new message" OP_MAIN_NEXT_SUBTHREAD "jump to the next subthread" OP_MAIN_NEXT_THREAD "jump to the next thread" diff -pruN mutt-1.5.1.orig/acconfig.h mutt-1.5.1/acconfig.h --- mutt-1.5.1.orig/acconfig.h Tue Jan 15 23:06:40 2002 +++ mutt-1.5.1/acconfig.h Thu May 2 19:44:28 2002 @@ -55,3 +55,7 @@ * all return values other than (size_t)(-1) as equivalent. */ #undef ICONV_NONTRANS +/* Do you want to use the rethreading functions with IMAP + * (--enable-imap-edit-threads) */ +#undef IMAP_EDIT_THREADS + diff -pruN mutt-1.5.1.orig/configure.in mutt-1.5.1/configure.in --- mutt-1.5.1.orig/configure.in Thu May 2 01:21:49 2002 +++ mutt-1.5.1/configure.in Thu May 2 19:44:28 2002 @@ -527,6 +527,16 @@ AM_CONDITIONAL(USE_GSS, test x$need_gss dnl -- end imap dependencies -- +AC_ARG_ENABLE(imap_edit_threads, [ --enable-imap-edit-threads Enable editing threads support for IMAP], +[ + if test "$enableval" = "yes"; then + if test "$need_imap" = "yes"; then + AC_DEFINE(IMAP_EDIT_THREADS) + else + AC_MSG_WARN([IMAP support for edit_threads is only useful with IMAP support]) + fi +fi]) + AC_ARG_WITH(ssl, [ --with-ssl[=PFX] Compile in SSL support for POP/IMAP], [ if test "$with_ssl" != "no" then diff -pruN mutt-1.5.1.orig/copy.c mutt-1.5.1/copy.c --- mutt-1.5.1.orig/copy.c Sat Apr 20 10:09:54 2002 +++ mutt-1.5.1/copy.c Thu May 2 19:47:14 2002 @@ -99,6 +99,12 @@ mutt_copy_hdr (FILE *in, FILE *out, long (ascii_strncasecmp ("Content-Length:", buf, 15) == 0 || ascii_strncasecmp ("Lines:", buf, 6) == 0)) continue; + if ((flags & CH_UPDATE_REFS) && + ascii_strncasecmp ("References:", buf, 11) == 0) + continue; + if ((flags & CH_UPDATE_IRT) && + ascii_strncasecmp ("In-Reply-To:", buf, 12) == 0) + continue; ignore = 0; } @@ -175,6 +181,12 @@ mutt_copy_hdr (FILE *in, FILE *out, long ascii_strncasecmp ("type:", buf + 8, 5) == 0)) || ascii_strncasecmp ("mime-version:", buf, 13) == 0)) continue; + if ((flags & CH_UPDATE_REFS) && + ascii_strncasecmp ("References:", buf, 11) == 0) + continue; + if ((flags & CH_UPDATE_IRT) && + ascii_strncasecmp ("In-Reply-To:", buf, 12) == 0) + continue; /* Find x -- the array entry where this header is to be saved */ if (flags & CH_REORDER) @@ -285,6 +297,8 @@ mutt_copy_hdr (FILE *in, FILE *out, long CH_XMIT ignore Lines: and Content-Length: CH_WEED do header weeding CH_NOQFROM ignore ">From " line + CH_UPDATE_IRT update the In-Reply-To: header + CH_UPDATE_REFS update the References: header prefix string to use if CH_PREFIX is set @@ -294,6 +308,9 @@ int mutt_copy_header (FILE *in, HEADER *h, FILE *out, int flags, const char *prefix) { char buffer[SHORT_STRING]; + + flags |= (h->irt_changed ? CH_UPDATE_IRT : 0) + | (h->refs_changed ? CH_UPDATE_REFS : 0); if (mutt_copy_hdr (in, out, h->offset, h->content->offset, flags, prefix) == -1) return (-1); @@ -317,7 +334,56 @@ mutt_copy_header (FILE *in, HEADER *h, F if (flags & CH_UPDATE) { if ((flags & CH_NOSTATUS) == 0) +#ifdef IMAP_EDIT_THREADS +#define NEW_ENV new_env +#else +#define NEW_ENV env +#endif { + if (h->irt_changed && h->NEW_ENV->in_reply_to) + { + LIST *listp = h->NEW_ENV->in_reply_to; + + if (fputs ("In-Reply-To: ", out) == EOF) + return (-1); + + for (; listp; listp = listp->next) + if ((fputs (listp->data, out) == EOF) || (fputc (' ', out) == EOF)) + return (-1); + + if (fputc ('\n', out) == EOF) + return (-1); + } + + if (h->refs_changed && h->NEW_ENV->references) + { + LIST *listp = h->NEW_ENV->references, *refs = NULL, *t; + + if (fputs ("References: ", out) == EOF) + return (-1); + + /* Mutt stores references in reverse order, thus we create + * a reordered refs list that we can put in the headers */ + for (; listp; listp = listp->next, refs = t) + { + t = (LIST *)safe_malloc (sizeof (LIST)); + t->data = listp->data; + t->next = refs; + } + + for (; refs; refs = refs->next) + if ((fputs (refs->data, out) == EOF) || (fputc (' ', out) == EOF)) + return (-1); + + /* clearing refs from memory */ + for (t = refs; refs; refs = t->next, t = refs) + safe_free ((void **)&refs); + + if (fputc ('\n', out) == EOF) + return (-1); + } +#undef NEW_ENV + if (h->old || h->read) { if (fputs ("Status: ", out) == EOF) diff -pruN mutt-1.5.1.orig/curs_main.c mutt-1.5.1/curs_main.c --- mutt-1.5.1.orig/curs_main.c Thu Feb 28 09:23:32 2002 +++ mutt-1.5.1/curs_main.c Thu May 2 19:44:28 2002 @@ -888,6 +888,11 @@ int mutt_index_menu (void) else { mutt_set_flag (Context, CURHDR, M_TAG, !CURHDR->tagged); + + Context->last_tag = CURHDR->tagged ? CURHDR : + ((Context->last_tag == CURHDR && !CURHDR->tagged) + ? NULL : Context->last_tag); + menu->redraw = REDRAW_STATUS; if (option (OPTRESOLVE) && menu->current < Context->vcount - 1) { @@ -1108,6 +1113,89 @@ int mutt_index_menu (void) } done = 1; } + break; + + case OP_MAIN_BREAK_THREAD: + + CHECK_MSGCOUNT; + CHECK_VISIBLE; + CHECK_READONLY; + + if ((Sort & SORT_MASK) != SORT_THREADS) + mutt_error _("Threading is not enabled."); + +#if defined (USE_IMAP) && ! defined (IMAP_EDIT_THREADS) + else if (Context->magic == M_IMAP) + mutt_error _("Compile Mutt with --enable-imap-edit-threads for break-thread support"); +#endif + + else + { + { + HEADER *oldcur = CURHDR; + + mutt_break_thread (CURHDR); + mutt_sort_headers (Context, 1); + menu->current = oldcur->virtual; + } + + Context->changed = 1; + mutt_message _("Thread broken"); + + if (menu->menu == MENU_PAGER) + { + op = OP_DISPLAY_MESSAGE; + continue; + } + else + menu->redraw |= REDRAW_INDEX; + } + + break; + + case OP_MAIN_LINK_THREADS: + + CHECK_MSGCOUNT; + CHECK_VISIBLE; + CHECK_READONLY; + + if ((Sort & SORT_MASK) != SORT_THREADS) + mutt_error _("Threading is not enabled."); + +#if defined (USE_IMAP) && ! defined (IMAP_EDIT_THREADS) + else if (Context->magic == M_IMAP) + mutt_error _("Compile Mutt with --enable-imap-edit-threads for link-threads support"); +#endif + + else if (!CURHDR->env->message_id) + mutt_error _("No Message-ID: header available to link thread"); + else if (!tag && (!Context->last_tag || !Context->last_tag->tagged)) + mutt_error _("First, please tag a message to be linked here"); + else + { + HEADER *oldcur = CURHDR; + + if (mutt_link_threads (CURHDR, tag ? NULL : Context->last_tag, + Context)) + { + mutt_sort_headers (Context, 1); + menu->current = oldcur->virtual; + + Context->changed = 1; + mutt_message _("Threads linked"); + } + else + mutt_error _("No thread linked"); + } + + if (menu->menu == MENU_PAGER) + { + op = OP_DISPLAY_MESSAGE; + continue; + } + else + menu->redraw |= REDRAW_STATUS | REDRAW_INDEX; + break; case OP_EDIT_TYPE: diff -pruN mutt-1.5.1.orig/doc/manual.sgml.head mutt-1.5.1/doc/manual.sgml.head --- mutt-1.5.1.orig/doc/manual.sgml.head Thu Apr 25 15:28:03 2002 +++ mutt-1.5.1/doc/manual.sgml.head Thu May 2 19:44:28 2002 @@ -2115,8 +2115,43 @@ used a threaded news client, this is the with large volume mailing lists easier because you can easily delete uninteresting threads and quickly find topics of value. +Editing threads +

+Mutt has the ability to dynamically restructure threads that are broken +either by misconfigured software or bad behaviour from some +correspondents. This allows to clean your mailboxes formats) from these +annoyances which make it hard to follow a discussion. + +If you want to use these functions with IMAP, you need to compile Mutt +with the Linking threads +

+ +Some mailers tend to "forget" to correctly set the "In-Reply-To:" and +"References:" headers when replying to a message. This results in broken +discussions because Mutt has not enough information to guess the correct +threading. +You can fix this by tagging the reply to a mail, then go onto this mail +and use the ``link-threads'' function (bound to & by default). The +reply will then be connected to its "parent" message. + +You can also connect multiple childs at once, tagging them and using the +tag-prefix command (';') or the auto_tag option. + +Breaking threads +

+ +On mailing lists, some people are in the bad habit of starting a new +discussion by hitting "reply" to any message from the list and changing +the subject to a totally unrelated one. +You can fix such threads by using the ``break-thread'' function (bound +by default to #), which will turn the subthread starting from the +current message into a whole different thread. + Delivery Status Notification (DSN) Support

+ RFC1894 defines a set of MIME content types for relaying information about the status of electronic mail messages. These can be thought of as ``return receipts.'' Berkeley sendmail 8.8.x currently has some command diff -pruN mutt-1.5.1.orig/functions.h mutt-1.5.1/functions.h --- mutt-1.5.1.orig/functions.h Thu Jan 24 22:12:15 2002 +++ mutt-1.5.1/functions.h Thu May 2 19:44:28 2002 @@ -66,6 +66,7 @@ struct binding_t OpGeneric[] = { struct binding_t OpMain[] = { { "create-alias", OP_CREATE_ALIAS, "a" }, { "bounce-message", OP_BOUNCE_MESSAGE, "b" }, + { "break-thread", OP_MAIN_BREAK_THREAD, "#" }, { "change-folder", OP_MAIN_CHANGE_FOLDER, "c" }, { "change-folder-readonly", OP_MAIN_CHANGE_FOLDER_READONLY, "\033c" }, { "collapse-thread", OP_MAIN_COLLAPSE_THREAD, "\033v" }, @@ -92,6 +93,7 @@ struct binding_t OpMain[] = { { "next-undeleted", OP_MAIN_NEXT_UNDELETED, "j" }, { "previous-undeleted", OP_MAIN_PREV_UNDELETED, "k" }, { "limit", OP_MAIN_LIMIT, "l" }, + { "link-threads", OP_MAIN_LINK_THREADS, "&" }, { "list-reply", OP_LIST_REPLY, "L" }, { "mail", OP_MAIL, "m" }, { "toggle-new", OP_TOGGLE_NEW, "N" }, @@ -152,6 +154,7 @@ struct binding_t OpMain[] = { }; struct binding_t OpPager[] = { + { "break-thread", OP_MAIN_BREAK_THREAD, "#" }, { "create-alias", OP_CREATE_ALIAS, "a" }, { "bounce-message", OP_BOUNCE_MESSAGE, "b" }, { "change-folder", OP_MAIN_CHANGE_FOLDER, "c" }, @@ -174,6 +177,7 @@ struct binding_t OpPager[] = { { "next-entry", OP_NEXT_ENTRY, "J" }, { "previous-undeleted",OP_MAIN_PREV_UNDELETED, "k" }, { "previous-entry", OP_PREV_ENTRY, "K" }, + { "link-threads", OP_MAIN_LINK_THREADS, "&" }, { "list-reply", OP_LIST_REPLY, "L" }, { "redraw-screen", OP_REDRAW, "\014" }, { "mail", OP_MAIL, "m" }, diff -pruN mutt-1.5.1.orig/imap/imap.c mutt-1.5.1/imap/imap.c --- mutt-1.5.1.orig/imap/imap.c Sun Apr 7 21:21:08 2002 +++ mutt-1.5.1/imap/imap.c Thu May 2 19:44:28 2002 @@ -957,9 +957,11 @@ int imap_sync_mailbox (CONTEXT* ctx, int mutt_buffer_addstr (&cmd, "UID STORE "); mutt_buffer_addstr (&cmd, uid); - /* if attachments have been deleted we delete the message and reupload - * it. This works better if we're expunging, of course. */ - if (ctx->hdrs[n]->attach_del) + /* if the message has been rethreaded or attachments have been deleted + * we delete the message and reupload it. + * This works better if we're expunging, of course. */ + if (ctx->hdrs[n]->refs_changed || ctx->hdrs[n]->irt_changed || + ctx->hdrs[n]->attach_del) { dprint (3, (debugfile, "imap_sync_mailbox: Attachments to be deleted, falling back to _mutt_save_message\n")); if (!appendctx) diff -pruN mutt-1.5.1.orig/main.c mutt-1.5.1/main.c --- mutt-1.5.1.orig/main.c Thu Mar 28 14:21:18 2002 +++ mutt-1.5.1/main.c Thu May 2 19:44:28 2002 @@ -215,6 +215,12 @@ static void show_version (void) "-USE_IMAP " #endif +#ifdef IMAP_EDIT_THREADS + "+IMAP_EDIT_THREADS " +#else + "-IMAP_EDIT_THREADS " +#endif + #ifdef USE_GSS "+USE_GSS " #else diff -pruN mutt-1.5.1.orig/mh.c mutt-1.5.1/mh.c --- mutt-1.5.1.orig/mh.c Thu Mar 28 14:23:11 2002 +++ mutt-1.5.1/mh.c Thu May 2 19:44:28 2002 @@ -1168,7 +1168,7 @@ static int mh_sync_message (CONTEXT * ct { HEADER *h = ctx->hdrs[msgno]; - if (h->attach_del) + if (h->attach_del || h->refs_changed || h->irt_changed) if (mh_rewrite_message (ctx, msgno) != 0) return -1; @@ -1179,9 +1179,9 @@ static int maildir_sync_message (CONTEXT { HEADER *h = ctx->hdrs[msgno]; - if (h->attach_del) + if (h->attach_del || h->refs_changed || h->irt_changed) { - /* when doing attachment deletion, fall back to the MH case. */ + /* when doing attachment deletion/rethreading, fall back to the MH case. */ if (mh_rewrite_message (ctx, msgno) != 0) return (-1); } diff -pruN mutt-1.5.1.orig/mutt.h mutt-1.5.1/mutt.h --- mutt-1.5.1.orig/mutt.h Sat Apr 20 10:09:54 2002 +++ mutt-1.5.1/mutt.h Thu May 2 19:48:40 2002 @@ -82,6 +82,8 @@ #define CH_WEED_DELIVERED (1<<13) /* weed eventual Delivered-To headers */ #define CH_FORCE_FROM (1<<14) /* give CH_FROM precedence over CH_WEED? */ #define CH_NOQFROM (1<<15) /* give CH_FROM precedence over CH_WEED? */ +#define CH_UPDATE_IRT (1<<16) /* update In-Reply-To: */ +#define CH_UPDATE_REFS (1<<17) /* update References: */ /* flags for mutt_enter_string() */ #define M_ALIAS 1 /* do alias "completion" by calling up the alias-menu */ @@ -506,6 +508,7 @@ typedef struct list_t #define mutt_new_list() safe_calloc (1, sizeof (LIST)) void mutt_free_list (LIST **); +LIST *mutt_copy_list (LIST *); int mutt_matches_ignore (const char *, LIST *); /* add an element to a list */ @@ -649,6 +652,8 @@ typedef struct header unsigned int subject_changed : 1; /* used for threading */ unsigned int threaded : 1; /* used for threading */ unsigned int display_subject : 1; /* used for threading */ + unsigned int irt_changed : 1; /* In-Reply-To changed to link/break threads */ + unsigned int refs_changed : 1; /* References changed to break thread */ unsigned int recip_valid : 1; /* is_recipient is valid */ unsigned int active : 1; /* message is not to be removed */ unsigned int trash : 1; /* message is marked as trashed on disk. @@ -689,6 +694,10 @@ typedef struct header char *tree; /* character string to print thread tree */ struct thread *thread; +#ifdef IMAP_EDIT_THREADS + ENVELOPE *new_env; /* envelope information for rethreading */ +#endif + #ifdef MIXMASTER LIST *chain; #endif @@ -752,6 +761,7 @@ typedef struct char *pattern; /* limit pattern string */ pattern_t *limit_pattern; /* compiled limit pattern */ HEADER **hdrs; + HEADER *last_tag; /* last tagged msg. used to link threads */ THREAD *tree; /* top of thread tree */ HASH *id_hash; /* hash table by msg id */ HASH *subj_hash; /* hash table by subject */ diff -pruN mutt-1.5.1.orig/mx.c mutt-1.5.1/mx.c --- mutt-1.5.1.orig/mx.c Thu Mar 28 14:21:18 2002 +++ mutt-1.5.1/mx.c Thu May 2 19:44:28 2002 @@ -1166,6 +1166,8 @@ int mx_sync_mailbox (CONTEXT *ctx, int * ctx->deleted = 0; } } + else if (ctx->last_tag && ctx->last_tag->deleted) + ctx->last_tag = NULL; /* reset last tagged msg now useless */ } /* really only for IMAP - imap_sync_mailbox results in a call to diff -pruN mutt-1.5.1.orig/pager.c mutt-1.5.1/pager.c --- mutt-1.5.1.orig/pager.c Thu Jan 24 22:12:18 2002 +++ mutt-1.5.1/pager.c Thu May 2 19:44:28 2002 @@ -2439,6 +2439,11 @@ mutt_pager (const char *banner, const ch case OP_TAG: CHECK_MODE(IsHeader (extra)); mutt_set_flag (Context, extra->hdr, M_TAG, !extra->hdr->tagged); + + Context->last_tag = extra->hdr->tagged ? extra->hdr : + ((Context->last_tag == extra->hdr && !extra->hdr->tagged) + ? NULL : Context->last_tag); + redraw = REDRAW_STATUS | REDRAW_INDEX; if (option (OPTRESOLVE)) { diff -pruN mutt-1.5.1.orig/protos.h mutt-1.5.1/protos.h --- mutt-1.5.1.orig/protos.h Mon Apr 29 19:09:16 2002 +++ mutt-1.5.1/protos.h Thu May 2 19:49:25 2002 @@ -148,6 +148,7 @@ void mutt_block_signals (void); void mutt_block_signals_system (void); void mutt_body_handler (BODY *, STATE *); void mutt_bounce_message (FILE *fp, HEADER *, ADDRESS *); +void mutt_break_thread (HEADER *); void mutt_buffy (char *, size_t); void mutt_canonical_charset (char *, size_t, const char *); void mutt_check_rescore (CONTEXT *); @@ -284,6 +285,7 @@ int mutt_is_list_recipient (int, ADDRESS int mutt_is_subscribed_list (ADDRESS *); int mutt_is_text_part (BODY *); int mutt_is_valid_mailbox (const char *); +int mutt_link_threads (HEADER *, HEADER *, CONTEXT *); int mutt_lookup_mime_type (BODY *, const char *); int mutt_messages_in_thread (CONTEXT *, HEADER *, int); int mutt_multi_choice (char *prompt, char *letters); diff -pruN mutt-1.5.1.orig/thread.c mutt-1.5.1/thread.c --- mutt-1.5.1.orig/thread.c Thu Feb 28 09:23:29 2002 +++ mutt-1.5.1/thread.c Thu May 2 19:44:28 2002 @@ -1334,3 +1334,105 @@ HASH *mutt_make_subj_hash (CONTEXT *ctx) return hash; } + +static void clean_references (THREAD *brk, THREAD *cur) +{ + THREAD *p; + LIST *ref = NULL; + int done = 0; + + for (; cur; cur = cur->next, done = 0) + { + /* parse subthread recursively */ + clean_references (brk, cur->child); + + if (!cur->message) + break; /* skip pseudo-message */ + + /* Looking for the first bad reference according to the new threading. + * Optimal since Mutt stores the references in reverse order, and the + * first loop should match immediatly for mails respecting RFC2822. */ + for (p = brk; !done && p; p = p->parent) + for (ref = cur->message->env->references; p->message && ref; ref = ref->next) + if (!mutt_strcasecmp (ref->data, p->message->env->message_id)) + { + done = 1; + break; + } + + if (done) + { + HEADER *h = cur->message; + + /* clearing the References: header from obsolete Message-Id(s) */ + mutt_free_list (&ref->next); + +#ifdef IMAP_EDIT_THREADS + if (h->new_env) + mutt_free_list (&h->new_env->references); + else + h->new_env = mutt_new_envelope (); + + h->new_env->references = mutt_copy_list (h->env->references); +#endif + + h->refs_changed = h->changed = 1; + } + } +} + +void mutt_break_thread (HEADER *hdr) +{ + mutt_free_list (&hdr->env->in_reply_to); + mutt_free_list (&hdr->env->references); + hdr->irt_changed = hdr->refs_changed = hdr->changed = 1; + +#ifdef IMAP_EDIT_THREADS + if (hdr->new_env) + { + mutt_free_list (&hdr->new_env->in_reply_to); + mutt_free_list (&hdr->new_env->references); + } + else + hdr->new_env = mutt_new_envelope (); +#endif + + clean_references (hdr->thread, hdr->thread->child); +} + +static int link_threads (HEADER *parent, HEADER *child, CONTEXT *ctx) +{ + if (child == parent) + return 0; + + mutt_break_thread (child); + + child->env->in_reply_to = mutt_new_list (); + child->env->in_reply_to->data = safe_strdup (parent->env->message_id); + +#ifdef IMAP_EDIT_THREADS + child->new_env->in_reply_to = mutt_new_list (); + child->new_env->in_reply_to->data = safe_strdup (parent->env->message_id); +#endif + + mutt_set_flag (ctx, child, M_TAG, 0); + + child->irt_changed = child->changed = 1; + return 1; +} + +int mutt_link_threads (HEADER *cur, HEADER *last, CONTEXT *ctx) +{ + int i, changed = 0; + + if (!last) + { + for (i = 0; i < ctx->vcount; i++) + if (ctx->hdrs[Context->v2r[i]]->tagged) + changed |= link_threads (cur, ctx->hdrs[Context->v2r[i]], ctx); + } + else + changed = link_threads (cur, last, ctx); + + return changed; +} diff -pruN mutt-1.5.1.orig/PATCHES mutt-1.5.1/PATCHES --- mutt-1.5.1.orig/PATCHES Sun Feb 3 10:58:30 2002 +++ mutt-1.5.1/PATCHES Thu May 2 19:44:28 2002 @@ -1,0 +1 @@ +patch-1.5.1.cd.edit_threads.9.2