Commits

Anonymous committed 08c783c

against xemacs-devel: Patcher 3.0 is released.

Comments (0)

Files changed (2)

+2002-02-25  Didier Verna  <didier@xemacs.org>
+
+	* Patcher 3.0 is released.
+
 2002-02-22  Steve Youngs  <youngs@xemacs.org>
 
 	* package-info.in: Add patch-keywords.
 	(patch-keywords-message-beginning-of-line): Use
 	`patch-keywords-in-header-p' in older gnus-versions < 5.090004.
 	(patch-keywords-insert): Supply version	argument to
-	`gnus-continuum-version'.  It wasn't optional in gnus-versions 
-	< 5.090004. 
+	`gnus-continuum-version'.  It wasn't optional in gnus-versions
+	< 5.090004.
 
 2002-01-14  Steve Youngs  <youngs@xemacs.org>
 
 	of concat to build path.
 	* bench.el (bench-small-lisp-file): Ditto.
 
-2001-09-04  Jeff Mincy  <jeff@delphioutpost.com>  
+2001-09-04  Jeff Mincy  <jeff@delphioutpost.com>
 
 	* hide-copyleft.el (hide-copyleft-region): Make it interactive.
 
 ;;; patcher.el --- Utility for mailing patch information
 
-;; Copyright (C) 1999-2001 Didier Verna.
+;; Copyright (C) 1999-2002 Didier Verna.
 
 ;; Author:        Didier Verna <didier@xemacs.org>
 ;; Maintainer:    Didier Verna <didier@xemacs.org>
 ;; Created:       Tue Sep 28 18:12:43 1999
-;; Last Revision: Wed Apr 25 14:57:40 2001
+;; Last Revision: Tue Jan 22 15:14:34 2002
 ;; Keywords:      maint
 
 ;; This file is part of Patcher.
 
 ;; Contents management by FCM version 0.1.
 
-;; Many thanks to Ben Wing <ben@xemacs.org> for good ideas and code.
-
 ;; This package automates the process of building and submitting patches for
-;; archive-based projects you're working on.  In one or two keystrokes, it
+;; archive-based projects you're working on. In one or two keystrokes, it
 ;; prepares a mail with a patch corresponding to the differences between your
-;; working version and the archive state, and prepares a skeleton for the
-;; ChangeLog entries, that you can fill in and insert into the message before
-;; sending.  You also have the possibility of committing your changes directly
-;; from your XEmacs session.
+;; working version and the archive state, optionally prepares a skeleton for
+;; the ChangeLog entries, that you can fill in and insert into the message
+;; before sending. You also have the opportunity to commit your changes
+;; directly from your XEmacs session.
 
 ;; Patcher currently supports the `compose-mail' mail sending interface, as
 ;; well as the `sendmail' and `message' libraries individually, which is
 ;; probably redundant. There is also a special support for sending mail from a
-;; running Gnus session.
+;; running Gnus session, and a `fake' method which lets you do everything but
+;; don't really send any message.
 
 ;; Here's a typical usage:
 
 ;;    message will be prepared and the ChangeLog skeletons built.
 ;;    Alternately, use `M-x patcher-mail-subproject' to mail (and possibly
 ;;    commit) changes to particular directories and/or files in the project.
-;; 2/ Edit the different ChangeLog buffers that have opened, and fill in the
-;;    skeletons.  You can save the ChangeLogs buffers, BUT DON't KILL THEM !!
+;; 2/ If you have let Patcher generate ChangeLog skeletons, edit them in the
+;;    different ChangeLog  buffers that have opened. You can save the
+;;    ChangeLog buffers, BUT DON't KILL THEM !!
 ;; 3/ If you want to commit your changes immediately, type `C-c C-p c' in
 ;;    the message buffer.  You'll have the opportunity to edit the message log
 ;;    and/or the commit command, and finally commit your changes.
 ;; 4/ Otherwise, or just after that, type `C-c C-p i' in the message buffer.
