2 This is the edit threads patch by Cedric Duval <cedricduval+web@free.fr>.
4 The home page for this patch is:
6 http://cedricduval.free.fr/mutt/patches/#threads
8 * Patch last synced with upstream:
10 - File: http://cedricduval.free.fr/mutt/patches/download/patch-1.5.5.1.cd.edit_threads.9.5
13 - modified mutt.h part to apply cleanly.
14 - removed diffs for the following files: acconfig.h, configure,
15 configure.in, config.h.in.
18 diff -pruN mutt-1.5.5.1-orig/OPS mutt-1.5.5.1/OPS
19 --- mutt-1.5.5.1-orig/OPS Wed Nov 5 10:41:31 2003
20 +++ mutt-1.5.5.1/OPS Tue Nov 11 02:52:33 2003
21 @@ -96,6 +96,7 @@ OP_LAST_ENTRY "move to the last entry"
22 OP_LIST_REPLY "reply to specified mailing list"
23 OP_MACRO "execute a macro"
24 OP_MAIL "compose a new mail message"
25 +OP_MAIN_BREAK_THREAD "break the thread in two"
26 OP_MAIN_CHANGE_FOLDER "open a different folder"
27 OP_MAIN_CHANGE_FOLDER_READONLY "open a different folder in read only mode"
28 OP_MAIN_CLEAR_FLAG "clear a status flag from a message"
29 @@ -105,6 +106,7 @@ OP_MAIN_FETCH_MAIL "retrieve mail from P
30 OP_MAIN_FIRST_MESSAGE "move to the first message"
31 OP_MAIN_LAST_MESSAGE "move to the last message"
32 OP_MAIN_LIMIT "show only messages matching a pattern"
33 +OP_MAIN_LINK_THREADS "link tagged message to the current one"
34 OP_MAIN_NEXT_NEW "jump to the next new message"
35 OP_MAIN_NEXT_NEW_THEN_UNREAD "jump to the next new or unread message"
36 OP_MAIN_NEXT_SUBTHREAD "jump to the next subthread"
37 diff -pruN mutt-1.5.5.1-orig/copy.c mutt-1.5.5.1/copy.c
38 --- mutt-1.5.5.1-orig/copy.c Wed Nov 5 10:41:31 2003
39 +++ mutt-1.5.5.1/copy.c Tue Nov 11 02:52:33 2003
40 @@ -95,6 +95,12 @@ mutt_copy_hdr (FILE *in, FILE *out, long
41 (ascii_strncasecmp ("Content-Length:", buf, 15) == 0 ||
42 ascii_strncasecmp ("Lines:", buf, 6) == 0))
44 + if ((flags & CH_UPDATE_REFS) &&
45 + ascii_strncasecmp ("References:", buf, 11) == 0)
47 + if ((flags & CH_UPDATE_IRT) &&
48 + ascii_strncasecmp ("In-Reply-To:", buf, 12) == 0)
53 @@ -193,6 +199,12 @@ mutt_copy_hdr (FILE *in, FILE *out, long
54 ascii_strncasecmp ("type:", buf + 8, 5) == 0)) ||
55 ascii_strncasecmp ("mime-version:", buf, 13) == 0))
57 + if ((flags & CH_UPDATE_REFS) &&
58 + ascii_strncasecmp ("References:", buf, 11) == 0)
60 + if ((flags & CH_UPDATE_IRT) &&
61 + ascii_strncasecmp ("In-Reply-To:", buf, 12) == 0)
64 /* Find x -- the array entry where this header is to be saved */
65 if (flags & CH_REORDER)
66 @@ -326,6 +338,8 @@ mutt_copy_hdr (FILE *in, FILE *out, long
67 CH_XMIT ignore Lines: and Content-Length:
68 CH_WEED do header weeding
69 CH_NOQFROM ignore ">From " line
70 + CH_UPDATE_IRT update the In-Reply-To: header
71 + CH_UPDATE_REFS update the References: header
74 string to use if CH_PREFIX is set
75 @@ -335,6 +349,9 @@ int
76 mutt_copy_header (FILE *in, HEADER *h, FILE *out, int flags, const char *prefix)
78 char buffer[SHORT_STRING];
80 + flags |= (h->irt_changed ? CH_UPDATE_IRT : 0)
81 + | (h->refs_changed ? CH_UPDATE_REFS : 0);
83 if (mutt_copy_hdr (in, out, h->offset, h->content->offset, flags, prefix) == -1)
85 @@ -358,7 +375,56 @@ mutt_copy_header (FILE *in, HEADER *h, F
86 if (flags & CH_UPDATE)
88 if ((flags & CH_NOSTATUS) == 0)
89 +#ifdef IMAP_EDIT_THREADS
90 +#define NEW_ENV new_env
95 + if (h->irt_changed && h->NEW_ENV->in_reply_to)
97 + LIST *listp = h->NEW_ENV->in_reply_to;
99 + if (fputs ("In-Reply-To: ", out) == EOF)
102 + for (; listp; listp = listp->next)
103 + if ((fputs (listp->data, out) == EOF) || (fputc (' ', out) == EOF))
106 + if (fputc ('\n', out) == EOF)
110 + if (h->refs_changed && h->NEW_ENV->references)
112 + LIST *listp = h->NEW_ENV->references, *refs = NULL, *t;
114 + if (fputs ("References: ", out) == EOF)
117 + /* Mutt stores references in reverse order, thus we create
118 + * a reordered refs list that we can put in the headers */
119 + for (; listp; listp = listp->next, refs = t)
121 + t = (LIST *)safe_malloc (sizeof (LIST));
122 + t->data = listp->data;
126 + for (; refs; refs = refs->next)
127 + if ((fputs (refs->data, out) == EOF) || (fputc (' ', out) == EOF))
130 + /* clearing refs from memory */
131 + for (t = refs; refs; refs = t->next, t = refs)
132 + safe_free ((void **)&refs);
134 + if (fputc ('\n', out) == EOF)
139 if (h->old || h->read)
141 if (fputs ("Status: ", out) == EOF)
142 diff -pruN mutt-1.5.5.1-orig/curs_main.c mutt-1.5.5.1/curs_main.c
143 --- mutt-1.5.5.1-orig/curs_main.c Wed Nov 5 10:41:31 2003
144 +++ mutt-1.5.5.1/curs_main.c Tue Nov 11 02:52:33 2003
145 @@ -930,6 +930,11 @@ CHECK_IMAP_ACL(IMAP_ACL_DELETE);
148 mutt_set_flag (Context, CURHDR, M_TAG, !CURHDR->tagged);
150 + Context->last_tag = CURHDR->tagged ? CURHDR :
151 + ((Context->last_tag == CURHDR && !CURHDR->tagged)
152 + ? NULL : Context->last_tag);
154 menu->redraw = REDRAW_STATUS;
155 if (option (OPTRESOLVE) && menu->current < Context->vcount - 1)
157 @@ -1162,6 +1167,89 @@ CHECK_IMAP_ACL(IMAP_ACL_DELETE);
163 + case OP_MAIN_BREAK_THREAD:
169 + if ((Sort & SORT_MASK) != SORT_THREADS)
170 + mutt_error _("Threading is not enabled.");
172 +#if defined (USE_IMAP) && ! defined (IMAP_EDIT_THREADS)
173 + else if (Context->magic == M_IMAP)
174 + mutt_error _("Compile Mutt with --enable-imap-edit-threads for break-thread support");
180 + HEADER *oldcur = CURHDR;
182 + mutt_break_thread (CURHDR);
183 + mutt_sort_headers (Context, 1);
184 + menu->current = oldcur->virtual;
187 + Context->changed = 1;
188 + mutt_message _("Thread broken");
190 + if (menu->menu == MENU_PAGER)
192 + op = OP_DISPLAY_MESSAGE;
196 + menu->redraw |= REDRAW_INDEX;
201 + case OP_MAIN_LINK_THREADS:
207 + if ((Sort & SORT_MASK) != SORT_THREADS)
208 + mutt_error _("Threading is not enabled.");
210 +#if defined (USE_IMAP) && ! defined (IMAP_EDIT_THREADS)
211 + else if (Context->magic == M_IMAP)
212 + mutt_error _("Compile Mutt with --enable-imap-edit-threads for link-threads support");
215 + else if (!CURHDR->env->message_id)
216 + mutt_error _("No Message-ID: header available to link thread");
217 + else if (!tag && (!Context->last_tag || !Context->last_tag->tagged))
218 + mutt_error _("First, please tag a message to be linked here");
221 + HEADER *oldcur = CURHDR;
223 + if (mutt_link_threads (CURHDR, tag ? NULL : Context->last_tag,
226 + mutt_sort_headers (Context, 1);
227 + menu->current = oldcur->virtual;
229 + Context->changed = 1;
230 + mutt_message _("Threads linked");
233 + mutt_error _("No thread linked");
236 + if (menu->menu == MENU_PAGER)
238 + op = OP_DISPLAY_MESSAGE;
242 + menu->redraw |= REDRAW_STATUS | REDRAW_INDEX;
247 diff -pruN mutt-1.5.5.1-orig/doc/manual.sgml.head mutt-1.5.5.1/doc/manual.sgml.head
248 --- mutt-1.5.5.1-orig/doc/manual.sgml.head Wed Nov 5 10:41:34 2003
249 +++ mutt-1.5.5.1/doc/manual.sgml.head Tue Nov 11 02:52:33 2003
250 @@ -2170,8 +2170,43 @@ used a threaded news client, this is the
251 with large volume mailing lists easier because you can easily delete
252 uninteresting threads and quickly find topics of value.
254 +<sect1>Editing threads
256 +Mutt has the ability to dynamically restructure threads that are broken
257 +either by misconfigured software or bad behaviour from some
258 +correspondents. This allows to clean your mailboxes formats) from these
259 +annoyances which make it hard to follow a discussion.
261 +If you want to use these functions with IMAP, you need to compile Mutt
262 +with the <em/--enable-imap-edit-threads/ configure flag.
264 +<sect2>Linking threads
267 +Some mailers tend to "forget" to correctly set the "In-Reply-To:" and
268 +"References:" headers when replying to a message. This results in broken
269 +discussions because Mutt has not enough information to guess the correct
271 +You can fix this by tagging the reply, then moving to the parent message
272 +and using the ``link-threads'' function (bound to & by default). The
273 +reply will then be connected to this "parent" message.
275 +You can also connect multiple childs at once, tagging them and using the
276 +tag-prefix command (';') or the auto_tag option.
278 +<sect2>Breaking threads
281 +On mailing lists, some people are in the bad habit of starting a new
282 +discussion by hitting "reply" to any message from the list and changing
283 +the subject to a totally unrelated one.
284 +You can fix such threads by using the ``break-thread'' function (bound
285 +by default to #), which will turn the subthread starting from the
286 +current message into a whole different thread.
288 <sect1>Delivery Status Notification (DSN) Support
291 RFC1894 defines a set of MIME content types for relaying information
292 about the status of electronic mail messages. These can be thought of as
293 ``return receipts.'' Berkeley sendmail 8.8.x currently has some command
294 diff -pruN mutt-1.5.5.1-orig/functions.h mutt-1.5.5.1/functions.h
295 --- mutt-1.5.5.1-orig/functions.h Wed Nov 5 10:41:31 2003
296 +++ mutt-1.5.5.1/functions.h Tue Nov 11 02:52:33 2003
297 @@ -69,6 +69,7 @@ struct binding_t OpGeneric[] = {
298 struct binding_t OpMain[] = {
299 { "create-alias", OP_CREATE_ALIAS, "a" },
300 { "bounce-message", OP_BOUNCE_MESSAGE, "b" },
301 + { "break-thread", OP_MAIN_BREAK_THREAD, "#" },
302 { "change-folder", OP_MAIN_CHANGE_FOLDER, "c" },
303 { "change-folder-readonly", OP_MAIN_CHANGE_FOLDER_READONLY, "\033c" },
304 { "collapse-thread", OP_MAIN_COLLAPSE_THREAD, "\033v" },
305 @@ -95,6 +96,7 @@ struct binding_t OpMain[] = {
306 { "next-undeleted", OP_MAIN_NEXT_UNDELETED, "j" },
307 { "previous-undeleted", OP_MAIN_PREV_UNDELETED, "k" },
308 { "limit", OP_MAIN_LIMIT, "l" },
309 + { "link-threads", OP_MAIN_LINK_THREADS, "&" },
310 { "list-reply", OP_LIST_REPLY, "L" },
311 { "mail", OP_MAIL, "m" },
312 { "toggle-new", OP_TOGGLE_NEW, "N" },
313 @@ -153,6 +155,7 @@ struct binding_t OpMain[] = {
316 struct binding_t OpPager[] = {
317 + { "break-thread", OP_MAIN_BREAK_THREAD, "#" },
318 { "create-alias", OP_CREATE_ALIAS, "a" },
319 { "bounce-message", OP_BOUNCE_MESSAGE, "b" },
320 { "change-folder", OP_MAIN_CHANGE_FOLDER, "c" },
321 @@ -175,6 +178,7 @@ struct binding_t OpPager[] = {
322 { "next-entry", OP_NEXT_ENTRY, "J" },
323 { "previous-undeleted",OP_MAIN_PREV_UNDELETED, "k" },
324 { "previous-entry", OP_PREV_ENTRY, "K" },
325 + { "link-threads", OP_MAIN_LINK_THREADS, "&" },
326 { "list-reply", OP_LIST_REPLY, "L" },
327 { "redraw-screen", OP_REDRAW, "\014" },
328 { "mail", OP_MAIL, "m" },
329 diff -pruN mutt-1.5.5.1-orig/imap/imap.c mutt-1.5.5.1/imap/imap.c
330 --- mutt-1.5.5.1-orig/imap/imap.c Wed Nov 5 10:41:36 2003
331 +++ mutt-1.5.5.1/imap/imap.c Tue Nov 11 02:52:33 2003
332 @@ -980,9 +980,11 @@ int imap_sync_mailbox (CONTEXT* ctx, int
333 mutt_buffer_addstr (&cmd, "UID STORE ");
334 mutt_buffer_addstr (&cmd, uid);
336 - /* if attachments have been deleted we delete the message and reupload
337 - * it. This works better if we're expunging, of course. */
338 - if (ctx->hdrs[n]->attach_del)
339 + /* if the message has been rethreaded or attachments have been deleted
340 + * we delete the message and reupload it.
341 + * This works better if we're expunging, of course. */
342 + if (ctx->hdrs[n]->refs_changed || ctx->hdrs[n]->irt_changed ||
343 + ctx->hdrs[n]->attach_del)
345 dprint (3, (debugfile, "imap_sync_mailbox: Attachments to be deleted, falling back to _mutt_save_message\n"));
347 diff -pruN mutt-1.5.5.1-orig/main.c mutt-1.5.5.1/main.c
348 --- mutt-1.5.5.1-orig/main.c Tue Mar 4 08:49:48 2003
349 +++ mutt-1.5.5.1/main.c Tue Nov 11 02:52:33 2003
350 @@ -228,6 +228,12 @@ static void show_version (void)
354 +#ifdef IMAP_EDIT_THREADS
355 + "+IMAP_EDIT_THREADS "
357 + "-IMAP_EDIT_THREADS "
363 diff -pruN mutt-1.5.5.1-orig/mh.c mutt-1.5.5.1/mh.c
364 --- mutt-1.5.5.1-orig/mh.c Wed Nov 5 10:41:32 2003
365 +++ mutt-1.5.5.1/mh.c Tue Nov 11 02:52:33 2003
366 @@ -1220,7 +1220,7 @@ static int mh_sync_message (CONTEXT * ct
368 HEADER *h = ctx->hdrs[msgno];
371 + if (h->attach_del || h->refs_changed || h->irt_changed)
372 if (mh_rewrite_message (ctx, msgno) != 0)
375 @@ -1231,9 +1231,9 @@ static int maildir_sync_message (CONTEXT
377 HEADER *h = ctx->hdrs[msgno];
380 + if (h->attach_del || h->refs_changed || h->irt_changed)
382 - /* when doing attachment deletion, fall back to the MH case. */
383 + /* when doing attachment deletion/rethreading, fall back to the MH case. */
384 if (mh_rewrite_message (ctx, msgno) != 0)
387 diff -pruN mutt-1.5.5.1-orig/mutt.h mutt-1.5.5.1/mutt.h
388 --- mutt-1.5.5.1-orig/mutt.h Wed Nov 5 10:41:32 2003
389 +++ mutt-1.5.5.1/mutt.h Tue Nov 11 02:52:33 2003
391 #define CH_WEED_DELIVERED (1<<13) /* weed eventual Delivered-To headers */
392 #define CH_FORCE_FROM (1<<14) /* give CH_FROM precedence over CH_WEED? */
393 #define CH_NOQFROM (1<<15) /* give CH_FROM precedence over CH_WEED? */
394 +#define CH_UPDATE_IRT (1<<16) /* update In-Reply-To: */
395 +#define CH_UPDATE_REFS (1<<17) /* update References: */
397 /* flags for mutt_enter_string() */
398 #define M_ALIAS 1 /* do alias "completion" by calling up the alias-menu */
399 @@ -506,8 +508,9 @@ typedef struct list_t
401 #define mutt_new_list() safe_calloc (1, sizeof (LIST))
402 void mutt_free_list (LIST **);
403 void mutt_free_rx_list (RX_LIST **);
404 void mutt_free_spam_list (SPAM_LIST **);
405 +LIST *mutt_copy_list (LIST *);
406 int mutt_matches_ignore (const char *, LIST *);
408 /* add an element to a list */
409 @@ -647,6 +650,8 @@ typedef struct header
410 unsigned int subject_changed : 1; /* used for threading */
411 unsigned int threaded : 1; /* used for threading */
412 unsigned int display_subject : 1; /* used for threading */
413 + unsigned int irt_changed : 1; /* In-Reply-To changed to link/break threads */
414 + unsigned int refs_changed : 1; /* References changed to break thread */
415 unsigned int recip_valid : 1; /* is_recipient is valid */
416 unsigned int active : 1; /* message is not to be removed */
417 unsigned int trash : 1; /* message is marked as trashed on disk.
418 @@ -687,6 +692,10 @@ typedef struct header
419 char *tree; /* character string to print thread tree */
420 struct thread *thread;
422 +#ifdef IMAP_EDIT_THREADS
423 + ENVELOPE *new_env; /* envelope information for rethreading */
429 @@ -752,6 +761,7 @@ typedef struct
430 char *pattern; /* limit pattern string */
431 pattern_t *limit_pattern; /* compiled limit pattern */
433 + HEADER *last_tag; /* last tagged msg. used to link threads */
434 THREAD *tree; /* top of thread tree */
435 HASH *id_hash; /* hash table by msg id */
436 HASH *subj_hash; /* hash table by subject */
437 diff -pruN mutt-1.5.5.1-orig/mx.c mutt-1.5.5.1/mx.c
438 --- mutt-1.5.5.1-orig/mx.c Wed Nov 5 10:41:32 2003
439 +++ mutt-1.5.5.1/mx.c Tue Nov 11 02:52:33 2003
440 @@ -1161,6 +1161,8 @@ int mx_sync_mailbox (CONTEXT *ctx, int *
444 + else if (ctx->last_tag && ctx->last_tag->deleted)
445 + ctx->last_tag = NULL; /* reset last tagged msg now useless */
448 /* really only for IMAP - imap_sync_mailbox results in a call to
449 diff -pruN mutt-1.5.5.1-orig/pager.c mutt-1.5.5.1/pager.c
450 --- mutt-1.5.5.1-orig/pager.c Wed Nov 5 10:41:32 2003
451 +++ mutt-1.5.5.1/pager.c Tue Nov 11 02:52:33 2003
452 @@ -2481,6 +2481,11 @@ CHECK_IMAP_ACL(IMAP_ACL_WRITE);
454 CHECK_MODE(IsHeader (extra));
455 mutt_set_flag (Context, extra->hdr, M_TAG, !extra->hdr->tagged);
457 + Context->last_tag = extra->hdr->tagged ? extra->hdr :
458 + ((Context->last_tag == extra->hdr && !extra->hdr->tagged)
459 + ? NULL : Context->last_tag);
461 redraw = REDRAW_STATUS | REDRAW_INDEX;
462 if (option (OPTRESOLVE))
464 diff -pruN mutt-1.5.5.1-orig/protos.h mutt-1.5.5.1/protos.h
465 --- mutt-1.5.5.1-orig/protos.h Wed Nov 5 10:41:33 2003
466 +++ mutt-1.5.5.1/protos.h Tue Nov 11 02:52:33 2003
467 @@ -146,6 +146,7 @@ void mutt_block_signals (void);
468 void mutt_block_signals_system (void);
469 void mutt_body_handler (BODY *, STATE *);
470 int mutt_bounce_message (FILE *fp, HEADER *, ADDRESS *);
471 +void mutt_break_thread (HEADER *);
472 void mutt_buffy (char *, size_t);
473 int mutt_buffy_list (void);
474 void mutt_canonical_charset (char *, size_t, const char *);
475 @@ -286,6 +287,7 @@ int mutt_is_list_recipient (int, ADDRESS
476 int mutt_is_subscribed_list (ADDRESS *);
477 int mutt_is_text_part (BODY *);
478 int mutt_is_valid_mailbox (const char *);
479 +int mutt_link_threads (HEADER *, HEADER *, CONTEXT *);
480 int mutt_lookup_mime_type (BODY *, const char *);
481 int mutt_messages_in_thread (CONTEXT *, HEADER *, int);
482 int mutt_multi_choice (char *prompt, char *letters);
483 diff -pruN mutt-1.5.5.1-orig/thread.c mutt-1.5.5.1/thread.c
484 --- mutt-1.5.5.1-orig/thread.c Wed Nov 5 10:41:34 2003
485 +++ mutt-1.5.5.1/thread.c Tue Nov 11 02:52:33 2003
486 @@ -1333,3 +1333,105 @@ HASH *mutt_make_subj_hash (CONTEXT *ctx)
491 +static void clean_references (THREAD *brk, THREAD *cur)
497 + for (; cur; cur = cur->next, done = 0)
499 + /* parse subthread recursively */
500 + clean_references (brk, cur->child);
503 + break; /* skip pseudo-message */
505 + /* Looking for the first bad reference according to the new threading.
506 + * Optimal since Mutt stores the references in reverse order, and the
507 + * first loop should match immediatly for mails respecting RFC2822. */
508 + for (p = brk; !done && p; p = p->parent)
509 + for (ref = cur->message->env->references; p->message && ref; ref = ref->next)
510 + if (!mutt_strcasecmp (ref->data, p->message->env->message_id))
518 + HEADER *h = cur->message;
520 + /* clearing the References: header from obsolete Message-Id(s) */
521 + mutt_free_list (&ref->next);
523 +#ifdef IMAP_EDIT_THREADS
525 + mutt_free_list (&h->new_env->references);
527 + h->new_env = mutt_new_envelope ();
529 + h->new_env->references = mutt_copy_list (h->env->references);
532 + h->refs_changed = h->changed = 1;
537 +void mutt_break_thread (HEADER *hdr)
539 + mutt_free_list (&hdr->env->in_reply_to);
540 + mutt_free_list (&hdr->env->references);
541 + hdr->irt_changed = hdr->refs_changed = hdr->changed = 1;
543 +#ifdef IMAP_EDIT_THREADS
546 + mutt_free_list (&hdr->new_env->in_reply_to);
547 + mutt_free_list (&hdr->new_env->references);
550 + hdr->new_env = mutt_new_envelope ();
553 + clean_references (hdr->thread, hdr->thread->child);
556 +static int link_threads (HEADER *parent, HEADER *child, CONTEXT *ctx)
558 + if (child == parent)
561 + mutt_break_thread (child);
563 + child->env->in_reply_to = mutt_new_list ();
564 + child->env->in_reply_to->data = safe_strdup (parent->env->message_id);
566 +#ifdef IMAP_EDIT_THREADS
567 + child->new_env->in_reply_to = mutt_new_list ();
568 + child->new_env->in_reply_to->data = safe_strdup (parent->env->message_id);
571 + mutt_set_flag (ctx, child, M_TAG, 0);
573 + child->irt_changed = child->changed = 1;
577 +int mutt_link_threads (HEADER *cur, HEADER *last, CONTEXT *ctx)
579 + int i, changed = 0;
583 + for (i = 0; i < ctx->vcount; i++)
584 + if (ctx->hdrs[Context->v2r[i]]->tagged)
585 + changed |= link_threads (cur, ctx->hdrs[Context->v2r[i]], ctx);
588 + changed = link_threads (cur, last, ctx);
592 diff -pruN mutt-1.5.5.1-orig/PATCHES mutt-1.5.5.1/PATCHES
593 --- mutt-1.5.5.1-orig/PATCHES Tue Apr 15 15:18:34 2003
594 +++ mutt-1.5.5.1/PATCHES Tue Nov 11 02:52:33 2003
596 +patch-1.5.5.1.cd.edit_threads.9.5