;;; ediff-mult.el --- support for multi-file/multi-buffer processing in Ediff;; Copyright (C) 1995, 96, 97, 98, 99, 2000, 01, 02 Free Software Foundation, Inc.;; Author: Michael Kifer <kifer@cs.stonybrook.edu>;; This file is part of GNU Emacs.;; GNU Emacs is free software; you can redistribute it and/or modify;; it under the terms of the GNU General Public License as published by;; the Free Software Foundation; either version 2, or (at your option);; any later version.;; GNU Emacs is distributed in the hope that it will be useful,;; but WITHOUT ANY WARRANTY; without even the implied warranty of;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the;; GNU General Public License for more details.;; You should have received a copy of the GNU General Public License;; along with GNU Emacs; see the file COPYING. If not, write to the;; Free Software Foundation, Inc., 59 Temple Place - Suite 330,;; Boston, MA 02111-1307, USA.;;; Commentary:;; Users are encouraged to add functionality to this file.;; The present file contains all the infrastructure needed for that.;;;; Generally, to implement a new multisession capability within Ediff,;; you need to tell it ;;;; 1. How to display the session group buffer.;; This function must indicate which Ediff sessions are active (+) and;; which are finished (-).;; See ediff-redraw-directory-group-buffer for an example.;; In all likelihood, ediff-redraw-directory-group-buffer can be used;; directly or after a small modification.;; 2. What action to take when the user clicks button 2 or types v,e, or;; RET. See ediff-filegroup-action.;; 3. Provide a list of pairs or triples of file names (or buffers,;; depending on the particular Ediff operation you want to invoke);; in the following format:;; (HEADER (nil nil (obj1 nil) (obj2 nil) (obj3 nil));; (...) ...);; The function ediff-make-new-meta-list-element can be used to create;; 2nd and subsequent elements of that list (i.e., after the;; description header). See ediff-make-new-meta-list-element for the;; explanation of the two nil placeholders in such elements.;;;; There is API for extracting the components of the members of the;; above list. Search for `API for ediff-meta-list' for details.;;;; HEADER must be a list of SIX elements (nil or string):;; (regexp metaobj1 metaobj2 metaobj3 merge-save-buffer;; comparison-function);; The function ediff-redraw-registry-buffer displays the ;; 1st - 4th of these in the registry buffer. ;; For some jobs some of the members of the header might be nil.;; The meaning of metaobj1, metaobj2, and metaobj3 depend on the job.;; Typically these are directories where the files to be compared are;; found.;; Also, keep in mind that the function ediff-prepare-meta-buffer;; (which see) prepends the session group buffer to the descriptor, so;; the descriptor becomes 7-long.;; Ediff expects that your function (in 2 above) will arrange to;; replace this prepended nil (via setcar) with the actual ediff;; control buffer associated with an appropriate Ediff session.;; This is arranged through internal startup hooks that can be passed;; to any of Ediff major entries (such as ediff-files, epatch, etc.).;; See how this is done in ediff-filegroup-action.;;;; Session descriptions are of the form;; (nil nil (obj1 . nil) (obj2 . nil) (obj3 . nil));; which describe the objects relevant to the session.;; Use ediff-make-new-meta-list-element to create these things.;; Usually obj1/2/3 are names of files, but they may also be other;; things for some jobs. For instance, obj3 is nil for jobs that;; involve only two files. For patch jobs, obj2 and obj3 are markers;; that specify the patch corresponding to the file;; (whose name is obj1).;; The nil's are placeholders, which are used internally by ediff.;; 4. Write a function that makes a call to ediff-prepare-meta-buffer;; passing all this info. ;; You may be able to use ediff-directories-internal as a template.;; 5. If you intend to add several related pieces of functionality,;; you may want to keep the function in 4 as an internal version;; and then write several top-level interactive functions that call it;; with different parameters.;; See how ediff-directories, ediff-merge-directories, and;; ediff-merge-directories-with-ancestor all use;; ediff-directories-internal. ;;;; A useful addition here could be session groups selected by patterns;; (which are different in each directory). For instance, one may want to;; compare files of the form abc{something}.c to files old{something}.d;; which may be in the same or different directories. Or, one may want to;; compare all files of the form {something} to files of the form {something}~.;;;; Implementing this requires writing an collating function, which should pair;; up appropriate files. It will also require a generalization of the;; functions that do the layout of the meta- and differences buffers and of;; ediff-filegroup-action.;;; Code:(provide'ediff-mult)(defgroupediff-multnil"Multi-file and multi-buffer processing in Ediff":prefix"ediff-":group'ediff);; compiler pacifier(eval-when-compile(let((load-path(cons(expand-file-name".")load-path)))(or(featurep'ediff-init)(load"ediff-init.el"nilnil'nosuffix))(or(featurep'ediff-util)(load"ediff-util.el"nilnil'nosuffix))));; end pacifier(require'ediff-init)(require'ediff-util);; meta-buffer(ediff-defvar-localediff-meta-buffernil"")(ediff-defvar-localediff-parent-meta-buffernil"");; the registry buffer(defvarediff-registry-buffernil)(defconstediff-meta-buffer-message"This is an Ediff Session Group Panel: %sUseful commands: button2, v, or RET over session record: start that Ediff session M:\tin sessions invoked from here, brings back this group panel R:\tdisplay the registry of active Ediff sessions h:\tmark session for hiding (toggle) x:\thide marked sessions; with prefix arg: unhide m:\tmark session for a non-hiding operation (toggle) uh/um:\tunmark all sessions marked for hiding/operation n,SPC:\tnext session p,DEL:\tprevious session E:\tbrowse Ediff on-line manual T:\ttoggle truncation of long file names q:\tquit this session group")(ediff-defvar-localediff-meta-buffer-mapnil"The keymap for the meta buffer.")(defvarediff-dir-diffs-buffer-map(make-sparse-keymap)"The keymap to be installed in the buffer showing differences betweendirectories.");; Variable specifying the action to take when the use invokes ediff in the;; meta buffer. This is usually ediff-registry-action or ediff-filegroup-action(ediff-defvar-localediff-meta-action-functionnil"");; Tells ediff-update-meta-buffer how to redraw it(ediff-defvar-localediff-meta-redraw-functionnil"");; Tells ediff-filegroup-action and similar procedures how to invoke Ediff for;; the sessions in a given session group(ediff-defvar-localediff-session-action-functionnil"")(ediff-defvar-localediff-metajob-namenil"");; buffer used to collect custom diffs from individual sessions in the group(ediff-defvar-localediff-meta-diff-buffernil"");; history var to use for filtering groups(defvarediff-filtering-regexp-historynil"");; This has the form ((meta-buf regexp dir1 dir2 dir3 merge-auto-store-dir);; (ctl-buf session-status (file1 . eq-status) (file2 . eq-status) (file3;; . eq-status)) (ctl-buf session-status (file1 . eq-status) (file2;; . eq-status)) ...);; If ctl-buf is nil, the file-pair hasn't processed yet. If it is;; killed-buffer object, the file pair has been processed. If it is a live;; buffer, this means ediff is still working on the pair.;; Eq-status of a file is t if the file equals some other file in the same;; group.(ediff-defvar-localediff-meta-listnil"")(ediff-defvar-localediff-meta-session-numbernil"");; the difference list between directories in a directory session group(ediff-defvar-localediff-dir-difference-listnil"")(ediff-defvar-localediff-dir-diffs-buffernil"");; The registry of Ediff sessions. A list of control buffers.(defvarediff-session-registrynil)(defcustomediff-meta-truncate-filenamest"*If non-nil, truncate long file names in the session group buffers.This can be toggled with `ediff-toggle-filename-truncation'.":type'hook:group'ediff-mult)(defcustomediff-registry-setup-hooknil"*Hooks run just after the registry control panel is set up.":type'hook:group'ediff-mult)(defcustomediff-before-session-group-setup-hooksnil"*Hooks to run before Ediff arranges the window for group-level operations.It is used by commands such as ediff-directories.This hook can be used to save the previous window config, which can be restoredon ediff-quit, ediff-suspend, or ediff-quit-session-group-hook.":type'hook:group'ediff-hook)(defcustomediff-after-session-group-setup-hooknil"*Hooks run just after a meta-buffer controlling a session group, such asediff-directories, is run.":type'hook:group'ediff-mult)(defcustomediff-quit-session-group-hooknil"*Hooks run just before exiting a session group.":type'hook:group'ediff-mult)(defcustomediff-show-registry-hooknil"*Hooks run just after the registry buffer is shown.":type'hook:group'ediff-mult)(defcustomediff-show-session-group-hook'(delete-other-windows)"*Hooks run just after a session group buffer is shown.":type'hook:group'ediff-mult)(defcustomediff-meta-buffer-keymap-setup-hooknil"*Hooks run just after setting up the ediff-meta-buffer-map.This keymap controls key bindings in the meta buffer and is a local variable.This means that you can set different bindings for different kinds of metabuffers.":type'hook:group'ediff-mult);; Buffer holding the multi-file patch. Local to the meta buffer(ediff-defvar-localediff-meta-patchbufernil"");;; API for ediff-meta-list;; A meta-list is either ediff-meta-list, which contains a header and the list;; of ediff sessions or ediff-dir-difference-list, which is a header followed;; by the list of differences among the directories (i.e., files that are not;; in all directories). The header is the same in all meta lists, but the rest;; is different.;; Structure of the meta-list:;; (HEADER SESSION1 SESSION2 ...);; HEADER: (GROUP-BUF REGEXP OBJA OBJB OBJC SAVE-DIR COMPARISON-FUNC);; OBJA - first directory;; OBJB - second directory;; OBJC - third directory;; SESSION1/2/... are described below;; group buffer/regexp(defsubstediff-get-group-buffer(meta-list)(nth0(carmeta-list)))(defsubstediff-get-group-regexp(meta-list)(nth1(carmeta-list)));; group objects(defsubstediff-get-group-objA(meta-list)(nth2(carmeta-list)))(defsubstediff-get-group-objB(meta-list)(nth3(carmeta-list)))(defsubstediff-get-group-objC(meta-list)(nth4(carmeta-list)))(defsubstediff-get-group-merge-autostore-dir(meta-list)(nth5(carmeta-list)))(defsubstediff-get-group-comparison-func(meta-list)(nth6(carmeta-list)));; ELT is a session meta descriptor (what is being preserved as;; 'ediff-meta-info);; The structure is: (SESSION-CTL-BUFFER STATUS OBJA OBJB OBJC);; STATUS is ?I (hidden or invalid), ?* (marked for operation), ?H (hidden);; nil (nothing);; OBJA/B/C is (FILENAME EQSTATUS);; EQSTATUS is ?= or nil (?= means that this file is equal to some other;; file in this session);; session buffer(defsubstediff-get-session-buffer(elt)(nth0elt))(defsubstediff-get-session-status(elt)(nth1elt))(defsubstediff-set-session-status(session-infonew-status)(setcar(cdrsession-info)new-status));; session objects(defsubstediff-get-session-objA(elt)(nth2elt))(defsubstediff-get-session-objB(elt)(nth3elt))(defsubstediff-get-session-objC(elt)(nth4elt));; Take the "name" component of the object into acount. ObjA/C/B is of the form;; (name . equality-indicator)(defsubstediff-get-session-objA-name(elt)(car(nth2elt)))(defsubstediff-get-session-objB-name(elt)(car(nth3elt)))(defsubstediff-get-session-objC-name(elt)(car(nth4elt)));; equality indicators(defsubstediff-get-file-eqstatus(elt)(nth1elt))(defsubstediff-set-file-eqstatus(eltvalue)(setcar(cdrelt)value));; Create a new element for the meta list out of obj1/2/3, which usually are;; files;;;; The first nil in such an element is later replaced with the session buffer.;; The second nil is reserved for session status.;;;; Also, session objects A/B/C are turned into lists of the form (obj nil).;; This nil is a placeholder for eq-indicator. It is either nil or =.;; If it is discovered that this file is = to some other;; file in the same session, eq-indicator is changed to `='.;; Curently, the eq-indicator is used only for 2 and 3-file jobs.(defunediff-make-new-meta-list-element(obj1obj2obj3)(listnilnil(listobj1nil)(listobj2nil)(listobj3nil)));; Constructs a meta list header.;; OBJA, OBJB, OBJC are usually directories involved, but can be different for;; different jobs. For instance, multifile patch has only OBJA, which is the;; patch buffer.(defunediff-make-new-meta-list-header(regexpobjAobjBobjCmerge-auto-store-dircomparison-func)(listregexpobjAobjBobjCmerge-auto-store-dircomparison-func));; The activity marker is either or + (active session, i.e., ediff is currently;; run in it), or - (finished session, i.e., we've ran ediff in it and then;; exited). Return nil, if session is neither active nor finished(defunediff-get-session-activity-marker(session)(let((session-buf(ediff-get-session-buffersession)))(cond((nullsession-buf)nil); virgin session((ediff-buffer-live-psession-buf)?+);active session(t?-))));; checks if the session is a meta session(defunediff-meta-session-p(session-info)(and(stringp(ediff-get-session-objA-namesession-info))(file-directory-p(ediff-get-session-objA-namesession-info))(stringp(ediff-get-session-objB-namesession-info))(file-directory-p(ediff-get-session-objB-namesession-info))(if(stringp(ediff-get-session-objC-namesession-info))(file-directory-p(ediff-get-session-objC-namesession-info))t)));; set up the keymap in the meta buffer(defunediff-setup-meta-map()(setqediff-meta-buffer-map(make-sparse-keymap))(suppress-keymapediff-meta-buffer-map)(define-keyediff-meta-buffer-map"q"'ediff-quit-meta-buffer)(define-keyediff-meta-buffer-map"T"'ediff-toggle-filename-truncation)(define-keyediff-meta-buffer-map"R"'ediff-show-registry)(define-keyediff-meta-buffer-map"E"'ediff-documentation)(define-keyediff-meta-buffer-map"v"ediff-meta-action-function)(define-keyediff-meta-buffer-map"\C-m"ediff-meta-action-function)(define-keyediff-meta-buffer-map" "'ediff-next-meta-item)(define-keyediff-meta-buffer-map"n"'ediff-next-meta-item)(define-keyediff-meta-buffer-map"\C-?"'ediff-previous-meta-item)(define-keyediff-meta-buffer-map"p"'ediff-previous-meta-item)(define-keyediff-meta-buffer-map[delete]'ediff-previous-meta-item)(define-keyediff-meta-buffer-map[backspace]'ediff-previous-meta-item)(or(ediff-one-filegroup-metajob)(progn(define-keyediff-meta-buffer-map"="nil)(define-keyediff-meta-buffer-map"=="'ediff-meta-mark-equal-files)(define-keyediff-meta-buffer-map"=m"'ediff-meta-mark-equal-files)(define-keyediff-meta-buffer-map"=h"'ediff-meta-mark-equal-files)))(ifediff-no-emacs-help-in-control-buffer(define-keyediff-meta-buffer-map"\C-h"'ediff-previous-meta-item))(ifediff-emacs-p(define-keyediff-meta-buffer-map[mouse-2]ediff-meta-action-function)(define-keyediff-meta-buffer-map[button2]ediff-meta-action-function))(use-local-mapediff-meta-buffer-map);; modify ediff-meta-buffer-map here(run-hooks'ediff-meta-buffer-keymap-setup-hook))(defunediff-meta-mode()"This mode controls all operations on Ediff session groups.It is entered through one of the following commands: `ediff-directories' `edirs' `ediff-directories3' `edirs3' `ediff-merge-directories' `edirs-merge' `ediff-merge-directories-with-ancestor' `edirs-merge-with-ancestor' `ediff-directory-revisions' `edir-revisions' `ediff-merge-directory-revisions' `edir-merge-revisions' `ediff-merge-directory-revisions-with-ancestor' `edir-merge-revisions-with-ancestor'Commands:\\{ediff-meta-buffer-map}"(kill-all-local-variables)(setqmajor-mode'ediff-meta-mode)(setqmode-name"MetaEdiff"));; the keymap for the buffer showing directory differences(suppress-keymapediff-dir-diffs-buffer-map)(define-keyediff-dir-diffs-buffer-map"q"'ediff-bury-dir-diffs-buffer)(define-keyediff-dir-diffs-buffer-map" "'next-line)(define-keyediff-dir-diffs-buffer-map"n"'next-line)(define-keyediff-dir-diffs-buffer-map"\C-?"'previous-line)(define-keyediff-dir-diffs-buffer-map"p"'previous-line)(define-keyediff-dir-diffs-buffer-map"C"'ediff-dir-diff-copy-file)(ifediff-emacs-p(define-keyediff-dir-diffs-buffer-map[mouse-2]'ediff-dir-diff-copy-file)(define-keyediff-dir-diffs-buffer-map[button2]'ediff-dir-diff-copy-file))(define-keyediff-dir-diffs-buffer-map[delete]'previous-line)(define-keyediff-dir-diffs-buffer-map[backspace]'previous-line)(defunediff-next-meta-item(count)"Move to the next item in Ediff registry or session group buffer.Moves in circular fashion. With numeric prefix arg, skip this many items."(interactive"p")(orcount(setqcount1))(let(overl)(while(<0count)(setqcount(1-count))(ediff-next-meta-item1)(setqoverl(ediff-get-meta-overlay-at-pos(point)));; skip invisible ones(while(andoverl(ediff-overlay-getoverl'invisible))(ediff-next-meta-item1)(setqoverl(ediff-get-meta-overlay-at-pos(point)))))));; Move to the next meta item(defunediff-next-meta-item1()(let(pos)(setqpos(ediff-next-meta-overlay-start(point)))(ifpos(goto-charpos))(if(eqediff-metajob-name'ediff-registry)(if(and(ediff-get-meta-info(current-buffer)pos'noerror)(search-forward"*Ediff"nilt))(skip-chars-backward"a-zA-Z*"))(if(>(skip-chars-forward"-+?H* \t0-9")0)(backward-char1)))))(defunediff-previous-meta-item(count)"Move to the previous item in Ediff registry or session group buffer.Moves in circular fashion. With numeric prefix arg, skip this many items."(interactive"p")(orcount(setqcount1))(let(overl)(while(<0count)(setqcount(1-count))(ediff-previous-meta-item1)(setqoverl(ediff-get-meta-overlay-at-pos(point)));; skip invisible ones(while(andoverl(ediff-overlay-getoverl'invisible))(ediff-previous-meta-item1)(setqoverl(ediff-get-meta-overlay-at-pos(point)))))))(defunediff-previous-meta-item1()(let(pos)(setqpos(ediff-previous-meta-overlay-start(point)));;; ;; skip deleted;;; (while (ediff-get-session-status;;; (ediff-get-meta-info (current-buffer) pos 'noerror));;; (setq pos (ediff-previous-meta-overlay-start pos)))(ifpos(goto-charpos))(if(eqediff-metajob-name'ediff-registry)(if(and(ediff-get-meta-info(current-buffer)pos'noerror)(search-forward"*Ediff"nilt))(skip-chars-backward"a-zA-Z*"))(if(>(skip-chars-forward"-+?H* \t0-9")0)(backward-char1)))))(defsubstediff-add-slash-if-directory(dirfile)(if(file-directory-p(concatdirfile))(file-name-as-directoryfile)file))(defunediff-toggle-filename-truncation()"Toggle truncation of long file names in session group buffers.Set `ediff-meta-truncate-filenames' variable if you want to change the defaultbehavior."(interactive)(setqediff-meta-truncate-filenames(notediff-meta-truncate-filenames))(ediff-update-meta-buffer(current-buffer)'must-redraw));; These are used to encode membership of files in directory1/2/3;; Membership code of a file is a product of codes for the directories where;; this file is in(defvarediff-membership-code12)(defvarediff-membership-code23)(defvarediff-membership-code35)(defvarediff-product-of-memcodes(*ediff-membership-code1ediff-membership-code2ediff-membership-code3));; DIR1, DIR2, DIR3 are directories. DIR3 can be nil.;; OUTPUT-DIR is a directory for auto-storing the results of merge jobs.;; Can be nil.;; REGEXP is a regexp used to filter out files in the directories.;; If a file is a directory in dir1 but not dir2 (or vice versa), it is not;; included in the intersection. However, a regular file that is a dir in dir3;; is included, since dir3 files are supposed to be ancestors for merging.;; If COMPARISON-FUNC is given, use it. Otherwise, use string=;;;; Returns a list of the form:;; (COMMON-PART DIFF-LIST);; COMMON-PART is car and DIFF-LIST is cdr.;;;; COMMON-PART is of the form:;; (META-HEADER (f1 f2 f3) (f1 f2 f3) ...);; f3 can be nil if intersecting only 2 directories.;; Each triple (f1 f2 f3) represents the files to be compared in the;; corresponding ediff subsession.;;;; DIFF-LIST is of the form:;; (META-HEADER (file . num) (file . num)...);; where num encodes the set of dirs where the file is found:;; 2 - only dir1; 3 - only dir2; 5 - only dir3; 6 - dir1&2; 10 - dir1&3; etc.;; META-HEADER:;; Contains the meta info about this ediff operation;; (regexp dir1 dir2 dir3 merge-auto-store-dir comparison-func);; Later the meta-buffer is prepended to this list.;;;; Some operations might use a different meta header. For instance,;; ediff-multifile-patch doesn't have dir2 and dir3, and regexp,;; comparison-func don't apply.;;(defunediff-intersect-directories(jobnameregexpdir1dir2&optionaldir3merge-autostore-dircomparison-func)(setqcomparison-func(orcomparison-func'string=))(let(lis1lis2lis3commonauxdir1auxdir2auxdir3common-partdifflist)(setqauxdir1(file-name-as-directorydir1)lis1(directory-filesauxdir1nilregexp)lis1(delete"."lis1)lis1(delete".."lis1)lis1(mapcar(lambda(elt)(ediff-add-slash-if-directoryauxdir1elt))lis1)auxdir2(file-name-as-directorydir2)lis2(mapcar(lambda(elt)(ediff-add-slash-if-directoryauxdir2elt))(directory-filesauxdir2nilregexp)))(if(stringpdir3)(setqauxdir3(file-name-as-directorydir3)lis3(mapcar(lambda(elt)(ediff-add-slash-if-directoryauxdir3elt))(directory-filesauxdir3nilregexp))))(if(ediff-nonempty-string-pmerge-autostore-dir)(setqmerge-autostore-dir(file-name-as-directorymerge-autostore-dir)))(setqcommon(ediff-intersectionlis1lis2comparison-func));; In merge with ancestor jobs, we don't intersect with lis3.;; If there is no ancestor, we'll offer to merge without the ancestor.;; So, we intersect with lis3 only when we are doing 3-way file comparison(if(andlis3(ediff-comparison-metajob3jobname))(setqcommon(ediff-intersectioncommonlis3comparison-func)));; copying is needed because sort sorts via side effects(setqcommon(sort(ediff-copy-listcommon)'string-lessp));; compute difference list(setqdifflist(ediff-set-difference(ediff-union(ediff-unionlis1lis2comparison-func)lis3comparison-func)commoncomparison-func)difflist(delete"."difflist);; copying is needed because sort sorts via side effectsdifflist(sort(ediff-copy-list(delete".."difflist))'string-lessp))(setqdifflist(mapcar(lambda(elt)(conselt1))difflist));; check for files belonging to lis1/2/3;; Each elt is of the norm (file . number);; Number encodes the directories to which file belongs.;; It is a product of a subset of ediff-membership-code1=2,;; ediff-membership-code2=3, and ediff-membership-code3=5.;; If file belongs to dir 1 only, the membership code is 2.;; If it is in dir1 and dir3, then the membership code is 2*5=10;;; if it is in dir1 and dir2, then the membership code is 2*3=6, etc.(mapcar(lambda(elt)(if(member(carelt)lis1)(setcdrelt(*(cdrelt)ediff-membership-code1)))(if(member(carelt)lis2)(setcdrelt(*(cdrelt)ediff-membership-code2)))(if(member(carelt)lis3)(setcdrelt(*(cdrelt)ediff-membership-code3))))difflist)(setqdifflist(cons;; diff metalist header(ediff-make-new-meta-list-headerregexpauxdir1auxdir2auxdir3merge-autostore-dircomparison-func)difflist))(setqcommon-part(cons;; metalist header(ediff-make-new-meta-list-headerregexpauxdir1auxdir2auxdir3merge-autostore-dircomparison-func)(mapcar(lambda(elt)(ediff-make-new-meta-list-element(concatauxdir1elt)(concatauxdir2elt)(iflis3(progn;; The following is done because: In merging with;; ancestor, we don't intersect with lis3. So, it is;; possible that elt is a file in auxdir1/2 but a;; directory in auxdir3 Or elt may not exist in auxdir3 at;; all. In the first case, we add a slash at the end. In;; the second case, we insert nil.(setqelt(ediff-add-slash-if-directoryauxdir3elt))(if(file-exists-p(concatauxdir3elt))(concatauxdir3elt))))))common)));; return result(conscommon-partdifflist)));; find directory files that are under revision. Include subdirectories, since;; we may visit them recursively. DIR1 is the directory to inspect.;; MERGE-AUTOSTORE-DIR is the directory where to auto-store the results of;; merges. Can be nil.(defunediff-get-directory-files-under-revision(jobnameregexpdir1&optionalmerge-autostore-dir)(let(lis1eltcommonauxdir1)(setqauxdir1(file-name-as-directorydir1)lis1(directory-filesauxdir1nilregexp))(if(ediff-nonempty-string-pmerge-autostore-dir)(setqmerge-autostore-dir(file-name-as-directorymerge-autostore-dir)))(whilelis1(setqelt(carlis1)lis1(cdrlis1));; take files under revision control(cond((file-directory-p(concatauxdir1elt))(setqcommon(cons(ediff-add-slash-if-directoryauxdir1elt)common)))((and(featurep'vc-hooks)(vc-backend(concatauxdir1elt)))(setqcommon(conseltcommon)));; The following two are needed only if vc-hooks isn't loaded.;; They won't recognize CVS files.((file-exists-p(concatauxdir1elt",v"))(setqcommon(conseltcommon)))((file-exists-p(concatauxdir1"RCS/"elt",v"))(setqcommon(conseltcommon)))); cond); while(setqcommon(delete"./"common)common(delete"../"common)common(delete"RCS"common)common(delete"CVS"common));; copying is needed because sort sorts via side effects(setqcommon(sort(ediff-copy-listcommon)'string-lessp));; return result(cons;; header -- has 6 elements. Meta buffer is prepended later by;; ediff-prepare-meta-buffer (ediff-make-new-meta-list-headerregexpauxdir1nilnilmerge-autostore-dirnil)(mapcar(lambda(elt)(ediff-make-new-meta-list-element(concatauxdir1elt)nilnil))common))));; If file groups selected by patterns will ever be implemented, this;; comparison function might become useful.;;;; uses external variables PAT1 PAT2 to compare str1/2;;;; patterns must be of the form ???*???? where ??? are strings of chars;;;; containing no *.;;(defun ediff-pattern= (str1 str2);; (let (pos11 pos12 pos21 pos22 len1 len2);; (setq pos11 0;; len (length epat1);; pos12 len);; (while (and (< pos11 len) (not (= (aref epat1 pos11) ?*)));; (setq pos11 (1+ pos11)));; (while (and (> pos12 0) (not (= (aref epat1 (1- pos12)) ?*)));; (setq pos12 (1- pos12)));;;; (setq pos21 0;; len (length epat2);; pos22 len);; (while (and (< pos21 len) (not (= (aref epat2 pos21) ?*)));; (setq pos21 (1+ pos21)));; (while (and (> pos22 0) (not (= (aref epat2 (1- pos22)) ?*)));; (setq pos22 (1- pos22)));;;; (if (and (> (length str1) pos12) (>= pos12 pos11) (> pos11 -1);; (> (length str2) pos22) (>= pos22 pos21) (> pos21 -1));; (string= (substring str1 pos11 pos12);; (substring str2 pos21 pos22)));; ));; Prepare meta-buffer in accordance with the argument-function and;; redraw-function. Must return the created meta-buffer.(defunediff-prepare-meta-buffer(action-funcmeta-listmeta-buffer-nameredraw-functionjobname&optionalstartup-hooks)(let*((meta-buffer-name(ediff-unique-buffer-namemeta-buffer-name"*"))(meta-buffer(get-buffer-createmeta-buffer-name)))(ediff-with-current-buffermeta-buffer;; comes first(ediff-meta-mode)(setqediff-meta-action-functionaction-funcediff-meta-redraw-functionredraw-functionediff-metajob-namejobnameediff-meta-buffermeta-buffer);; comes after ediff-meta-action-function is set(ediff-setup-meta-map)(if(eqediff-metajob-name'ediff-registry)(progn(setqediff-registry-buffermeta-bufferediff-meta-listmeta-list);; this func is used only from registry buffer, not from other;; meta-buffs.(define-keyediff-meta-buffer-map"M"'ediff-show-meta-buff-from-registry));; Initialize the meta list -- we don't do this for registry.(setqediff-meta-list;; add meta-buffer to the list header(cons(consmeta-buffer(carmeta-list))(cdrmeta-list))))(or(eqmeta-bufferediff-registry-buffer)(setqediff-session-registry(consmeta-bufferediff-session-registry)));; redraw-function uses ediff-meta-list(funcallredraw-functionediff-meta-list);; set read-only/non-modified(setqbuffer-read-onlyt)(set-buffer-modified-pnil)(run-hooks'startup-hooks);; Arrange to show directory contents differences;; Must be after run startup-hooks, since ediff-dir-difference-list is;; set inside these hooks(if(eqaction-func'ediff-filegroup-action)(progn;; put meta buffer in (car ediff-dir-difference-list)(setqediff-dir-difference-list(cons(consmeta-buffer(carediff-dir-difference-list))(cdrediff-dir-difference-list)))(or(ediff-one-filegroup-metajobjobname)(ediff-draw-dir-diffsediff-dir-difference-list))(define-keyediff-meta-buffer-map"h"'ediff-mark-for-hiding-at-pos)(define-keyediff-meta-buffer-map"x"'ediff-hide-marked-sessions)(define-keyediff-meta-buffer-map"m"'ediff-mark-for-operation-at-pos)(define-keyediff-meta-buffer-map"u"nil)(define-keyediff-meta-buffer-map"um"'ediff-unmark-all-for-operation)(define-keyediff-meta-buffer-map"uh"'ediff-unmark-all-for-hiding)(cond((ediff-collect-diffs-metajobjobname)(define-keyediff-meta-buffer-map"P"'ediff-collect-custom-diffs))((ediff-patch-metajobjobname)(define-keyediff-meta-buffer-map"P"'ediff-meta-show-patch)))(define-keyediff-meta-buffer-map"^"'ediff-up-meta-hierarchy)(define-keyediff-meta-buffer-map"D"'ediff-show-dir-diffs)))(if(eqediff-metajob-name'ediff-registry)(run-hooks'ediff-registry-setup-hook)(run-hooks'ediff-after-session-group-setup-hook))); eval in meta-buffermeta-buffer));; Insert the activity marker for session SESSION in the meta buffer at point;; The activity marker is either SPC (untouched session), or + (active session,;; i.e., ediff is currently run in it), or - (finished session, i.e., we've ran;; ediff in it and then exited)(defunediff-insert-session-activity-marker-in-meta-buffer(session)(insert(cond((ediff-get-session-activity-markersession));; virgin session(t" "))));; Insert session status at point. Status is either ?H (marked for hiding), or;; ?I (hidden or invalid), or ?* (meaning marked for an operation; currently,;; such op can only be checking for equality)), or SPC (meaning neither marked;; nor invalid) (defunediff-insert-session-status-in-meta-buffer(session)(insert(cond((ediff-get-session-statussession)); session has status: ?H, ?I, ?*;; normal session, no marks or hidings(t" "))));; If NEW-MARKER is non-nil, use it to substitute the current activity marker;; in the meta buffer. If nil, use SPC(defunediff-replace-session-activity-marker-in-meta-buffer(pointnew-marker)(let*((overl(ediff-get-meta-overlay-at-pospoint))(session-info(ediff-overlay-getoverl'ediff-meta-info))(activity-marker(ediff-get-session-activity-markersession-info))buffer-read-only)(ornew-markeractivity-marker(setqnew-marker?\ ))(goto-char(ediff-overlay-startoverl))(if(eq(char-after(point))new-marker)(); if marker shown in buffer is the same as new-marker, do nothing(insertnew-marker)(delete-char1)(set-buffer-modified-pnil))));; If NEW-STATUS is non-nil, use it to substitute the current status marker in;; the meta buffer. If nil, use SPC(defunediff-replace-session-status-in-meta-buffer(pointnew-status)(let*((overl(ediff-get-meta-overlay-at-pospoint))(session-info(ediff-overlay-getoverl'ediff-meta-info))(status(ediff-get-session-statussession-info))buffer-read-only)(setqnew-status(ornew-statusstatus?\ ))(goto-char(ediff-overlay-startoverl))(forward-char1); status is the second char in session record(if(eq(char-after(point))new-status)(); if marker shown in buffer is the same as new-marker, do nothing(insertnew-status)(delete-char1)(set-buffer-modified-pnil))));; insert all file info in meta buffer for a given session(defunediff-insert-session-info-in-meta-buffer(session-infosessionNum)(let((f1(ediff-get-session-objAsession-info))(f2(ediff-get-session-objBsession-info))(f3(ediff-get-session-objCsession-info))(pt(point))(hidden(eq(ediff-get-session-statussession-info)?I)));; insert activity marker, i.e., SPC, - or +(ediff-insert-session-activity-marker-in-meta-buffersession-info);; insert session status, i.e., *, H(ediff-insert-session-status-in-meta-buffersession-info)(insert" Session "(int-to-stringsessionNum)":\n")(ediff-meta-insert-file-info1f1)(ediff-meta-insert-file-info1f2)(ediff-meta-insert-file-info1f3)(ediff-set-meta-overlaypt(point)session-infosessionNumhidden)));; this is a setup function for ediff-directories;; must return meta-buffer(defunediff-redraw-directory-group-buffer(meta-list);; extract directories(let((meta-buf(ediff-get-group-buffermeta-list))(emptyt)(sessionNum0)regexpeltmerge-autostore-dirpointtmp-listbuffer-read-only)(ediff-with-current-buffermeta-buf(setqpoint(point))(erase-buffer);; delete phony overlays that used to represent sessions before the buff;; was redrawn(ediff-cond-compile-for-xemacs-or-emacs(map-extents'delete-extent); xemacs(mapcar'delete-overlay(overlays-in11)); emacs)(insert(formatediff-meta-buffer-message(ediff-abbrev-jobnameediff-metajob-name)))(setqregexp(ediff-get-group-regexpmeta-list)merge-autostore-dir(ediff-get-group-merge-autostore-dirmeta-list))(cond((ediff-collect-diffs-metajob)(insert" P:\tcollect custom diffs of all marked sessions\n"))((ediff-patch-metajob)(insert" P:\tshow patch appropriately for the context (session or group)\n")))(insert" ^:\tshow parent session group\n")(or(ediff-one-filegroup-metajob)(insert" D:\tshow differences among directories\n"" ==:\tfor each session, show which files are identical\n"" =h:\tlike ==, but also marks those sessions for hiding\n"" =m:\tlike ==, but also marks those sessions for operation\n\n"))(insert"\n")(if(and(stringpregexp)(>(lengthregexp)0))(insert(format"*** Filter-through regular expression: %s\n"regexp)))(ediff-insert-dirs-in-meta-buffermeta-list)(if(andediff-autostore-merges(ediff-merge-metajob)(ediff-nonempty-string-pmerge-autostore-dir))(insert(format"\nMerge results are automatically stored in:\n\t%s\n"merge-autostore-dir)))(insert"\n Size Last modified Name ----------------------------------------------");; discard info on directories and regexp(setqmeta-list(cdrmeta-list)tmp-listmeta-list)(while(andtmp-listempty)(if(and(cartmp-list)(not(eq(ediff-get-session-status(cartmp-list))?I)))(setqemptynil))(setqtmp-list(cdrtmp-list)))(ifempty(insert" ****** ****** This session group has no members\n"));; now organize file names like this:;; use-mark sizeA dateA sizeB dateB filename;; make sure directories are displayed with a trailing slash.(whilemeta-list(setqelt(carmeta-list)meta-list(cdrmeta-list)sessionNum(1+sessionNum))(if(eq(ediff-get-session-statuselt)?I)()(ediff-insert-session-info-in-meta-buffereltsessionNum)))(set-buffer-modified-pnil)(goto-charpoint)meta-buf)))(defunediff-update-markers-in-dir-meta-buffer(meta-list)(let((meta-buf(ediff-get-group-buffermeta-list))session-infopointoverlbuffer-read-only)(ediff-with-current-buffermeta-buf(setqpoint(point))(goto-char(point-min))(ediff-next-meta-item1)(while(not(bobp))(setqsession-info(ediff-get-meta-infometa-buf(point)'no-error)overl(ediff-get-meta-overlay-at-pos(point)))(ifsession-info(progn(cond((eq(ediff-get-session-statussession-info)?I);; Do hiding(ifoverl(ediff-overlay-putoverl'invisiblet)))((and(eq(ediff-get-session-statussession-info)?H)overl(ediff-overlay-getoverl'invisible));; Do unhiding(ediff-overlay-putoverl'invisiblenil))(t(ediff-replace-session-activity-marker-in-meta-buffer(point)(ediff-get-session-activity-markersession-info))(ediff-replace-session-status-in-meta-buffer(point)(ediff-get-session-statussession-info))))))(ediff-next-meta-item1); advance to the next item); end while(set-buffer-modified-pnil)(goto-charpoint))meta-buf))(defunediff-update-session-marker-in-dir-meta-buffer(session-num)(let(buffer-meta-overlayssession-infooverlbuffer-read-only)(setqoverl(ediff-cond-compile-for-xemacs-or-emacs(map-extents; xemacs(lambda(extmaparg)(if(and(ediff-overlay-getext'ediff-meta-info)(eq(ediff-overlay-getext'ediff-meta-session-number)session-num))ext)));; Emacs doesn't have map-extents, so try harder;; Splice overlay lists to get all buffer overlays(progn(setqbuffer-meta-overlays(overlay-lists)buffer-meta-overlays(append(carbuffer-meta-overlays)(cdrbuffer-meta-overlays)))(car(delqnil(mapcar(lambda(overl)(if(and(ediff-overlay-getoverl'ediff-meta-info)(eq(ediff-overlay-getoverl'ediff-meta-session-number)session-num))overl))buffer-meta-overlays))))))(oroverl(error"Bug in ediff-update-session-marker-in-dir-meta-buffer: no overlay with given number %S"session-num))(setqsession-info(ediff-overlay-getoverl'ediff-meta-info))(goto-char(ediff-overlay-startoverl))(ediff-replace-session-activity-marker-in-meta-buffer(point)(ediff-get-session-activity-markersession-info))(ediff-replace-session-status-in-meta-buffer(point)(ediff-get-session-statussession-info)))(ediff-next-meta-item1));; Check if this is a problematic session.;; Return nil if not. Otherwise, return symbol representing the problem;; At present, problematic sessions occur only in -with-ancestor comparisons;; when the ancestor is a directory rather than a file, or when there is no;; suitable ancestor file in the ancestor directory(defunediff-problematic-session-p(session)(let((f1(ediff-get-session-objA-namesession))(f2(ediff-get-session-objB-namesession))(f3(ediff-get-session-objC-namesession)))(cond((and(stringpf1)(not(file-directory-pf1))(stringpf2)(not(file-directory-pf2));; either invalid file name or a directory(or(not(stringpf3))(file-directory-pf3))(ediff-ancestor-metajob));; more may be added later'ancestor-is-dir)(tnil))))(defunediff-meta-insert-file-info1(fileinfo)(let((fname(carfileinfo))(feq(ediff-get-file-eqstatusfileinfo))(max-filename-width(ifediff-meta-truncate-filenames(-(window-width)41)500))file-modtimefile-size)(cond((not(stringpfname))(setqfile-size-2)); file doesn't exits((ediff-listable-filefname)(if(file-exists-pfname);; set real size and modtime(setqfile-size(ediff-file-sizefname)file-modtime(ediff-file-modtimefname))(setqfile-size-2))); file doesn't exist(t(setqfile-size-1))); remote file(if(stringpfname)(insert(format"%s %s %-20s %s\n"(iffeq"="" "); equality indicator(format"%10s"(cond((=file-size-1)"--")((<file-size-1)"--")(tfile-size)))(cond((=file-size-1)"*remote file*")((<file-size-1)"*file doesn't exist*")(t(ediff-format-date(decode-timefile-modtime))));; dir names in meta lists have training slashes, so we just;; abbreviate the file name, if file exists(if(and(not(stringpfname))(<file-size-1))"-------"; file doesn't exist(ediff-truncate-string-left(ediff-abbreviate-file-namefname)max-filename-width)))))))(defconstediff-months'((1."Jan")(2."Feb")(3."Mar")(4."Apr")(5."May")(6."Jun")(7."Jul")(8."Aug")(9."Sep")(10."Oct")(11."Nov")(12."Dec"))"Months' associative array.");; returns 2char string(defsubstediff-fill-leading-zero(num)(if(<num10)(format"0%d"num)(number-to-stringnum)));; TIME is like the output of decode-time(defunediff-format-date(time)(format"%s %2d %4d %s:%s:%s"(cdr(assoc(nth4time)ediff-months)); month(nth3time); day(nth5time); year(ediff-fill-leading-zero(nth2time)); hour(ediff-fill-leading-zero(nth1time)); min(ediff-fill-leading-zero(nth0time)); sec));; Draw the directories(defunediff-insert-dirs-in-meta-buffer(meta-list)(let*((dir1(ediff-abbreviate-file-name(ediff-get-group-objAmeta-list)))(dir2(ediff-get-group-objBmeta-list))(dir2(if(stringpdir2)(ediff-abbreviate-file-namedir2)))(dir3(ediff-get-group-objCmeta-list))(dir3(if(stringpdir3)(ediff-abbreviate-file-namedir3))))(insert"*** Directory A: "dir1"\n")(ifdir2(insert"*** Directory B: "dir2"\n"))(ifdir3(insert"*** Directory C: "dir3"\n"))(insert"\n")))(defunediff-draw-dir-diffs(diff-list&optionalbuf-name)(if(nulldiff-list)(error"Lost difference info on these directories"))(setqbuf-name(orbuf-name(ediff-unique-buffer-name"*Ediff File Group Differences""*")))(let*((regexp(ediff-get-group-regexpdiff-list))(dir1(ediff-abbreviate-file-name(ediff-get-group-objAdiff-list)))(dir2(ediff-abbreviate-file-name(ediff-get-group-objBdiff-list)))(dir3(ediff-get-group-objCdiff-list))(dir3(if(stringpdir3)(ediff-abbreviate-file-namedir3)))(meta-buf(ediff-get-group-bufferdiff-list))(underline(make-string26?-))filemembership-codesaved-pointbuffer-read-only);; skip the directory part(setqdiff-list(cdrdiff-list))(setqediff-dir-diffs-buffer(get-buffer-createbuf-name))(ediff-with-current-bufferediff-dir-diffs-buffer(setqsaved-point(point))(use-local-mapediff-dir-diffs-buffer-map)(erase-buffer)(setqediff-meta-buffermeta-buf)(insert"\t\t*** Directory Differences ***\n")(insert"Useful commands: C,button2: over file name -- copy this file to directory that doesn't have it q: hide this buffer n,SPC: next line p,DEL: previous line\n\n")(insert(format"\n*** Directory A: %s\n"dir1))(ifdir2(insert(format"*** Directory B: %s\n"dir2)))(ifdir3(insert(format"*** Directory C: %s\n"dir3)))(if(and(stringpregexp)(>(lengthregexp)0))(insert(format"*** Filter-through regular expression: %s\n"regexp)))(insert"\n")(insert(format"\n%-27s%-26s""Directory A""Directory B"))(ifdir3(insert(format" %-25s\n""Directory C"))(insert"\n"))(insert(format"%s%s"underlineunderline))(if(stringpdir3)(insert(format"%s\n\n"underline))(insert"\n\n"))(if(nulldiff-list)(insert"\n\t*** No differences ***\n"))(whilediff-list(setqfile(car(cardiff-list))membership-code(cdr(cardiff-list))diff-list(cdrdiff-list))(if(=(modmembership-codeediff-membership-code1)0); dir1(let((beg(point)))(insert(format"%-27s"(ediff-truncate-string-left(ediff-abbreviate-file-name(if(file-directory-p(concatdir1file))(file-name-as-directoryfile)file))24)));; format of meta info in the dir-diff-buffer:;; (filename-tail filename-full otherdir1 otherdir2 otherdir3)(ediff-set-meta-overlaybeg(point)(listmeta-buffile(concatdir1file)dir1dir2dir3)))(insert(format"%-27s""---")))(if(=(modmembership-codeediff-membership-code2)0); dir2(let((beg(point)))(insert(format"%-26s"(ediff-truncate-string-left(ediff-abbreviate-file-name(if(file-directory-p(concatdir2file))(file-name-as-directoryfile)file))24)))(ediff-set-meta-overlaybeg(point)(listmeta-buffile(concatdir2file)dir1dir2dir3)))(insert(format"%-26s""---")))(if(stringpdir3)(if(=(modmembership-codeediff-membership-code3)0); dir3(let((beg(point)))(insert(format" %-25s"(ediff-truncate-string-left(ediff-abbreviate-file-name(if(file-directory-p(concatdir3file))(file-name-as-directoryfile)file))24)))(ediff-set-meta-overlaybeg(point)(listmeta-buffile(concatdir3file)dir1dir2dir3)))(insert(format" %-25s""---"))))(insert"\n"))(setqbuffer-read-onlyt)(set-buffer-modified-pnil)(goto-charsaved-point)); end eval in diff buffer))(defunediff-bury-dir-diffs-buffer()"Bury the directory difference buffer. Display the meta buffer instead."(interactive);; ediff-meta-buffer is set in ediff-draw-dir-diffs so the directory;; difference buffer remembers the meta buffer(let((bufediff-meta-buffer)wind)(ediff-kill-buffer-carefullyediff-dir-diffs-buffer)(if(setqwind(ediff-get-visible-buffer-windowbuf))(select-windowwind)(set-window-buffer(selected-window)buf))));; executes in dir session group buffer;; show buffer differences(defunediff-show-dir-diffs()"Display differences among the directories involved in session group."(interactive)(if(ediff-one-filegroup-metajob)(error"This command is inapplicable in the present context"))(or(ediff-buffer-live-pediff-dir-diffs-buffer)(ediff-draw-dir-diffsediff-dir-difference-list))(let((bufediff-dir-diffs-buffer))(other-window1)(set-window-buffer(selected-window)buf)(goto-char(point-min))));; Format of meta info in dir-diff-buffer:;; (filename-tail filename-full otherdir1 otherdir2)(defunediff-dir-diff-copy-file()"Copy file described at point to directories where this file is missing."(interactive)(let*((pos(ediff-event-pointlast-command-event))(info(ediff-get-meta-info(current-buffer)pos'noerror))(meta-buf(carinfo))(file-tail(nth1info))(file-abs(nth2info))(otherdir1(nth3info))(otherfile1(ifotherdir1(concatotherdir1file-tail)))(otherdir2(nth4info))(otherfile2(ifotherdir2(concatotherdir2file-tail)))(otherdir3(nth5info))(otherfile3(ifotherdir3(concatotherdir3file-tail)))meta-listdir-diff-list)(if(nullinfo)(error"No file suitable for copying described at this location"))(ediff-with-current-buffermeta-buf(setqmeta-listediff-meta-listdir-diff-listediff-dir-difference-list));; copy file to directories where it doesn't exist, update;; ediff-dir-difference-list and redisplay(mapcar(lambda(otherfile-struct)(let((otherfile(carotherfile-struct))(file-mem-code(cdrotherfile-struct)))(ifotherfile(or(file-exists-potherfile)(if(y-or-n-p(format"Copy %s to %s ? "file-absotherfile))(let*((file-diff-record(assocfile-taildir-diff-list))(new-mem-code(*(cdrfile-diff-record)file-mem-code)))(copy-filefile-absotherfile)(setcdrfile-diff-recordnew-mem-code)(ediff-draw-dir-diffsdir-diff-list(buffer-name))(sit-for0);; if file is in all three dirs or in two dirs and only;; two dirs are involved, delete this file's record(if(or(=new-mem-codeediff-product-of-memcodes)(and(>new-mem-codeediff-membership-code3)(nullotherfile3)))(delqfile-diff-recorddir-diff-list))))))));; 2,3,5 are numbers used to encode membership of a file in;; dir1/2/3. See ediff-intersect-directories.(list(consotherfile12)(consotherfile23)(consotherfile35)))(if(and(file-exists-potherfile1)(file-exists-potherfile2)(or(nototherfile3)(file-exists-potherfile3)));; update ediff-meta-list by direct modification(nconcmeta-list(list(ediff-make-new-meta-list-elementotherfile1otherfile2otherfile3))))(ediff-update-meta-buffermeta-buf'must-redraw)))(defunediff-up-meta-hierarchy()"Go to the parent session group buffer."(interactive)(if(ediff-buffer-live-pediff-parent-meta-buffer)(ediff-show-meta-bufferediff-parent-meta-bufferediff-meta-session-number)(error"This session group has no parent")));; argument is ignored(defunediff-redraw-registry-buffer(&optionalignore)(ediff-with-current-bufferediff-registry-buffer(let((point(point))eltbufAnamebufBnamebufCnamecur-difftotal-diffsptjob-namemeta-listregistry-listbuffer-read-only)(erase-buffer);; delete phony overlays that used to represent sessions before the buff;; was redrawn(ediff-cond-compile-for-xemacs-or-emacs(map-extents'delete-extent); xemacs(mapcar'delete-overlay(overlays-in11)); emacs)(insert"This is a registry of all active Ediff sessions.Useful commands: button2, `v', RET over a session record: switch to that session M over a session record: display the associated session group R in any Ediff session: display session registry n,SPC: next session p,DEL: previous session E: browse Ediff on-line manual q: bury registry\t\tActive Ediff Sessions:\t\t----------------------");; purge registry list from dead buffers(mapcar(lambda(elt)(if(not(ediff-buffer-live-pelt))(setqediff-session-registry(delqeltediff-session-registry))))ediff-session-registry)(if(nullediff-session-registry)(insert" ******* No active Ediff sessions *******\n"))(setqregistry-listediff-session-registry)(whileregistry-list(setqelt(carregistry-list)registry-list(cdrregistry-list))(if(ediff-buffer-live-pelt)(if(ediff-with-current-bufferelt(setqjob-nameediff-metajob-namemeta-listediff-meta-list)(andediff-metajob-name(not(eqediff-metajob-name'ediff-registry))))(progn(setqpt(point))(insert(format" *group*\t%s: %s\n"(buffer-nameelt)(ediff-abbrev-jobnamejob-name)))(insert(format"\t\t %s %s %s\n"(ediff-abbreviate-file-name(ediff-get-group-objAmeta-list))(ediff-abbreviate-file-name(if(stringp(ediff-get-group-objBmeta-list))(ediff-get-group-objBmeta-list)""))(ediff-abbreviate-file-name(if(stringp(ediff-get-group-objCmeta-list))(ediff-get-group-objCmeta-list)""))))(ediff-set-meta-overlaypt(point)elt))(progn(ediff-with-current-bufferelt(setqbufAname(if(ediff-buffer-live-pediff-buffer-A)(buffer-nameediff-buffer-A)"!!!killed buffer!!!")bufBname(if(ediff-buffer-live-pediff-buffer-B)(buffer-nameediff-buffer-B)"!!!killed buffer!!!")bufCname(cond((not(ediff-3way-job))"")((ediff-buffer-live-pediff-buffer-C)(buffer-nameediff-buffer-C))(t"!!!killed buffer!!!")))(setqtotal-diffs(format"%-4d"ediff-number-of-differences)cur-diff(cond((=ediff-current-difference-1)" _")((=ediff-current-differenceediff-number-of-differences)" $")(t(format"%4d"(1+ediff-current-difference))))job-nameediff-job-name));; back in the meta buf(setqpt(point))(insertcur-diff"/"total-diffs"\t"(buffer-nameelt)(format": %s"(ediff-abbrev-jobnamejob-name)))(insert"\n\t\t "bufAname" "bufBname" "bufCname"\n")(ediff-set-meta-overlaypt(point)elt))))); while(set-buffer-modified-pnil)(goto-charpoint))));; Sets overlay around a meta record with 'ediff-meta-info property PROP;; If optional SESSION-NUMBER, make it a property of the overlay,;; ediff-meta-session-number;; PROP is either the ctl or meta buffer (used when we work with the registry);; or a session meta descriptor of the form;; (SESSION-CTL-BUFFER STATUS OBJA OBJB OBJC)(defunediff-set-meta-overlay(beprop&optionalsession-numberhidden)(let(overl)(setqoverl(ediff-make-overlaybe))(ifediff-emacs-p(ediff-overlay-putoverl'mouse-face'highlight)(ediff-overlay-putoverl'highlightt))(ediff-overlay-putoverl'ediff-meta-infoprop)(ediff-overlay-putoverl'invisiblehidden)(if(numberpsession-number)(ediff-overlay-putoverl'ediff-meta-session-numbersession-number))))(defunediff-mark-for-hiding-at-pos(unmark)"Mark session for hiding. With prefix arg, unmark."(interactive"P")(let*((pos(ediff-event-pointlast-command-event))(meta-buf(ediff-event-bufferlast-command-event));; ediff-get-meta-info gives error if meta-buf or pos are invalid(info(ediff-get-meta-infometa-bufpos))(session-number(ediff-get-session-number-at-pospos)))(ediff-mark-session-for-hidinginfounmark)(ediff-next-meta-item1)(save-excursion(ediff-update-meta-buffermeta-bufnilsession-number))));; Returns whether session was marked or unmarked(defunediff-mark-session-for-hiding(infounmark)(let((session-buf(ediff-get-session-bufferinfo))ignore)(cond((equnmark'mark)(setqunmarknil))((eq(ediff-get-session-statusinfo)?H)(setqunmarkt))(unmark; says unmark, but the marker is different from H(setqignoret)))(cond(ignore)(unmark(ediff-set-session-statusinfonil));;; (if (ediff-buffer-live-p session-buf);;; (error "Can't hide active session, %s" (buffer-name session-buf)))(t(ediff-set-session-statusinfo?H))))unmark)(defunediff-mark-for-operation-at-pos(unmark)"Mark session for a group operation. With prefix arg, unmark."(interactive"P")(let*((pos(ediff-event-pointlast-command-event))(meta-buf(ediff-event-bufferlast-command-event));; ediff-get-meta-info gives error if meta-buf or pos are invalid(info(ediff-get-meta-infometa-bufpos))(session-number(ediff-get-session-number-at-pospos)))(ediff-mark-session-for-operationinfounmark)(ediff-next-meta-item1)(save-excursion(ediff-update-meta-buffermeta-bufnilsession-number))));; returns whether session was unmarked.;; remember: this is a toggle op(defunediff-mark-session-for-operation(infounmark)(let(ignore)(cond((equnmark'mark)(setqunmarknil))((eq(ediff-get-session-statusinfo)?*)(setqunmarkt))(unmark; says unmark, but the marker is different from *(setqignoret)))(cond(ignore)(unmark(ediff-set-session-statusinfonil))(t(ediff-set-session-statusinfo?*))))unmark)(defunediff-hide-marked-sessions(unhide)"Hide marked sessions. With prefix arg, unhide."(interactive"P")(let((grp-buf(ediff-get-group-bufferediff-meta-list))(meta-list(cdrediff-meta-list))(from(ifunhide?I?H))(to(ifunhide?H?I))(numMarked0)active-sessions-existsession-bufelt)(whilemeta-list(setqelt(carmeta-list)meta-list(cdrmeta-list)session-buf(ediff-get-session-bufferelt))(if(eq(ediff-get-session-statuselt)from)(progn(setqnumMarked(1+numMarked))(if(and(eqto?I)(buffer-live-psession-buf));; shouldn't hide active sessions(setqactive-sessions-existt)(ediff-set-session-statuseltto)))))(if(>numMarked0)(ediff-update-meta-buffergrp-buf'must-redraw)(beep)(ifunhide(message"Nothing to reveal...")(message"Nothing to hide...")))(ifactive-sessions-exist(message"Note: Ediff didn't hide active sessions!"))));; Apply OPERATION to marked sessions. Operation expects one argument of type;; meta-list member (not the first one), i.e., a regular session description.;; Returns number of marked sessions on which operation was performed(defunediff-operate-on-marked-sessions(operation)(let((grp-buf(ediff-get-group-bufferediff-meta-list))(meta-list(cdrediff-meta-list))(marksym?*)(numMarked0)(sessionNum0)(diff-bufferediff-meta-diff-buffer)session-bufelt)(whilemeta-list(setqelt(carmeta-list)meta-list(cdrmeta-list)sessionNum(1+sessionNum))(cond((eq(ediff-get-session-statuselt)marksym)(save-excursion(setqnumMarked(1+numMarked))(funcalloperationeltsessionNum)));; The following goes into a session represented by a subdirectory;; and applies operation to marked sessions there((and(ediff-meta-session-pelt)(ediff-buffer-live-p(setqsession-buf(ediff-get-session-bufferelt))))(setqnumMarked(+numMarked(ediff-with-current-buffersession-buf;; pass meta-diff along(setqediff-meta-diff-bufferdiff-buffer);; collect diffs in child group(ediff-operate-on-marked-sessionsoperation)))))))(ediff-update-meta-buffergrp-buf'must-redraw); just in casenumMarked))(defunediff-append-custom-diff(sessionsessionNum)(or(ediff-collect-diffs-metajob)(error"Can't compute multifile patch in this context"))(let((session-buf(ediff-get-session-buffersession))(meta-diff-buffediff-meta-diff-buffer)(metajobediff-metajob-name)tmp-bufcustom-diff-buf)(if(ediff-buffer-live-psession-buf)(ediff-with-current-buffersession-buf(if(eqediff-control-buffersession-buf); individual session(progn(ediff-compute-custom-diffs-maybe)(setqcustom-diff-bufediff-custom-diff-buffer)))))(or(ediff-buffer-live-pmeta-diff-buff)(error"Ediff: something wrong--killed multiple diff's buffer"))(cond((ediff-buffer-live-pcustom-diff-buf);; for live session buffers we do them first because the user may;; have changed them with respect to the underlying files(save-excursion(set-buffermeta-diff-buff)(goto-char(point-max))(insert-buffercustom-diff-buf)(insert"\n")));; if ediff session is not live, run diff directly on the files((memqmetajob'(ediff-directoriesediff-merge-directoriesediff-merge-directories-with-ancestor));; get diffs by calling shell command on ediff-custom-diff-program(save-excursion(set-buffer(setqtmp-buf(get-buffer-createediff-tmp-buffer)))(erase-buffer)(shell-command(format"%s %s %s %s"ediff-custom-diff-programediff-custom-diff-options(ediff-get-session-objA-namesession)(ediff-get-session-objB-namesession))t))(save-excursion(set-buffermeta-diff-buff)(goto-char(point-max))(insert-buffertmp-buf)(insert"\n")))(t(ediff-kill-buffer-carefullymeta-diff-buff)(error"Session %d compares versions of file. Such session must be active to enable multifile patch collection"sessionNum)))))(defunediff-collect-custom-diffs()"Collect custom diffs of marked sessions in buffer `*Ediff Multifile Diffs*'.This operation is defined only for `ediff-directories' and`ediff-directory-revisions', since its intent is to producemultifile patches. For `ediff-directory-revisions', we insist thatall marked sessions must be active."(interactive)(or(ediff-buffer-live-pediff-meta-diff-buffer)(setqediff-meta-diff-buffer(get-buffer-create(ediff-unique-buffer-name"*Ediff Multifile Diffs""*"))))(ediff-with-current-bufferediff-meta-diff-buffer(setqbuffer-read-onlynil)(erase-buffer))(if(>(ediff-operate-on-marked-sessions'ediff-append-custom-diff)0);; did something(progn(display-bufferediff-meta-diff-buffer'not-this-window)(ediff-with-current-bufferediff-meta-diff-buffer(set-buffer-modified-pnil)(setqbuffer-read-onlyt)))(beep)(message"No marked sessions found")))(defunediff-meta-show-patch()"Show the multi-file patch associated with this group session."(interactive)(let*((pos(ediff-event-pointlast-command-event))(meta-buf(ediff-event-bufferlast-command-event))(info(ediff-get-meta-infometa-bufpos'noerror))(patchbufferediff-meta-patchbufer))(if(ediff-buffer-live-ppatchbuffer)(ediff-with-current-bufferpatchbuffer(save-restriction(if(notinfo)(widen)(narrow-to-region(ediff-get-session-objB-nameinfo)(ediff-get-session-objC-nameinfo)))(set-buffer(get-buffer-createediff-tmp-buffer))(erase-buffer)(insert-bufferpatchbuffer)(display-bufferediff-tmp-buffer'not-this-window)))(error"The patch buffer wasn't found"))));; This function executes in meta buffer. It knows where event happened.(defunediff-filegroup-action()"Execute appropriate action for a selected session."(interactive)(let*((pos(ediff-event-pointlast-command-event))(meta-buf(ediff-event-bufferlast-command-event));; ediff-get-meta-info gives error if meta-buf or pos are invalid(info(ediff-get-meta-infometa-bufpos))(session-buf(ediff-get-session-bufferinfo))(session-number(ediff-get-session-number-at-posposmeta-buf))merge-autostore-dirfile1file2file3regexp)(setqfile1(ediff-get-session-objA-nameinfo)file2(ediff-get-session-objB-nameinfo)file3(ediff-get-session-objC-nameinfo));; make sure we don't start on hidden sessions;; ?H means marked for hiding. ?I means invalid (hidden).(if(memq(ediff-get-session-statusinfo)'(?I))(progn(beep)(if(y-or-n-p"This session is marked as hidden, unmark? ")(progn(ediff-set-session-statusinfonil)(ediff-update-meta-buffermeta-bufnilsession-number))(error"Aborted"))))(ediff-with-current-buffermeta-buf(setqmerge-autostore-dir(ediff-get-group-merge-autostore-dirediff-meta-list))(goto-charpos); if the user clicked on session--move point there;; First handle sessions involving directories (which are themselves;; session groups);; After that handle individual sessions(cond((ediff-meta-session-pinfo);; do ediff/ediff-merge on subdirectories(if(ediff-buffer-live-psession-buf)(ediff-show-meta-buffersession-buf)(setqregexp(read-string"Filter through regular expression: "nil'ediff-filtering-regexp-history))(ediff-directories-internalfile1file2file3regexpediff-session-action-functionediff-metajob-name;; make it update (car info) after startup`(list(lambda();; child session group should know its parent(setqediff-parent-meta-buffer(quote,ediff-meta-buffer)ediff-meta-session-number,session-number);; and parent will know its child(setcar(quote,info)ediff-meta-buffer))))));; Do ediff-revision on a subdirectory((and(ediff-one-filegroup-metajob)(ediff-revision-metajob)(file-directory-pfile1))(if(ediff-buffer-live-psession-buf)(ediff-show-meta-buffersession-buf)(setqregexp(read-string"Filter through regular expression: "nil'ediff-filtering-regexp-history))(ediff-directory-revisions-internalfile1regexpediff-session-action-functionediff-metajob-name;; make it update (car info) after startup`(list(lambda();; child session group should know its parent and;; its number(setqediff-parent-meta-buffer(quote,ediff-meta-buffer)ediff-meta-session-number,session-number);; and parent will know its child(setcar(quote,info)ediff-meta-buffer))))));; From here on---only individual session handlers;; handle an individual session with a live control buffer((ediff-buffer-live-psession-buf)(ediff-with-current-buffersession-buf(setqediff-mouse-pixel-position(mouse-pixel-position))(ediff-recenter'no-rehighlight)))((ediff-problematic-session-pinfo)(beep)(if(y-or-n-p"This session has no ancestor. Merge without the ancestor? ")(ediff-merge-filesfile1file2;; provide startup hooks `(list(lambda()(add-hook'ediff-after-quit-hook-internal(lambda()(if(ediff-buffer-live-p,(current-buffer))(ediff-show-meta-buffer,(current-buffer),session-number)))nil'local)(setqediff-meta-buffer,(current-buffer)ediff-meta-session-number,session-number)(setqediff-merge-store-file,(if(ediff-nonempty-string-pmerge-autostore-dir)(concatmerge-autostore-dirediff-merge-filename-prefix(file-name-nondirectoryfile1))));; make ediff-startup pass;; ediff-control-buffer back to the meta;; level; see below(setcar(quote,info)ediff-control-buffer))))(error"Aborted")))((ediff-one-filegroup-metajob); needs 1 file arg(funcallediff-session-action-functionfile1;; provide startup hooks `(list(lambda()(add-hook'ediff-after-quit-hook-internal(lambda()(if(ediff-buffer-live-p,(current-buffer))(ediff-show-meta-buffer,(current-buffer),session-number)))nil'local)(setqediff-meta-buffer,(current-buffer)ediff-meta-session-number,session-number)(setqediff-merge-store-file,(if(ediff-nonempty-string-pmerge-autostore-dir)(concatmerge-autostore-dirediff-merge-filename-prefix(file-name-nondirectoryfile1))));; make ediff-startup pass;; ediff-control-buffer back to the meta;; level; see below(setcar(quote,info)ediff-control-buffer)))))((not(ediff-metajob3)); need 2 file args(funcallediff-session-action-functionfile1file2;; provide startup hooks `(list(lambda()(add-hook'ediff-after-quit-hook-internal(lambda()(if(ediff-buffer-live-p,(current-buffer))(ediff-show-meta-buffer,(current-buffer),session-number)))nil'local)(setqediff-meta-buffer,(current-buffer)ediff-meta-session-number,session-number)(setqediff-merge-store-file,(if(ediff-nonempty-string-pmerge-autostore-dir)(concatmerge-autostore-dirediff-merge-filename-prefix(file-name-nondirectoryfile1))));; make ediff-startup pass;; ediff-control-buffer back to the meta;; level; see below(setcar(quote,info)ediff-control-buffer)))))((ediff-metajob3); need 3 file args(funcallediff-session-action-functionfile1file2file3;; arrange startup hooks `(list(lambda()(add-hook'ediff-after-quit-hook-internal(lambda()(if(ediff-buffer-live-p,(current-buffer))(ediff-show-meta-buffer,(current-buffer),session-number)))nil'local)(setqediff-merge-store-file,(if(ediff-nonempty-string-pmerge-autostore-dir)(concatmerge-autostore-dirediff-merge-filename-prefix(file-name-nondirectoryfile1))))(setqediff-meta-buffer,(current-buffer)ediff-meta-session-number,session-number);; this arranges that ediff-startup will pass;; the value of ediff-control-buffer back to;; the meta level, to the record in the meta;; list containing the information about the;; session associated with that;; ediff-control-buffer(setcar(quote,info)ediff-control-buffer)))))); cond); eval in meta-buf))(defunediff-registry-action()"Switch to a selected session."(interactive)(let*((pos(ediff-event-pointlast-command-event))(buf(ediff-event-bufferlast-command-event))(ctl-buf(ediff-get-meta-infobufpos)))(if(ediff-buffer-live-pctl-buf);; check if this is ediff-control-buffer or ediff-meta-buffer(if(ediff-with-current-bufferctl-buf(eq(key-binding"q")'ediff-quit-meta-buffer));; it's a meta-buffer -- last action should just display it(ediff-show-meta-bufferctl-buft);; it's a session buffer -- invoke go back to session(ediff-with-current-bufferctl-buf(setqediff-mouse-pixel-position(mouse-pixel-position))(ediff-recenter'no-rehighlight)))(beep)(message"You've selected a stale session --- try again")(ediff-update-registry))(ediff-with-current-bufferbuf(goto-charpos))));; If session number is t, means don't update meta buffer(defunediff-show-meta-buffer(&optionalmeta-bufsession-number)"Show the session group buffer."(interactive)(run-hooks'ediff-before-directory-setup-hooks)(let(windframesilent)(ifmeta-buf(setqsilentt))(setqmeta-buf(ormeta-bufediff-meta-buffer))(cond((not(bufferpmeta-buf))(error"This Ediff session is not part of a session group"))((not(ediff-buffer-live-pmeta-buf))(error"Can't find this session's group panel -- session itself is ok")))(cond((numberpsession-number)(ediff-update-meta-buffermeta-bufnilsession-number));; if session-number is t, don't update(session-number)(t(ediff-cleanup-meta-buffermeta-buf)))(ediff-with-current-buffermeta-buf(save-excursion(cond((setqwind(ediff-get-visible-buffer-windowmeta-buf))(orsilent(message"Already showing the group panel for this session"))(set-window-bufferwindmeta-buf)(select-windowwind))((window-live-p(setqwindediff-window-C));in merge--merge buf(set-window-bufferediff-window-Cmeta-buf)(select-windowwind))((window-live-p(setqwindediff-window-A))(set-window-bufferediff-window-Ameta-buf)(select-windowwind))((window-live-p(setqwindediff-window-B))(set-window-bufferediff-window-Bmeta-buf)(select-windowwind))((and(setqwind(ediff-get-visible-buffer-windowediff-registry-buffer))(ediff-window-display-p))(select-windowwind)(other-window1)(set-window-buffer(selected-window)meta-buf))(t(ediff-skip-unsuitable-frames'ok-unsplittable)(set-window-buffer(selected-window)meta-buf)))))(if(and(ediff-window-display-p)(window-live-p(setqwind(ediff-get-visible-buffer-windowmeta-buf))))(progn(setqframe(window-framewind))(raise-frameframe)(ediff-reset-mouseframe)))(sit-for0); sometimes needed to synch the display and ensure that the; point ends up after the just completed session(run-hooks'ediff-show-session-group-hook)))(defunediff-show-current-session-meta-buffer()(interactive)(ediff-show-meta-buffernilediff-meta-session-number))(defunediff-show-meta-buff-from-registry()"Display the session group buffer for a selected session group."(interactive)(let*((pos(ediff-event-pointlast-command-event))(meta-buf(ediff-event-bufferlast-command-event))(info(ediff-get-meta-infometa-bufpos))(meta-or-session-bufinfo))(ediff-with-current-buffermeta-or-session-buf(ediff-show-meta-buffernilt))));;;###autoload(defunediff-show-registry()"Display Ediff's registry."(interactive)(ediff-update-registry)(if(not(ediff-buffer-live-pediff-registry-buffer))(error"No active Ediff sessions or corrupted session registry"))(let(windframe);; for some reason, point moves in ediff-registry-buffer, so we preserve it;; explicitly(ediff-with-current-bufferediff-registry-buffer(save-excursion(cond((setqwind(ediff-get-visible-buffer-windowediff-registry-buffer))(message"Already showing the registry")(set-window-bufferwindediff-registry-buffer)(select-windowwind))((window-live-pediff-window-C)(set-window-bufferediff-window-Cediff-registry-buffer)(select-windowediff-window-C))((window-live-pediff-window-A)(set-window-bufferediff-window-Aediff-registry-buffer)(select-windowediff-window-A))((window-live-pediff-window-B)(set-window-bufferediff-window-Bediff-registry-buffer)(select-windowediff-window-B))((and(setqwind(ediff-get-visible-buffer-windowediff-meta-buffer))(ediff-window-display-p))(select-windowwind)(other-window1)(set-window-buffer(selected-window)ediff-registry-buffer))(t(ediff-skip-unsuitable-frames'ok-unsplittable)(set-window-buffer(selected-window)ediff-registry-buffer)))))(if(ediff-window-display-p)(progn(setqframe(window-frame(ediff-get-visible-buffer-windowediff-registry-buffer)))(raise-frameframe)(ediff-reset-mouseframe)))(run-hooks'ediff-show-registry-hook)));;;###autoload(defalias'eregistry'ediff-show-registry);; If meta-buf doesn't exist, it is created. In that case, id doesn't have a;; parent meta-buf;; Check if META-BUF exists before calling this function;; Optional MUST-REDRAW, if non-nil, would force redrawal of the whole meta;; buffer. Otherwise, it will just go over the buffer and update activity marks;; and session status.;; SESSION-NUMBER, if specified, says which session caused the update.(defunediff-update-meta-buffer(meta-buf&optionalmust-redrawsession-number)(if(ediff-buffer-live-pmeta-buf)(ediff-with-current-buffermeta-buf(let(overl)(cond(must-redraw; completely redraw the meta buffer(funcallediff-meta-redraw-functionediff-meta-list))((numberpsession-number); redraw only for the given session(ediff-update-session-marker-in-dir-meta-buffersession-number))(t; update what changed only, but scan the entire meta buffer(ediff-update-markers-in-dir-meta-bufferediff-meta-list)))(setqoverl(ediff-get-meta-overlay-at-pos(point)));; skip the invisible sessions(while(andoverl(ediff-overlay-getoverl'invisible))(ediff-next-meta-item1)(setqoverl(ediff-get-meta-overlay-at-pos(point))))))))(defunediff-update-registry()(ediff-with-current-buffer(current-buffer)(if(ediff-buffer-live-pediff-registry-buffer)(ediff-redraw-registry-buffer)(ediff-prepare-meta-buffer'ediff-registry-actionediff-session-registry"*Ediff Registry"'ediff-redraw-registry-buffer'ediff-registry))));; If meta-buf exists, it is redrawn along with parent. ;; Otherwise, nothing happens.(defunediff-cleanup-meta-buffer(meta-buffer)(if(ediff-buffer-live-pmeta-buffer)(ediff-with-current-buffermeta-buffer(ediff-update-meta-buffermeta-buffer)(if(ediff-buffer-live-pediff-parent-meta-buffer)(ediff-update-meta-bufferediff-parent-meta-buffernilediff-meta-session-number)))));; t if no session is in progress(defunediff-safe-to-quit(meta-buffer)(if(ediff-buffer-live-pmeta-buffer)(let((lisediff-meta-list)(contt)buffer-read-only);;(ediff-update-meta-buffer meta-buffer)(ediff-with-current-buffermeta-buffer(setqlis(cdrlis)); discard the description part of meta-list(while(andcontlis)(if(ediff-buffer-live-p(ediff-get-group-bufferlis)); in progress(setqcontnil))(setqlis(cdrlis)))cont))))(defunediff-quit-meta-buffer()"If the group has no active session, delete the meta buffer.If no session is in progress, ask to confirm before deleting meta buffer.Otherwise, bury the meta buffer.If this is a session registry buffer then just bury it."(interactive)(let*((buf(current-buffer))(dir-diffs-bufferediff-dir-diffs-buffer)(meta-diff-bufferediff-meta-diff-buffer)(session-numberediff-meta-session-number)(parent-bufediff-parent-meta-buffer)(dont-show-registry(eqbufediff-registry-buffer)))(ifdont-show-registry(bury-buffer);;(ediff-cleanup-meta-buffer buf)(cond((and(ediff-safe-to-quitbuf)(y-or-n-p"Quit this session group? "))(run-hooks'ediff-quit-session-group-hook)(message"")(ediff-dispose-of-meta-bufferbuf))((ediff-safe-to-quitbuf)(bury-buffer))(t(error"This session group has active sessions---cannot exit")))(ediff-update-meta-bufferparent-bufnilsession-number)(ediff-kill-buffer-carefullydir-diffs-buffer)(ediff-kill-buffer-carefullymeta-diff-buffer)(if(ediff-buffer-live-pparent-buf)(progn(setqdont-show-registryt)(ediff-show-meta-bufferparent-bufsession-number))))(ordont-show-registry(ediff-show-registry))))(defunediff-dispose-of-meta-buffer(buf)(setqediff-session-registry(delqbufediff-session-registry))(ediff-with-current-bufferbuf(if(ediff-buffer-live-pediff-dir-diffs-buffer)(kill-bufferediff-dir-diffs-buffer)))(kill-bufferbuf));; Obtain information on a meta record where the user clicked or typed;; BUF is the buffer where this happened and POINT is the position;; If optional NOERROR arg is given, don't report error and return nil if no;; meta info is found on line.(defunediff-get-meta-info(bufpoint&optionalnoerror)(let(resultolisttmp)(if(andpoint(ediff-buffer-live-pbuf))(ediff-with-current-bufferbuf(ediff-cond-compile-for-xemacs-or-emacs(setqresult; xemacs(if(setqtmp(extent-atpointbuf'ediff-meta-info))(ediff-overlay-gettmp'ediff-meta-info)))(progn; emacs(setqolist(overlays-atpoint))(setqolist(mapcar(lambda(elt)(unless(overlay-getelt'invisible)(overlay-getelt'ediff-meta-info)))olist))(while(andolist(null(carolist)))(setqolist(cdrolist)))(setqresult(carolist))))))(ifresultresult(ifnoerrornil(ediff-update-registry)(error"No session info in this line")))))(defunediff-get-meta-overlay-at-pos(point)(ediff-cond-compile-for-xemacs-or-emacs(extent-atpoint(current-buffer)'ediff-meta-info); xemacs;; emacs(let*((overl-list(overlays-atpoint))(overl(caroverl-list)))(while(andoverl(null(overlay-getoverl'ediff-meta-info)))(setqoverl-list(cdroverl-list)overl(caroverl-list)))overl)))(defsubstediff-get-session-number-at-pos(point&optionalmeta-buffer)(setqmeta-buffer(if(ediff-buffer-live-pmeta-buffer)meta-buffer(current-buffer)))(ediff-with-current-buffermeta-buffer(ediff-overlay-get(ediff-get-meta-overlay-at-pospoint)'ediff-meta-session-number)));; Return location of the next meta overlay after point(defunediff-next-meta-overlay-start(point)(if(eobp)(goto-char(point-min))(let((overl(ediff-get-meta-overlay-at-pospoint)))(ediff-cond-compile-for-xemacs-or-emacs(progn; xemacs(ifoverl(setqoverl(next-extentoverl))(setqoverl(next-extent(current-buffer))))(ifoverl(extent-start-positionoverl)(point-max)));; emacs(ifoverl;; note: end of current overlay is the beginning of the next one(overlay-endoverl)(next-overlay-changepoint))))))(defunediff-previous-meta-overlay-start(point)(if(bobp)(goto-char(point-max))(let((overl(ediff-get-meta-overlay-at-pospoint)))(ediff-cond-compile-for-xemacs-or-emacs(progn(ifoverl(setqoverl(previous-extentoverl))(setqoverl(previous-extent(current-buffer))))(ifoverl(extent-start-positionoverl)(point-min)))(progn(ifoverl(setqpoint(overlay-startoverl)));; to get to the beginning of prev overlay(if(not(bobp));; trick to overcome an emacs bug--doesn't always find previous;; overlay change correctly(setqpoint(1-point)))(setqpoint(previous-overlay-changepoint));; If we are not over an overlay after subtracting 1, it means we are;; in the description area preceding session records. In this case,;; goto the top of the registry buffer.(or(car(overlays-atpoint))(setqpoint(point-min)))point)))));; this is the action invoked when the user selects a patch from the meta;; buffer.(defunediff-patch-file-form-meta(file&optionalstartup-hooks)(let*((pos(ediff-event-pointlast-command-event))(meta-buf(ediff-event-bufferlast-command-event));; ediff-get-meta-info gives error if meta-buf or pos are invalid(info(ediff-get-meta-infometa-bufpos))(meta-patchbufediff-meta-patchbufer)session-bufbeg-markerend-marker)(if(or(file-directory-pfile)(string-match"/dev/null"file))(error"`%s' is not an ordinary file"(file-name-as-directoryfile)))(setqsession-buf(ediff-get-session-bufferinfo)beg-marker(ediff-get-session-objB-nameinfo)end-marker(ediff-get-session-objC-nameinfo))(or(ediff-buffer-live-psession-buf); either an active patch session(nullsession-buf); or it is a virgin session(error"Patch has already been applied to this file -- can't repeat!"))(ediff-with-current-buffermeta-patchbuf(save-restriction(widen)(narrow-to-regionbeg-markerend-marker)(ediff-patch-file-internalmeta-patchbuffilestartup-hooks)))))(defunediff-unmark-all-for-operation()"Unmark all sessions marked for operation."(interactive)(let((list(cdrediff-meta-list))elt)(while(setqelt(carlist))(ediff-mark-session-for-operationelt'unmark)(setqlist(cdrlist))))(ediff-update-meta-buffer(current-buffer)'must-redraw))(defunediff-unmark-all-for-hiding()"Unmark all sessions marked for hiding."(interactive)(let((list(cdrediff-meta-list))elt)(while(setqelt(carlist))(ediff-mark-session-for-hidingelt'unmark)(setqlist(cdrlist))))(ediff-update-meta-buffer(current-buffer)'must-redraw));; ACTION is ?h, ?m, ?=: to mark for hiding, mark for operation, or simply;; indicate which are equal files(defunediff-meta-mark-equal-files(&optionalaction)"Run through the session list and mark identical files.This is used only for sessions that involve 2 or 3 files at the same time.ACTION is an optional argument that can be ?h, ?m, ?=, to mark for hiding, markfor operation, or simply indicate which are equal files. If it is nil, thenlast-command-char is used to decide which action to take."(interactive)(if(nullaction)(setqactionlast-command-char))(let((list(cdrediff-meta-list))marked1marked2marked3fileinfo1fileinfo2fileinfo3elt)(message"Comparing files ...")(while(setqelt(carlist))(setqfileinfo1(ediff-get-session-objAelt)fileinfo2(ediff-get-session-objBelt)fileinfo3(ediff-get-session-objCelt))(ediff-set-file-eqstatusfileinfo1nil)(ediff-set-file-eqstatusfileinfo2nil)(ediff-set-file-eqstatusfileinfo3nil)(setqmarked1tmarked2tmarked3t)(or(ediff-mark-if-equalfileinfo1fileinfo2)(setqmarked1nil))(if(ediff-metajob3)(progn(or(ediff-mark-if-equalfileinfo1fileinfo3)(setqmarked2nil))(or(ediff-mark-if-equalfileinfo2fileinfo3)(setqmarked3nil))))(if(andmarked1marked2marked3)(cond((eqaction?h)(ediff-mark-session-for-hidingelt'mark))((eqaction?m)(ediff-mark-session-for-operationelt'mark))))(setqlist(cdrlist)))(message"Comparing files ... Done"))(ediff-update-meta-buffer(current-buffer)'must-redraw));; mark files 1 and 2 as equal, if they are.;; returns t, if something was marked(defunediff-mark-if-equal(fileinfo1fileinfo2)(let((f1(carfileinfo1))(f2(carfileinfo2)))(cond((file-directory-pf1)nil)((file-directory-pf2)nil)((ediff-same-file-contentsf1f2)(ediff-set-file-eqstatusfileinfo1t)(ediff-set-file-eqstatusfileinfo2t)t))));;; Local Variables:;;; eval: (put 'ediff-defvar-local 'lisp-indent-hook 'defun);;; eval: (put 'ediff-with-current-buffer 'lisp-indent-hook 1);;; eval: (put 'ediff-with-current-buffer 'edebug-form-spec '(form body));;; End:;;; ediff-mult.el ends here