-;;    The new ChangeLog entries will be inserted just above the patch.
+;;    The new ChangeLog entries will be inserted in the message.
 ;; 5/ At any time after step 1, you can change your mind (like further modify
 ;;    source files) and regenerate the diff again. To do that, just type
 ;;    `C-c C-p d' in the message buffer.  If ChangeLog skeletons had already
 ;;    the initial diff command failed for some reason.
 ;; 6/ That's all folks.  You can send the message.
 
+
 ;; Requirements:
 
-;; This package requires a recent `add-log' library (from the xemacs-base
-;; package version 1.48 or later), and the `sendmail' library.  The different
+;; This package requires the `add-log' library from the xemacs-base package
+;; version 1.48 or later, and the `sendmail' library.  The different
 ;; mail methods will also have specific requirements (especially the Gnus
-;; one).  Please, refer to them for further information.
+;; one).  Please refer to them for further information.
+
 
 ;; Suggestions for further improvements:
 
+;; #### Before sending the message, we could check that the contents is ok
+;; (like, there's no more diff errors and stuff).
+
+;; #### Implement a real error mechanism.
+
+;; #### When a project is found to be out of date, we could implement
+;; something to update it and re-run patcher again.
+
+;; #### For the 'gnus mail method, add the possibility to temporarily use a
+;; different one if the user don't want to start Gnus.
+;;
 ;; #### Consider doing a better job of handling overlapping patcher
 ;; instances.  e.g. use a different extent property for each instance,
 ;; and keep a usage count for the ChangeLog files so that they're only
 ;; problem and do include-changelogs again.
 
 
+;; Thanks to these people for their suggestions, testing and contributions:
+
+;; Ben Wing       <ben@xemacs.org>,
+;; Adrian Aichner <adrian@xemacs.org>,
+;; Steve Youngs   <youngs@xemacs.org>.
+
+
 ;;; Code:
 
 (require 'add-log)
 ;; this.
 (require 'sendmail)
 
-(defgroup patcher nil
-  "Patch mailing utilities.")
-
 (defmacro patcher-globally-declare-fboundp (symbol)
   ;; copied from bytecomp-runtime.el
   (when (cl-compiling-file)
  '(gnus-alive-p gnus-post-news message-goto-subject message-goto-body))
 
 
-;; Projects description =====================================================
+
+;; ===========================================================================
+;; Version management
+;; ===========================================================================
+
+;; $Format: "(defconst patcher-prcs-major-version \"$ProjectMajorVersion$\")"$
+(defconst patcher-prcs-major-version "version-3-0")
+;; $Format: "(defconst patcher-prcs-minor-version \"$ProjectMinorVersion$\")"$
+(defconst patcher-prcs-minor-version "1")
+(defconst patcher-version
+  (let ((level patcher-prcs-minor-version)
+	major minor status)
+    (string-match "\\(branch\\|version\\)-\\([0-9]+\\)-\\([0-9]+\\)"
+		  patcher-prcs-major-version)
+    (setq major (match-string 2 patcher-prcs-major-version)
+	  minor (match-string 3 patcher-prcs-major-version)
+	  status (match-string 1 patcher-prcs-major-version))
+    (cond ((string= status "version")
+	   (setq level (int-to-string (1- (string-to-int level))))
+	   (if (string-equal level "0")
+	       (concat major "." minor)
+	     (concat major "." minor "." level)))
+	  ((string= status "branch")
+	   (concat major "." minor "-b" level)))
+    ))
+
+;;;###autoload
+(defun patcher-version ()
+  "Show the current version of Patcher."
+  (interactive)
+  (message "Patcher version %s" patcher-version))
+
+
+
+;; ===========================================================================
+;; Internal utilities
+;; ===========================================================================
+
+;; #### NOTE: this is currently useless.
+(defvar patcher-instances nil
+  ;; A list of all alive instances of Patcher (an instance is dead after the
+  ;; mail has been sent. Each element is of the form '(BUFFER_NAME . BUFFER).
+  )
+
+(defconst patcher-change-log-entry-start-regexp
+  "^[0-9]\\{4,4\\}-[0-9]\\{2,2\\}-[0-9]\\{2,2\\} "
+  ;; Regexp matching the beginning of a ChangeLog entry
+  )
+
+;; Buffer local variables ====================================================
+
+;; The following variables get local values in various Patcher buffers (mail
+;; buffer, process output buffer etc).
+
+(make-variable-buffer-local
+ (defvar patcher-project nil
+   ;; Patcher project related to the current patch. This is also set in
+   ;; auxiliary buffers.
+   ))
+
+(make-variable-buffer-local
+ (defvar patcher-mail-buffer nil
+   ;; Mail buffer corresponding to Patcher auxiliary buffers.
+   ))
+
+
+;; Utility functions =========================================================
+
+;;(defun patcher-keyword-value (keyword values)
+;;  ;; Return the value of KEYWORD from a (KEY VAL ...) list. VAL may be omitted
+;;  ;; in the list, in which case t is returned.
+;;  (let ((vals values)
+;;	key val)
+;;    (catch 'found
+;;      (while (setq key (pop vals))
+;;	(setq val (or (not (car vals))
+;;		      (if (keywordp (car vals)) t (pop vals))))
+;;	(and (eq keyword key)
+;;	     (throw 'found val))))
+;;    ))
+
+(defsubst patcher-error (msg &rest args)
+  ;; Like `patcher-message, but triggers a Patcher error instead.
+  (error (substitute-command-keys (apply 'format msg args))))
+
+(defsubst patcher-message (msg &rest args)
+  ;; Print a message, letting XEmacs time to display it. Also, handle command
+  ;; substitution.
+  (message (substitute-command-keys (apply 'format msg args)))
+  (sit-for 0))
+
+(defmacro patcher-with-progression (msg &rest body)
+  ;; wrap BODY in "msg..." / "msg...done" messages.
+  ;; Return the value of BODY execution.
+  `(prog2
+       (patcher-message (concat ,msg "..."))
+       (progn ,@body)
+     (patcher-message (concat ,msg "...done"))))
+(put 'patcher-with-progression 'lisp-indent-function 1)
+
+
+(defsubst patcher-file-relative-name (file &optional dir raw)
+  ;; Construct a filename from FILE relative to DIR (default directory if not
+  ;; given). Unless RAW is given, force unix syntax
+  (let ((directory-sep-char (if raw directory-sep-char ?/)))
+    (or dir (setq dir (default-directory)))
+    (file-relative-name (expand-file-name file (expand-file-name dir))
+			(expand-file-name dir))
+    ))
+
+(defsubst patcher-files-string (files)
+  ;; Convert FILES (a list of file names) to a string of relative file names.
+  ;; Unless RAW is given, force unix syntax.
+  (mapconcat 'patcher-file-relative-name  files " "))
+
+(defun patcher-files-buffers (files &optional find)
+  ;; Find a buffer visiting each file in FILES, and return a list of
+  ;; corresponding buffers. Skip files that are not visited, unless optional
+  ;; argument FIND is non nil. In that case, visit the file.
+  (let (buffer buffers)
+    (dolist (file files buffers)
+      (setq buffer (or (get-file-buffer file)
+		       (and find (find-file-noselect file))))
+      (when buffer (push buffer buffers)))
+    ))
+
+(defun patcher-save-buffers (buffers)
+  ;; Offer to save some buffers.
+  ;; #### FIXME: this should be a standard function somewhere.
+  (map-y-or-n-p
+   (lambda (buffer)
+     (and (buffer-modified-p buffer)
+	  (not (buffer-base-buffer buffer))
+	  (buffer-file-name buffer)
+	  (format "Save file %s? "
+		  (buffer-file-name buffer))))
+   (lambda (buffer)
+     (save-excursion
+       (set-buffer buffer)
+       (condition-case ()
+	   (save-buffer)
+	 (error nil))))
+   buffers
+   '("buffer" "buffers" "save")))
+
+
+(defsubst patcher-call-process (command &optional buffer)
+  ;; Call a shell process which executes COMMAND (a string) with output to
+  ;; BUFFER (the current buffer by default).
+  (apply 'call-process shell-file-name nil (or buffer t) nil
+	 shell-command-switch (list command)))
+
+(defun patcher-command (command files)
+  ;; Replace the %f in the string COMMAND with the string FILES (if any), or
+  ;; append it to the end of COMMAND.
+  (or files (setq files ""))
+  ;; force Unix syntax
+  (setq files (replace-in-string files "\\\\" "/"))
+  (cond ((string-match "%f" command)
+	 (replace-in-string (replace-in-string command "%f" files)
+			    "[ \t]+$" ""))
+	((> (length files) 0)
+	 (concat command " " files))
+	(t command)))
+
+(defsubst patcher-extent (property &optional value buffer)
+  ;; Get the extent that has PROPERTY set to VALUE (t if not given) in BUFFER
+  ;; (current buffer if nil).
+  (car (mapcar-extents #'identity nil buffer nil nil nil
+		       property (or value t))))
+
+(defun patcher-process-output-buffer (&optional mail-buffer)
+  ;; Get a process output buffer for the current Patcher MAIL-BUFFER (current
+  ;; buffer by default), and prepare it. We can reuse an already existing one
+  ;; because auxiliary buffers are currently used only in one Lisp shot, so
+  ;; there's no risk of Patcher instances overlapping.
+  (let ((project patcher-project)
+	(directory (default-directory))
+	(buffer (get-buffer-create " *Patcher Process Output*")))
+    (or mail-buffer (setq mail-buffer (current-buffer)))
+    (with-current-buffer buffer
+      (cd directory)
+      (setq patcher-project project)
+      (setq patcher-mail-buffer mail-buffer)
+      (erase-buffer))
+    buffer))
+
+
+
+;; ===========================================================================
+;; Projects description
+;; ===========================================================================
+
+(defgroup patcher nil
+  "Patch mailing utilities.")
 
 (defgroup patcher-default nil
   "Default settings for Patcher projects."
   :group 'patcher)
 
 (defcustom patcher-default-mail-method 'compose-mail
-  "*Default method used by Patcher to prepare a mail \(a symbol).
+  "*Default method used by Patcher to prepare a mail.
 Currently, there are four built-in methods: 'compose-mail \(the default),
 'sendmail, 'message, 'gnus and 'fake.  Please refer to the corresponding
 `patcher-mail-*' function for a description of each method.  You can also
 function must prepare a mail buffer.  If you want to do this, please see
 how it's done for the built-in methods."
   :group 'patcher-default
-  :type '(radio (const :value compose-mail)
-		(const :value sendmail)
-		(const :value message)
-		(const :value gnus)
-		(const :value fake)
+  :type '(radio (const compose-mail)
+		(const sendmail)
+		(const message)
+		(const gnus)
+		(const fake)
 		(symbol :tag "other")))
 
-(defcustom patcher-default-subject ""
-  "*Default subject to use when sending a Patcher mail \(a string).
-A %n occurring in this string will be replaced by the project's name.
-All subjects are unconditionally prefixed with the string \"[PATCH] \"."
+(defcustom patcher-default-subject-prefix "[PATCH]"
+  "*Default prefix for the subject of Patcher mails.
+A %n occurring in this string will be replaced with the project's name.
+A space will be inserted between the prefix and the rest of the subject,
+as appropriate.  This part of the subject is never prompted for.  See
+also the variables `patcher-default-subject'
+and `patcher-default-subject-committed-prefix'.
+
+NOTE: In order to comply with the current policy, XEmacs reviewers are
+encouraged to notify their auto-approval privilege by using a value of
+\"[PATCH APPROVE]\" for their `subject-prefix' project option."
   :group 'patcher-default
-  :type 'string)
+  :type '(choice (const :tag "None" nil)
+		 string))
 
-(defcustom patcher-default-mail-prologue ""
+(defcustom patcher-default-subject-committed-prefix "[COMMIT]"
+  "*Default prefix for the subject of Patcher mails.
+This string is used rather than the one specified by the variable
+`patcher-default-subject-prefix' when you have committed your patch
+before sending the message.  A %n occurring in this string will be
+replaced with the project's name.  A space will be inserted between
+the prefix and the rest of the subject, as appropriate.  This part of
+the subject is never prompted for.  See also the variables
+`patcher-default-subject-prefix' and `patcher-default-subject'.
+
+A value of nil means don't change the subject.
+
+NOTE: In order to comply with the current policy, XEmacs reviewers are
+encouraged to notify their commit action by using a value of
+\"[PATCH APPROVE COMMIT]\" for their `subject-committed-prefix' project
+option."
+  :group 'patcher-default
+  :type '(choice (const :tag "Don't change" nil)
+		 string))
+
+(defcustom patcher-default-subject nil
+  "*Default subject to use when sending a Patcher mail.
+A %n occurring in this string will be replaced with the project's name.
+Please note that this variable (and its corresponding project option) is
+used *only* to provide a default value for prompted subjects.  Subjects
+are *always* prompted for.
+
+See also the variables `patcher-default-subject-prefix' and
+`patcher-default-subject-committed-prefix' which are not subject to
+prompting."
+  :group 'patcher-default
+  :type '(choice (const :tag "None" nil)
+		 string))
+
+(defcustom patcher-default-mail-prologue nil
   "*Default string to insert at the beginning of every Patcher mail."
   :group 'patcher-default
-  :type 'string)
+  :type '(choice (const :tag "None" nil)
+		 string))
+
+(defcustom patcher-default-change-logs-updating 'automatic
+  "*Controls the way ChangeLog fiels are updated.
+Possible values and their meaning are:
+- 'automatic: (the default) Patcher generates ChangeLog skeletons
+   automatically based on the created diff (you then have to fill up the
+   entries as needed).
+- 'manual: you are supposed to have updated the ChangeLog files by hand,
+   prior to calling Patcher.
+-  nil: you don't do ChangeLogs at all."
+  :group 'patcher-default
+  :type '(radio (const :tag "Automatic" automatic)
+		(const :tag "Manual" manual)
+		(const :tag "None" nil)))
+
+(defcustom patcher-default-change-logs-user-name nil
+  "*User full name for generated ChangeLog entries.
+If nil, let `patch-to-change-log' decide what to use.
+Otherwise, it should be a string."
+  :group 'patcher-default
+  :type '(choice (const :tag "Default" nil)
+		 string))
+
+(defcustom patcher-default-change-logs-user-mail nil
+  "*User mail address for generated ChangeLog entries.
+If nil, let `patch-to-change-log' decide what to use.
+Otherwise, it should be a string."
+  :group 'patcher-default
+  :type '(choice (const :tag "Default" nil)
+		 string))
+
+(defcustom patcher-default-change-logs-appearance 'verbatim
+  "*Controls the appearance of ChangeLog entries in Patcher mails.
+The values currently supported are:
+- 'verbatim \(the default): ChangeLog entries appear simply as text above
+   the patch.  A short line mentioning the ChangeLog file they belong to
+   is added when necessary.
+- 'packed: ChangeLog files are diff'ed, but the output is packed above the
+   rest of the patch.
+- 'patch: ChangeLog files are diff'ed, and the output appears as part of
+   the patch itself.
+-  nil: ChangeLog entries don't appear at all.
+
+See also the variable `patcher-default-change-logs-diff-command'."
+  :group 'patcher-default
+  :type '(radio (const :tag "Verbatim" verbatim)
+		(const :tag "Diff, packed together" packed)
+		(const :tag "Diff, part of the patch" patch)
+		(const :tag "Don't appear in message" nil)))
+
+(defcustom patcher-default-change-logs-prologue "%f addition:"
+  "*Default string to insert just before ChangeLogs in every Patcher mail.
+This applies when ChangeLog additions appear verbatim in the message.
+A %f occuring in this string will be replaced with the ChangeLog file
+name (relative to the project's directory)."
+  :group 'patcher-default
+  :type '(choice (const :tag "None" nil)
+		 string))
+
+(defcustom patcher-default-diff-prologue-function
+  'patcher-default-diff-prologue
+  "*Function used to insert a prologue before each diff output.
+Insertion must occur at current point in current buffer.
+This function should take one argument indicating the kind of diff:
+- a value of 'sources indicates a source diff only,
+- a value of 'change-logs indicates a ChangeLog diff only,
+- a value of 'mixed indicates a diff on both source and ChangeLog files.
+
+The following variables are bound (when appropriate) when this function
+is executed:
+- `name': the name of the current Patcher project,
+- `source-diff': the command used to create a source diff,
+- `change-log-diff': the command used to create a ChangeLog diff,
+- `source-files': sources files affected by the current patch,
+- `change-log-files': ChangeLog files affected by the current patch.
+
+In the case of a 'mixed diff, a nil value for `change-log-diff' indicates
+that the same command was used for both the source and ChangeLog files."
+  :group 'patcher-default
+  :type '(choice (const :tag "Default" patcher-default-diff-prologue)
+		 (const :tag "None" nil)
+		 (symbol :tag "Other")))
 
 (defcustom patcher-default-diff-command "cvs -q diff -u %f"
-  "*Default method used by Patcher to generate a patch \(a string).
-A %f occurring in this string will be replaced by the files affected by
+  "*Default method used by Patcher to generate a patch.
+A %f occurring in this string will be replaced with the files affected by
 the patch.  These files can be specified by using `patcher-mail-subproject'
 instead of `patcher-mail' to prepare the patch.  Otherwise, the %f will
 simply be removed."
   :group 'patcher-default
   :type 'string)
 
-(defcustom patcher-default-diff-prologue "%n Patch (%c):"
-  "*Default string to insert just before the patch in every Patcher mails.
-A %n occurring in this string will be replaced with the project's name.
-A %c occurring in this string will be replaced with the diff command."
+(defcustom patcher-default-change-logs-diff-command "cvs -q diff -U 0 %f"
+  "*Command to use to generate ChangeLog diffs.
+This value is used when the ChangeLog appearance is either 'packed or
+'patch (see the variable `patcher-default-change-logs-appearance').
+If set to 'diff, use the same command as for the rest of the patch.
+Otherwise, it should be a string.  A `%f' occuring in this string will be
+replaced with the ChangeLog filenames.
+
+Note: it is highly recommended to remove the context from ChangeLog diffs
+because they often fail to apply correctly."
   :group 'patcher-default
-  :type 'string)
+  :type '(choice (const  :tag "Project diff command" diff)
+		 (string :tag "Other diff command" "cvs -q diff -U 0 %f")))
 
 (defcustom patcher-default-commit-command "cvs commit -F %s %f"
-  "*Default method used by Patcher to commit a patch \(a string).
+  "*Default method used by Patcher to commit a patch.
 This command must contain a %s which will be replaced with the name of a
 file containing the commit message log.
 A %f occurring in the command will be replaced with the files affected by
   "NOTE: This patch has been committed."
   "*Notice added to a mail when the patch is committed before sending."
   :group 'patcher-default
-  :type 'string)
+  :type '(choice (const :tag "None" nil)
+		 string))
 
 (defcustom patcher-default-failed-command-regexp "^cvs \\[[^]]* aborted\\]"
   "*Default regular expression for matching the result of a failed command.
   :group 'patcher-default
   :type 'regexp)
 
-(defcustom patcher-default-init-log-message 'subject
-  "*How to initialize the commit log message \(a symbol).
-The values currently supported are:
-- 'subject \(the default): use the subject of the mail.
-- 'change-logs: use the ChangeLog entries.
-- 'compressed-change-logs: use the ChangeLog entries, but compress them
-   into something more suitable as a log message.
-- 'compressed-change-logs-with-original: insert the compressed ChangeLog
-   entries, as above, but also append the original entries at the end for
-   easy reference.  You should delete the originals before committing.
-- nil: don't initialize the log message.
+(defcustom patcher-default-log-message-items '(subject)
+  "*Elements used to initialize a Patcher commit log message.
+This is nil, or a list of the following items:
+- 'subject: the subject of the correspoding Patcher mail (sans the prefix),
+- 'compressed-change-logs: the compressed ChangeLog entries for the current
+   patch.
+- 'change-logs: the ChangeLog entries for the current patch.  If some items
+   appear before the ChangeLog entries, the ChangeLogs separator will
+   automatically be included."
+  :group 'patcher-default
+  :type '(set (const :tag "Subject" subject)
+	      (const :tag "Compressed ChangeLogs" compressed-change-logs)
+	      (const :tag "ChangeLogs" change-logs)))
 
-If at commit time, the log message is empty, \"\(none)\" will be used."
+(defcustom patcher-default-change-logs-separator
+  "-------------------- ChangeLog entries follow: --------------------"
+  "*ChangeLog entries separator for Patcher commit log messages.
+Either nil, or a string which will be inserted in a line of its own.
+See also the function `patcher-logmsg-insert-change-logs'."
   :group 'patcher-default
-  :type '(radio (const :value subject)
-		(const :value change-logs)
-		(const :value compressed-change-logs)
-		(const :value compressed-change-logs-with-original)
-		(const :value nil :tag "nothing")))
+  :type '(choice (const :tag "None" nil)
+		 string))
 
 (defcustom patcher-default-edit-log-message t
   "*Whether Patcher lets you edit the commit log message.
   :type 'boolean)
 
 (defcustom patcher-default-to-address "xemacs-patches@xemacs.org"
-  "*Default email address to use when sending a Patcher mail (a string).
+  "*Default email address to use when sending a Patcher mail.
 This variable is used by all mail methods except the 'gnus one \(see
 `patcher-default-mail-method').  If not defined, it is prompted for."
   :group 'patcher-default
   :type 'string)
 
-(defcustom patcher-default-gnus-group ""
-  "*Default Gnus group to use when sending a Patcher mail (a string).
+(defcustom patcher-default-gnus-group nil
+  "*Default Gnus group to use when sending a Patcher mail.
 This variable is used only in the 'gnus mail method \(see
 `patcher-default-mail-method').  The mail sending process will behave as if
 you had type `C-u a' in the group buffer on that Gnus group.  If not
   :group 'patcher-default
   :type 'string)
 
+
 ;; Defining these const avoids coding special cases for the :inheritance,
 ;; :subdirectory and :files (sub)project option in the accessor functions.
 (defconst patcher-default-inheritance nil)
 (defconst patcher-default-subdirectory nil)
 (defconst patcher-default-files nil)
 
+
 (defconst patcher-project-options-custom-type
   '((list :inline t :tag "Mail method"
 	  :format "%{%t%}: %v"
 	  (const :tag "" :value :mail-method)
-	  (choice (const :value compose-mail)
-		  (const :value sendmail)
-		  (const :value message)
-		  (const :value gnus)
-		  (const :value fake)
+	  (choice (const compose-mail)
+		  (const sendmail)
+		  (const message)
+		  (const gnus)
+		  (const fake)
 		  (symbol :tag "other")))
+    (list :inline t :tag "Subject prefix"
+	  :format "%{%t%}: %v"
+	  (const  :tag "" :value :subject-prefix)
+	  (choice (const :tag "None" nil)
+		  string))
+    (list :inline t :tag "Subject committed prefix"
+	  :format "%{%t%}: %v"
+	  (const  :tag "" :value :subject-committed-prefix)
+	  (choice (const :tag "None" nil)
+		  string))
     (list :inline t :tag "Subject"
 	  :format "%{%t%}: %v"
 	  (const  :tag "" :value :subject)
-	  (string :tag "Value"))
+	  (choice (const :tag "None")
+		  string))
     (list :inline t :tag "Mail Prologue"
 	  :format "%{%t%}: %v"
 	  (const :tag "" :value :mail-prologue)
-	  (string :tag "Value"))
+	  (choice (const :tag "None" nil)
+		  string))
+    (list :inline t :tag "ChangeLogs updating"
+	  :format "%{%t%}: %v"
+	  (const :tag "" :value :change-logs-updating)
+	  (choice (const :tag "Automatic" automatic)
+		  (const :tag "Manual" manual)
+		  (const :tag "None" nil)))
+    (list :inline t :tag "ChangeLogs user name"
+	  :format "%{%t%}: %v"
+	  (const :tag "" :value :change-logs-user-name)
+	  (choice (const :tag "Default" nil)
+		  string))
+    (list :inline t :tag "ChangeLogs user mail"
+	  :format "%{%t%}: %v"
+	  (const :tag "" :value :change-logs-user-mail)
+	  (choice (const :tag "Default" nil)
+		  string))
+    (list :inline t :tag "ChangeLogs appearance"
+	  :format "%{%t%}: %v"
+	  (const :tag "" :value :change-logs-appearance)
+	  (choice (const :tag "Verbatim" verbatim)
+		  (const :tag "Diff, packed together" packed)
+		  (const :tag "Diff, part of the patch" patch)
+		  (const :tag "Don't appear" nil)))
+    (list :inline t :tag "ChangeLogs prologue"
+	  :format "%{%t%}: %v"
+	  (const :tag "" :value :change-logs-prologue)
+	  (choice (const :tag "None" nil)
+		  string))
+    (list :inline t :tag "Diff prologue function"
+	  :format "%{%t%}: %v"
+	  (const :tag "" :value :diff-prologue-function)
+	  (choice (const :tag "Default" patcher-default-diff-prologue)
+		  (const :tag "None" nil)
+		  (symbol :tag "Other")))
     (list :inline t :tag "Diff command"
 	  :format "%{%t%}: %v"
 	  (const :tag "" :value :diff-command)
-	  (string :tag "Value"))
-    (list :inline t :tag "Diff prologue"
+	  string)
+    (list :inline t :tag "ChangeLogs diff command"
 	  :format "%{%t%}: %v"
-	  (const :tag "" :value :diff-prologue)
-	  (string :tag "Value"))
+	  (const :tag "" :value :change-logs-diff-command)
+	  (choice (const :tag "Project diff command" diff)
+		  (string :tag "Other diff command" "cvs -q diff -U 0 %f")))
     (list :inline t :tag "Commit command"
 	  :format "%{%t%}: %v"
 	  (const :tag "" :value :commit-command)
-	  (string :tag "Value"))
+	  string)
     (list :inline t :tag "Confirm commits"
 	  :format "%{%t%}: %v"
 	  (const :tag "" :value :confirm-commits)
-	  (boolean :tag "Value"))
-    (list :inline t :tag "Committed-notice"
+	  boolean)
+    (list :inline t :tag "Committed notice"
 	  :format "%{%t%}: %v"
 	  (const :tag "" :value :committed-notice)
-	  (string :tag "Value"))
+	  (choice (const :tag "None" nil)
+		  string))
     (list :inline t :tag "Failed command regexp"
 	  :format "%{%t%}: %v"
 	  (const :tag "" :value :failed-command-regexp)
-	  (regexp :tag "Value"))
-    (list :inline t :tag "Init log message"
+	  regexp)
+    (list :inline t :tag "Log message items"
 	  :format "%{%t%}: %v"
-	  (const :tag "" :value :init-log-message)
-	  (choice (const :value subject)
-		  (const :value change-logs)
-		  (const :value compressed-change-logs)
-		  (const :value compressed-change-logs-with-original)))
+	  (const :tag "" :value :log-message-items)
+	  (set (const :tag "Subject" subject)
+	       (const :tag "Compressed ChangeLogs" compressed-change-logs)
+	       (const :tag "ChangeLogs" change-logs)))
+    (list :inline t :tag "ChangeLogs separator"
+	  :format "%{%t%}: %v"
+	  (const :tag "" :value :change-logs-separator)
+	  (choice (const :tag "None" nil)
+		  string))
     (list :inline t :tag "Edit log message"
 	  :format "%{%t%}: %v"
 	  (const :tag "" :value :edit-log-message)
-	  (boolean :tag "Value"))
+	  boolean)
     (list :inline t
 	  :tag "Kill source files after sending"
 	  :format "%{%t%}: %v"
-	  (const :tag ""
-		 :value
-		 :kill-source-files-after-sending)
-	  (boolean :tag "Value"))
+	  (const :tag "" :value :kill-source-files-after-sending)
+	  boolean)
     (list :inline t
 	  :tag "Kill ChangeLogs after sending"
 	  :format "%{%t%}: %v"
-	  (const :tag ""
-		 :value
-		 :kill-change-logs-after-sending)
-	  (boolean :tag "Value"))
+	  (const :tag "" :value :kill-change-logs-after-sending)
+	  boolean)
     (list :inline t
 	  :tag "Kill source files after diffing"
 	  :format "%{%t%}: %v"
-	  (const :tag ""
-		 :value
-		 :kill-source-files-after-diffing)
-	  (boolean :tag "Value"))
+	  (const :tag "" :value :kill-source-files-after-diffing)
+	  boolean)
     (list :inline t :tag "To Address"
 	  :format "%{%t%}: %v"
 	  (const  :tag "" :value :to-address)
-	  (string :tag "Value"))
+	  string)
     (list :inline t :tag "Gnus Group"
 	  :format "%{%t%}: %v"
 	  (const  :tag "" :value :gnus-group)
-	  (string :tag "Value"))
-    (list :inline t :tag "Other"
-	  symbol
-	  sexp))
+	  string))
+  ;; This is currently useless, and would cause problems in the custom type:
+  ;; it will match the inheritance field in patcher-projects before the
+  ;; corresponding custom type definition.
+  ;;    (list :inline t :tag "Other"
+  ;;	  symbol
+  ;;	  sexp))
   ;; Custom type elements for Patcher project options common to
   ;; `patcher-projects' and `patcher-subprojects'.
   )
 The remainder of the project descriptor is composed of \"project options\"
 \(keywords and associated values).  Keywords correspond to the variables
 `patcher-default-*'.  When Patcher needs a keyword value, it tries to find
-at different places:
+it at different places:
 - first, it looks for the keyword in the project options.
 - if that fails, it tries to find it in the project options of the projects
   appearing in the `:inheritance' option of the current project.
 variable.
 
 As an exception, the `:inheritance' keyword does not have a corresponding
-`patcher-default-inheritance' variable."
+`patcher-default-inheritance' variable.
+
+A simple setting for patcher-project would be:
+  '\(\(\"packages\" \"~/cvsroot/xemacs-packages\")
+    \(\"xemacs-21.5\" \"~/cvsroot/xemacs-21.5\"))
+This sets up 2 patcher projects with roots at \"~/cvsroot/xemacs-packages\"
+and \"~/cvsroot/xemacs-21.5\", respectively."
   :group 'patcher
   :type `(repeat
 	  (group (string :tag "Project")
 				 (list :inline t :tag "Subdirectory"
 				       :format "%{%t%}: %v"
 				       (const :tag "" :value :subdirectory)
-				       (directory :tag "Value"))
+				       directory)
 				 (list :inline t :tag "Files"
 				       :format "%{%t%}: %v"
 				       (const :tag "" :value :files)
-				       (repeat :format "\n%v%i\n"
-					       (file :tag "File")))
+				       (repeat :format "\n%v%i\n" file))
 				 ,@patcher-project-options-custom-type))
 		 ))
   )
 
 
-;; Project descriptors Accessors:
+;; Project descriptors Accessors =============================================
+
 (defsubst patcher-project-name (project) (nth 0 project))
 
 (defun patcher-project-directory (project)
   (if (member project patcher-subprojects)
       (let ((prj (assoc (nth 1 project) patcher-projects)))
 	(unless prj
-	  (error "Can't find base project for subproject `%s'"
-		 (patcher-project-name project)))
+	  (patcher-error "Can't find base project for subproject `%s'"
+			 (patcher-project-name project)))
 	(let ((subdir (patcher-project-option project :subdirectory)))
 	  (if subdir
 	      (expand-file-name subdir (patcher-project-directory prj))
 	;; If PROJECT is a subproject, we use only the base project as an
 	;; inheritance list.
 	(when is-subproject
-	    (if projs
-		(warn "Option :inheritance in subproject `%s' will be unused"
-		      (patcher-project-name project)))
-	    (setq projs (list (nth 1 project))))
+	  (if projs
+	      (warn "Option :inheritance in subproject `%s' will be unused"
+		    (patcher-project-name project)))
+	  (setq projs (list (nth 1 project))))
 	(when projs
 	  (while (and (not value) (setq proj (pop projs)))
 	    (setq value (patcher-project-option-1 (assoc proj patcher-projects)
        (intern-soft
 	(concat "patcher-default-" (substring (symbol-name option) 1))))
       )))
+(put 'patcher-project-option 'lisp-indent-function 1)
 
 
-;; Version management =======================================================
 
-;; $Format: "(defconst patcher-prcs-major-version \"$ProjectMajorVersion$\")"$
-(defconst patcher-prcs-major-version "version-2-5")
-;; $Format: "(defconst patcher-prcs-minor-version \"$ProjectMinorVersion$\")"$
-(defconst patcher-prcs-minor-version "1")
-(defconst patcher-version
-  (let ((level patcher-prcs-minor-version)
-	major minor status)
-    (string-match "\\(branch\\|version\\)-\\([0-9]+\\)-\\([0-9]+\\)"
-		  patcher-prcs-major-version)
-    (setq major (match-string 2 patcher-prcs-major-version)
-	  minor (match-string 3 patcher-prcs-major-version)
-	  status (match-string 1 patcher-prcs-major-version))
-    (cond ((string= status "version")
-	   (setq level (int-to-string (1- (string-to-int level))))
-	   (if (string-equal level "0")
-	       (concat major "." minor)
-	     (concat major "." minor "." level)))
-	  ((string= status "branch")
-	   (concat major "." minor "-b" level)))
-    ))
+;; ==========================================================================
+;; ChangeLog buffers
+;; ==========================================================================
 
-;;;###autoload
-(defun patcher-version ()
-  "Show the current version of Patcher."
-  (interactive)
-  (message "Patcher version %s" patcher-version))
+(defun patcher-read-natnum (prompt &optional default-value)
+  ;; Hacked from read-number
+  ;; Read a natural number from the minibuffer, prompting with PROMPT.
+  ;; If optional second argument DEFAULT-VALUE is non-nil, return that if user
+  ;; enters an empty line.
+  (let ((pred (lambda (val) (and (integerp val) (> val 0))))
+	num)
+    (while (not (funcall pred num))
+      (setq num (condition-case ()
+		    (let ((minibuffer-completion-table nil))
+		      (read-from-minibuffer
+		       prompt (if num (prin1-to-string num)) nil t
+		       nil nil (and default-value
+				    (prin1-to-string default-value))))
+		  (input-error nil)
+		  (invalid-read-syntax nil)
+		  (end-of-file nil)))
+      (or (funcall pred num) (beep)))
+    num))
 
+(defun patcher-change-log-extent (change-log mail)
+  ;; Return (maybe after creating it) the extent in buffer CHANGE-LOG which
+  ;; has the 'patcher property set to the buffer MAIL.
+  (let ((extent (patcher-extent 'patcher mail change-log)))
+    (unless extent
+      (save-window-excursion
+	(display-buffer change-log t)
+	(let ((entries (patcher-read-natnum "Number of entries (1): " 1))
+	      beg end)
+	  (save-excursion
+	    (set-buffer change-log)
+	    (save-restriction
+	      (widen)
+	      (goto-char (point-min))
+	      (skip-chars-forward " \n\t")
+	      (unless (looking-at patcher-change-log-entry-start-regexp)
+		(patcher-error "\
+Beginning of buffer doesn't look like a ChangeLog entry."))
+	      (setq beg (point))
+	      (condition-case nil
+		  (while (> entries 0)
+		    (re-search-forward patcher-change-log-entry-start-regexp)
+		    (setq entries (1- entries)))
+		(t
+		 (patcher-error "\
+Buffer is missing %s ChangeLog entr%s to do the count."
+				entries (if (= entries 1) "y" "ies"))))
+	      (setq end
+		    (or (and (re-search-forward
+			      patcher-change-log-entry-start-regexp nil t)
+			     (progn (beginning-of-line) (point)))
+			(point-max)))
+	      (set-extent-properties (setq extent (make-extent beg end))
+		`(patcher ,mail))
+	      ))
+	  )))
+    extent))
 
-;; Internal variables =======================================================
 
-;; These variables get proper values in each mail buffer.
+
+;; ==========================================================================
+;; The LogMsg buffer
+;; ==========================================================================
 
 (make-variable-buffer-local
- (defvar patcher-project nil
-   ;; Patcher project related to the current patch.
+ (defvar patcher-logmsg-file-name nil
+   ;; Name of the temporary file where the log message is stored.
    ))
 
 (make-variable-buffer-local
- (defvar patcher-files nil
-   ;; Files and/or directories concerned by the current patch.
-   ;; This is set by `patcher-mail-subproject'.
+ (defvar patcher-logmsg-commit-command
+   ;; Commit command used for the current Patcher LogMsg buffer. This variable
+   ;; is needed because the user has the ability to override the command with
+   ;; a prefix argument.
    ))
 
-(make-variable-buffer-local
- (defvar patcher-diff-command nil
-   ;; Complete diff command to use for making the current patch.
-   ))
-
-(make-variable-buffer-local
- (defvar patcher-change-logs nil
-   ;; List of ChangeLog file buffers concerned by the current patch.
-   ))
-
-(make-variable-buffer-local
- (defvar patcher-change-logs-marker nil
-   ;; Marker indicating the beginning of the ChangeLog entries in the mail
-   ;; buffer.
-   ))
-
-(make-variable-buffer-local
- (defvar patcher-patch-marker nil
-   ;; Marker indicating the beginning of the patch in the mail buffer.
-   ))
-
-(make-variable-buffer-local
- (defvar patcher-commit-output-buffer nil
-   ;; Buffer containing the output of the commit command.
-   ))
-
-(make-variable-buffer-local
- (defvar patcher-commit-logmsg-buffer nil
-   ;; Buffer containing the log message of the commit command.
-   ))
-
-(make-variable-buffer-local
- (defvar patcher-pre-commit-window-config nil
-   ;; Window configuration when we're in the mail buffer, just prior to
-   ;; beginning a commit operation, so we can get back to it at the
-   ;; appropriate times after prompting for log messages, displaying
-   ;; subprocess output, etc.
-   ))
-
-(make-variable-buffer-local
- (defvar patcher-logmsg-file-name
-   ;; Name of the temporary file where the log message is stored.
-   ))
-
-
-;; Internal functions =======================================================
-
-(defun patcher-file-relative-name (file dir)
-  ;; Construct a filename relative to DIR.
-  (file-relative-name (expand-file-name file (expand-file-name dir))
-		      (expand-file-name dir)))
-
-(defun patcher-offer-save-buffers (buffers)
-  ;; Offer to save some buffers.
-  ;; #### this should be a standard function somewhere.
-  (map-y-or-n-p
-   (lambda (buffer)
-     (and (buffer-modified-p buffer)
-	  (not (buffer-base-buffer buffer))
-	  (buffer-file-name buffer)
-	  (format "Save file %s? "
-		  (buffer-file-name buffer))))
-   (lambda (buffer)
-     (save-excursion
-       (set-buffer buffer)
-       (condition-case ()
-	   (save-buffer)
-	 (error nil))))
-   buffers
-   '("buffer" "buffers" "save")))
-
-(defun patcher-insert-change-logs-1 (mail-buffer)
-  ;; Insert ChangeLog entries in the current buffer at the current position.
-  ;; ChangeLog entries are those corresponding to the patch being edited in
-  ;; MAIL_BUFFER.
-  (let* ((project (symbol-value-in-buffer 'patcher-project mail-buffer))
-	 (directory (patcher-project-directory project))
-	 (change-logs (symbol-value-in-buffer 'patcher-change-logs
-					      mail-buffer)))
-    (dolist (change-log change-logs)
-      (map-extents
-       #'(lambda (extent unused)
-	   ;; Force forward slashes (for native Windows). -- APA
-	   (let ((directory-sep-char ?/))
-	     (message "%s %s" (extent-start-position extent)
-		      (extent-end-position extent))
-	     (sit-for 1)
-	     (insert (format "%s addition:\n\n"
-			     (file-relative-name (buffer-file-name change-log)
-						 directory)))
-	     (insert (extent-string extent))
-	     ;; Be sure to map all extents.
-	     nil))
-       change-log nil nil nil nil 'patcher mail-buffer))
+(defun patcher-logmsg-compress-change-logs ()
+  ;; Compress ChangeLog entries appearing in the current buffer between FROM
+  ;; and TO. This function compresses the output into something that conveys
+  ;; the essence of what has been changed, but much more compactly.
+  (save-excursion
+    (goto-char (point-min))
+    (let ((prologue (patcher-project-option patcher-project
+		      :change-logs-prologue)))
+      (and prologue
+	   (> (length prologue) 0)
+	   (setq prologue (concat
+			   "^"
+			   (replace-in-string
+			    (regexp-quote prologue) "%f" ".+")
+			   "$"))
+	   (delete-matching-lines prologue)))
+    (delete-matching-lines patcher-change-log-entry-start-regexp)
+    ;; Now compress the change log specs into just files, so that mostly just
+    ;; the annotations are left.
+    (let ((change-log-change-line
+	   "^\\([ \t]+\\)\\* \\(\\S-+\\)\\( (.*)\\)?:\\( New\\.\\)?"))
+      (while (re-search-forward change-log-change-line nil t)
+	(let ((beg (match-beginning 1));; Change to match-end if you want the
+	      ;; indentation.
+	      (end (match-end 0))
+	      files)
+	  (push (match-string 2) files)
+	  (forward-line 1)
+	  (while (looking-at change-log-change-line)
+	    (setq end (match-end 0))
+	    (unless (member (match-string 2) files)
+	      (push (match-string 2) files))
+	    (forward-line 1))
+	  (goto-char beg)
+	  (delete-region beg end)
+	  (insert (mapconcat 'identity (nreverse files) ", ") ":")
+	  (when (looking-at "\\s-+")
+	    (let ((p (point))
+		  (end (match-end 0)))
+	      ;; If there's no annotation at all for this change, make sure we
+	      ;; don't treat the next change as an annotation for this one!
+	      (if (save-excursion
+		    (goto-char end)
+		    (beginning-of-line)
+		    (looking-at change-log-change-line))
+		  (progn
+		    (if (looking-at "[ \t]+")
+			(delete-region p (match-end 0))))
+		(delete-region p end)
+		(insert " "))))
+	  )))
+    ;; Shrink extra blank lines.
+    (let ((blank-line "^\\s-*$"))
+      (goto-char (point-min))
+      (while (and (not (eobp))
+		  (progn (forward-line 1)
+			 (re-search-forward blank-line nil t)))
+	(delete-blank-lines))
+      (goto-char (point-min))
+      (if (looking-at blank-line)
+	  (delete-blank-lines)))
     ))
 
-(defun patcher-construct-command (command files)
-  ;; Replace the %f with the specified files (if any), or append.
-  (or files (setq files ""))
-  (setq files (replace-in-string files "\\\\" "/"))
-  (cond ((string-match "%f" command)
-	 (replace-in-string (replace-in-string command "%f" files)
-			    "[ \t]+$" ""))
-	((> (length files) 0)
-	 (concat command " " files))
-	(t command)))
-
-(defun patcher-after-send (&optional unused)
-  ;; Do some cleanup after sending the mail.
-  (when (patcher-project-option patcher-project
-				:kill-change-logs-after-sending)
-    (patcher-offer-save-buffers patcher-change-logs)
-    (mapcar #'kill-buffer patcher-change-logs))
-  ;; #### Implement kill-source-files-after-sending here.
-  (when patcher-pre-commit-window-config
-    (set-window-configuration patcher-pre-commit-window-config))
-  (when patcher-commit-logmsg-buffer
-    (kill-buffer patcher-commit-logmsg-buffer))
-  (when patcher-commit-output-buffer
-    (bury-buffer patcher-commit-output-buffer)))
-
 
 ;; Patcher LogMsg mode ======================================================
 
+(defun patcher-logmsg-insert-subject ()
+  "Insert the Patcher mail subject into the current LogMsg buffer at point."
+  (interactive)
+  (let ((subject "(none)"))
+    (with-current-buffer patcher-mail-buffer
+      (save-excursion
+	(let ((extent (patcher-extent 'patcher-subject-prefix)))
+	  (if extent
+	      (progn
+		(goto-char (extent-end-position extent))
+		(skip-chars-forward " \t\f\r")
+		(unless (eq (point) (point-at-eol))
+		  (setq subject
+			(buffer-substring (point) (point-at-eol)))))
+	    (goto-char (point-min))
+	    (when (re-search-forward "^Subject: " nil t)
+	      (skip-chars-forward " \t\f\r")
+	      (unless (eq (point) (point-at-eol))
+		(setq subject
+		      (buffer-substring (point) (point-at-eol))))))
+	  )))
+    (insert subject "\n\n")
+    ))
+
+(defun patcher-logmsg-insert-change-logs (&optional separator)
+  "Insert ChangeLog entries in the current Patcher LogMsg buffer at point.
+When used interactively, use a prefix argument to also insert the
+ChangeLogs separator string defined by the :change-logs-separator project
+option."
+  (interactive "P")
+  (when separator
+    (setq separator (patcher-project-option patcher-project
+		      :change-logs-separator))
+    (and separator (> (length separator) 0)
+	 (insert "\n\n" separator "\n\n")))
+  (let ((prologue (patcher-project-option patcher-project
+		    :change-logs-prologue)))
+    (dolist (change-log (patcher-files-buffers
+			 (symbol-value-in-buffer 'patcher-change-logs
+						 patcher-mail-buffer)
+			 'find))
+      (insert "\n")
+      (and prologue (> (length prologue) 0)
+	   (insert (replace-in-string prologue "%f"
+				      (patcher-file-relative-name
+				       (buffer-file-name change-log)))
+		   "\n\n"))
+      (insert (extent-string
+	       (patcher-change-log-extent change-log patcher-mail-buffer))))
+    ))
+
+(defun patcher-logmsg-insert-compressed-change-logs ()
+  "Insert compressed ChangeLog entries in the current Patcher LogMsg buffer."
+  (interactive)
+  (let ((beg (point)))
+    (patcher-logmsg-insert-change-logs)
+    (narrow-to-region beg (point))
+    (patcher-logmsg-compress-change-logs)
+    (widen)
+    ))
+
+(defun patcher-logmsg-commit (&optional arg)
+  "Commit the change described in the current Patcher LogMsg buffer.
+When called interactively, use a prefix to override the commit command."
+  (interactive "P")
+  (let ((buffer (patcher-process-output-buffer patcher-mail-buffer))
+	(change-logs (symbol-value-in-buffer 'patcher-change-logs
+					     patcher-mail-buffer))
+	(sources (symbol-value-in-buffer 'patcher-sources
+					 patcher-mail-buffer))
+	(pre-commit-window-config (symbol-value-in-buffer
+				   'patcher-pre-commit-window-config
+				   patcher-mail-buffer))
+	(confirm-commits (patcher-project-option patcher-project
+			   :confirm-commits)))
+    (patcher-save-buffers (patcher-files-buffers change-logs))
+    (and arg (setq patcher-logmsg-commit-command
+		   (read-shell-command "Commit command: "
+				       patcher-logmsg-commit-command)))
+    (let ((command
+	   (patcher-command (replace-in-string patcher-logmsg-commit-command
+					       "%s"
+					       patcher-logmsg-file-name t)
+			    (if sources
+				(concat (patcher-files-string change-logs)
+					" "
+					sources)
+			      ""))))
+      ;; Maybe display the commit command, and make sure the user agrees.
+      (when (or (not confirm-commits)
+		(save-window-excursion
+		  (let ((runbuf (get-buffer-create
+				 "*Patcher Commit Command*")))
+		    (erase-buffer runbuf)
+		    (insert-string (format "Command to run:\n\n%s" command)
+				   runbuf)
+		    (display-buffer runbuf)
+		    (y-or-n-p "Run commit command? "))))
+	;; Write out the log message, or "(none)"
+	(and (= (point-min) (point-max)) (insert "(none)"))
+	(write-region (point-min) (point-max) patcher-logmsg-file-name
+		      nil 'silent)
+	(patcher-with-progression "Committing changes"
+	  (patcher-call-process command buffer))
+	;; Don't kill the log message buffer. This will be done after sending
+	;; the message -- i.e. when we are done with this project. We don't
+	;; kill the log message buffer now in case the user needs it later --
+	;; e.g. if the commit failed and needs to be redone (we try to detect
+	;; this, but we might not succeed in all cases.).
+	;; Try to see if the commit failed.
+	(with-current-buffer buffer
+	  (goto-char (point-min))
+	  (when (re-search-forward (patcher-project-option patcher-project
+				     :failed-command-regexp) nil t)
+	    (display-buffer buffer t)
+	    (patcher-error "\
+Error during commit.  Please fix the problem and type \
+\\[patcher-logmsg-commit] to try again.")))
+	;; Otherwise, record the successful commit in the mail message.
+	;; #### Note: it is normal to protect the re-search-forward calls
+	;; against errors, because when the `fake mail' method is used,
+	;; neither the Subject line nore the mail-header-separator one exist.
+	(with-current-buffer patcher-mail-buffer
+	  (save-excursion
+	    ;; Possibly change the subject:
+	    (goto-char (point-min))
+	    (when (re-search-forward "^Subject: " nil t)
+	      (let ((subject-committed-prefix
+		     (patcher-project-option patcher-project
+		       :subject-committed-prefix))
+		    (extent (patcher-extent 'patcher-subject-prefix)))
+		(when subject-committed-prefix
+		  (setq subject-committed-prefix
+			(replace-in-string subject-committed-prefix "%n"
+					   (patcher-project-name
+					    patcher-project)))
+		  (when extent
+		    (goto-char (extent-start-position extent))
+		    (delete-region (point) (extent-end-position extent)))
+		  (insert subject-committed-prefix)
+		  (and (looking-at "\\S-") (insert " ")))
+		))
+	    ;; Insert the `committed' notice:
+	    (goto-char (point-min))
+	    (when (re-search-forward
+		   (concat "^" (regexp-quote mail-header-separator))
+		   nil t)
+	      (forward-line 1)
+	      (let ((notice (patcher-project-option patcher-project
+			      :committed-notice)))
+		(and notice (> (length notice) 0)
+		     (insert notice "\n"))))
+	    ))
+	;; Bury the log message (see above).  Remove the log message window
+	;; and display the output buffer.
+	(bury-buffer (current-buffer))
+	(set-window-configuration pre-commit-window-config)
+	(display-buffer buffer t))
+      )))
+
+(defun patcher-logmsg-init-message ()
+  "(Re)Init the log message in the current Patcher LogMsg buffer.
+This is done conforming to the :log-message-items project option."
+  (interactive)
+  (erase-buffer)
+  (let ((items (patcher-project-option patcher-project :log-message-items))
+	(edit-log-message (patcher-project-option patcher-project
+			    :edit-log-message))
+	separator)
+    (dolist (item items)
+      (cond ((eq item 'subject)
+	     (patcher-logmsg-insert-subject)
+	     (setq separator t))
+	    ((eq item 'compressed-change-logs)
+	     (patcher-logmsg-insert-compressed-change-logs)
+	     (setq separator t))
+	    ((eq item 'change-logs)
+	     (patcher-logmsg-insert-change-logs separator))
+	    (t
+	     (patcher-error "invalid log message item: %s" item)))
+      (insert "\n"))
+    (goto-char (point-min))
+    (if edit-log-message
+	(patcher-message "\
+Edit the log message, and press \\[patcher-logmsg-commit] when done.")
+      (patcher-logmsg-commit))
+    ))
+
 (defcustom patcher-logmsg-mode-hook nil
   "*Hook to run after setting up Patcher-Logmsg mode."
   :group 'patcher
   :type 'hook)
 
-(make-variable-buffer-local
- (defvar patcher-logmsg-mail-buffer
-   ;; Mail buffer where the relevant Patcher message is being edited.
-   ;; This variable gets a proper value in the logmsg buffers.
-   ))
-
-(defun patcher-logmsg-do-it (arg)
-  "Function used to commit a change reported by `patcher-mail'.
-When called interactively, use a prefix to override the default commit
-command for this project."
-  (interactive "P")
-  (let* ((project (symbol-value-in-buffer 'patcher-project
-					  patcher-logmsg-mail-buffer))
-	 (command (patcher-project-option project :commit-command))
-	 (failed-command-regexp (patcher-project-option
-				 project :failed-command-regexp))
-	 (confirm-commits (patcher-project-option project :confirm-commits))
-	 (change-logs (symbol-value-in-buffer 'patcher-change-logs
-					      patcher-logmsg-mail-buffer))
-	 (pre-commit-window-config
-	  (symbol-value-in-buffer 'patcher-pre-commit-window-config
-				  patcher-logmsg-mail-buffer))
-	 (logmsg-file-name
-	  (symbol-value-in-buffer 'patcher-logmsg-file-name
-				  patcher-logmsg-mail-buffer))
-	 (output-buffer (get-buffer-create "*Patcher-Commit-Output*")))
-    ;; First, make sure the ChangeLogs are saved.
-    (patcher-offer-save-buffers change-logs)
-    ;; Now, construct the commit command by starting with what was specified
-    ;; in this project's options (or read from the user, if the prefix arg was
-    ;; given) and (if subproject files were given) combining the specified
-    ;; files with the relevant ChangeLogs.  If the whole project is being
-    ;; committed, the ChangeLogs will automatically be committed, otherwise we
-    ;; have to specify them explicitly.
-    (let* ((directory (patcher-project-directory project))
-	   (files
-	    (let ((f (symbol-value-in-buffer
-		      'patcher-files
-		      patcher-logmsg-mail-buffer)))
-	      (if (not f)
-		  ""
-		(mapconcat
-		 #'identity
-		 (cons f (mapcar
-			  #'(lambda (buf)
-			      (patcher-file-relative-name
-			       (buffer-file-name buf)
-			       directory))
-			  change-logs)) " ")))))
-      (when arg
-	(setq command (read-shell-command "Commit command: " command)))
-      (setq command (patcher-construct-command
-		     (replace-in-string command "%s"
-					logmsg-file-name t)
-		     files)))
-    ;; Maybe display the commit command, and make sure the user agrees.
-    (when (or (not confirm-commits)
-	      (save-window-excursion
-		(let ((runbuf (get-buffer-create "*Patcher-Commit-Command*")))
-		  (erase-buffer runbuf)
-		  (insert-string (format "Command to run:\n\n%s" command)
-				 runbuf)
-		  (display-buffer runbuf)
-		  (y-or-n-p "Run commit command? "))))
-      ;; Write out the log message and ...
-      (write-region (point-min) (point-max) logmsg-file-name nil 'silent)
-      (erase-buffer output-buffer)
-      ;; ... commit!
-      (shell-command command output-buffer)
-      ;; Record the buffers that we will get rid of later.  Specifically, we
-      ;; will bury the output buffer and kill the log message buffer when we
-      ;; send the patch mail -- i.e. when we are done with this project.  We
-      ;; don't kill the log message buffer now in case the user needs it later
-      ;; -- e.g. if the commit failed and needs to be redone (we try to
-      ;; detect this, but we might not succeed in all cases.).  The reason we
-      ;; kill, not bury, the log message buffer is that it is generated anew
-      ;; for each commit action, and we don't want to accumulate these buffers
-      ;; endlessly. the output buffer is reused each time we execute the
-      ;; commit, so no such problem exists here.
-      (let ((curbuf (current-buffer)))
-	(with-current-buffer patcher-logmsg-mail-buffer
-	  ;; Note that this sets a buffer-local variable in the mail buffer,
-	  ;; not our own buffer.
-	  (setq patcher-commit-logmsg-buffer curbuf)
-	  (if (buffer-live-p output-buffer)
-	      (setq patcher-commit-output-buffer output-buffer))))
-      ;; Try to see if the commit failed.
-      (if (and (buffer-live-p output-buffer)
-	       (with-current-buffer output-buffer
-		 (save-excursion
-		   (goto-char (point-min))
-		   (re-search-forward failed-command-regexp nil t))))
-	  ;; It failed.
-	  (progn
-	    (display-buffer output-buffer)
-	    (message "Error during commit.  Please correct and try again."))
-	;; Otherwise, record the successful commit in the mail message.
-	(with-current-buffer patcher-logmsg-mail-buffer
-	  (save-excursion
-	    (goto-char (point-min))
-	    (when (search-forward mail-header-separator nil t)
-	      (forward-line 1)
-	      (insert (concat (patcher-project-option patcher-project
-						      :committed-notice)
-			      "\n")))))
-	;; Bury the log message (see above).  Remove the log message window
-	;; and display the output buffer.
-	(bury-buffer (current-buffer))
-	(set-window-configuration pre-commit-window-config)
-	(if (buffer-live-p output-buffer)
-	    (display-buffer output-buffer)))
-      )))
-
 (defvar patcher-logmsg-mode-map
   (let ((map (make-sparse-keymap)))
-    (define-key map [(control c) (control c)] 'patcher-logmsg-do-it)
+    (define-key map [(control c) (control p) s] 'patcher-logmsg-insert-subject)
+    (define-key map [(control c) (control p) l]
+      'patcher-logmsg-insert-change-logs)
+    (define-key map [(control c) (control p) c]
+      'patcher-logmsg-insert-compressed-change-logs)
+    (define-key map [(control c) (control p) i] 'patcher-logmsg-init-message)
+    (define-key map [(control c) (control c)] 'patcher-logmsg-commit)
     map))
 
 (defun patcher-logmsg-mode ()
-  "Sets up Patcher-LogMsg major mode.
-Used for editing the log message for `patcher-commit-change'.  To commit
-the change, use \\<patcher-logmsg-mode-map>\\[patcher-logmsg-do-it].
-You're not supposed to use this, unless you know what you're doing."
+  "Major mode for Patcher commit log message management.
+You're not supposed to use this mode manually, unless you know what you're
+doing.
+
+\\{patcher-logmsg-mode-map}"
   (interactive)
   (kill-all-local-variables)
   (setq major-mode 'patcher-logmsg)
   (run-hooks 'patcher-logmsg-mode-hook))
 
 
+
+;; ===========================================================================
+;; The Patcher mail buffer
+;; ===========================================================================
+
+(make-variable-buffer-local
+ (defvar patcher-diff-marker nil
+   ;; Marker indicating the beginning of the diff.
+   ))
+
+(make-variable-buffer-local
+ (defvar patcher-diff-command nil
+   ;; String containing the diff command to use. This string is not supposed
+   ;; to include the files to which the command applies. Only the command
+   ;; itself. This variable is needed because the user has the ability to
+   ;; override the project's command by giving a prefix to
+   ;; `patcher-generate-diff'.
+   ))
+
+(make-variable-buffer-local
+ (defvar patcher-sources nil
+   ;; String containing files/directories command-line specification for the
+   ;; diff command (it will be appended to it). This variable is needed
+   ;; because the user has the ability to override the project's files by
+   ;; calling `patcher-mail-subproject' instead of `patcher-mail'.
+   ))
+
+(make-variable-buffer-local
+ (defvar patcher-change-logs-marker nil
+   ;; Marker indicating the beginning of the ChangeLog entries, when they are
+   ;; separated from the patch.
+   ))
+
+(make-variable-buffer-local
+ (defvar patcher-change-logs nil
+   ;; List of ChangeLog absolute file names. This is computed after the
+   ;; initial diff by `patcher-generate-first-diff'.
+   ))
+
+(make-variable-buffer-local
+ (defvar patcher-pre-commit-window-config nil
+   ;; Window configuration, just prior to beginning a commit operation, so we
+   ;; can get back to it at the appropriate time later.
+   ))
+
+(make-variable-buffer-local
+ (defvar patcher-logmsg-buffer nil
+   ;; Buffer containing the commit log message of the current Patcher mail.
+   ;; This buffer is not killed after the commit operation, but should when
+   ;; the message is sent.
+   ))
+
+(defmacro patcher-with-information (information &rest body)
+  `(save-window-excursion
+     (save-excursion
+       (with-output-to-temp-buffer
+	   " *Patcher Information*"
+	 (set-buffer " *Patcher Information*")
+	 (insert ,information))
+       ,@body)))
+(put 'patcher-with-information 'lisp-indent-function 1)
+
+(defsubst patcher-delete-extent-and-region (extent)
+  ;; Delete EXTENT and the corresponding region.
+  (when extent
+    (delete-region (extent-start-position extent) (extent-end-position extent)
+		   (extent-object extent))
+    (delete-extent extent)
+    ))
+
+(defun patcher-parse-region (&optional min max buffer)
+  ;; Parse a diff output between MIN and MAX in BUFFER. Defaults to point min,
+  ;; point max and current buffer respectively.
+  ;; For each diffed file, create an extent with the following properties:
+  ;; 'patcher-change-log = <absolute filename> for ChangeLog files.
+  ;; 'patcher-source = <absolute filename> for source files.
+  ;; Return non nil if an error occured.
+  (with-current-buffer (or buffer (current-buffer))
+    (let ((file-re1 "^Index: \\(\\S-*\\)");; for archive  diff
+	  (file-re "^\\+\\+\\+ \\(\\S-*\\)");; for standard diff
+	  (basename-re "\\`\\(.*\\)/\\(.*\\)\\'")
+	  (min (or min (point-min)))
+	  (max (or max (point-max)))
+	  change-log file absfile dirname beg end)
+      (save-excursion
+	(goto-char min)
+	(save-excursion
+	  (and (re-search-forward file-re1 max t)
+	       (setq file-re file-re1)))
+	(while (re-search-forward file-re max t)
+	  (setq file (match-string 1))
+	  (if (string-match basename-re file)
+	      (setq dirname  (match-string 1 file))
+	    (setq dirname ""))
+	  (setq absfile (expand-file-name file (default-directory)))
+	  (setq beg (point-at-bol))
+	  (setq end (or (save-excursion
+			  (and (re-search-forward file-re max t)
+			       (point-at-bol)))
+			max))
+	  (setq change-log
+		(with-temp-buffer
+		  (cd (expand-file-name dirname (default-directory)))
+		  (find-change-log)))
+	  (let ((extent (make-extent beg end)))
+	    (set-extent-properties extent '(start-open t duplicable t))
+	    (set-extent-property extent
+				 (if (string= change-log absfile)
+				     'patcher-change-log
+				   'patcher-source)
+				 absfile)))
+	(goto-char min)
+	(re-search-forward (patcher-project-option patcher-project
+			     :failed-command-regexp)
+			   max t))
+      )))
+
+(defun patcher-generate-change-logs (&optional min max buffer)
+  ;; Generate ChangeLog skeletons based on the diff between MIN and MAX in
+  ;; BUFFER. Defaults to point min, point max and current buffer respectively.
+  (with-current-buffer (or buffer (current-buffer))
+    (patcher-with-progression "Generating ChangeLog skeletons"
+      (narrow-to-region (or min (point-min)) (or max (point-max)))
+      (patch-to-change-log
+       (default-directory)
+       :my-name (patcher-project-option patcher-project
+		  :change-logs-user-name)
+       :my-email (patcher-project-option patcher-project
+		   :change-logs-user-mail)
+       :keep-source-files (not (patcher-project-option patcher-project
+				 :kill-source-files-after-diffing))
+       :extent-property 'patcher
+       ;; Check `patcher-mail-buffer' first because if that is non nil, we're
+       ;; in an auxiliary buffer. Otherwise, we're in a Patcher mail one.
+       :extent-property-value (or patcher-mail-buffer (current-buffer)))
+      (widen))
+    ))
+
+(defun patcher-ungenerate-change-logs ()
+  ;; Delete ChangeLog skeletons created by a former call to
+  ;; `patcher-generate-change-logs', in the current Patcher mail buffer.
+  (dolist (change-log (patcher-files-buffers patcher-change-logs 'find))
+    (patcher-delete-extent-and-region
+     (patcher-change-log-extent change-log (current-buffer)))
+    (with-current-buffer change-log (save-buffer))))
+
+(defmacro patcher-map-change-log-extents (&optional buffer &rest body)
+  ;; Map BODY over all extents marking a ChangeLog contents in BUFFER.
+  `(mapcar-extents
+    (lambda (extent) ,@body)
+    nil (or ,buffer (current-buffer)) nil nil nil 'patcher-change-log))
+(put 'patcher-map-change-log-extents 'lisp-indent-function 1)
+
+(defmacro patcher-map-source-extents (&optional buffer &rest body)
+  ;; Map BODY over all extents marking a source contents in BUFFER.
+  `(mapcar-extents
+    (lambda (extent) ,@body)
+    nil (or ,buffer (current-buffer)) nil nil nil 'patcher-source))
+(put 'patcher-map-source-extents 'lisp-indent-function 1)
+
+(defun patcher-change-logs (&optional buffer)
+  ;; Return the list of ChangeLog absolute file names appearing in BUFFER
+  ;; (current buffer by default).
+  (let (change-logs)
+    (patcher-map-change-log-extents buffer
+      (let ((change-log (extent-property extent 'patcher-change-log)))
+	(push change-log change-logs)))
+    change-logs))
+
+(defun patcher-sources (&optional buffer)
+  ;; Return the list of source absolute file names appearing in BUFFER
+  ;; (current buffer by default).
+  (let (sources)
+    (patcher-map-source-extents buffer
+      (let ((source (extent-property extent 'patcher-source)))
+	(push source sources)))
+    sources))
+
+(defun patcher-remove-change-logs (&optional buffer)
+  ;; Remove ChangeLog contents from BUFFER (current buffer by default).
+  (patcher-with-progression "Removing ChangeLog contents"
+    (patcher-map-change-log-extents buffer
+      (patcher-delete-extent-and-region extent))))
+
+(defun patcher-default-diff-prologue (kind)
+  ;; Default function for inserting a diff prologue.
+  (cond ((eq kind 'sources)
+	 (insert name " source patch:\n"
+		 "Diff command:   " source-diff "\n"
+		 "Files affected: " source-files "\n"
+		 "\n")
+	 )
+	((eq kind 'change-logs)
+	 (insert name " ChangeLog patch:\n"
+		 "Diff command:   " change-log-diff "\n"
+		 "Files affected: " change-log-files "\n"
+		 "\n")
+	 )
+	((eq kind 'mixed)
+	 (insert name " patch:\n")
+	 (if (not change-log-diff)
+	     (insert "Diff command:             " source-diff "\n"
+		     "ChangeLog files affected: " change-log-files "\n"
+		     "Source files affected:    " source-files "\n")
+	   (insert "ChangeLog files diff command: " change-log-diff "\n"
+		   "Files affected:               " change-log-files "\n"
+		   "Source files diff command:    " source-diff "\n"
+		   "Files affected:               " source-files "\n"))
+	 (insert "\n")
+	 )
+	))
+
+(defun patcher-diff-all ()
+  ;; Create a global diff with both ChangeLogs and given files, insert it in
+  ;; the current Patcher mail buffer at the patcher-diff-marker position if it
+  ;; succeeded, and create the patcher-diff extent.
+  (patcher-save-buffers (patcher-files-buffers patcher-change-logs))
+  (let ((command
+	 (patcher-command patcher-diff-command
+			  (if patcher-sources
+			      (concat (patcher-files-string
+				       patcher-change-logs)
+				      " "
+				      patcher-sources)
+			    "")))
+	(buffer (patcher-process-output-buffer)))
+    (patcher-with-progression "Generating global diff"
+      (patcher-call-process command buffer))
+    (when (patcher-parse-region nil nil buffer)
+      (display-buffer buffer t)
+      (patcher-error "\
+Error during diff. Please fix the problem and type \
+\\[patcher-generate-diff] to try again."))
+    (save-excursion
+      (goto-char patcher-diff-marker)
+      (let ((font-lock-always-fontify-immediately t))
+	(insert (buffer-substring nil nil buffer)))
+      (set-extent-properties (make-extent patcher-diff-marker (point))
+	'(start-open t patcher-diff t)))
+    ))
+
+(defun patcher-insert-change-logs-verbatim ()
+  ;; Insert ChangeLog contents verbatim in the current Patcher mail buffer,
+  ;; and create the patcher-change-logs extent.
+  (let ((prologue
+	 (patcher-project-option patcher-project :change-logs-prologue)))
+    (patcher-with-progression "Inserting ChangeLog contents"
+      (save-excursion
+	(goto-char patcher-change-logs-marker)
+	(dolist (change-log (patcher-files-buffers patcher-change-logs 'find))
+	  (let ((extent
+		 (patcher-change-log-extent change-log (current-buffer)))
+		(beg (point)))
+	    ;; Force forward slashes (for native Windows). -- APA
+	    (insert "\n")
+	    (and prologue (> (length prologue) 0)
+		 (insert (replace-in-string prologue "%f"
+					    (patcher-file-relative-name
+					     (buffer-file-name change-log)))
+			 "\n\n"))
+	    (insert (extent-string extent))
+	    (set-extent-properties (make-extent beg (point))
+	      `(start-open t patcher-change-log
+			   ,(buffer-file-name change-log)))))
+	(set-extent-properties (make-extent patcher-change-logs-marker (point))
+	  '(start-open t patcher-change-logs t))))
+    ))
+
+(defun patcher-insert-change-logs-diff-prologue (command)
+  ;; Insert a ChangeLog diff prologue at point in current Patcher mail buffer.
+  (let ((function (patcher-project-option patcher-project
+		    :diff-prologue-function)))
+    (when function
+      (let ((name (patcher-project-name patcher-project))
+	    (change-log-files (patcher-files-string patcher-change-logs))
+	    (change-log-diff (patcher-command command "")))
+	(funcall function 'change-logs))
+      )))
+
+(defun patcher-diff-change-logs (command)
+  ;; Create a diff with only ChangeLogs, insert it in the current Patcher mail
+  ;; buffer at the patcher-change-logs-marker position if it succeeded, and
+  ;; create the patcher-change-logs extent.
+  (patcher-save-buffers (patcher-files-buffers patcher-change-logs))
+  (let ((buffer (patcher-process-output-buffer)))
+    (patcher-with-progression "Generating the ChangeLogs diff"
+      (patcher-call-process
+       (patcher-command command (patcher-files-string patcher-change-logs))
+       buffer))
+    (when (patcher-parse-region nil nil buffer)
+      (display-buffer buffer t)
+      (patcher-error "\
+Error during diff. Please fix the problem and type \
+\\[patcher-insert-change-logs] to try again."))
+    ;; #### FIXME: maybe check that all changelogs are diff'ed (meaning the
+    ;; user has not forgotten to update one of them).
+    (save-excursion
+      (goto-char patcher-change-logs-marker)
+      (patcher-insert-change-logs-diff-prologue command)
+      (let ((font-lock-always-fontify-immediately t))
+	(insert (buffer-substring nil nil buffer)))
+      (set-extent-properties (make-extent patcher-change-logs-marker (point))
+	'(start-open t patcher-change-logs t)))
+    ))
+
+(defun patcher-pack-change-logs ()
+  ;; Pack ChangeLog diffs to the change-logs marker in the current Patcher
+  ;; mail buffer, and create the patcher-change-logs extent.
+  (patcher-with-progression "Packing ChangeLog diffs"
+    (save-excursion
+      (goto-char patcher-change-logs-marker)
+      (patcher-insert-change-logs-diff-prologue patcher-diff-command)
+      (patcher-map-change-log-extents nil
+	(let ((contents (extent-string extent))
+	      (change-log (extent-property extent 'patcher-change-log))
+	      (beg (point)))
+	  (patcher-delete-extent-and-region extent)
+	  (insert contents)
+	  (set-extent-properties (make-extent beg (point))
+	    `(start-open t patcher-change-log ,change-log))))
+      (set-extent-properties (make-extent patcher-change-logs-marker (point))
+	`(start-open t patcher-change-logs t)))
+    ))
+
+(defun patcher-extent-error (extent)
+  ;; Look for an error in EXTENT.
+  ;; Update the 'patcher-error property as needed.
+  ;; Return 0 if status is unchanged, 1 if an error appeared, -1 if an error
+  ;; was fixed.
+  (let* ((old-error (if (extent-property extent 'patcher-error) 1 0))
+	 (new-error (if (save-excursion
+			  (goto-char (extent-start-position extent))
+			  (re-search-forward
+			   (patcher-project-option patcher-project
+			     :failed-command-regexp)
+			   (extent-end-position extent) t))
+			1 0))
+	 (error (- new-error old-error)))
+    (cond ((eq error 1)
+	   (set-extent-property extent 'patcher-error t))
+	  ((eq error -1)
+	   (set-extent-property extent 'patcher-error nil)))
+    error))
+
+(defun patcher-convert-change-log-diffs (command)
+  ;; Scan all ChangeLog diffs in the current Patcher mail buffer, and remake
+  ;; them one by one with the proper diff COMMAND, directly in place.
+  (save-excursion
+    (let ((diff-extent (patcher-extent 'patcher-diff))
+	  (errors 0)
+	  change-log cmd beg)
+      ;; #### Don't forget to start-close the diff extent !! A ChangeLog could
+      ;; appear at the beginning of the diff.
+      (set-extent-property diff-extent 'start-open nil)
+      (patcher-with-progression "Regenerating ChangeLog diffs"
+	(patcher-map-change-log-extents nil
+	  ;; #### WARNING: it seems that if I modify the extent contents here,
+	  ;; instead of deleting and recreating it, map(car)-extents goes into
+	  ;; an infinite loop, on all extents over and over again.
+	  (setq change-log (extent-property extent 'patcher-change-log))
+	  (setq cmd (patcher-command command
+				     (patcher-file-relative-name change-log)))
+	  (goto-char (extent-start-position extent))
+	  (setq beg (point))
+	  (patcher-delete-extent-and-region extent)
+	  (patcher-call-process cmd)
+	  (setq extent (make-extent beg (point)))
+	  (set-extent-properties extent
+	    `(start-open t patcher-change-log ,change-log))
+	  (setq errors (+ errors (patcher-extent-error extent)))))
+      (set-extent-property diff-extent 'start-open t)
+      (and (> errors 0)
+	   (set-extent-property (patcher-extent 'patcher-diff)
+				'patcher-error errors)
+	   (patcher-error "\
+Problems during diff.  \
+Please type \\[patcher-insert-change-logs] to try again."))
+      )))
+
+(defun patcher-insert-diff-prologue (command)
+  ;; Insert a prologue at the top of the diff in the current Patcher mail
+  ;; buffer.
+  (let ((function (patcher-project-option patcher-project
+		    :diff-prologue-function)))
+    (when function
+      (let ((extent (patcher-extent 'patcher-diff))
+	    (name (patcher-project-name patcher-project))
+	    (source-diff (patcher-command patcher-diff-command ""))
+	    (source-files (patcher-files-string (patcher-sources)))
+	    (change-log-files (patcher-files-string patcher-change-logs))
+	    (change-log-diff (and (stringp command)
+				  (patcher-command command ""))))
+	(set-extent-property extent 'start-open nil)
+	(save-excursion
+	  (goto-char patcher-diff-marker)
+	  (funcall function (if (symbolp command) command 'mixed)))
+	(set-extent-property extent 'start-open t)
+	))
+    ))
+
+(defun patcher-insert-diff (buffer)
+  ;; Insert the diff created in auxiliary BUFFER, and create the patcher-diff
+  ;; extent. KIND specifies whether the diff is meant to contain sources or
+  ;; ChangeLogs only, or both.
+  (save-excursion
+    (goto-char patcher-diff-marker)
+    (let ((font-lock-always-fontify-immediately t))
+      (insert (buffer-substring nil nil buffer)))
+    (set-extent-properties (make-extent patcher-diff-marker (point))
+      '(start-open t patcher-diff t))
+    ))
+
+(defun patcher-diff-base (buffer)
+  ;; Create the initial diff and deduce the ChangeLog files (these files can't
+  ;; be deduced from the variable `patcher-sources', even when set, because it
+  ;; might contain directory specifications).
+  (patcher-with-progression "Diff'ing the project"
+    (patcher-call-process (patcher-command patcher-diff-command
+					   patcher-sources)
+			  buffer))
+  (when (patcher-parse-region nil nil buffer)
+    (display-buffer buffer t)
+    (patcher-error "\
+Error during diff.  \
+Please fix the problem and type \\[patcher-generate-diff] to try again."))
+  (unless (patcher-sources buffer)
+    (patcher-error "Your source files do not differ from the archive."))
+  (when (patcher-project-option patcher-project :change-logs-updating)
+    (setq patcher-change-logs nil)
+    (patcher-map-source-extents buffer
+      (let* ((file (extent-property extent 'patcher-source))
+	     (change-log (with-temp-buffer
+			   (cd (file-name-directory file))
+			   (find-change-log))))
+	(or (member change-log patcher-change-logs)
+	    (push change-log patcher-change-logs))))))
+
+(defun patcher-generate-diff-1 ()
+  ;; (Re)Create a diff in the current Patcher mail buffer.
+  (let ((buffer (patcher-process-output-buffer))
+	(updating (patcher-project-option patcher-project
+		    :change-logs-updating))
+	(appearance (patcher-project-option patcher-project
+		      :change-logs-appearance))
+	(regenerate (or (patcher-extent 'patcher-diff) patcher-change-logs)))
+    ;; Maybe clean up the place for a new diff.
+    (and regenerate
+	 (patcher-delete-extent-and-region (patcher-extent 'patcher-diff)))
+    (if (not updating)
+	;; We don't do ChangeLogs: just (re)diff the project.
+	(progn
+	  (patcher-diff-base buffer)
+	  (patcher-insert-diff buffer)
+	  (patcher-insert-diff-prologue 'sources)
+	  (patcher-message "\
+To commit your changes, type \\[patcher-commit-change]."))
+      ;; We do ChangeLogs, so deal with the formatting.
+      (cond ((eq updating 'automatic)
+	     ;; In the "automatic" case, ChangeLog contents insertion is
+	     ;; postponed until the user has edited the skeletons. If no files
+	     ;; were specified, we have a chance to check that the project is
+	     ;; up to date: if a ChangeLog appears in the diff, the project
+	     ;; needs to be updated first. Note that this does not catch all
+	     ;; cases though.
+	     (cond ((or (eq appearance 'verbatim)
+			(eq appearance 'packed))
+		    (let ((generate-change-logs t)
+			  (change-logs-extent
+			   (patcher-extent 'patcher-change-logs)))
+		      (when regenerate
+			(patcher-with-information
+			    (format "\
+ChangeLog skeletons for this patch have already been generated%s.
+
+If you answer `yes' to the question below, both the diff and the ChangeLog
+entries will be regenerated.  This means that current ChangeLog entries will be
+lost.  If otherwise your answer is `no', only the diff will be regenerated."
+				    (if change-logs-extent " and inserted" ""))
+			  (setq generate-change-logs (yes-or-no-p "\
+Regenerate ChangeLog skeletons ? ")))
+			(when generate-change-logs
+			  (patcher-delete-extent-and-region change-logs-extent)
+			  (patcher-ungenerate-change-logs)))
+		      (patcher-diff-base buffer)
+		      (when (if regenerate
+				(and generate-change-logs
+				     (not patcher-sources)
+				     (patcher-change-logs buffer))
+			      (and (not patcher-sources)
+				   (patcher-change-logs buffer)))
+			(patcher-error "\
+It seems that your project is out of date.  \
+Please update it before running Patcher."))
+		      ;; ChangeLogs appear outside the patch, so we can insert
+		      ;; the diff right now, and then generate the skeletons.
+		      (patcher-insert-diff buffer)
+		      (patcher-insert-diff-prologue 'sources)
+		      (if generate-change-logs
+			  (progn
+			    (patcher-generate-change-logs patcher-diff-marker
+							  (extent-end-position
+							   (patcher-extent
+							    'patcher-diff)))
+			    (patcher-message "\
+Please annotate the ChangeLog skeletons, \
+and type \\[patcher-insert-change-logs] to %s them."
+					     (if (eq appearance 'verbatim)
+						 "insert"
+					       "diff")))
+			;; not generate-change-logs
+			(if change-logs-extent
+			    (patcher-message "\
+To commit your changes, type \\[patcher-commit-change].")
+			  (patcher-message "\
+Please type \\[patcher-insert-change-logs] to %s the ChangeLogs"
+					   (if (eq appearance 'verbatim)
+					       "insert"
+					     "diff")))
+			)))
+		   ((eq appearance 'patch)
+		    (let ((generate-change-logs t))
+		      (when regenerate
+			(patcher-with-information "\
+ChangeLog skeletons for this patch have already been generated.
+
+If you answer `yes' to the question below, the ChangeLog entries will be
+regenerated.  This means that current ones will be lost.  If otherwise your
+answer is `no', it is assumed that you have edited the skeletons, and the
+project will be rediff'ed with them."
+			  (setq generate-change-logs (yes-or-no-p "\
+Regenerate ChangeLog skeletons ? ")))
+			(and generate-change-logs
+			     (patcher-ungenerate-change-logs)))
+		      (if generate-change-logs
+			  (progn
+			    (patcher-diff-base buffer)
+			    (when (and (not patcher-sources)
+				       (patcher-change-logs buffer))
+			      (patcher-error "\
+It seems that your project is out of date.  \
+Please update it before running Patcher."))
+			    ;; ChangeLogs must appear in the patch, so there's
+			    ;; no point in inserting the diff right now. It
+			    ;; needs to be redone afterwards.
+			    (patcher-generate-change-logs nil nil buffer)
+			    (patcher-message "\
+Please annotate the ChangeLog skeletons, \
+and type \\[patcher-insert-change-logs] to create the whole diff.")
+			    )
+			;; not generate-change-logs
+			;; ChangeLogs are supposed to be written, so
+			;; everything goes as if we were in a 'manual case:
+			(let ((command (patcher-project-option patcher-project
+					 :change-logs-diff-command)))
+			  (cond ((eq command 'diff)
+				 (patcher-diff-all)
+				 (patcher-insert-diff-prologue 'mixed)
+				 )
+				((stringp command)
+				 (patcher-diff-all)
+				 (patcher-convert-change-log-diffs command)
+				 (patcher-insert-diff-prologue command)
+				 )
+				(t
+				 (patcher-error "\
+invalid `change-logs-diff-command' option: %s" command))
+				))
+			(patcher-message "\
+To commit your changes, type \\[patcher-commit-change].")
+			)))
+		   ((not appearance)
+		    (let ((generate-change-logs t))
+		      (when regenerate
+			(patcher-with-information "\
+ChangeLog skeletons for this patch have already been generated.
+
+If you answer `yes' to the question below, the ChangeLog entries will be
+regenerated.  This means that current ones will be lost.  If otherwise your
+answer is `no', the current CHangeLog entries won't be touched."
+			  (setq generate-change-logs (yes-or-no-p "\
+Regenerate ChangeLog skeletons ? ")))
+			(and generate-change-logs
+			     (patcher-ungenerate-change-logs)))
+		      (if generate-change-logs
+			  (progn
+			    (patcher-diff-base buffer)
+			    (when (and (not patcher-sources)
+				       (patcher-change-logs buffer))
+			      (patcher-error "\
+It seems that your project is out of date.  \
+Please update it before running Patcher."))
+			    ;; ChangeLogs do not appear, so we can insert the
+			    ;; diff right now, and then generate the
+			    ;; skeletons.
+			    (patcher-insert-diff buffer)
+			    (patcher-insert-diff-prologue 'sources)
+			    (patcher-generate-change-logs patcher-diff-marker
+							  (extent-end-position
+							   (patcher-extent
+							    'patcher-diff)))
+			    (message "\
+Please don't forget to annotate the ChangeLog skeletons.")
+			    )
+			;; not generate-change-logs
+			(patcher-diff-base buffer)
+			(patcher-remove-change-logs buffer)
+			(patcher-insert-diff buffer)
+			(patcher-insert-diff-prologue 'sources)
+			(patcher-message "\
+To commit your changes, type \\[patcher-commit-change].")
+			)))
+		   (t
+		    (patcher-error "\
+invalid `change-logs-appearance' option: %s" appearance)))
+	     )
+	    ((eq updating 'manual)
+	     ;; In the "manual" case, ChangeLogs are supposed to be already
+	     ;; written, so their insertion does not have to be postponed. If
+	     ;; no files were specified, we have a chance to check that
+	     ;; ChangeLogs /really/ are up to date: the diff output should
+	     ;; contain all ChangeLog entries.
+	     (patcher-diff-base buffer)
+	     (when (and (not patcher-sources)
+			(not (equal (patcher-change-logs buffer)
+				    patcher-change-logs)))
+	       (patcher-error "\
+Some ChangeLog files are not updated. \
+Please update them before running Patcher."))
+	     (cond ((eq appearance 'verbatim)
+		    ;; #### NOTE: when ChangeLog entries are part of the diff,
+		    ;; we could try to convert the diff to a verbatim version
+		    ;; instead of calling
+		    ;; `patcher-insert-change-logs-verbatim'.
+		    (patcher-remove-change-logs buffer)
+		    (patcher-insert-diff buffer)
+		    (patcher-insert-diff-prologue 'sources)
+		    (or regenerate (patcher-insert-change-logs-verbatim)))
+		   ((eq appearance 'packed)
+		    (let ((command (patcher-project-option patcher-project
+				     :change-logs-diff-command)))
+		      (cond ((eq command 'diff)
+			     ;; We use the same diff command:
+			     (if (not patcher-sources)
+				 ;; All ChangeLogs appear in the diff. We can
+				 ;; just move them to a pack.
+				 (progn
+				   (and regenerate
+					(patcher-remove-change-logs buffer))
+				   (patcher-insert-diff buffer)
+				   (patcher-insert-diff-prologue 'sources)
+				   (or regenerate
+				       (patcher-pack-change-logs)))
+			       ;; Otherwise, some ChangeLogs may not be there,
+			       ;; so rediff them all.
+			       (patcher-remove-change-logs buffer)
+			       (patcher-insert-diff buffer)
+			       (patcher-insert-diff-prologue 'sources)
+			       (or regenerate
+				   (patcher-diff-change-logs
+				    patcher-diff-command)))
+			     )
+			    ((stringp command)
+			     ;; The diff command is different. We have to
+			     ;; (re)diff them anyway.
+			     (patcher-remove-change-logs buffer)
+			     (patcher-insert-diff buffer)
+			     (patcher-insert-diff-prologue 'sources)
+			     (or regenerate
+				 (patcher-diff-change-logs command))
+			     )
+			    (t
+			     (patcher-error "\
+invalid `change-logs-diff-command' option: %s" command))
+			    )))
+		   ((eq appearance 'patch)
+		    (let ((command (patcher-project-option patcher-project
+				     :change-logs-diff-command)))
+		      (cond ((eq command 'diff)
+			     (if patcher-sources
+				 ;; Some ChangeLog entries might not be
+				 ;; present, so we must rediff the whole
+				 ;; stuff.
+				 (progn
+				   (patcher-diff-all)
+				   (patcher-insert-diff-prologue 'mixed))
+			       ;; Otherwise, the ChangeLog entries are in the
+			       ;; diff.
+			       (patcher-insert-diff buffer)
+			       (patcher-insert-diff-prologue 'mixed)
+			       )
+			     )
+			    ((stringp command)
+			     (if (not patcher-sources)
+				 (progn
+				   (patcher-insert-diff buffer)
+				   (patcher-insert-diff-prologue command)