# HG changeset patch # User Katsumi Yamaoka # Date 1276469875 0 # Node ID e93288477c43c7a749164b96faa4b1ebbaf065fd # Parent 681cd08dc0f74a66c2004f1be3f5f1b907a04dab# Parent f2d0b93e668c1a96d42bd33ccf68fe3bd960fa14 Merge from mainline. diff -r 681cd08dc0f7 -r e93288477c43 ChangeLog --- a/ChangeLog Thu Jun 10 22:43:47 2010 +0000 +++ b/ChangeLog Sun Jun 13 22:57:55 2010 +0000 @@ -1,3 +1,16 @@ +2010-06-12 Glenn Morris + + * Makefile.in (install-arch-indep): Delete any old info .gz files first. + +2010-06-11 Glenn Morris + + * configure.in (--without-compress-info): New option. + (GZIP_INFO): New output variable. + + * Makefile.in (GZIP_INFO): New, set by configure. + (install-arch-indep): Don't gzip info pages if GZIP_INFO is nil. + Handle man pages in the same way. + 2010-06-10 Glenn Morris * Makefile.in (install-arch-indep): Gzip the info files too. diff -r 681cd08dc0f7 -r e93288477c43 Makefile.in --- a/Makefile.in Thu Jun 10 22:43:47 2010 +0000 +++ b/Makefile.in Sun Jun 13 22:57:55 2010 +0000 @@ -243,6 +243,8 @@ # We use gzip to compress installed .el files. GZIP_PROG = @GZIP_PROG@ +# If non-nil, gzip the installed Info and man pages. +GZIP_INFO = @GZIP_INFO@ # ============================= Targets ============================== @@ -580,8 +582,10 @@ for f in `ls $$elt $$elt-[1-9] $$elt-[1-9][0-9] 2>/dev/null`; do \ ${INSTALL_DATA} $$f $(DESTDIR)${infodir}/$$f; \ chmod a+r $(DESTDIR)${infodir}/$$f; \ - [ -n "${GZIP_PROG}" ] && \ + if [ -n "${GZIP_INFO}" ] && [ -n "${GZIP_PROG}" ]; then \ + rm -f $(DESTDIR)${infodir}/$$f.gz; \ ${GZIP_PROG} -9n $(DESTDIR)${infodir}/$$f; \ + else true; fi; \ done; \ done); \ else true; fi @@ -601,7 +605,11 @@ for page in ${MAN_PAGES}; do \ (cd $${thisdir}; \ ${INSTALL_DATA} ${mansrcdir}/$${page} $(DESTDIR)${man1dir}/$${page}; \ - chmod a+r $(DESTDIR)${man1dir}/$${page}); \ + chmod a+r $(DESTDIR)${man1dir}/$${page}; \ + if [ -n "${GZIP_INFO}" ] && [ -n "${GZIP_PROG}" ]; then \ + rm -f $(DESTDIR)${man1dir}/$${page}.gz; \ + ${GZIP_PROG} -9n $(DESTDIR)${man1dir}/$${page}; \ + else true; fi ); \ done ## Install those items from etc/ that need to end up elsewhere. diff -r 681cd08dc0f7 -r e93288477c43 admin/ChangeLog --- a/admin/ChangeLog Thu Jun 10 22:43:47 2010 +0000 +++ b/admin/ChangeLog Sun Jun 13 22:57:55 2010 +0000 @@ -1,3 +1,22 @@ +2010-06-12 Eli Zaretskii + + * unidata/bidimirror.awk: New file. + + * unidata/BidiMirroring.txt: New file from + http://www.unicode.org/Public/6.0.0/ucd/BidiMirroring-6.0.0d1.txt. + + * unidata/Makefile.in: (../../src/bidimirror.h): New target. + (all): Depend on ../../src/biditype.h and ../../src/bidimirror.h. + + * unidata/makefile.w32-in (../../src/bidimirror.h): New target. + (all): Depend on ../../src/biditype.h and ../../src/bidimirror.h. + + * unidata/biditype.awk: New file. + + * unidata/Makefile.in (../../src/biditype.h): New target. + + * unidata/makefile.w32-in (../../src/biditype.h): New target. + 2010-06-09 Juanma Barranquero * unidata/UnicodeData.txt: Update from diff -r 681cd08dc0f7 -r e93288477c43 admin/unidata/BidiMirroring.txt --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/admin/unidata/BidiMirroring.txt Sun Jun 13 22:57:55 2010 +0000 @@ -0,0 +1,597 @@ +# BidiMirroring-6.0.0.txt +# Date: 2009-11-10, 17:09:00 PST [KW] +# +# Bidi_Mirroring_Glyph Property +# +# This file is an informative contributory data file in the +# Unicode Character Database. +# +# Copyright (c) 1991-2009 Unicode, Inc. +# For terms of use, see http://www.unicode.org/terms_of_use.html +# +# This data file lists characters that have the Bidi_Mirrored=True property +# value, for which there is another Unicode character that typically has a glyph +# that is the mirror image of the original character's glyph. +# +# The repertoire covered by the file is Unicode 6.0.0. +# +# The file contains a list of lines with mappings from one code point +# to another one for character-based mirroring. +# Note that for "real" mirroring, a rendering engine needs to select +# appropriate alternative glyphs, and that many Unicode characters do not +# have a mirror-image Unicode character. +# +# Each mapping line contains two fields, separated by a semicolon (';'). +# Each of the two fields contains a code point represented as a +# variable-length hexadecimal value with 4 to 6 digits. +# A comment indicates where the characters are "BEST FIT" mirroring. +# +# Code points for which Bidi_Mirrored=True, but for which no appropriate +# characters exist with mirrored glyphs, are +# listed as comments at the end of the file. +# +# Formally, the default value of the Bidi_Mirroring_Glyph property +# for each code point is the code point itself, unless a mapping to +# some other character is specified in this data file. When a code +# point has the default value for the Bidi_Mirroring_Glyph property, +# that means that no other character exists whose glyph is suitable +# for character-based mirroring. +# +# For information on bidi mirroring, see UAX #9: Bidirectional Algorithm, +# at http://www.unicode.org/unicode/reports/tr9/ +# +# This file was originally created by Markus Scherer. +# Extended for Unicode 3.2, 4.0, 4.1, 5.0, 5.1, 5.2, and 6.0 by Ken Whistler. +# +# ############################################################ + +0028; 0029 # LEFT PARENTHESIS +0029; 0028 # RIGHT PARENTHESIS +003C; 003E # LESS-THAN SIGN +003E; 003C # GREATER-THAN SIGN +005B; 005D # LEFT SQUARE BRACKET +005D; 005B # RIGHT SQUARE BRACKET +007B; 007D # LEFT CURLY BRACKET +007D; 007B # RIGHT CURLY BRACKET +00AB; 00BB # LEFT-POINTING DOUBLE ANGLE QUOTATION MARK +00BB; 00AB # RIGHT-POINTING DOUBLE ANGLE QUOTATION MARK +0F3A; 0F3B # TIBETAN MARK GUG RTAGS GYON +0F3B; 0F3A # TIBETAN MARK GUG RTAGS GYAS +0F3C; 0F3D # TIBETAN MARK ANG KHANG GYON +0F3D; 0F3C # TIBETAN MARK ANG KHANG GYAS +169B; 169C # OGHAM FEATHER MARK +169C; 169B # OGHAM REVERSED FEATHER MARK +2039; 203A # SINGLE LEFT-POINTING ANGLE QUOTATION MARK +203A; 2039 # SINGLE RIGHT-POINTING ANGLE QUOTATION MARK +2045; 2046 # LEFT SQUARE BRACKET WITH QUILL +2046; 2045 # RIGHT SQUARE BRACKET WITH QUILL +207D; 207E # SUPERSCRIPT LEFT PARENTHESIS +207E; 207D # SUPERSCRIPT RIGHT PARENTHESIS +208D; 208E # SUBSCRIPT LEFT PARENTHESIS +208E; 208D # SUBSCRIPT RIGHT PARENTHESIS +2208; 220B # ELEMENT OF +2209; 220C # NOT AN ELEMENT OF +220A; 220D # SMALL ELEMENT OF +220B; 2208 # CONTAINS AS MEMBER +220C; 2209 # DOES NOT CONTAIN AS MEMBER +220D; 220A # SMALL CONTAINS AS MEMBER +2215; 29F5 # DIVISION SLASH +223C; 223D # TILDE OPERATOR +223D; 223C # REVERSED TILDE +2243; 22CD # ASYMPTOTICALLY EQUAL TO +2252; 2253 # APPROXIMATELY EQUAL TO OR THE IMAGE OF +2253; 2252 # IMAGE OF OR APPROXIMATELY EQUAL TO +2254; 2255 # COLON EQUALS +2255; 2254 # EQUALS COLON +2264; 2265 # LESS-THAN OR EQUAL TO +2265; 2264 # GREATER-THAN OR EQUAL TO +2266; 2267 # LESS-THAN OVER EQUAL TO +2267; 2266 # GREATER-THAN OVER EQUAL TO +2268; 2269 # [BEST FIT] LESS-THAN BUT NOT EQUAL TO +2269; 2268 # [BEST FIT] GREATER-THAN BUT NOT EQUAL TO +226A; 226B # MUCH LESS-THAN +226B; 226A # MUCH GREATER-THAN +226E; 226F # [BEST FIT] NOT LESS-THAN +226F; 226E # [BEST FIT] NOT GREATER-THAN +2270; 2271 # [BEST FIT] NEITHER LESS-THAN NOR EQUAL TO +2271; 2270 # [BEST FIT] NEITHER GREATER-THAN NOR EQUAL TO +2272; 2273 # [BEST FIT] LESS-THAN OR EQUIVALENT TO +2273; 2272 # [BEST FIT] GREATER-THAN OR EQUIVALENT TO +2274; 2275 # [BEST FIT] NEITHER LESS-THAN NOR EQUIVALENT TO +2275; 2274 # [BEST FIT] NEITHER GREATER-THAN NOR EQUIVALENT TO +2276; 2277 # LESS-THAN OR GREATER-THAN +2277; 2276 # GREATER-THAN OR LESS-THAN +2278; 2279 # [BEST FIT] NEITHER LESS-THAN NOR GREATER-THAN +2279; 2278 # [BEST FIT] NEITHER GREATER-THAN NOR LESS-THAN +227A; 227B # PRECEDES +227B; 227A # SUCCEEDS +227C; 227D # PRECEDES OR EQUAL TO +227D; 227C # SUCCEEDS OR EQUAL TO +227E; 227F # [BEST FIT] PRECEDES OR EQUIVALENT TO +227F; 227E # [BEST FIT] SUCCEEDS OR EQUIVALENT TO +2280; 2281 # [BEST FIT] DOES NOT PRECEDE +2281; 2280 # [BEST FIT] DOES NOT SUCCEED +2282; 2283 # SUBSET OF +2283; 2282 # SUPERSET OF +2284; 2285 # [BEST FIT] NOT A SUBSET OF +2285; 2284 # [BEST FIT] NOT A SUPERSET OF +2286; 2287 # SUBSET OF OR EQUAL TO +2287; 2286 # SUPERSET OF OR EQUAL TO +2288; 2289 # [BEST FIT] NEITHER A SUBSET OF NOR EQUAL TO +2289; 2288 # [BEST FIT] NEITHER A SUPERSET OF NOR EQUAL TO +228A; 228B # [BEST FIT] SUBSET OF WITH NOT EQUAL TO +228B; 228A # [BEST FIT] SUPERSET OF WITH NOT EQUAL TO +228F; 2290 # SQUARE IMAGE OF +2290; 228F # SQUARE ORIGINAL OF +2291; 2292 # SQUARE IMAGE OF OR EQUAL TO +2292; 2291 # SQUARE ORIGINAL OF OR EQUAL TO +2298; 29B8 # CIRCLED DIVISION SLASH +22A2; 22A3 # RIGHT TACK +22A3; 22A2 # LEFT TACK +22A6; 2ADE # ASSERTION +22A8; 2AE4 # TRUE +22A9; 2AE3 # FORCES +22AB; 2AE5 # DOUBLE VERTICAL BAR DOUBLE RIGHT TURNSTILE +22B0; 22B1 # PRECEDES UNDER RELATION +22B1; 22B0 # SUCCEEDS UNDER RELATION +22B2; 22B3 # NORMAL SUBGROUP OF +22B3; 22B2 # CONTAINS AS NORMAL SUBGROUP +22B4; 22B5 # NORMAL SUBGROUP OF OR EQUAL TO +22B5; 22B4 # CONTAINS AS NORMAL SUBGROUP OR EQUAL TO +22B6; 22B7 # ORIGINAL OF +22B7; 22B6 # IMAGE OF +22C9; 22CA # LEFT NORMAL FACTOR SEMIDIRECT PRODUCT +22CA; 22C9 # RIGHT NORMAL FACTOR SEMIDIRECT PRODUCT +22CB; 22CC # LEFT SEMIDIRECT PRODUCT +22CC; 22CB # RIGHT SEMIDIRECT PRODUCT +22CD; 2243 # REVERSED TILDE EQUALS +22D0; 22D1 # DOUBLE SUBSET +22D1; 22D0 # DOUBLE SUPERSET +22D6; 22D7 # LESS-THAN WITH DOT +22D7; 22D6 # GREATER-THAN WITH DOT +22D8; 22D9 # VERY MUCH LESS-THAN +22D9; 22D8 # VERY MUCH GREATER-THAN +22DA; 22DB # LESS-THAN EQUAL TO OR GREATER-THAN +22DB; 22DA # GREATER-THAN EQUAL TO OR LESS-THAN +22DC; 22DD # EQUAL TO OR LESS-THAN +22DD; 22DC # EQUAL TO OR GREATER-THAN +22DE; 22DF # EQUAL TO OR PRECEDES +22DF; 22DE # EQUAL TO OR SUCCEEDS +22E0; 22E1 # [BEST FIT] DOES NOT PRECEDE OR EQUAL +22E1; 22E0 # [BEST FIT] DOES NOT SUCCEED OR EQUAL +22E2; 22E3 # [BEST FIT] NOT SQUARE IMAGE OF OR EQUAL TO +22E3; 22E2 # [BEST FIT] NOT SQUARE ORIGINAL OF OR EQUAL TO +22E4; 22E5 # [BEST FIT] SQUARE IMAGE OF OR NOT EQUAL TO +22E5; 22E4 # [BEST FIT] SQUARE ORIGINAL OF OR NOT EQUAL TO +22E6; 22E7 # [BEST FIT] LESS-THAN BUT NOT EQUIVALENT TO +22E7; 22E6 # [BEST FIT] GREATER-THAN BUT NOT EQUIVALENT TO +22E8; 22E9 # [BEST FIT] PRECEDES BUT NOT EQUIVALENT TO +22E9; 22E8 # [BEST FIT] SUCCEEDS BUT NOT EQUIVALENT TO +22EA; 22EB # [BEST FIT] NOT NORMAL SUBGROUP OF +22EB; 22EA # [BEST FIT] DOES NOT CONTAIN AS NORMAL SUBGROUP +22EC; 22ED # [BEST FIT] NOT NORMAL SUBGROUP OF OR EQUAL TO +22ED; 22EC # [BEST FIT] DOES NOT CONTAIN AS NORMAL SUBGROUP OR EQUAL +22F0; 22F1 # UP RIGHT DIAGONAL ELLIPSIS +22F1; 22F0 # DOWN RIGHT DIAGONAL ELLIPSIS +22F2; 22FA # ELEMENT OF WITH LONG HORIZONTAL STROKE +22F3; 22FB # ELEMENT OF WITH VERTICAL BAR AT END OF HORIZONTAL STROKE +22F4; 22FC # SMALL ELEMENT OF WITH VERTICAL BAR AT END OF HORIZONTAL STROKE +22F6; 22FD # ELEMENT OF WITH OVERBAR +22F7; 22FE # SMALL ELEMENT OF WITH OVERBAR +22FA; 22F2 # CONTAINS WITH LONG HORIZONTAL STROKE +22FB; 22F3 # CONTAINS WITH VERTICAL BAR AT END OF HORIZONTAL STROKE +22FC; 22F4 # SMALL CONTAINS WITH VERTICAL BAR AT END OF HORIZONTAL STROKE +22FD; 22F6 # CONTAINS WITH OVERBAR +22FE; 22F7 # SMALL CONTAINS WITH OVERBAR +2308; 2309 # LEFT CEILING +2309; 2308 # RIGHT CEILING +230A; 230B # LEFT FLOOR +230B; 230A # RIGHT FLOOR +2329; 232A # LEFT-POINTING ANGLE BRACKET +232A; 2329 # RIGHT-POINTING ANGLE BRACKET +2768; 2769 # MEDIUM LEFT PARENTHESIS ORNAMENT +2769; 2768 # MEDIUM RIGHT PARENTHESIS ORNAMENT +276A; 276B # MEDIUM FLATTENED LEFT PARENTHESIS ORNAMENT +276B; 276A # MEDIUM FLATTENED RIGHT PARENTHESIS ORNAMENT +276C; 276D # MEDIUM LEFT-POINTING ANGLE BRACKET ORNAMENT +276D; 276C # MEDIUM RIGHT-POINTING ANGLE BRACKET ORNAMENT +276E; 276F # HEAVY LEFT-POINTING ANGLE QUOTATION MARK ORNAMENT +276F; 276E # HEAVY RIGHT-POINTING ANGLE QUOTATION MARK ORNAMENT +2770; 2771 # HEAVY LEFT-POINTING ANGLE BRACKET ORNAMENT +2771; 2770 # HEAVY RIGHT-POINTING ANGLE BRACKET ORNAMENT +2772; 2773 # LIGHT LEFT TORTOISE SHELL BRACKET +2773; 2772 # LIGHT RIGHT TORTOISE SHELL BRACKET +2774; 2775 # MEDIUM LEFT CURLY BRACKET ORNAMENT +2775; 2774 # MEDIUM RIGHT CURLY BRACKET ORNAMENT +27C3; 27C4 # OPEN SUBSET +27C4; 27C3 # OPEN SUPERSET +27C5; 27C6 # LEFT S-SHAPED BAG DELIMITER +27C6; 27C5 # RIGHT S-SHAPED BAG DELIMITER +27C8; 27C9 # REVERSE SOLIDUS PRECEDING SUBSET +27C9; 27C8 # SUPERSET PRECEDING SOLIDUS +27D5; 27D6 # LEFT OUTER JOIN +27D6; 27D5 # RIGHT OUTER JOIN +27DD; 27DE # LONG RIGHT TACK +27DE; 27DD # LONG LEFT TACK +27E2; 27E3 # WHITE CONCAVE-SIDED DIAMOND WITH LEFTWARDS TICK +27E3; 27E2 # WHITE CONCAVE-SIDED DIAMOND WITH RIGHTWARDS TICK +27E4; 27E5 # WHITE SQUARE WITH LEFTWARDS TICK +27E5; 27E4 # WHITE SQUARE WITH RIGHTWARDS TICK +27E6; 27E7 # MATHEMATICAL LEFT WHITE SQUARE BRACKET +27E7; 27E6 # MATHEMATICAL RIGHT WHITE SQUARE BRACKET +27E8; 27E9 # MATHEMATICAL LEFT ANGLE BRACKET +27E9; 27E8 # MATHEMATICAL RIGHT ANGLE BRACKET +27EA; 27EB # MATHEMATICAL LEFT DOUBLE ANGLE BRACKET +27EB; 27EA # MATHEMATICAL RIGHT DOUBLE ANGLE BRACKET +27EC; 27ED # MATHEMATICAL LEFT WHITE TORTOISE SHELL BRACKET +27ED; 27EC # MATHEMATICAL RIGHT WHITE TORTOISE SHELL BRACKET +27EE; 27EF # MATHEMATICAL LEFT FLATTENED PARENTHESIS +27EF; 27EE # MATHEMATICAL RIGHT FLATTENED PARENTHESIS +2983; 2984 # LEFT WHITE CURLY BRACKET +2984; 2983 # RIGHT WHITE CURLY BRACKET +2985; 2986 # LEFT WHITE PARENTHESIS +2986; 2985 # RIGHT WHITE PARENTHESIS +2987; 2988 # Z NOTATION LEFT IMAGE BRACKET +2988; 2987 # Z NOTATION RIGHT IMAGE BRACKET +2989; 298A # Z NOTATION LEFT BINDING BRACKET +298A; 2989 # Z NOTATION RIGHT BINDING BRACKET +298B; 298C # LEFT SQUARE BRACKET WITH UNDERBAR +298C; 298B # RIGHT SQUARE BRACKET WITH UNDERBAR +298D; 2990 # LEFT SQUARE BRACKET WITH TICK IN TOP CORNER +298E; 298F # RIGHT SQUARE BRACKET WITH TICK IN BOTTOM CORNER +298F; 298E # LEFT SQUARE BRACKET WITH TICK IN BOTTOM CORNER +2990; 298D # RIGHT SQUARE BRACKET WITH TICK IN TOP CORNER +2991; 2992 # LEFT ANGLE BRACKET WITH DOT +2992; 2991 # RIGHT ANGLE BRACKET WITH DOT +2993; 2994 # LEFT ARC LESS-THAN BRACKET +2994; 2993 # RIGHT ARC GREATER-THAN BRACKET +2995; 2996 # DOUBLE LEFT ARC GREATER-THAN BRACKET +2996; 2995 # DOUBLE RIGHT ARC LESS-THAN BRACKET +2997; 2998 # LEFT BLACK TORTOISE SHELL BRACKET +2998; 2997 # RIGHT BLACK TORTOISE SHELL BRACKET +29B8; 2298 # CIRCLED REVERSE SOLIDUS +29C0; 29C1 # CIRCLED LESS-THAN +29C1; 29C0 # CIRCLED GREATER-THAN +29C4; 29C5 # SQUARED RISING DIAGONAL SLASH +29C5; 29C4 # SQUARED FALLING DIAGONAL SLASH +29CF; 29D0 # LEFT TRIANGLE BESIDE VERTICAL BAR +29D0; 29CF # VERTICAL BAR BESIDE RIGHT TRIANGLE +29D1; 29D2 # BOWTIE WITH LEFT HALF BLACK +29D2; 29D1 # BOWTIE WITH RIGHT HALF BLACK +29D4; 29D5 # TIMES WITH LEFT HALF BLACK +29D5; 29D4 # TIMES WITH RIGHT HALF BLACK +29D8; 29D9 # LEFT WIGGLY FENCE +29D9; 29D8 # RIGHT WIGGLY FENCE +29DA; 29DB # LEFT DOUBLE WIGGLY FENCE +29DB; 29DA # RIGHT DOUBLE WIGGLY FENCE +29F5; 2215 # REVERSE SOLIDUS OPERATOR +29F8; 29F9 # BIG SOLIDUS +29F9; 29F8 # BIG REVERSE SOLIDUS +29FC; 29FD # LEFT-POINTING CURVED ANGLE BRACKET +29FD; 29FC # RIGHT-POINTING CURVED ANGLE BRACKET +2A2B; 2A2C # MINUS SIGN WITH FALLING DOTS +2A2C; 2A2B # MINUS SIGN WITH RISING DOTS +2A2D; 2A2E # PLUS SIGN IN LEFT HALF CIRCLE +2A2E; 2A2D # PLUS SIGN IN RIGHT HALF CIRCLE +2A34; 2A35 # MULTIPLICATION SIGN IN LEFT HALF CIRCLE +2A35; 2A34 # MULTIPLICATION SIGN IN RIGHT HALF CIRCLE +2A3C; 2A3D # INTERIOR PRODUCT +2A3D; 2A3C # RIGHTHAND INTERIOR PRODUCT +2A64; 2A65 # Z NOTATION DOMAIN ANTIRESTRICTION +2A65; 2A64 # Z NOTATION RANGE ANTIRESTRICTION +2A79; 2A7A # LESS-THAN WITH CIRCLE INSIDE +2A7A; 2A79 # GREATER-THAN WITH CIRCLE INSIDE +2A7D; 2A7E # LESS-THAN OR SLANTED EQUAL TO +2A7E; 2A7D # GREATER-THAN OR SLANTED EQUAL TO +2A7F; 2A80 # LESS-THAN OR SLANTED EQUAL TO WITH DOT INSIDE +2A80; 2A7F # GREATER-THAN OR SLANTED EQUAL TO WITH DOT INSIDE +2A81; 2A82 # LESS-THAN OR SLANTED EQUAL TO WITH DOT ABOVE +2A82; 2A81 # GREATER-THAN OR SLANTED EQUAL TO WITH DOT ABOVE +2A83; 2A84 # LESS-THAN OR SLANTED EQUAL TO WITH DOT ABOVE RIGHT +2A84; 2A83 # GREATER-THAN OR SLANTED EQUAL TO WITH DOT ABOVE LEFT +2A8B; 2A8C # LESS-THAN ABOVE DOUBLE-LINE EQUAL ABOVE GREATER-THAN +2A8C; 2A8B # GREATER-THAN ABOVE DOUBLE-LINE EQUAL ABOVE LESS-THAN +2A91; 2A92 # LESS-THAN ABOVE GREATER-THAN ABOVE DOUBLE-LINE EQUAL +2A92; 2A91 # GREATER-THAN ABOVE LESS-THAN ABOVE DOUBLE-LINE EQUAL +2A93; 2A94 # LESS-THAN ABOVE SLANTED EQUAL ABOVE GREATER-THAN ABOVE SLANTED EQUAL +2A94; 2A93 # GREATER-THAN ABOVE SLANTED EQUAL ABOVE LESS-THAN ABOVE SLANTED EQUAL +2A95; 2A96 # SLANTED EQUAL TO OR LESS-THAN +2A96; 2A95 # SLANTED EQUAL TO OR GREATER-THAN +2A97; 2A98 # SLANTED EQUAL TO OR LESS-THAN WITH DOT INSIDE +2A98; 2A97 # SLANTED EQUAL TO OR GREATER-THAN WITH DOT INSIDE +2A99; 2A9A # DOUBLE-LINE EQUAL TO OR LESS-THAN +2A9A; 2A99 # DOUBLE-LINE EQUAL TO OR GREATER-THAN +2A9B; 2A9C # DOUBLE-LINE SLANTED EQUAL TO OR LESS-THAN +2A9C; 2A9B # DOUBLE-LINE SLANTED EQUAL TO OR GREATER-THAN +2AA1; 2AA2 # DOUBLE NESTED LESS-THAN +2AA2; 2AA1 # DOUBLE NESTED GREATER-THAN +2AA6; 2AA7 # LESS-THAN CLOSED BY CURVE +2AA7; 2AA6 # GREATER-THAN CLOSED BY CURVE +2AA8; 2AA9 # LESS-THAN CLOSED BY CURVE ABOVE SLANTED EQUAL +2AA9; 2AA8 # GREATER-THAN CLOSED BY CURVE ABOVE SLANTED EQUAL +2AAA; 2AAB # SMALLER THAN +2AAB; 2AAA # LARGER THAN +2AAC; 2AAD # SMALLER THAN OR EQUAL TO +2AAD; 2AAC # LARGER THAN OR EQUAL TO +2AAF; 2AB0 # PRECEDES ABOVE SINGLE-LINE EQUALS SIGN +2AB0; 2AAF # SUCCEEDS ABOVE SINGLE-LINE EQUALS SIGN +2AB3; 2AB4 # PRECEDES ABOVE EQUALS SIGN +2AB4; 2AB3 # SUCCEEDS ABOVE EQUALS SIGN +2ABB; 2ABC # DOUBLE PRECEDES +2ABC; 2ABB # DOUBLE SUCCEEDS +2ABD; 2ABE # SUBSET WITH DOT +2ABE; 2ABD # SUPERSET WITH DOT +2ABF; 2AC0 # SUBSET WITH PLUS SIGN BELOW +2AC0; 2ABF # SUPERSET WITH PLUS SIGN BELOW +2AC1; 2AC2 # SUBSET WITH MULTIPLICATION SIGN BELOW +2AC2; 2AC1 # SUPERSET WITH MULTIPLICATION SIGN BELOW +2AC3; 2AC4 # SUBSET OF OR EQUAL TO WITH DOT ABOVE +2AC4; 2AC3 # SUPERSET OF OR EQUAL TO WITH DOT ABOVE +2AC5; 2AC6 # SUBSET OF ABOVE EQUALS SIGN +2AC6; 2AC5 # SUPERSET OF ABOVE EQUALS SIGN +2ACD; 2ACE # SQUARE LEFT OPEN BOX OPERATOR +2ACE; 2ACD # SQUARE RIGHT OPEN BOX OPERATOR +2ACF; 2AD0 # CLOSED SUBSET +2AD0; 2ACF # CLOSED SUPERSET +2AD1; 2AD2 # CLOSED SUBSET OR EQUAL TO +2AD2; 2AD1 # CLOSED SUPERSET OR EQUAL TO +2AD3; 2AD4 # SUBSET ABOVE SUPERSET +2AD4; 2AD3 # SUPERSET ABOVE SUBSET +2AD5; 2AD6 # SUBSET ABOVE SUBSET +2AD6; 2AD5 # SUPERSET ABOVE SUPERSET +2ADE; 22A6 # SHORT LEFT TACK +2AE3; 22A9 # DOUBLE VERTICAL BAR LEFT TURNSTILE +2AE4; 22A8 # VERTICAL BAR DOUBLE LEFT TURNSTILE +2AE5; 22AB # DOUBLE VERTICAL BAR DOUBLE LEFT TURNSTILE +2AEC; 2AED # DOUBLE STROKE NOT SIGN +2AED; 2AEC # REVERSED DOUBLE STROKE NOT SIGN +2AF7; 2AF8 # TRIPLE NESTED LESS-THAN +2AF8; 2AF7 # TRIPLE NESTED GREATER-THAN +2AF9; 2AFA # DOUBLE-LINE SLANTED LESS-THAN OR EQUAL TO +2AFA; 2AF9 # DOUBLE-LINE SLANTED GREATER-THAN OR EQUAL TO +2E02; 2E03 # LEFT SUBSTITUTION BRACKET +2E03; 2E02 # RIGHT SUBSTITUTION BRACKET +2E04; 2E05 # LEFT DOTTED SUBSTITUTION BRACKET +2E05; 2E04 # RIGHT DOTTED SUBSTITUTION BRACKET +2E09; 2E0A # LEFT TRANSPOSITION BRACKET +2E0A; 2E09 # RIGHT TRANSPOSITION BRACKET +2E0C; 2E0D # LEFT RAISED OMISSION BRACKET +2E0D; 2E0C # RIGHT RAISED OMISSION BRACKET +2E1C; 2E1D # LEFT LOW PARAPHRASE BRACKET +2E1D; 2E1C # RIGHT LOW PARAPHRASE BRACKET +2E20; 2E21 # LEFT VERTICAL BAR WITH QUILL +2E21; 2E20 # RIGHT VERTICAL BAR WITH QUILL +2E22; 2E23 # TOP LEFT HALF BRACKET +2E23; 2E22 # TOP RIGHT HALF BRACKET +2E24; 2E25 # BOTTOM LEFT HALF BRACKET +2E25; 2E24 # BOTTOM RIGHT HALF BRACKET +2E26; 2E27 # LEFT SIDEWAYS U BRACKET +2E27; 2E26 # RIGHT SIDEWAYS U BRACKET +2E28; 2E29 # LEFT DOUBLE PARENTHESIS +2E29; 2E28 # RIGHT DOUBLE PARENTHESIS +3008; 3009 # LEFT ANGLE BRACKET +3009; 3008 # RIGHT ANGLE BRACKET +300A; 300B # LEFT DOUBLE ANGLE BRACKET +300B; 300A # RIGHT DOUBLE ANGLE BRACKET +300C; 300D # [BEST FIT] LEFT CORNER BRACKET +300D; 300C # [BEST FIT] RIGHT CORNER BRACKET +300E; 300F # [BEST FIT] LEFT WHITE CORNER BRACKET +300F; 300E # [BEST FIT] RIGHT WHITE CORNER BRACKET +3010; 3011 # LEFT BLACK LENTICULAR BRACKET +3011; 3010 # RIGHT BLACK LENTICULAR BRACKET +3014; 3015 # LEFT TORTOISE SHELL BRACKET +3015; 3014 # RIGHT TORTOISE SHELL BRACKET +3016; 3017 # LEFT WHITE LENTICULAR BRACKET +3017; 3016 # RIGHT WHITE LENTICULAR BRACKET +3018; 3019 # LEFT WHITE TORTOISE SHELL BRACKET +3019; 3018 # RIGHT WHITE TORTOISE SHELL BRACKET +301A; 301B # LEFT WHITE SQUARE BRACKET +301B; 301A # RIGHT WHITE SQUARE BRACKET +FE59; FE5A # SMALL LEFT PARENTHESIS +FE5A; FE59 # SMALL RIGHT PARENTHESIS +FE5B; FE5C # SMALL LEFT CURLY BRACKET +FE5C; FE5B # SMALL RIGHT CURLY BRACKET +FE5D; FE5E # SMALL LEFT TORTOISE SHELL BRACKET +FE5E; FE5D # SMALL RIGHT TORTOISE SHELL BRACKET +FE64; FE65 # SMALL LESS-THAN SIGN +FE65; FE64 # SMALL GREATER-THAN SIGN +FF08; FF09 # FULLWIDTH LEFT PARENTHESIS +FF09; FF08 # FULLWIDTH RIGHT PARENTHESIS +FF1C; FF1E # FULLWIDTH LESS-THAN SIGN +FF1E; FF1C # FULLWIDTH GREATER-THAN SIGN +FF3B; FF3D # FULLWIDTH LEFT SQUARE BRACKET +FF3D; FF3B # FULLWIDTH RIGHT SQUARE BRACKET +FF5B; FF5D # FULLWIDTH LEFT CURLY BRACKET +FF5D; FF5B # FULLWIDTH RIGHT CURLY BRACKET +FF5F; FF60 # FULLWIDTH LEFT WHITE PARENTHESIS +FF60; FF5F # FULLWIDTH RIGHT WHITE PARENTHESIS +FF62; FF63 # [BEST FIT] HALFWIDTH LEFT CORNER BRACKET +FF63; FF62 # [BEST FIT] HALFWIDTH RIGHT CORNER BRACKET + +# The following characters have no appropriate mirroring character. +# For these characters it is up to the rendering system +# to provide mirrored glyphs. + +# 2140; DOUBLE-STRUCK N-ARY SUMMATION +# 2201; COMPLEMENT +# 2202; PARTIAL DIFFERENTIAL +# 2203; THERE EXISTS +# 2204; THERE DOES NOT EXIST +# 2211; N-ARY SUMMATION +# 2216; SET MINUS +# 221A; SQUARE ROOT +# 221B; CUBE ROOT +# 221C; FOURTH ROOT +# 221D; PROPORTIONAL TO +# 221F; RIGHT ANGLE +# 2220; ANGLE +# 2221; MEASURED ANGLE +# 2222; SPHERICAL ANGLE +# 2224; DOES NOT DIVIDE +# 2226; NOT PARALLEL TO +# 222B; INTEGRAL +# 222C; DOUBLE INTEGRAL +# 222D; TRIPLE INTEGRAL +# 222E; CONTOUR INTEGRAL +# 222F; SURFACE INTEGRAL +# 2230; VOLUME INTEGRAL +# 2231; CLOCKWISE INTEGRAL +# 2232; CLOCKWISE CONTOUR INTEGRAL +# 2233; ANTICLOCKWISE CONTOUR INTEGRAL +# 2239; EXCESS +# 223B; HOMOTHETIC +# 223E; INVERTED LAZY S +# 223F; SINE WAVE +# 2240; WREATH PRODUCT +# 2241; NOT TILDE +# 2242; MINUS TILDE +# 2244; NOT ASYMPTOTICALLY EQUAL TO +# 2245; APPROXIMATELY EQUAL TO +# 2246; APPROXIMATELY BUT NOT ACTUALLY EQUAL TO +# 2247; NEITHER APPROXIMATELY NOR ACTUALLY EQUAL TO +# 2248; ALMOST EQUAL TO +# 2249; NOT ALMOST EQUAL TO +# 224A; ALMOST EQUAL OR EQUAL TO +# 224B; TRIPLE TILDE +# 224C; ALL EQUAL TO +# 225F; QUESTIONED EQUAL TO +# 2260; NOT EQUAL TO +# 2262; NOT IDENTICAL TO +# 228C; MULTISET +# 22A7; MODELS +# 22AA; TRIPLE VERTICAL BAR RIGHT TURNSTILE +# 22AC; DOES NOT PROVE +# 22AD; NOT TRUE +# 22AE; DOES NOT FORCE +# 22AF; NEGATED DOUBLE VERTICAL BAR DOUBLE RIGHT TURNSTILE +# 22B8; MULTIMAP +# 22BE; RIGHT ANGLE WITH ARC +# 22BF; RIGHT TRIANGLE +# 22F5; ELEMENT OF WITH DOT ABOVE +# 22F8; ELEMENT OF WITH UNDERBAR +# 22F9; ELEMENT OF WITH TWO HORIZONTAL STROKES +# 22FF; Z NOTATION BAG MEMBERSHIP +# 2320; TOP HALF INTEGRAL +# 2321; BOTTOM HALF INTEGRAL +# 27CC; LONG DIVISION +# 27C0; THREE DIMENSIONAL ANGLE +# 27D3; LOWER RIGHT CORNER WITH DOT +# 27D4; UPPER LEFT CORNER WITH DOT +# 27DC; LEFT MULTIMAP +# 299B; MEASURED ANGLE OPENING LEFT +# 299C; RIGHT ANGLE VARIANT WITH SQUARE +# 299D; MEASURED RIGHT ANGLE WITH DOT +# 299E; ANGLE WITH S INSIDE +# 299F; ACUTE ANGLE +# 29A0; SPHERICAL ANGLE OPENING LEFT +# 29A1; SPHERICAL ANGLE OPENING UP +# 29A2; TURNED ANGLE +# 29A3; REVERSED ANGLE +# 29A4; ANGLE WITH UNDERBAR +# 29A5; REVERSED ANGLE WITH UNDERBAR +# 29A6; OBLIQUE ANGLE OPENING UP +# 29A7; OBLIQUE ANGLE OPENING DOWN +# 29A8; MEASURED ANGLE WITH OPEN ARM ENDING IN ARROW POINTING UP AND RIGHT +# 29A9; MEASURED ANGLE WITH OPEN ARM ENDING IN ARROW POINTING UP AND LEFT +# 29AA; MEASURED ANGLE WITH OPEN ARM ENDING IN ARROW POINTING DOWN AND RIGHT +# 29AB; MEASURED ANGLE WITH OPEN ARM ENDING IN ARROW POINTING DOWN AND LEFT +# 29AC; MEASURED ANGLE WITH OPEN ARM ENDING IN ARROW POINTING RIGHT AND UP +# 29AD; MEASURED ANGLE WITH OPEN ARM ENDING IN ARROW POINTING LEFT AND UP +# 29AE; MEASURED ANGLE WITH OPEN ARM ENDING IN ARROW POINTING RIGHT AND DOWN +# 29AF; MEASURED ANGLE WITH OPEN ARM ENDING IN ARROW POINTING LEFT AND DOWN +# 29C2; CIRCLE WITH SMALL CIRCLE TO THE RIGHT +# 29C3; CIRCLE WITH TWO HORIZONTAL STROKES TO THE RIGHT +# 29C9; TWO JOINED SQUARES +# 29CE; RIGHT TRIANGLE ABOVE LEFT TRIANGLE +# 29DC; INCOMPLETE INFINITY +# 29E1; INCREASES AS +# 29E3; EQUALS SIGN AND SLANTED PARALLEL +# 29E4; EQUALS SIGN AND SLANTED PARALLEL WITH TILDE ABOVE +# 29E5; IDENTICAL TO AND SLANTED PARALLEL +# 29E8; DOWN-POINTING TRIANGLE WITH LEFT HALF BLACK +# 29E9; DOWN-POINTING TRIANGLE WITH RIGHT HALF BLACK +# 29F4; RULE-DELAYED +# 29F6; SOLIDUS WITH OVERBAR +# 29F7; REVERSE SOLIDUS WITH HORIZONTAL STROKE +# 2A0A; MODULO TWO SUM +# 2A0B; SUMMATION WITH INTEGRAL +# 2A0C; QUADRUPLE INTEGRAL OPERATOR +# 2A0D; FINITE PART INTEGRAL +# 2A0E; INTEGRAL WITH DOUBLE STROKE +# 2A0F; INTEGRAL AVERAGE WITH SLASH +# 2A10; CIRCULATION FUNCTION +# 2A11; ANTICLOCKWISE INTEGRATION +# 2A12; LINE INTEGRATION WITH RECTANGULAR PATH AROUND POLE +# 2A13; LINE INTEGRATION WITH SEMICIRCULAR PATH AROUND POLE +# 2A14; LINE INTEGRATION NOT INCLUDING THE POLE +# 2A15; INTEGRAL AROUND A POINT OPERATOR +# 2A16; QUATERNION INTEGRAL OPERATOR +# 2A17; INTEGRAL WITH LEFTWARDS ARROW WITH HOOK +# 2A18; INTEGRAL WITH TIMES SIGN +# 2A19; INTEGRAL WITH INTERSECTION +# 2A1A; INTEGRAL WITH UNION +# 2A1B; INTEGRAL WITH OVERBAR +# 2A1C; INTEGRAL WITH UNDERBAR +# 2A1E; LARGE LEFT TRIANGLE OPERATOR +# 2A1F; Z NOTATION SCHEMA COMPOSITION +# 2A20; Z NOTATION SCHEMA PIPING +# 2A21; Z NOTATION SCHEMA PROJECTION +# 2A24; PLUS SIGN WITH TILDE ABOVE +# 2A26; PLUS SIGN WITH TILDE BELOW +# 2A29; MINUS SIGN WITH COMMA ABOVE +# 2A3E; Z NOTATION RELATIONAL COMPOSITION +# 2A57; SLOPING LARGE OR +# 2A58; SLOPING LARGE AND +# 2A6A; TILDE OPERATOR WITH DOT ABOVE +# 2A6B; TILDE OPERATOR WITH RISING DOTS +# 2A6C; SIMILAR MINUS SIMILAR +# 2A6D; CONGRUENT WITH DOT ABOVE +# 2A6F; ALMOST EQUAL TO WITH CIRCUMFLEX ACCENT +# 2A70; APPROXIMATELY EQUAL OR EQUAL TO +# 2A73; EQUALS SIGN ABOVE TILDE OPERATOR +# 2A74; DOUBLE COLON EQUAL +# 2A7B; LESS-THAN WITH QUESTION MARK ABOVE +# 2A7C; GREATER-THAN WITH QUESTION MARK ABOVE +# 2A85; LESS-THAN OR APPROXIMATE +# 2A86; GREATER-THAN OR APPROXIMATE +# 2A87; LESS-THAN AND SINGLE-LINE NOT EQUAL TO +# 2A88; GREATER-THAN AND SINGLE-LINE NOT EQUAL TO +# 2A89; LESS-THAN AND NOT APPROXIMATE +# 2A8A; GREATER-THAN AND NOT APPROXIMATE +# 2A8D; LESS-THAN ABOVE SIMILAR OR EQUAL +# 2A8E; GREATER-THAN ABOVE SIMILAR OR EQUAL +# 2A8F; LESS-THAN ABOVE SIMILAR ABOVE GREATER-THAN +# 2A90; GREATER-THAN ABOVE SIMILAR ABOVE LESS-THAN +# 2A9D; SIMILAR OR LESS-THAN +# 2A9E; SIMILAR OR GREATER-THAN +# 2A9F; SIMILAR ABOVE LESS-THAN ABOVE EQUALS SIGN +# 2AA0; SIMILAR ABOVE GREATER-THAN ABOVE EQUALS SIGN +# 2AA3; DOUBLE NESTED LESS-THAN WITH UNDERBAR +# 2AB1; PRECEDES ABOVE SINGLE-LINE NOT EQUAL TO +# 2AB2; SUCCEEDS ABOVE SINGLE-LINE NOT EQUAL TO +# 2AB5; PRECEDES ABOVE NOT EQUAL TO +# 2AB6; SUCCEEDS ABOVE NOT EQUAL TO +# 2AB7; PRECEDES ABOVE ALMOST EQUAL TO +# 2AB8; SUCCEEDS ABOVE ALMOST EQUAL TO +# 2AB9; PRECEDES ABOVE NOT ALMOST EQUAL TO +# 2ABA; SUCCEEDS ABOVE NOT ALMOST EQUAL TO +# 2AC7; SUBSET OF ABOVE TILDE OPERATOR +# 2AC8; SUPERSET OF ABOVE TILDE OPERATOR +# 2AC9; SUBSET OF ABOVE ALMOST EQUAL TO +# 2ACA; SUPERSET OF ABOVE ALMOST EQUAL TO +# 2ACB; SUBSET OF ABOVE NOT EQUAL TO +# 2ACC; SUPERSET OF ABOVE NOT EQUAL TO +# 2ADC; FORKING +# 2AE2; VERTICAL BAR TRIPLE RIGHT TURNSTILE +# 2AE6; LONG DASH FROM LEFT MEMBER OF DOUBLE VERTICAL +# 2AEE; DOES NOT DIVIDE WITH REVERSED NEGATION SLASH +# 2AF3; PARALLEL WITH TILDE OPERATOR +# 2AFB; TRIPLE SOLIDUS BINARY RELATION +# 2AFD; DOUBLE SOLIDUS OPERATOR +# 1D6DB; MATHEMATICAL BOLD PARTIAL DIFFERENTIAL +# 1D715; MATHEMATICAL ITALIC PARTIAL DIFFERENTIAL +# 1D74F; MATHEMATICAL BOLD ITALIC PARTIAL DIFFERENTIAL +# 1D789; MATHEMATICAL SANS-SERIF BOLD PARTIAL DIFFERENTIAL +# 1D7C3; MATHEMATICAL SANS-SERIF BOLD ITALIC PARTIAL DIFFERENTIAL + +# EOF diff -r 681cd08dc0f7 -r e93288477c43 admin/unidata/Makefile.in --- a/admin/unidata/Makefile.in Thu Jun 10 22:43:47 2010 +0000 +++ b/admin/unidata/Makefile.in Sun Jun 13 22:57:55 2010 +0000 @@ -23,7 +23,7 @@ DSTDIR = ../../lisp/international RUNEMACS = ${EMACS} -Q --multibyte -batch -all: ${DSTDIR}/charprop.el +all: ${DSTDIR}/charprop.el ../../src/biditype.h ../../src/bidimirror.h .el.elc: ${RUNEMACS} -batch -f batch-byte-compile $< @@ -37,6 +37,12 @@ cd ${DSTDIR}; \ ${RUNEMACS} -batch --load $${ELC} -f unidata-gen-files $${DATA} +../../src/biditype.h: UnicodeData.txt + gawk -F";" -f biditype.awk $< > $@ + +../../src/bidimirror.h: BidiMirroring.txt + gawk -F"[; ]+" -f bidimirror.awk $< > $@ + install: charprop.el cp charprop.el ${DSTDIR} cp `sed -n 's/^;; FILE: //p' < charprop.el` ${DSTDIR} diff -r 681cd08dc0f7 -r e93288477c43 admin/unidata/bidimirror.awk --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/admin/unidata/bidimirror.awk Sun Jun 13 22:57:55 2010 +0000 @@ -0,0 +1,37 @@ +# Generate data for bidi_mirroring_table, see src/bidi.c:bidi_initialize. + +# Copyright (C) 2010, Free Software Foundation, Inc. + +# 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 3 of the License, 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. If not, see . + +# Written by Eli Zaretskii + +BEGIN { + printf " struct {\n int from, to;\n } bidi_mirror[] = {\n"; + first = 1; + } + +$1 !~ /^#/ && NF >= 2 { + if (!first) + printf ",\n"; + else + first = 0; + printf "\t{ 0x%s, 0x%s }", $1, $2; + } + +END { + printf " };\n"; + } diff -r 681cd08dc0f7 -r e93288477c43 admin/unidata/biditype.awk --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/admin/unidata/biditype.awk Sun Jun 13 22:57:55 2010 +0000 @@ -0,0 +1,93 @@ +# Generate data for filling bidi_type_table, see src/bidi.c:bidi_initialize. + +# Copyright (C) 2010, Free Software Foundation, Inc. + +# 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 3 of the License, 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. If not, see . + +# Written by Eli Zaretskii + +function trtype(type) +{ + # Types are listed in the order of decresing use in UnicodeData.txt: + if (type == "ON") + return "NEUTRAL_ON"; + else if (type == "NSM") + return "WEAK_NSM"; + else if (type == "AL") + return "STRONG_AL"; + else if (type == "R") + return "STRONG_R"; + else if (type == "BN") + return "WEAK_BN"; + else if (type == "EN") + return "WEAK_EN"; + else if (type == "ET") + return "WEAK_ET"; + else if (type == "AN") + return "WEAK_AN"; + else if (type == "WS") + return "NEUTRAL_WS"; + else if (type == "CS") + return "WEAK_CS"; + else if (type == "ES") + return "WEAK_ES"; + else if (type == "B") + return "NEUTRAL_B"; + else if (type == "S") + return "NEUTRAL_S"; + else if (type == "LRE" || type == "RLE" || type == "LRO" || type == "RLO" || type == "PDF") + return type; + else if (type == "L") + return "STRONG_L"; + else + { + printf "Unknown type: %s\n", type > "/dev/stderr"; + exit 1; + } +} + +BEGIN { + otype = ""; + startcode = ""; + endcode = ""; + printf " struct {\n int from, to;\n bidi_type_t type;\n } bidi_type[] = {\n"; + first = 1; + } + + { code = $1; + ntype = $5; + if (ntype != otype) + { + # Don't output data for L, as that's the default value, see bidi.c. + if (otype != "L" && startcode != "") + { + if (!first) + printf ",\n"; + else + first = 0; + printf "\t{ 0x%s, 0x%s, %s }", startcode, endcode, trtype(otype); + } + otype = ntype; + startcode = code; + endcode = code; + } + else + endcode = code; + } + +END { + printf " };\n"; + } diff -r 681cd08dc0f7 -r e93288477c43 admin/unidata/makefile.w32-in --- a/admin/unidata/makefile.w32-in Thu Jun 10 22:43:47 2010 +0000 +++ b/admin/unidata/makefile.w32-in Sun Jun 13 22:57:55 2010 +0000 @@ -29,7 +29,7 @@ # Quote EMACS so it could be a file name with embedded whitespace RUNEMACS = "$(EMACS)" -Q --multibyte -batch -all: $(DSTDIR)/charprop.el +all: $(DSTDIR)/charprop.el ../../src/biditype.h ../../src/bidimirror.h .el.elc: $(RUNEMACS) -f batch-byte-compile $< @@ -50,6 +50,16 @@ ${DSTDIR}/charprop.el: charprop-$(SHELLTYPE) +../../src/biditype.h: UnicodeData.txt + gawk -F";" -f biditype.awk -v BINMODE=2 $< > biditype.h + $(CP) biditype.h $@ + $(DEL) biditype.h + +../../src/bidimirror.h: BidiMirroring.txt + gawk -F"[; ]+" -f bidimirror.awk -v BINMODE=2 $< > bidimirror.h + $(CP) bidimirror.h $@ + $(DEL) bidimirror.h + clean: - - $(DEL) unidata-gen.elc unidata.txt + - $(DEL) unidata-gen.elc unidata.txt biditype.h bidimirror.h diff -r 681cd08dc0f7 -r e93288477c43 configure.in --- a/configure.in Thu Jun 10 22:43:47 2010 +0000 +++ b/configure.in Sun Jun 13 22:57:55 2010 +0000 @@ -174,6 +174,16 @@ dnl http://lists.gnu.org/archive/html/emacs-devel/2008-04/msg01844.html OPTION_DEFAULT_ON([makeinfo],[don't require makeinfo for building manuals]) +## This is an option because I do not know if all info/man support +## compressed files, nor how to test if they do so. +OPTION_DEFAULT_ON([compress-info],[don't compress the installed Info pages]) +if test $with_compress_info = yes; then + GZIP_INFO=yes +else + GZIP_INFO= +fi +AC_SUBST(GZIP_INFO) + AC_ARG_WITH([pkg-config-prog],dnl [AS_HELP_STRING([--with-pkg-config-prog=PATH], [path to pkg-config for finding GTK and librsvg])]) diff -r 681cd08dc0f7 -r e93288477c43 etc/NEWS --- a/etc/NEWS Thu Jun 10 22:43:47 2010 +0000 +++ b/etc/NEWS Sun Jun 13 22:57:55 2010 +0000 @@ -24,6 +24,13 @@ * Installation Changes in Emacs 24.1 +** Configure links against libselinux if it is found. +You can disable this by using --without-selinux. + +--- +** By default, the installed Info and man pages are compressed. +You can disable this by configuring --without-compress-info. + --- ** There are new configure options: --with-mmdf, --with-mail-unlink, --with-mailhost. @@ -34,9 +41,6 @@ ** There is a new configure option --with-crt-dir. This is only useful if your crt*.o files are in a non-standard location. -** Configure links against libselinux if it is found. -You can disable this by using --without-selinux. - * Startup Changes in Emacs 24.1 diff -r 681cd08dc0f7 -r e93288477c43 lib-src/ChangeLog --- a/lib-src/ChangeLog Thu Jun 10 22:43:47 2010 +0000 +++ b/lib-src/ChangeLog Sun Jun 13 22:57:55 2010 +0000 @@ -1,3 +1,8 @@ +2010-06-11 Juanma Barranquero + + * makefile.w32-in (lisp2): Fix references to vc/vc-hooks.elc + and vc/ediff-hook.elc. + 2010-06-06 Dan Nicolaescu * ntlib.h: Remove code dealing with BSTRING. diff -r 681cd08dc0f7 -r e93288477c43 lib-src/makefile.w32-in --- a/lib-src/makefile.w32-in Thu Jun 10 22:43:47 2010 +0000 +++ b/lib-src/makefile.w32-in Sun Jun 13 22:57:55 2010 +0000 @@ -279,8 +279,8 @@ $(lispsource)textmodes/text-mode.elc \ $(lispsource)emacs-lisp/timer.elc \ $(lispsource)jka-cmpr-hook.elc \ - $(lispsource)vc-hooks.elc \ - $(lispsource)ediff-hook.elc \ + $(lispsource)vc/vc-hooks.elc \ + $(lispsource)vc/ediff-hook.elc \ $(lispsource)epa-hook.elc \ $(TOOLTIP_SUPPORT) \ $(WINNT_SUPPORT) \ diff -r 681cd08dc0f7 -r e93288477c43 lisp/ChangeLog --- a/lisp/ChangeLog Thu Jun 10 22:43:47 2010 +0000 +++ b/lisp/ChangeLog Sun Jun 13 22:57:55 2010 +0000 @@ -1,3 +1,73 @@ +2010-06-12 Chong Yidong + + * term/common-win.el (x-colors): Add all the color names defined + in rgb.txt (Bug#6332). + + * facemenu.el (list-colors-print): Don't print extra names if it + will overflow the window width. + + * vc/log-edit.el (log-edit-font-lock-keywords): Revert 2010-06-02 + change (Bug#6343). + +2010-06-12 Eli Zaretskii + + * files.el (make-directory): Doc fix (bug#6396). + +2010-06-12 Michael Albinus + + * net/tramp.el (tramp-remote-process-environment): Protect version + string by apostroph. + (tramp-shell-prompt-pattern): Do not use a shy group in case of + XEmacs. + (tramp-file-name-for-operation): Add `call-process-region'. + (tramp-set-process-query-on-exit-flag): Fix wrong parentheses. + + * net/tramp-compat.el (top): Do not autoload + `tramp-handle-file-remote-p'. Load tramp-util.el and tramp-vc.el + only when `start-file-process' is not bound. + (tramp-advice-file-expand-wildcards): Do not use + `tramp-handle-file-remote-p'. + (tramp-compat-make-temp-file): Handle the case, that + `make-temp-file' has no third argument EXTENSION. + +2010-06-11 Juanma Barranquero + + * makefile.w32-in (WINS_BASIC): Include new directory vc. + + * loadup.el ("vc-hooks", "ediff-hook"): Load from lisp/vc/. + +2010-06-11 Juri Linkov + + * finder.el (finder-known-keywords): Add keyword "vc" + for version control. + + * add-log.el, cvs-status.el, diff.el, diff-mode.el, ediff.el, + * emerge.el, log-edit.el, log-view.el, pcvs.el, smerge-mode.el, + * vc-annotate.el, vc-bzr.el, vc-dir.el, vc-dispatcher.el, vc-git.el, + * vc-hg.el, vc-mtn.el, vc.el: Add keyword "vc". + +2010-06-11 Juri Linkov + + Move version control related files to the "vc" subdirectory. + * add-log.el, cvs-status.el, diff.el, diff-mode.el, ediff-diff.el, + * ediff.el, ediff-help.el, ediff-hook.el, ediff-init.el, + * ediff-merg.el, ediff-mult.el, ediff-ptch.el, ediff-util.el, + * ediff-vers.el, ediff-wind.el, emerge.el, log-edit.el, log-view.el, + * pcvs-defs.el, pcvs.el, pcvs-info.el, pcvs-parse.el, pcvs-util.el, + * smerge-mode.el, vc-annotate.el, vc-arch.el, vc-bzr.el, vc-cvs.el, + * vc-dav.el, vc-dir.el, vc-dispatcher.el, vc.el, vc-git.el, + * vc-hg.el, vc-hooks.el, vc-mtn.el, vc-rcs.el, vc-sccs.el, vc-svn.el: + Move files to the "vc" subdirectory. + +2010-06-11 Chong Yidong + + * comint.el (comint-password-prompt-regexp): Fix 2010-04-10 change + (Bug#6367). + +2010-06-11 Stephen Eglen + + * shell.el: Bind `shell-resync-dirs' to M-RET. + 2010-06-10 Michael Albinus * notifications.el: Move file from lisp/net, because it is @@ -7,7 +77,7 @@ * net/notifications.el (notifications-on-action-signal) (notifications-on-closed-signal): Pass notification id as first - argument to the callback functions. Add docstrings. + argument to the callback functions. Add docstrings. (notifications-notify): Fix docstring. 2010-06-10 Glenn Morris @@ -276,7 +346,7 @@ exists. Raise an error, if not (due to a corresponding answer "no" in interactive questions, for example). -22010-06-02 Dan Nicolaescu +2010-06-02 Dan Nicolaescu * log-edit.el (log-edit-font-lock-keywords): Make group 4 match lax. diff -r 681cd08dc0f7 -r e93288477c43 lisp/add-log.el --- a/lisp/add-log.el Thu Jun 10 22:43:47 2010 +0000 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,1365 +0,0 @@ -;;; add-log.el --- change log maintenance commands for Emacs - -;; Copyright (C) 1985, 1986, 1988, 1993, 1994, 1997, 1998, 2000, 2001, -;; 2002, 2003, 2004, 2005, 2006, 2007, 2008, 2009, 2010 -;; Free Software Foundation, Inc. - -;; Maintainer: FSF -;; Keywords: tools - -;; 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 3 of the License, 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. If not, see . - -;;; Commentary: - -;; This facility is documented in the Emacs Manual. - -;; Todo: - -;; - Find/use/create _MTN/log if there's a _MTN directory. -;; - Find/use/create ++log.* if there's an {arch} directory. -;; - Use an open *VC-Log* or *cvs-commit* buffer if it's related to the -;; source file. -;; - Don't add TAB indents (and username?) if inserting entries in those -;; special places. - -;;; Code: - -(eval-when-compile - (require 'timezone)) - -(defgroup change-log nil - "Change log maintenance." - :group 'tools - :link '(custom-manual "(emacs)Change Log") - :prefix "change-log-" - :prefix "add-log-") - - -(defcustom change-log-default-name nil - "Name of a change log file for \\[add-change-log-entry]." - :type '(choice (const :tag "default" nil) - string) - :group 'change-log) -;;;###autoload -(put 'change-log-default-name 'safe-local-variable 'string-or-null-p) - -(defcustom change-log-mode-hook nil - "Normal hook run by `change-log-mode'." - :type 'hook - :group 'change-log) - -;; Many modes set this variable, so avoid warnings. -;;;###autoload -(defcustom add-log-current-defun-function nil - "If non-nil, function to guess name of surrounding function. -It is used by `add-log-current-defun' in preference to built-in rules. -Returns function's name as a string, or nil if outside a function." - :type '(choice (const nil) function) - :group 'change-log) - -;;;###autoload -(defcustom add-log-full-name nil - "Full name of user, for inclusion in ChangeLog daily headers. -This defaults to the value returned by the function `user-full-name'." - :type '(choice (const :tag "Default" nil) - string) - :group 'change-log) - -;;;###autoload -(defcustom add-log-mailing-address nil - "Email addresses of user, for inclusion in ChangeLog headers. -This defaults to the value of `user-mail-address'. In addition to -being a simple string, this value can also be a list. All elements -will be recognized as referring to the same user; when creating a new -ChangeLog entry, one element will be chosen at random." - :type '(choice (const :tag "Default" nil) - (string :tag "String") - (repeat :tag "List of Strings" string)) - :group 'change-log) - -(defcustom add-log-time-format 'add-log-iso8601-time-string - "Function that defines the time format. -For example, `add-log-iso8601-time-string', which gives the -date in international ISO 8601 format, -and `current-time-string' are two valid values." - :type '(radio (const :tag "International ISO 8601 format" - add-log-iso8601-time-string) - (const :tag "Old format, as returned by `current-time-string'" - current-time-string) - (function :tag "Other")) - :group 'change-log) - -(defcustom add-log-keep-changes-together nil - "If non-nil, normally keep day's log entries for one file together. - -Log entries for a given file made with \\[add-change-log-entry] or -\\[add-change-log-entry-other-window] will only be added to others \ -for that file made -today if this variable is non-nil or that file comes first in today's -entries. Otherwise another entry for that file will be started. An -original log: - - * foo (...): ... - * bar (...): change 1 - -in the latter case, \\[add-change-log-entry-other-window] in a \ -buffer visiting `bar', yields: - - * bar (...): -!- - * foo (...): ... - * bar (...): change 1 - -and in the former: - - * foo (...): ... - * bar (...): change 1 - (...): -!- - -The NEW-ENTRY arg to `add-change-log-entry' can override the effect of -this variable." - :version "20.3" - :type 'boolean - :group 'change-log) - -(defcustom add-log-always-start-new-record nil - "If non-nil, `add-change-log-entry' will always start a new record." - :version "22.1" - :type 'boolean - :group 'change-log) - -(defcustom add-log-buffer-file-name-function nil - "If non-nil, function to call to identify the full filename of a buffer. -This function is called with no argument. If this is nil, the default is to -use `buffer-file-name'." - :type '(choice (const nil) function) - :group 'change-log) - -(defcustom add-log-file-name-function nil - "If non-nil, function to call to identify the filename for a ChangeLog entry. -This function is called with one argument, the value of variable -`buffer-file-name' in that buffer. If this is nil, the default is to -use the file's name relative to the directory of the change log file." - :type '(choice (const nil) function) - :group 'change-log) - - -(defcustom change-log-version-info-enabled nil - "If non-nil, enable recording version numbers with the changes." - :version "21.1" - :type 'boolean - :group 'change-log) - -(defcustom change-log-version-number-regexp-list - (let ((re "\\([0-9]+\.[0-9.]+\\)")) - (list - ;; (defconst ad-version "2.15" - (concat "^(def[^ \t\n]+[ \t]+[^ \t\n][ \t]\"" re) - ;; Revision: pcl-cvs.el,v 1.72 1999/09/05 20:21:54 monnier Exp - (concat "^;+ *Revision: +[^ \t\n]+[ \t]+" re))) - "List of regexps to search for version number. -The version number must be in group 1. -Note: The search is conducted only within 10%, at the beginning of the file." - :version "21.1" - :type '(repeat regexp) - :group 'change-log) - -(defface change-log-date - '((t (:inherit font-lock-string-face))) - "Face used to highlight dates in date lines." - :version "21.1" - :group 'change-log) -(define-obsolete-face-alias 'change-log-date-face 'change-log-date "22.1") - -(defface change-log-name - '((t (:inherit font-lock-constant-face))) - "Face for highlighting author names." - :version "21.1" - :group 'change-log) -(define-obsolete-face-alias 'change-log-name-face 'change-log-name "22.1") - -(defface change-log-email - '((t (:inherit font-lock-variable-name-face))) - "Face for highlighting author email addresses." - :version "21.1" - :group 'change-log) -(define-obsolete-face-alias 'change-log-email-face 'change-log-email "22.1") - -(defface change-log-file - '((t (:inherit font-lock-function-name-face))) - "Face for highlighting file names." - :version "21.1" - :group 'change-log) -(define-obsolete-face-alias 'change-log-file-face 'change-log-file "22.1") - -(defface change-log-list - '((t (:inherit font-lock-keyword-face))) - "Face for highlighting parenthesized lists of functions or variables." - :version "21.1" - :group 'change-log) -(define-obsolete-face-alias 'change-log-list-face 'change-log-list "22.1") - -(defface change-log-conditionals - '((t (:inherit font-lock-variable-name-face))) - "Face for highlighting conditionals of the form `[...]'." - :version "21.1" - :group 'change-log) -(define-obsolete-face-alias 'change-log-conditionals-face - 'change-log-conditionals "22.1") - -(defface change-log-function - '((t (:inherit font-lock-variable-name-face))) - "Face for highlighting items of the form `<....>'." - :version "21.1" - :group 'change-log) -(define-obsolete-face-alias 'change-log-function-face - 'change-log-function "22.1") - -(defface change-log-acknowledgement - '((t (:inherit font-lock-comment-face))) - "Face for highlighting acknowledgments." - :version "21.1" - :group 'change-log) -(define-obsolete-face-alias 'change-log-acknowledgement-face - 'change-log-acknowledgement "22.1") - -(defconst change-log-file-names-re "^\\( +\\|\t\\)\\* \\([^ ,:([\n]+\\)") -(defconst change-log-start-entry-re "^\\sw.........[0-9:+ ]*") - -(defvar change-log-font-lock-keywords - `(;; - ;; Date lines, new (2000-01-01) and old (Sat Jan 1 00:00:00 2000) styles. - ;; Fixme: this regepx is just an approximate one and may match - ;; wrongly with a non-date line existing as a random note. In - ;; addition, using any kind of fixed setting like this doesn't - ;; work if a user customizes add-log-time-format. - ("^[0-9-]+ +\\|^ \\{11,\\}\\|^\\(Sun\\|Mon\\|Tue\\|Wed\\|Thu\\|Fri\\|Sat\\) [A-z][a-z][a-z] [0-9:+ ]+" - (0 'change-log-date-face) - ;; Name and e-mail; some people put e-mail in parens, not angles. - ("\\([^<(]+?\\)[ \t]*[(<]\\([A-Za-z0-9_.+-]+@[A-Za-z0-9_.-]+\\)[>)]" nil nil - (1 'change-log-name) - (2 'change-log-email))) - ;; - ;; File names. - (,change-log-file-names-re - (2 'change-log-file) - ;; Possibly further names in a list: - ("\\=, \\([^ ,:([\n]+\\)" nil nil (1 'change-log-file)) - ;; Possibly a parenthesized list of names: - ("\\= (\\([^(),\n]+\\|(\\(setf\\|SETF\\) [^() ,\n]+)\\)" - nil nil (1 'change-log-list)) - ("\\=, *\\([^(),\n]+\\|(\\(setf\\|SETF\\) [^() ,\n]+)\\)" - nil nil (1 'change-log-list))) - ;; - ;; Function or variable names. - ("^\\( +\\|\t\\)(\\([^(),\n]+\\|(\\(setf\\|SETF\\) [^() ,\n]+)\\)" - (2 'change-log-list) - ("\\=, *\\([^(),\n]+\\|(\\(setf\\|SETF\\) [^() ,\n]+)\\)" nil nil - (1 'change-log-list))) - ;; - ;; Conditionals. - ("\\[!?\\([^]\n]+\\)\\]\\(:\\| (\\)" (1 'change-log-conditionals)) - ;; - ;; Function of change. - ("<\\([^>\n]+\\)>\\(:\\| (\\)" (1 'change-log-function)) - ;; - ;; Acknowledgements. - ;; Don't include plain "From" because that is vague; - ;; we want to encourage people to say something more specific. - ;; Note that the FSF does not use "Patches by"; our convention - ;; is to put the name of the author of the changes at the top - ;; of the change log entry. - ("\\(^\\( +\\|\t\\)\\| \\)\\(Patch\\(es\\)? by\\|Report\\(ed by\\| from\\)\\|Suggest\\(ed by\\|ion from\\)\\)" - 3 'change-log-acknowledgement)) - "Additional expressions to highlight in Change Log mode.") - -(defun change-log-search-file-name (where) - "Return the file-name for the change under point." - (save-excursion - (goto-char where) - (beginning-of-line 1) - (if (looking-at change-log-start-entry-re) - ;; We are at the start of an entry, search forward for a file - ;; name. - (progn - (re-search-forward change-log-file-names-re nil t) - (match-string-no-properties 2)) - (if (looking-at change-log-file-names-re) - ;; We found a file name. - (match-string-no-properties 2) - ;; Look backwards for either a file name or the log entry start. - (if (re-search-backward - (concat "\\(" change-log-start-entry-re - "\\)\\|\\(" - change-log-file-names-re "\\)") nil t) - (if (match-beginning 1) - ;; We got the start of the entry, look forward for a - ;; file name. - (progn - (re-search-forward change-log-file-names-re nil t) - (match-string-no-properties 2)) - (match-string-no-properties 4)) - ;; We must be before any file name, look forward. - (re-search-forward change-log-file-names-re nil t) - (match-string-no-properties 2)))))) - -(defun change-log-find-file () - "Visit the file for the change under point." - (interactive) - (let ((file (change-log-search-file-name (point)))) - (if (and file (file-exists-p file)) - (find-file file) - (message "No such file or directory: %s" file)))) - -(defun change-log-search-tag-name-1 (&optional from) - "Search for a tag name within subexpression 1 of last match. -Optional argument FROM specifies a buffer position where the tag -name should be located. Return value is a cons whose car is the -string representing the tag and whose cdr is the position where -the tag was found." - (save-restriction - (narrow-to-region (match-beginning 1) (match-end 1)) - (when from (goto-char from)) - ;; The regexp below skips any symbol near `point' (FROM) followed by - ;; whitespace and another symbol. This should skip, for example, - ;; "struct" in a specification like "(struct buffer)" and move to - ;; "buffer". A leading paren is ignored. - (when (looking-at - "[(]?\\(?:\\(?:\\sw\\|\\s_\\)+\\(?:[ \t]+\\(\\sw\\|\\s_\\)+\\)\\)") - (goto-char (match-beginning 1))) - (cons (find-tag-default) (point)))) - -(defconst change-log-tag-re - "(\\(\\(?:\\sw\\|\\s_\\)+\\(?:[, \t]+\\(?:\\sw\\|\\s_\\)+\\)*\\))" - "Regexp matching a tag name in change log entries.") - -(defun change-log-search-tag-name (&optional at) - "Search for a tag name near `point'. -Optional argument AT non-nil means search near buffer position AT. -Return value is a cons whose car is the string representing -the tag and whose cdr is the position where the tag was found." - (save-excursion - (goto-char (setq at (or at (point)))) - (save-restriction - (widen) - (or (condition-case nil - ;; Within parenthesized list? - (save-excursion - (backward-up-list) - (when (looking-at change-log-tag-re) - (change-log-search-tag-name-1 at))) - (error nil)) - (condition-case nil - ;; Before parenthesized list on same line? - (save-excursion - (when (and (skip-chars-forward " \t") - (looking-at change-log-tag-re)) - (change-log-search-tag-name-1))) - (error nil)) - (condition-case nil - ;; Near file name? - (save-excursion - (when (and (progn - (beginning-of-line) - (looking-at change-log-file-names-re)) - (goto-char (match-end 0)) - (skip-syntax-forward " ") - (looking-at change-log-tag-re)) - (change-log-search-tag-name-1))) - (error nil)) - (condition-case nil - ;; Anywhere else within current entry? - (let ((from - (save-excursion - (end-of-line) - (if (re-search-backward change-log-start-entry-re nil t) - (match-beginning 0) - (point-min)))) - (to - (save-excursion - (end-of-line) - (if (re-search-forward change-log-start-entry-re nil t) - (match-beginning 0) - (point-max))))) - (when (and (< from to) (<= from at) (<= at to)) - (save-restriction - ;; Narrow to current change log entry. - (narrow-to-region from to) - (cond - ((re-search-backward change-log-tag-re nil t) - (narrow-to-region (match-beginning 1) (match-end 1)) - (goto-char (point-max)) - (cons (find-tag-default) (point-max))) - ((re-search-forward change-log-tag-re nil t) - (narrow-to-region (match-beginning 1) (match-end 1)) - (goto-char (point-min)) - (cons (find-tag-default) (point-min))))))) - (error nil)))))) - -(defvar change-log-find-head nil) -(defvar change-log-find-tail nil) -(defvar change-log-find-window nil) - -(defun change-log-goto-source-1 (tag regexp file buffer - &optional window first last) - "Search for tag TAG in buffer BUFFER visiting file FILE. -REGEXP is a regular expression for TAG. The remaining arguments -are optional: WINDOW denotes the window to display the results of -the search. FIRST is a position in BUFFER denoting the first -match from previous searches for TAG. LAST is the position in -BUFFER denoting the last match for TAG in the last search." - (with-current-buffer buffer - (save-excursion - (save-restriction - (widen) - (if last - (progn - ;; When LAST is set make sure we continue from the next - ;; line end to not find the same tag again. - (goto-char last) - (end-of-line) - (condition-case nil - ;; Try to go to the end of the current defun to avoid - ;; false positives within the current defun's body - ;; since these would match `add-log-current-defun'. - (end-of-defun) - ;; Don't fall behind when `end-of-defun' fails. - (error (progn (goto-char last) (end-of-line)))) - (setq last nil)) - ;; When LAST was not set start at beginning of BUFFER. - (goto-char (point-min))) - (let (current-defun) - (while (and (not last) (re-search-forward regexp nil t)) - ;; Verify that `add-log-current-defun' invoked at the end - ;; of the match returns TAG. This heuristic works well - ;; whenever the name of the defun occurs within the first - ;; line of the defun. - (setq current-defun (add-log-current-defun)) - (when (and current-defun (string-equal current-defun tag)) - ;; Record this as last match. - (setq last (line-beginning-position)) - ;; Record this as first match when there's none. - (unless first (setq first last))))))) - (if (or last first) - (with-selected-window - (setq change-log-find-window (or window (display-buffer buffer))) - (if last - (progn - (when (or (< last (point-min)) (> last (point-max))) - ;; Widen to show TAG. - (widen)) - (push-mark) - (goto-char last)) - ;; When there are no more matches go (back) to FIRST. - (message "No more matches for tag `%s' in file `%s'" tag file) - (setq last first) - (goto-char first)) - ;; Return new "tail". - (list (selected-window) first last)) - (message "Source location of tag `%s' not found in file `%s'" tag file) - nil))) - -(defun change-log-goto-source () - "Go to source location of \"change log tag\" near `point'. -A change log tag is a symbol within a parenthesized, -comma-separated list. If no suitable tag can be found nearby, -try to visit the file for the change under `point' instead." - (interactive) - (if (and (eq last-command 'change-log-goto-source) - change-log-find-tail) - (setq change-log-find-tail - (condition-case nil - (apply 'change-log-goto-source-1 - (append change-log-find-head change-log-find-tail)) - (error - (format "Cannot find more matches for tag `%s' in file `%s'" - (car change-log-find-head) - (nth 2 change-log-find-head))))) - (save-excursion - (let* ((at (point)) - (tag-at (change-log-search-tag-name)) - (tag (car tag-at)) - (file (when tag-at (change-log-search-file-name (cdr tag-at)))) - (file-at (when file (match-beginning 2))) - ;; `file-2' is the file `change-log-search-file-name' finds - ;; at `point'. We use `file-2' as a fallback when `tag' or - ;; `file' are not suitable for some reason. - (file-2 (change-log-search-file-name at)) - (file-2-at (when file-2 (match-beginning 2)))) - (cond - ((and (or (not tag) (not file) (not (file-exists-p file))) - (or (not file-2) (not (file-exists-p file-2)))) - (error "Cannot find tag or file near `point'")) - ((and file-2 (file-exists-p file-2) - (or (not tag) (not file) (not (file-exists-p file)) - (and (or (and (< file-at file-2-at) (<= file-2-at at)) - (and (<= at file-2-at) (< file-2-at file-at)))))) - ;; We either have not found a suitable file name or `file-2' - ;; provides a "better" file name wrt `point'. Go to the - ;; buffer of `file-2' instead. - (setq change-log-find-window - (display-buffer (find-file-noselect file-2)))) - (t - (setq change-log-find-head - (list tag (concat "\\_<" (regexp-quote tag) "\\_>") - file (find-file-noselect file))) - (condition-case nil - (setq change-log-find-tail - (apply 'change-log-goto-source-1 change-log-find-head)) - (error - (format "Cannot find matches for tag `%s' in file `%s'" - tag file))))))))) - -(defun change-log-next-error (&optional argp reset) - "Move to the Nth (default 1) next match in a ChangeLog buffer. -Compatibility function for \\[next-error] invocations." - (interactive "p") - (let* ((argp (or argp 0)) - (count (abs argp)) ; how many cycles - (down (< argp 0)) ; are we going down? (is argp negative?) - (up (not down)) - (search-function (if up 're-search-forward 're-search-backward))) - - ;; set the starting position - (goto-char (cond (reset (point-min)) - (down (line-beginning-position)) - (up (line-end-position)) - ((point)))) - - (funcall search-function change-log-file-names-re nil t count)) - - (beginning-of-line) - ;; if we found a place to visit... - (when (looking-at change-log-file-names-re) - (let (change-log-find-window) - (change-log-goto-source) - (when change-log-find-window - ;; Select window displaying source file. - (select-window change-log-find-window))))) - -(defvar change-log-mode-map - (let ((map (make-sparse-keymap)) - (menu-map (make-sparse-keymap))) - (define-key map [?\C-c ?\C-p] 'add-log-edit-prev-comment) - (define-key map [?\C-c ?\C-n] 'add-log-edit-next-comment) - (define-key map [?\C-c ?\C-f] 'change-log-find-file) - (define-key map [?\C-c ?\C-c] 'change-log-goto-source) - (define-key map [menu-bar changelog] (cons "ChangeLog" menu-map)) - (define-key menu-map [gs] - '(menu-item "Go To Source" change-log-goto-source - :help "Go to source location of ChangeLog tag near point")) - (define-key menu-map [ff] - '(menu-item "Find File" change-log-find-file - :help "Visit the file for the change under point")) - (define-key menu-map [sep] '("--")) - (define-key menu-map [nx] - '(menu-item "Next Log-Edit Comment" add-log-edit-next-comment - :help "Cycle forward through Log-Edit mode comment history")) - (define-key menu-map [pr] - '(menu-item "Previous Log-Edit Comment" add-log-edit-prev-comment - :help "Cycle backward through Log-Edit mode comment history")) - map) - "Keymap for Change Log major mode.") - -;; It used to be called change-log-time-zone-rule but really should be -;; called add-log-time-zone-rule since it's only used from add-log-* code. -(defvaralias 'change-log-time-zone-rule 'add-log-time-zone-rule) -(defvar add-log-time-zone-rule nil - "Time zone used for calculating change log time stamps. -It takes the same format as the TZ argument of `set-time-zone-rule'. -If nil, use local time. -If t, use universal time.") -(put 'add-log-time-zone-rule 'safe-local-variable - '(lambda (x) (or (booleanp x) (stringp x)))) - -(defun add-log-iso8601-time-zone (&optional time) - (let* ((utc-offset (or (car (current-time-zone time)) 0)) - (sign (if (< utc-offset 0) ?- ?+)) - (sec (abs utc-offset)) - (ss (% sec 60)) - (min (/ sec 60)) - (mm (% min 60)) - (hh (/ min 60))) - (format (cond ((not (zerop ss)) "%c%02d:%02d:%02d") - ((not (zerop mm)) "%c%02d:%02d") - (t "%c%02d")) - sign hh mm ss))) - -(defvar add-log-iso8601-with-time-zone nil) - -(defun add-log-iso8601-time-string () - (let ((time (format-time-string "%Y-%m-%d" - nil (eq t add-log-time-zone-rule)))) - (if add-log-iso8601-with-time-zone - (concat time " " (add-log-iso8601-time-zone)) - time))) - -(defun change-log-name () - "Return (system-dependent) default name for a change log file." - (or change-log-default-name - "ChangeLog")) - -(defun add-log-edit-prev-comment (arg) - "Cycle backward through Log-Edit mode comment history. -With a numeric prefix ARG, go back ARG comments." - (interactive "*p") - (save-restriction - (narrow-to-region (point) - (if (memq last-command '(add-log-edit-prev-comment - add-log-edit-next-comment)) - (mark) (point))) - (when (fboundp 'log-edit-previous-comment) - (log-edit-previous-comment arg) - (indent-region (point-min) (point-max)) - (goto-char (point-min)) - (unless (save-restriction (widen) (bolp)) - (delete-region (point) (progn (skip-chars-forward " \t\n") (point)))) - (set-mark (point-min)) - (goto-char (point-max)) - (delete-region (point) (progn (skip-chars-backward " \t\n") (point)))))) - -(defun add-log-edit-next-comment (arg) - "Cycle forward through Log-Edit mode comment history. -With a numeric prefix ARG, go back ARG comments." - (interactive "*p") - (add-log-edit-prev-comment (- arg))) - -;;;###autoload -(defun prompt-for-change-log-name () - "Prompt for a change log name." - (let* ((default (change-log-name)) - (name (expand-file-name - (read-file-name (format "Log file (default %s): " default) - nil default)))) - ;; Handle something that is syntactically a directory name. - ;; Look for ChangeLog or whatever in that directory. - (if (string= (file-name-nondirectory name) "") - (expand-file-name (file-name-nondirectory default) - name) - ;; Handle specifying a file that is a directory. - (if (file-directory-p name) - (expand-file-name (file-name-nondirectory default) - (file-name-as-directory name)) - name)))) - -(defun change-log-version-number-search () - "Return version number of current buffer's file. -This is the value returned by `vc-working-revision' or, if that is -nil, by matching `change-log-version-number-regexp-list'." - (let* ((size (buffer-size)) - (limit - ;; The version number can be anywhere in the file, but - ;; restrict search to the file beginning: 10% should be - ;; enough to prevent some mishits. - ;; - ;; Apply percentage only if buffer size is bigger than - ;; approx 100 lines. - (if (> size (* 100 80)) (+ (point) (/ size 10))))) - (or (and buffer-file-name (vc-working-revision buffer-file-name)) - (save-restriction - (widen) - (let ((regexps change-log-version-number-regexp-list) - version) - (while regexps - (save-excursion - (goto-char (point-min)) - (when (re-search-forward (pop regexps) limit t) - (setq version (match-string 1) - regexps nil)))) - version))))) - -(declare-function diff-find-source-location "diff-mode" - (&optional other-file reverse noprompt)) - -;;;###autoload -(defun find-change-log (&optional file-name buffer-file) - "Find a change log file for \\[add-change-log-entry] and return the name. - -Optional arg FILE-NAME specifies the file to use. -If FILE-NAME is nil, use the value of `change-log-default-name'. -If `change-log-default-name' is nil, behave as though it were 'ChangeLog' -\(or whatever we use on this operating system). - -If `change-log-default-name' contains a leading directory component, then -simply find it in the current directory. Otherwise, search in the current -directory and its successive parents for a file so named. - -Once a file is found, `change-log-default-name' is set locally in the -current buffer to the complete file name. -Optional arg BUFFER-FILE overrides `buffer-file-name'." - ;; If we are called from a diff, first switch to the source buffer; - ;; in order to respect buffer-local settings of change-log-default-name, etc. - (with-current-buffer (let ((buff (if (eq major-mode 'diff-mode) - (car (ignore-errors - (diff-find-source-location)))))) - (if (buffer-live-p buff) buff - (current-buffer))) - ;; If user specified a file name or if this buffer knows which one to use, - ;; just use that. - (or file-name - (setq file-name (and change-log-default-name - (file-name-directory change-log-default-name) - change-log-default-name)) - (progn - ;; Chase links in the source file - ;; and use the change log in the dir where it points. - (setq file-name (or (and (or buffer-file buffer-file-name) - (file-name-directory - (file-chase-links - (or buffer-file buffer-file-name)))) - default-directory)) - (if (file-directory-p file-name) - (setq file-name (expand-file-name (change-log-name) file-name))) - ;; Chase links before visiting the file. - ;; This makes it easier to use a single change log file - ;; for several related directories. - (setq file-name (file-chase-links file-name)) - (setq file-name (expand-file-name file-name)) - ;; Move up in the dir hierarchy till we find a change log file. - (let ((file1 file-name) - parent-dir) - (while (and (not (or (get-file-buffer file1) (file-exists-p file1))) - (progn (setq parent-dir - (file-name-directory - (directory-file-name - (file-name-directory file1)))) - ;; Give up if we are already at the root dir. - (not (string= (file-name-directory file1) - parent-dir)))) - ;; Move up to the parent dir and try again. - (setq file1 (expand-file-name - (file-name-nondirectory (change-log-name)) - parent-dir))) - ;; If we found a change log in a parent, use that. - (if (or (get-file-buffer file1) (file-exists-p file1)) - (setq file-name file1))))) - ;; Make a local variable in this buffer so we needn't search again. - (set (make-local-variable 'change-log-default-name) file-name)) - file-name) - -(defun add-log-file-name (buffer-file log-file) - ;; Never want to add a change log entry for the ChangeLog file itself. - (unless (or (null buffer-file) (string= buffer-file log-file)) - (if add-log-file-name-function - (funcall add-log-file-name-function buffer-file) - (setq buffer-file - (file-relative-name buffer-file (file-name-directory log-file))) - ;; If we have a backup file, it's presumably because we're - ;; comparing old and new versions (e.g. for deleted - ;; functions) and we'll want to use the original name. - (if (backup-file-name-p buffer-file) - (file-name-sans-versions buffer-file) - buffer-file)))) - -;;;###autoload -(defun add-change-log-entry (&optional whoami file-name other-window new-entry - put-new-entry-on-new-line) - "Find change log file, and add an entry for today and an item for this file. -Optional arg WHOAMI (interactive prefix) non-nil means prompt for user -name and email (stored in `add-log-full-name' and `add-log-mailing-address'). - -Second arg FILE-NAME is file name of the change log. -If nil, use the value of `change-log-default-name'. - -Third arg OTHER-WINDOW non-nil means visit in other window. - -Fourth arg NEW-ENTRY non-nil means always create a new entry at the front; -never append to an existing entry. Option `add-log-keep-changes-together' -otherwise affects whether a new entry is created. - -Fifth arg PUT-NEW-ENTRY-ON-NEW-LINE non-nil means that if a new -entry is created, put it on a new line by itself, do not put it -after a comma on an existing line. - -Option `add-log-always-start-new-record' non-nil means always create a -new record, even when the last record was made on the same date and by -the same person. - -The change log file can start with a copyright notice and a copying -permission notice. The first blank line indicates the end of these -notices. - -Today's date is calculated according to `add-log-time-zone-rule' if -non-nil, otherwise in local time." - (interactive (list current-prefix-arg - (prompt-for-change-log-name))) - (let* ((defun (add-log-current-defun)) - (version (and change-log-version-info-enabled - (change-log-version-number-search))) - (buf-file-name (if add-log-buffer-file-name-function - (funcall add-log-buffer-file-name-function) - buffer-file-name)) - (buffer-file (if buf-file-name (expand-file-name buf-file-name))) - (file-name (expand-file-name (find-change-log file-name buffer-file))) - ;; Set ITEM to the file name to use in the new item. - (item (add-log-file-name buffer-file file-name))) - - (unless (equal file-name buffer-file-name) - (cond - ((equal file-name (buffer-file-name (window-buffer (selected-window)))) - ;; If the selected window already shows the desired buffer don't show - ;; it again (particularly important if other-window is true). - ;; This is important for diff-add-change-log-entries-other-window. - (set-buffer (window-buffer (selected-window)))) - ((or other-window (window-dedicated-p (selected-window))) - (find-file-other-window file-name)) - (t (find-file file-name)))) - (or (derived-mode-p 'change-log-mode) - (change-log-mode)) - (undo-boundary) - (goto-char (point-min)) - - (let ((full-name (or add-log-full-name (user-full-name))) - (mailing-address (or add-log-mailing-address user-mail-address))) - - (when whoami - (setq full-name (read-string "Full name: " full-name)) - ;; Note that some sites have room and phone number fields in - ;; full name which look silly when inserted. Rather than do - ;; anything about that here, let user give prefix argument so that - ;; s/he can edit the full name field in prompter if s/he wants. - (setq mailing-address - (read-string "Mailing address: " mailing-address))) - - ;; If file starts with a copyright and permission notice, skip them. - ;; Assume they end at first blank line. - (when (looking-at "Copyright") - (search-forward "\n\n") - (skip-chars-forward "\n")) - - ;; Advance into first entry if it is usable; else make new one. - (let ((new-entries - (mapcar (lambda (addr) - (concat - (if (stringp add-log-time-zone-rule) - (let ((tz (getenv "TZ"))) - (unwind-protect - (progn - (set-time-zone-rule add-log-time-zone-rule) - (funcall add-log-time-format)) - (set-time-zone-rule tz))) - (funcall add-log-time-format)) - " " full-name - " <" addr ">")) - (if (consp mailing-address) - mailing-address - (list mailing-address))))) - (if (and (not add-log-always-start-new-record) - (let ((hit nil)) - (dolist (entry new-entries hit) - (when (looking-at (regexp-quote entry)) - (setq hit t))))) - (forward-line 1) - (insert (nth (random (length new-entries)) - new-entries) - (if use-hard-newlines hard-newline "\n") - (if use-hard-newlines hard-newline "\n")) - (forward-line -1)))) - - ;; Determine where we should stop searching for a usable - ;; item to add to, within this entry. - (let ((bound - (save-excursion - (if (looking-at "\n*[^\n* \t]") - (skip-chars-forward "\n") - (if add-log-keep-changes-together - (forward-page) ; page delimits entries for date - (forward-paragraph))) ; paragraph delimits entries for file - (point)))) - - ;; Now insert the new line for this item. - (cond ((re-search-forward "^\\s *\\*\\s *$" bound t) - ;; Put this file name into the existing empty item. - (if item - (insert item))) - ((and (not new-entry) - (let (case-fold-search) - (re-search-forward - (concat (regexp-quote (concat "* " item)) - ;; Don't accept `foo.bar' when - ;; looking for `foo': - "\\(\\s \\|[(),:]\\)") - bound t))) - ;; Add to the existing item for the same file. - (re-search-forward "^\\s *$\\|^\\s \\*") - (goto-char (match-beginning 0)) - ;; Delete excess empty lines; make just 2. - (while (and (not (eobp)) (looking-at "^\\s *$")) - (delete-region (point) (line-beginning-position 2))) - (insert (if use-hard-newlines hard-newline "\n") - (if use-hard-newlines hard-newline "\n")) - (forward-line -2) - (indent-relative-maybe)) - (t - ;; Make a new item. - (while (looking-at "\\sW") - (forward-line 1)) - (while (and (not (eobp)) (looking-at "^\\s *$")) - (delete-region (point) (line-beginning-position 2))) - (insert (if use-hard-newlines hard-newline "\n") - (if use-hard-newlines hard-newline "\n") - (if use-hard-newlines hard-newline "\n")) - (forward-line -2) - (indent-to left-margin) - (insert "* ") - (if item (insert item))))) - ;; Now insert the function name, if we have one. - ;; Point is at the item for this file, - ;; either at the end of the line or at the first blank line. - (if (not defun) - ;; No function name, so put in a colon unless we have just a star. - (unless (save-excursion - (beginning-of-line 1) - (looking-at "\\s *\\(\\*\\s *\\)?$")) - (insert ": ") - (if version (insert version ?\s))) - ;; Make it easy to get rid of the function name. - (undo-boundary) - (unless (save-excursion - (beginning-of-line 1) - (looking-at "\\s *$")) - (insert ?\s)) - ;; See if the prev function name has a message yet or not. - ;; If not, merge the two items. - (let ((pos (point-marker))) - (skip-syntax-backward " ") - (skip-chars-backward "):") - (if (and (not put-new-entry-on-new-line) - (looking-at "):") - (let ((pos (save-excursion (backward-sexp 1) (point)))) - (when (equal (buffer-substring pos (point)) defun) - (delete-region pos (point))) - (> fill-column (+ (current-column) (length defun) 4)))) - (progn (skip-chars-backward ", ") - (delete-region (point) pos) - (unless (memq (char-before) '(?\()) (insert ", "))) - (when (and (not put-new-entry-on-new-line) (looking-at "):")) - (delete-region (+ 1 (point)) (line-end-position))) - (goto-char pos) - (insert "(")) - (set-marker pos nil)) - (insert defun "): ") - (if version (insert version ?\s))))) - -;;;###autoload -(defun add-change-log-entry-other-window (&optional whoami file-name) - "Find change log file in other window and add entry and item. -This is just like `add-change-log-entry' except that it displays -the change log file in another window." - (interactive (if current-prefix-arg - (list current-prefix-arg - (prompt-for-change-log-name)))) - (add-change-log-entry whoami file-name t)) - - -(defvar change-log-indent-text 0) - -(defun change-log-fill-parenthesized-list () - ;; Fill parenthesized lists of names according to GNU standards. - ;; * file-name.ext (very-long-foo, very-long-bar, very-long-foobar): - ;; should be filled as - ;; * file-name.ext (very-long-foo, very-long-bar) - ;; (very-long-foobar): - (save-excursion - (end-of-line 0) - (skip-chars-backward " \t") - (when (and (equal (char-before) ?\,) - (> (point) (1+ (point-min)))) - (condition-case nil - (when (save-excursion - (and (prog2 - (up-list -1) - (equal (char-after) ?\() - (skip-chars-backward " \t")) - (or (bolp) - ;; Skip everything but a whitespace or asterisk. - (and (not (zerop (skip-chars-backward "^ \t\n*"))) - (skip-chars-backward " \t") - ;; We want one asterisk here. - (= (skip-chars-backward "*") -1) - (skip-chars-backward " \t") - (bolp))))) - ;; Delete the comma. - (delete-char -1) - ;; Close list on previous line. - (insert ")") - (skip-chars-forward " \t\n") - ;; Start list on new line. - (insert-before-markers "(")) - (error nil))))) - -(defun change-log-indent () - (change-log-fill-parenthesized-list) - (let* ((indent - (save-excursion - (beginning-of-line) - (skip-chars-forward " \t") - (cond - ((and (looking-at "\\(.*\\) [^ \n].*[^ \n] <.*>\\(?: +(.*)\\)? *$") - ;; Matching the output of add-log-time-format is difficult, - ;; but I'll get it has at least two adjacent digits. - (string-match "[[:digit:]][[:digit:]]" (match-string 1))) - 0) - ((looking-at "[^*(]") - (+ (current-left-margin) change-log-indent-text)) - (t (current-left-margin))))) - (pos (save-excursion (indent-line-to indent) (point)))) - (if (> pos (point)) (goto-char pos)))) - - -(defvar smerge-resolve-function) -(defvar copyright-at-end-flag) - -;;;###autoload -(define-derived-mode change-log-mode text-mode "Change Log" - "Major mode for editing change logs; like Indented Text mode. -Prevents numeric backups and sets `left-margin' to 8 and `fill-column' to 74. -New log entries are usually made with \\[add-change-log-entry] or \\[add-change-log-entry-other-window]. -Each entry behaves as a paragraph, and the entries for one day as a page. -Runs `change-log-mode-hook'. -\n\\{change-log-mode-map}" - (setq left-margin 8 - fill-column 74 - indent-tabs-mode t - tab-width 8 - show-trailing-whitespace t) - (set (make-local-variable 'fill-forward-paragraph-function) - 'change-log-fill-forward-paragraph) - ;; Make sure we call `change-log-indent' when filling. - (set (make-local-variable 'fill-indent-according-to-mode) t) - ;; Avoid that filling leaves behind a single "*" on a line. - (add-hook 'fill-nobreak-predicate - '(lambda () - (looking-back "^\\s *\\*\\s *" (line-beginning-position))) - nil t) - (set (make-local-variable 'indent-line-function) 'change-log-indent) - (set (make-local-variable 'tab-always-indent) nil) - (set (make-local-variable 'copyright-at-end-flag) t) - ;; We really do want "^" in paragraph-start below: it is only the - ;; lines that begin at column 0 (despite the left-margin of 8) that - ;; we are looking for. Adding `* ' allows eliding the blank line - ;; between entries for different files. - (set (make-local-variable 'paragraph-start) "\\s *$\\|\f\\|^\\<") - (set (make-local-variable 'paragraph-separate) paragraph-start) - ;; Match null string on the date-line so that the date-line - ;; is grouped with what follows. - (set (make-local-variable 'page-delimiter) "^\\<\\|^\f") - (set (make-local-variable 'version-control) 'never) - (set (make-local-variable 'smerge-resolve-function) - 'change-log-resolve-conflict) - (set (make-local-variable 'adaptive-fill-regexp) "\\s *") - (set (make-local-variable 'font-lock-defaults) - '(change-log-font-lock-keywords t nil nil backward-paragraph)) - (set (make-local-variable 'multi-isearch-next-buffer-function) - 'change-log-next-buffer) - (set (make-local-variable 'beginning-of-defun-function) - 'change-log-beginning-of-defun) - (set (make-local-variable 'end-of-defun-function) - 'change-log-end-of-defun) - ;; next-error function glue - (setq next-error-function 'change-log-next-error) - (setq next-error-last-buffer (current-buffer))) - -(defun change-log-next-buffer (&optional buffer wrap) - "Return the next buffer in the series of ChangeLog file buffers. -This function is used for multiple buffers isearch. -A sequence of buffers is formed by ChangeLog files with decreasing -numeric file name suffixes in the directory of the initial ChangeLog -file were isearch was started." - (let* ((name (change-log-name)) - (files (cons name (sort (file-expand-wildcards - (concat name "[-.][0-9]*")) - (lambda (a b) - ;; The file's extension may not have a valid - ;; version form (e.g. VC backup revisions). - (ignore-errors - (version< (substring b (length name)) - (substring a (length name)))))))) - (files (if isearch-forward files (reverse files)))) - (find-file-noselect - (if wrap - (car files) - (cadr (member (file-name-nondirectory (buffer-file-name buffer)) - files)))))) - -(defun change-log-fill-forward-paragraph (n) - "Cut paragraphs so filling preserves open parentheses at beginning of lines." - (let (;; Add lines starting with whitespace followed by a left paren or an - ;; asterisk. - (paragraph-start (concat paragraph-start "\\|\\s *\\(?:\\s(\\|\\*\\)"))) - (forward-paragraph n))) - -(defcustom add-log-current-defun-header-regexp - "^\\([[:upper:]][[:upper:]_ ]*[[:upper:]_]\\|[-_[:alpha:]]+\\)[ \t]*[:=]" - "Heuristic regexp used by `add-log-current-defun' for unknown major modes. -The regexp's first submatch is placed in the ChangeLog entry, in -parentheses." - :type 'regexp - :group 'change-log) - -;;;###autoload -(defvar add-log-lisp-like-modes - '(emacs-lisp-mode lisp-mode scheme-mode dsssl-mode lisp-interaction-mode) - "*Modes that look like Lisp to `add-log-current-defun'.") - -;;;###autoload -(defvar add-log-c-like-modes - '(c-mode c++-mode c++-c-mode objc-mode) - "*Modes that look like C to `add-log-current-defun'.") - -;;;###autoload -(defvar add-log-tex-like-modes - '(TeX-mode plain-TeX-mode LaTeX-mode tex-mode) - "*Modes that look like TeX to `add-log-current-defun'.") - -(declare-function c-cpp-define-name "cc-cmds" ()) -(declare-function c-defun-name "cc-cmds" ()) - -;;;###autoload -(defun add-log-current-defun () - "Return name of function definition point is in, or nil. - -Understands C, Lisp, LaTeX (\"functions\" are chapters, sections, ...), -Texinfo (@node titles) and Perl. - -Other modes are handled by a heuristic that looks in the 10K before -point for uppercase headings starting in the first column or -identifiers followed by `:' or `='. See variables -`add-log-current-defun-header-regexp' and -`add-log-current-defun-function'. - -Has a preference of looking backwards." - (condition-case nil - (save-excursion - (let ((location (point))) - (cond (add-log-current-defun-function - (funcall add-log-current-defun-function)) - ((apply 'derived-mode-p add-log-lisp-like-modes) - ;; If we are now precisely at the beginning of a defun, - ;; make sure beginning-of-defun finds that one - ;; rather than the previous one. - (or (eobp) (forward-char 1)) - (beginning-of-defun) - ;; Make sure we are really inside the defun found, - ;; not after it. - (when (and (looking-at "\\s(") - (progn (end-of-defun) - (< location (point))) - (progn (forward-sexp -1) - (>= location (point)))) - (if (looking-at "\\s(") - (forward-char 1)) - ;; Skip the defining construct name, typically "defun" - ;; or "defvar". - (forward-sexp 1) - ;; The second element is usually a symbol being defined. - ;; If it is not, use the first symbol in it. - (skip-chars-forward " \t\n'(") - (buffer-substring-no-properties (point) - (progn (forward-sexp 1) - (point))))) - ((apply 'derived-mode-p add-log-c-like-modes) - (or (c-cpp-define-name) - (c-defun-name))) - ((memq major-mode add-log-tex-like-modes) - (if (re-search-backward - "\\\\\\(sub\\)*\\(section\\|paragraph\\|chapter\\)" - nil t) - (progn - (goto-char (match-beginning 0)) - (buffer-substring-no-properties - (1+ (point)) ; without initial backslash - (line-end-position))))) - ((derived-mode-p 'texinfo-mode) - (if (re-search-backward "^@node[ \t]+\\([^,\n]+\\)" nil t) - (match-string-no-properties 1))) - ((derived-mode-p 'perl-mode 'cperl-mode) - (if (re-search-backward "^sub[ \t]+\\([^({ \t\n]+\\)" nil t) - (match-string-no-properties 1))) - ;; Emacs's autoconf-mode installs its own - ;; `add-log-current-defun-function'. This applies to - ;; a different mode apparently for editing .m4 - ;; autoconf source. - ((derived-mode-p 'autoconf-mode) - (if (re-search-backward - "^\\(\\(m4_\\)?define\\|A._DEFUN\\)(\\[?\\([A-Za-z0-9_]+\\)" nil t) - (match-string-no-properties 3))) - (t - ;; If all else fails, try heuristics - (let (case-fold-search - result) - (end-of-line) - (when (re-search-backward - add-log-current-defun-header-regexp - (- (point) 10000) - t) - (setq result (or (match-string-no-properties 1) - (match-string-no-properties 0))) - ;; Strip whitespace away - (when (string-match "\\([^ \t\n\r\f].*[^ \t\n\r\f]\\)" - result) - (setq result (match-string-no-properties 1 result))) - result)))))) - (error nil))) - -(defvar change-log-get-method-definition-md) - -;; Subroutine used within change-log-get-method-definition. -;; Add the last match in the buffer to the end of `md', -;; followed by the string END; move to the end of that match. -(defun change-log-get-method-definition-1 (end) - (setq change-log-get-method-definition-md - (concat change-log-get-method-definition-md - (match-string 1) - end)) - (goto-char (match-end 0))) - -(defun change-log-get-method-definition () -"For Objective C, return the method name if we are in a method." - (let ((change-log-get-method-definition-md "[")) - (save-excursion - (if (re-search-backward "^@implementation\\s-*\\([A-Za-z_]*\\)" nil t) - (change-log-get-method-definition-1 " "))) - (save-excursion - (cond - ((re-search-forward "^\\([-+]\\)[ \t\n\f\r]*\\(([^)]*)\\)?\\s-*" nil t) - (change-log-get-method-definition-1 "") - (while (not (looking-at "[{;]")) - (looking-at - "\\([A-Za-z_]*:?\\)\\s-*\\(([^)]*)\\)?[A-Za-z_]*[ \t\n\f\r]*") - (change-log-get-method-definition-1 "")) - (concat change-log-get-method-definition-md "]")))))) - -(defun change-log-sortable-date-at () - "Return date of log entry in a consistent form for sorting. -Point is assumed to be at the start of the entry." - (require 'timezone) - (if (looking-at change-log-start-entry-re) - (let ((date (match-string-no-properties 0))) - (if date - (if (string-match "\\(....\\)-\\(..\\)-\\(..\\)\\s-+" date) - (concat (match-string 1 date) (match-string 2 date) - (match-string 3 date)) - (condition-case nil - (timezone-make-date-sortable date) - (error nil))))) - (error "Bad date"))) - -(defun change-log-resolve-conflict () - "Function to be used in `smerge-resolve-function'." - (save-excursion - (save-restriction - (narrow-to-region (match-beginning 0) (match-end 0)) - (let ((mb1 (match-beginning 1)) - (me1 (match-end 1)) - (mb3 (match-beginning 3)) - (me3 (match-end 3)) - (tmp1 (generate-new-buffer " *changelog-resolve-1*")) - (tmp2 (generate-new-buffer " *changelog-resolve-2*"))) - (unwind-protect - (let ((buf (current-buffer))) - (with-current-buffer tmp1 - (change-log-mode) - (insert-buffer-substring buf mb1 me1)) - (with-current-buffer tmp2 - (change-log-mode) - (insert-buffer-substring buf mb3 me3) - ;; Do the merge here instead of inside `buf' so as to be - ;; more robust in case change-log-merge fails. - (change-log-merge tmp1)) - (goto-char (point-max)) - (delete-region (point-min) - (prog1 (point) - (insert-buffer-substring tmp2)))) - (kill-buffer tmp1) - (kill-buffer tmp2)))))) - -;;;###autoload -(defun change-log-merge (other-log) - "Merge the contents of change log file OTHER-LOG with this buffer. -Both must be found in Change Log mode (since the merging depends on -the appropriate motion commands). OTHER-LOG can be either a file name -or a buffer. - -Entries are inserted in chronological order. Both the current and -old-style time formats for entries are supported." - (interactive "*fLog file name to merge: ") - (if (not (derived-mode-p 'change-log-mode)) - (error "Not in Change Log mode")) - (let ((other-buf (if (bufferp other-log) other-log - (find-file-noselect other-log))) - (buf (current-buffer)) - date1 start end) - (save-excursion - (goto-char (point-min)) - (set-buffer other-buf) - (goto-char (point-min)) - (if (not (derived-mode-p 'change-log-mode)) - (error "%s not found in Change Log mode" other-log)) - ;; Loop through all the entries in OTHER-LOG. - (while (not (eobp)) - (setq date1 (change-log-sortable-date-at)) - (setq start (point) - end (progn (forward-page) (point))) - ;; Look for an entry in original buffer that isn't later. - (with-current-buffer buf - (while (and (not (eobp)) - (string< date1 (change-log-sortable-date-at))) - (forward-page)) - (if (not (eobp)) - (insert-buffer-substring other-buf start end) - ;; At the end of the original buffer, insert a newline to - ;; separate entries and then the rest of the file being - ;; merged. - (unless (or (bobp) - (and (= ?\n (char-before)) - (or (<= (1- (point)) (point-min)) - (= ?\n (char-before (1- (point))))))) - (insert (if use-hard-newlines hard-newline "\n"))) - ;; Move to the end of it to terminate outer loop. - (with-current-buffer other-buf - (goto-char (point-max))) - (insert-buffer-substring other-buf start))))))) - -(defun change-log-beginning-of-defun () - (re-search-backward change-log-start-entry-re nil 'move)) - -(defun change-log-end-of-defun () - ;; Look back and if there is no entry there it means we are before - ;; the first ChangeLog entry, so go forward until finding one. - (unless (save-excursion (re-search-backward change-log-start-entry-re nil t)) - (re-search-forward change-log-start-entry-re nil t)) - - ;; In case we are at the end of log entry going forward a line will - ;; make us find the next entry when searching. If we are inside of - ;; an entry going forward a line will still keep the point inside - ;; the same entry. - (forward-line 1) - - ;; In case we are at the beginning of an entry, move past it. - (when (looking-at change-log-start-entry-re) - (goto-char (match-end 0)) - (forward-line 1)) - - ;; Search for the start of the next log entry. Go to the end of the - ;; buffer if we could not find a next entry. - (when (re-search-forward change-log-start-entry-re nil 'move) - (goto-char (match-beginning 0)) - (forward-line -1))) - -(provide 'add-log) - -;; arch-tag: 81eee6fc-088f-4372-a37f-80ad9620e762 -;;; add-log.el ends here diff -r 681cd08dc0f7 -r e93288477c43 lisp/comint.el --- a/lisp/comint.el Thu Jun 10 22:43:47 2010 +0000 +++ b/lisp/comint.el Sun Jun 13 22:57:55 2010 +0000 @@ -340,7 +340,7 @@ ;; Something called "perforce" uses "Enter password:". (defcustom comint-password-prompt-regexp (concat - "^\\(" + "\\(" (regexp-opt '("Enter" "Enter same" "Old" "old" "New" "new" "'s" "login" "Kerberos" "CVS" "UNIX" " SMB" "LDAP" "[sudo]" "Repeat" "Bad")) diff -r 681cd08dc0f7 -r e93288477c43 lisp/cvs-status.el --- a/lisp/cvs-status.el Thu Jun 10 22:43:47 2010 +0000 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,540 +0,0 @@ -;;; cvs-status.el --- major mode for browsing `cvs status' output -*- coding: utf-8 -*- - -;; Copyright (C) 1999, 2000, 2001, 2002, 2003, 2004, -;; 2005, 2006, 2007, 2008, 2009, 2010 Free Software Foundation, Inc. - -;; Author: Stefan Monnier -;; Keywords: pcl-cvs cvs status tree tools - -;; 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 3 of the License, 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. If not, see . - -;;; Commentary: - -;; Todo: - -;; - Somehow allow cvs-status-tree to work on-the-fly - -;;; Code: - -(eval-when-compile (require 'cl)) -(require 'pcvs-util) - -;;; - -(defgroup cvs-status nil - "Major mode for browsing `cvs status' output." - :group 'pcl-cvs - :prefix "cvs-status-") - -(easy-mmode-defmap cvs-status-mode-map - '(("n" . next-line) - ("p" . previous-line) - ("N" . cvs-status-next) - ("P" . cvs-status-prev) - ("\M-n" . cvs-status-next) - ("\M-p" . cvs-status-prev) - ("t" . cvs-status-cvstrees) - ("T" . cvs-status-trees) - (">" . cvs-mode-checkout)) - "CVS-Status' keymap." - :group 'cvs-status - :inherit 'cvs-mode-map) - -;;(easy-menu-define cvs-status-menu cvs-status-mode-map -;; "Menu for `cvs-status-mode'." -;; '("CVS-Status" -;; ["Show Tag Trees" cvs-status-tree t] -;; )) - -(defvar cvs-status-mode-hook nil - "Hook run at the end of `cvs-status-mode'.") - -(defconst cvs-status-tags-leader-re "^ Existing Tags:$") -(defconst cvs-status-entry-leader-re - "^File:\\s-+\\(?:no file \\)?\\(.*\\S-\\)\\s-+Status: \\(.+\\)$") -(defconst cvs-status-dir-re "^cvs[.ex]* [a-z]+: Examining \\(.+\\)$") -(defconst cvs-status-rev-re "[0-9][.0-9]*\\.[.0-9]*[0-9]") -(defconst cvs-status-tag-re "[ \t]\\([a-zA-Z][^ \t\n.]*\\)") - -(defconst cvs-status-font-lock-keywords - `((,cvs-status-entry-leader-re - (1 'cvs-filename) - (2 'cvs-need-action)) - (,cvs-status-tags-leader-re - (,cvs-status-rev-re - (save-excursion (re-search-forward "^\n" nil 'move) (point)) - (progn (re-search-backward cvs-status-tags-leader-re nil t) - (forward-line 1)) - (0 font-lock-comment-face)) - (,cvs-status-tag-re - (save-excursion (re-search-forward "^\n" nil 'move) (point)) - (progn (re-search-backward cvs-status-tags-leader-re nil t) - (forward-line 1)) - (1 font-lock-function-name-face))))) -(defconst cvs-status-font-lock-defaults - '(cvs-status-font-lock-keywords t nil nil nil (font-lock-multiline . t))) - -(defvar cvs-minor-wrap-function) -(put 'cvs-status-mode 'mode-class 'special) -;;;###autoload -(define-derived-mode cvs-status-mode fundamental-mode "CVS-Status" - "Mode used for cvs status output." - (set (make-local-variable 'font-lock-defaults) cvs-status-font-lock-defaults) - (set (make-local-variable 'cvs-minor-wrap-function) 'cvs-status-minor-wrap)) - -;; Define cvs-status-next and cvs-status-prev -(easy-mmode-define-navigation cvs-status cvs-status-entry-leader-re "entry") - -(defun cvs-status-current-file () - (save-excursion - (forward-line 1) - (or (re-search-backward cvs-status-entry-leader-re nil t) - (re-search-forward cvs-status-entry-leader-re)) - (let* ((file (match-string 1)) - (cvsdir (and (re-search-backward cvs-status-dir-re nil t) - (match-string 1))) - (pcldir (and (if (boundp 'cvs-pcl-cvs-dirchange-re) - (re-search-backward cvs-pcl-cvs-dirchange-re nil t)) - (match-string 1))) - (dir "")) - (let ((default-directory "")) - (when pcldir (setq dir (expand-file-name pcldir dir))) - (when cvsdir (setq dir (expand-file-name cvsdir dir))) - (expand-file-name file dir))))) - -(defun cvs-status-current-tag () - (save-excursion - (let ((pt (point)) - (col (current-column)) - (start (progn (re-search-backward cvs-status-tags-leader-re nil t) (point))) - (end (progn (re-search-forward "^$" nil t) (point)))) - (when (and (< start pt) (> end pt)) - (goto-char pt) - (end-of-line) - (let ((tag nil) (dist pt) (end (point))) - (beginning-of-line) - (while (re-search-forward cvs-status-tag-re end t) - (let* ((cole (current-column)) - (colb (save-excursion - (goto-char (match-beginning 1)) (current-column))) - (ndist (min (abs (- cole col)) (abs (- colb col))))) - (when (< ndist dist) - (setq dist ndist) - (setq tag (match-string 1))))) - tag))))) - -(defun cvs-status-minor-wrap (buf f) - (let ((data (with-current-buffer buf - (cons - (cons (cvs-status-current-file) - (cvs-status-current-tag)) - (when mark-active - (save-excursion - (goto-char (mark)) - (cons (cvs-status-current-file) - (cvs-status-current-tag)))))))) - (let ((cvs-branch-prefix (cdar data)) - (cvs-secondary-branch-prefix (and (cdar data) (cddr data))) - (cvs-minor-current-files - (cons (caar data) - (when (and (cadr data) (not (equal (caar data) (cadr data)))) - (list (cadr data))))) - ;; FIXME: I need to force because the fileinfos are UNKNOWN - (cvs-force-command "/F")) - (funcall f)))) - -;; -;; Tagelt, tag element -;; - -(defstruct (cvs-tag - (:constructor nil) - (:constructor cvs-tag-make - (vlist &optional name type)) - (:conc-name cvs-tag->)) - vlist - name - type) - -(defsubst cvs-status-vl-to-str (vl) (mapconcat 'number-to-string vl ".")) - -(defun cvs-tag->string (tag) - (if (stringp tag) tag - (let ((name (cvs-tag->name tag)) - (vl (cvs-tag->vlist tag))) - (if (null name) (cvs-status-vl-to-str vl) - (let ((rev (if vl (concat " (" (cvs-status-vl-to-str vl) ")") ""))) - (if (consp name) (mapcar (lambda (name) (concat name rev)) name) - (concat name rev))))))) - -(defun cvs-tag-compare-1 (vl1 vl2) - (cond - ((and (null vl1) (null vl2)) 'equal) - ((null vl1) 'more2) - ((null vl2) 'more1) - (t (let ((v1 (car vl1)) - (v2 (car vl2))) - (cond - ((> v1 v2) 'more1) - ((< v1 v2) 'more2) - (t (cvs-tag-compare-1 (cdr vl1) (cdr vl2)))))))) - -(defsubst cvs-tag-compare (tag1 tag2) - (cvs-tag-compare-1 (cvs-tag->vlist tag1) (cvs-tag->vlist tag2))) - -(defun cvs-tag-merge (tag1 tag2) - "Merge TAG1 and TAG2 into one." - (let ((type1 (cvs-tag->type tag1)) - (type2 (cvs-tag->type tag2)) - (name1 (cvs-tag->name tag1)) - (name2 (cvs-tag->name tag2))) - (unless (equal (cvs-tag->vlist tag1) (cvs-tag->vlist tag2)) - (setf (cvs-tag->vlist tag1) nil)) - (if type1 - (unless (or (not type2) (equal type1 type2)) - (setf (cvs-tag->type tag1) nil)) - (setf (cvs-tag->type tag1) type2)) - (if name1 - (setf (cvs-tag->name tag1) (cvs-append name1 name2)) - (setf (cvs-tag->name tag1) name2)) - tag1)) - -(defun cvs-tree-print (tags printer column) - "Print the tree of TAGS where each tag's string is given by PRINTER. -PRINTER should accept both a tag (in which case it should return a string) -or a string (in which case it should simply return its argument). -A tag cannot be a CONS. The return value can also be a list of strings, -if several nodes where merged into one. -The tree will be printed no closer than column COLUMN." - - (let* ((eol (save-excursion (end-of-line) (current-column))) - (column (max (+ eol 2) column))) - (if (null tags) column - ;;(move-to-column-force column) - (let* ((rev (cvs-car tags)) - (name (funcall printer (cvs-car rev))) - (rest (append (cvs-cdr name) (cvs-cdr tags))) - (prefix - (save-excursion - (or (= (forward-line 1) 0) (insert "\n")) - (cvs-tree-print rest printer column)))) - (assert (>= prefix column)) - (move-to-column prefix t) - (assert (eolp)) - (insert (cvs-car name)) - (dolist (br (cvs-cdr rev)) - (let* ((column (current-column)) - (brrev (funcall printer (cvs-car br))) - (brlength (length (cvs-car brrev))) - (brfill (concat (make-string (/ brlength 2) ? ) "|")) - (prefix - (save-excursion - (insert " -- ") - (cvs-tree-print (cvs-append brrev brfill (cvs-cdr br)) - printer (current-column))))) - (delete-region (save-excursion (move-to-column prefix) (point)) - (point)) - (insert " " (make-string (- prefix column 2) ?-) " ") - (end-of-line))) - prefix)))) - -(defun cvs-tree-merge (tree1 tree2) - "Merge tags trees TREE1 and TREE2 into one. -BEWARE: because of stability issues, this is not a symetric operation." - (assert (and (listp tree1) (listp tree2))) - (cond - ((null tree1) tree2) - ((null tree2) tree1) - (t - (let* ((rev1 (car tree1)) - (tag1 (cvs-car rev1)) - (vl1 (cvs-tag->vlist tag1)) - (l1 (length vl1)) - (rev2 (car tree2)) - (tag2 (cvs-car rev2)) - (vl2 (cvs-tag->vlist tag2)) - (l2 (length vl2))) - (cond - ((= l1 l2) - (case (cvs-tag-compare tag1 tag2) - (more1 (list* rev2 (cvs-tree-merge tree1 (cdr tree2)))) - (more2 (list* rev1 (cvs-tree-merge (cdr tree1) tree2))) - (equal - (cons (cons (cvs-tag-merge tag1 tag2) - (cvs-tree-merge (cvs-cdr rev1) (cvs-cdr rev2))) - (cvs-tree-merge (cdr tree1) (cdr tree2)))))) - ((> l1 l2) - (cvs-tree-merge - (list (cons (cvs-tag-make (butlast vl1)) tree1)) tree2)) - ((< l1 l2) - (cvs-tree-merge - tree1 (list (cons (cvs-tag-make (butlast vl2)) tree2))))))))) - -(defun cvs-tag-make-tag (tag) - (let ((vl (mapcar 'string-to-number (split-string (nth 2 tag) "\\.")))) - (cvs-tag-make vl (nth 0 tag) (intern (nth 1 tag))))) - -(defun cvs-tags->tree (tags) - "Make a tree out of a list of TAGS." - (let ((tags - (mapcar - (lambda (tag) - (let ((tag (cvs-tag-make-tag tag))) - (list (if (not (eq (cvs-tag->type tag) 'branch)) tag - (list (cvs-tag-make (butlast (cvs-tag->vlist tag))) - tag))))) - tags))) - (while (cdr tags) - (let (tl) - (while tags - (push (cvs-tree-merge (pop tags) (pop tags)) tl)) - (setq tags (nreverse tl)))) - (car tags))) - -(defun cvs-status-get-tags () - "Look for a list of tags, read them in and delete them. -Return nil if there was an empty list of tags and t if there wasn't -even a list. Else, return the list of tags where each element of -the list is a three-string list TAG, KIND, REV." - (let ((tags nil)) - (if (not (re-search-forward cvs-status-tags-leader-re nil t)) t - (forward-char 1) - (let ((pt (point)) - (lastrev nil) - (case-fold-search t)) - (or - (looking-at "\\s-+no\\s-+tags") - - (progn ; normal listing - (while (looking-at "^[ \t]+\\([^ \t\n]+\\)[ \t]+(\\([a-z]+\\): \\(.+\\))$") - (push (list (match-string 1) (match-string 2) (match-string 3)) tags) - (forward-line 1)) - (unless (looking-at "^$") (setq tags nil) (goto-char pt)) - tags) - - (progn ; cvstree-style listing - (while (or (looking-at "^ .+\\(.\\) \\([0-9.]+\\): \\([^\n\t .0-9][^\n\t ]*\\)?$") - (and lastrev - (looking-at "^ .+\\(\\) \\(8\\)? \\([^\n\t .0-9][^\n\t ]*\\)$"))) - (setq lastrev (or (match-string 2) lastrev)) - (push (list (match-string 3) - (if (equal (match-string 1) " ") "branch" "revision") - lastrev) tags) - (forward-line 1)) - (unless (looking-at "^$") (setq tags nil) (goto-char pt)) - (setq tags (nreverse tags))) - - (progn ; new tree style listing - (let* ((re-lead "[ \t]*\\(-+\\)?\\(|\n?[ \t]+\\)*") - (re3 (concat re-lead "\\(\\.\\)?\\(" cvs-status-rev-re "\\)")) - (re2 (concat re-lead cvs-status-tag-re "\\(\\)")) - (re1 (concat re-lead cvs-status-tag-re - " (\\(" cvs-status-rev-re "\\))"))) - (while (or (looking-at re1) (looking-at re2) (looking-at re3)) - (push (list (match-string 3) - (if (match-string 1) "branch" "revision") - (match-string 4)) tags) - (goto-char (match-end 0)) - (when (eolp) (forward-char 1)))) - (unless (looking-at "^$") (setq tags nil) (goto-char pt)) - (setq tags (nreverse tags)))) - - (delete-region pt (point))) - tags))) - -(defvar font-lock-mode) -;; (defun cvs-refontify (beg end) -;; (when (and (boundp 'font-lock-mode) -;; font-lock-mode -;; (fboundp 'font-lock-fontify-region)) -;; (font-lock-fontify-region (1- beg) (1+ end)))) - -(defun cvs-status-trees () - "Look for a lists of tags, and replace them with trees." - (interactive) - (save-excursion - (goto-char (point-min)) - (let ((inhibit-read-only t) - (tags nil)) - (while (listp (setq tags (cvs-status-get-tags))) - ;;(let ((pt (save-excursion (forward-line -1) (point)))) - (save-restriction - (narrow-to-region (point) (point)) - ;;(newline) - (combine-after-change-calls - (cvs-tree-print (cvs-tags->tree tags) 'cvs-tag->string 3))) - ;;(cvs-refontify pt (point)) - ;;(sit-for 0) - ;;) - )))) - -;;;; -;;;; CVSTree-style trees -;;;; - -(defvar cvs-tree-use-jisx0208 nil) ;Old compat var. -(defvar cvs-tree-use-charset - (cond - (cvs-tree-use-jisx0208 'jisx0208) - ((char-displayable-p ?━) 'unicode) - ((char-displayable-p (make-char 'japanese-jisx0208 40 44)) 'jisx0208)) - "*Non-nil if we should use the graphical glyphs from `japanese-jisx0208'. -Otherwise, default to ASCII chars like +, - and |.") - -(defconst cvs-tree-char-space - (case cvs-tree-use-charset - (jisx0208 (make-char 'japanese-jisx0208 33 33)) - (unicode " ") - (t " "))) -(defconst cvs-tree-char-hbar - (case cvs-tree-use-charset - (jisx0208 (make-char 'japanese-jisx0208 40 44)) - (unicode "━") - (t "--"))) -(defconst cvs-tree-char-vbar - (case cvs-tree-use-charset - (jisx0208 (make-char 'japanese-jisx0208 40 45)) - (unicode "┃") - (t "| "))) -(defconst cvs-tree-char-branch - (case cvs-tree-use-charset - (jisx0208 (make-char 'japanese-jisx0208 40 50)) - (unicode "┣") - (t "+-"))) -(defconst cvs-tree-char-eob ;end of branch - (case cvs-tree-use-charset - (jisx0208 (make-char 'japanese-jisx0208 40 49)) - (unicode "┗") - (t "`-"))) -(defconst cvs-tree-char-bob ;beginning of branch - (case cvs-tree-use-charset - (jisx0208 (make-char 'japanese-jisx0208 40 51)) - (unicode "┳") - (t "+-"))) - -(defun cvs-tag-lessp (tag1 tag2) - (eq (cvs-tag-compare tag1 tag2) 'more2)) - -(defvar cvs-tree-nomerge nil) - -(defun cvs-status-cvstrees (&optional arg) - "Look for a list of tags, and replace it with a tree. -Optional prefix ARG chooses between two representations." - (interactive "P") - (when (and cvs-tree-use-charset - (not enable-multibyte-characters)) - ;; We need to convert the buffer from unibyte to multibyte - ;; since we'll use multibyte chars for the tree. - (let ((modified (buffer-modified-p)) - (inhibit-read-only t) - (inhibit-modification-hooks t)) - (unwind-protect - (progn - (decode-coding-region (point-min) (point-max) 'undecided) - (set-buffer-multibyte t)) - (restore-buffer-modified-p modified)))) - (save-excursion - (goto-char (point-min)) - (let ((inhibit-read-only t) - (tags nil) - (cvs-tree-nomerge (if arg (not cvs-tree-nomerge) cvs-tree-nomerge))) - (while (listp (setq tags (cvs-status-get-tags))) - (let ((tags (mapcar 'cvs-tag-make-tag tags)) - ;;(pt (save-excursion (forward-line -1) (point))) - ) - (setq tags (sort tags 'cvs-tag-lessp)) - (let* ((first (car tags)) - (prev (if (cvs-tag-p first) - (list (car (cvs-tag->vlist first))) nil))) - (combine-after-change-calls - (cvs-tree-tags-insert tags prev)) - ;;(cvs-refontify pt (point)) - ;;(sit-for 0) - )))))) - -(defun cvs-tree-tags-insert (tags prev) - (when tags - (let* ((tag (car tags)) - (vlist (cvs-tag->vlist tag)) - (nprev ;"next prev" - (let* ((next (cvs-car (cadr tags))) - (nprev (if (and cvs-tree-nomerge next - (equal vlist (cvs-tag->vlist next))) - prev vlist))) - (cvs-map (lambda (v p) v) nprev prev))) - (after (save-excursion - (newline) - (cvs-tree-tags-insert (cdr tags) nprev))) - (pe t) ;"prev equal" - (nas nil)) ;"next afters" to be returned - (insert " ") - (do* ((vs vlist (cdr vs)) - (ps prev (cdr ps)) - (as after (cdr as))) - ((and (null as) (null vs) (null ps)) - (let ((revname (cvs-status-vl-to-str vlist))) - (if (cvs-every 'identity (cvs-map 'equal prev vlist)) - (insert (make-string (+ 4 (length revname)) ? ) - (or (cvs-tag->name tag) "")) - (insert " " revname ": " (or (cvs-tag->name tag) ""))))) - (let* ((eq (and pe (equal (car ps) (car vs)))) - (next-eq (equal (cadr ps) (cadr vs)))) - (let* ((na+char - (if (car as) - (if eq - (if next-eq (cons t cvs-tree-char-vbar) - (cons t cvs-tree-char-branch)) - (cons nil cvs-tree-char-bob)) - (if eq - (if next-eq (cons nil cvs-tree-char-space) - (cons t cvs-tree-char-eob)) - (cons nil (if (and (eq (cvs-tag->type tag) 'branch) - (cvs-every 'null as)) - cvs-tree-char-space - cvs-tree-char-hbar)))))) - (insert (cdr na+char)) - (push (car na+char) nas)) - (setq pe eq))) - (nreverse nas)))) - -;;;; -;;;; Merged trees from different files -;;;; - -(defun cvs-tree-fuzzy-merge-1 (trees tree prev) - ) - -(defun cvs-tree-fuzzy-merge (trees tree) - "Do the impossible: merge TREE into TREES." - ()) - -(defun cvs-tree () - "Get tags from the status output and merge tham all into a big tree." - (save-excursion - (goto-char (point-min)) - (let ((inhibit-read-only t) - (trees (make-vector 31 0)) tree) - (while (listp (setq tree (cvs-tags->tree (cvs-status-get-tags)))) - (cvs-tree-fuzzy-merge trees tree)) - (erase-buffer) - (let ((cvs-tag-print-rev nil)) - (cvs-tree-print tree 'cvs-tag->string 3))))) - - -(provide 'cvs-status) - -;; arch-tag: db8b5094-d02a-473e-a476-544e89ff5ad0 -;;; cvs-status.el ends here diff -r 681cd08dc0f7 -r e93288477c43 lisp/diff-mode.el --- a/lisp/diff-mode.el Thu Jun 10 22:43:47 2010 +0000 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,1935 +0,0 @@ -;;; diff-mode.el --- a mode for viewing/editing context diffs - -;; Copyright (C) 1998, 1999, 2000, 2001, 2002, 2003, 2004,2005, 2006, -;; 2007, 2008, 2009, 2010 Free Software Foundation, Inc. - -;; Author: Stefan Monnier -;; Keywords: convenience patch diff - -;; 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 3 of the License, 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. If not, see . - -;;; Commentary: - -;; Provides support for font-lock, outline, navigation -;; commands, editing and various conversions as well as jumping -;; to the corresponding source file. - -;; Inspired by Pavel Machek's patch-mode.el () -;; Some efforts were spent to have it somewhat compatible with XEmacs' -;; diff-mode as well as with compilation-minor-mode - -;; Bugs: - -;; - Reverse doesn't work with normal diffs. - -;; Todo: - -;; - Improve `diff-add-change-log-entries-other-window', -;; it is very simplistic now. -;; -;; - Add a `delete-after-apply' so C-c C-a automatically deletes hunks. -;; Also allow C-c C-a to delete already-applied hunks. -;; -;; - Try `diff ' to try and fuzzily discover the source location -;; of a hunk. Show then the changes between and and make it -;; possible to apply them to , , or . -;; Or maybe just make it into a ".rej to diff3-markers converter". -;; Maybe just use `wiggle' (by Neil Brown) to do it for us. -;; -;; - in diff-apply-hunk, strip context in replace-match to better -;; preserve markers and spacing. -;; - Handle `diff -b' output in context->unified. - -;;; Code: -(eval-when-compile (require 'cl)) - -(defvar add-log-buffer-file-name-function) - - -(defgroup diff-mode () - "Major mode for viewing/editing diffs." - :version "21.1" - :group 'tools - :group 'diff) - -(defcustom diff-default-read-only nil - "If non-nil, `diff-mode' buffers default to being read-only." - :type 'boolean - :group 'diff-mode) - -(defcustom diff-jump-to-old-file nil - "Non-nil means `diff-goto-source' jumps to the old file. -Else, it jumps to the new file." - :type 'boolean - :group 'diff-mode) - -(defcustom diff-update-on-the-fly t - "Non-nil means hunk headers are kept up-to-date on-the-fly. -When editing a diff file, the line numbers in the hunk headers -need to be kept consistent with the actual diff. This can -either be done on the fly (but this sometimes interacts poorly with the -undo mechanism) or whenever the file is written (can be slow -when editing big diffs)." - :type 'boolean - :group 'diff-mode) - -(defcustom diff-advance-after-apply-hunk t - "Non-nil means `diff-apply-hunk' will move to the next hunk after applying." - :type 'boolean - :group 'diff-mode) - -(defcustom diff-mode-hook nil - "Run after setting up the `diff-mode' major mode." - :type 'hook - :options '(diff-delete-empty-files diff-make-unified) - :group 'diff-mode) - -(defvar diff-outline-regexp - "\\([*+][*+][*+] [^0-9]\\|@@ ...\\|\\*\\*\\* [0-9].\\|--- [0-9]..\\)") - -;;;; -;;;; keymap, menu, ... -;;;; - -(easy-mmode-defmap diff-mode-shared-map - '(;; From Pavel Machek's patch-mode. - ("n" . diff-hunk-next) - ("N" . diff-file-next) - ("p" . diff-hunk-prev) - ("P" . diff-file-prev) - ("\t" . diff-hunk-next) - ([backtab] . diff-hunk-prev) - ("k" . diff-hunk-kill) - ("K" . diff-file-kill) - ;; From compilation-minor-mode. - ("}" . diff-file-next) - ("{" . diff-file-prev) - ("\C-m" . diff-goto-source) - ([mouse-2] . diff-goto-source) - ;; From XEmacs' diff-mode. - ;; Standard M-w is useful, so don't change M-W. - ;;("W" . widen) - ;;("." . diff-goto-source) ;display-buffer - ;;("f" . diff-goto-source) ;find-file - ("o" . diff-goto-source) ;other-window - ;;("w" . diff-goto-source) ;other-frame - ;;("N" . diff-narrow) - ;;("h" . diff-show-header) - ;;("j" . diff-show-difference) ;jump to Nth diff - ;;("q" . diff-quit) - ;; Not useful if you have to metafy them. - ;;(" " . scroll-up) - ;;("\177" . scroll-down) - ;; Standard M-a is useful, so don't change M-A. - ;;("A" . diff-ediff-patch) - ;; Standard M-r is useful, so don't change M-r or M-R. - ;;("r" . diff-restrict-view) - ;;("R" . diff-reverse-direction) - ("q" . quit-window)) - "Basic keymap for `diff-mode', bound to various prefix keys.") - -(easy-mmode-defmap diff-mode-map - `(("\e" . ,diff-mode-shared-map) - ;; From compilation-minor-mode. - ("\C-c\C-c" . diff-goto-source) - ;; By analogy with the global C-x 4 a binding. - ("\C-x4A" . diff-add-change-log-entries-other-window) - ;; Misc operations. - ("\C-c\C-a" . diff-apply-hunk) - ("\C-c\C-e" . diff-ediff-patch) - ("\C-c\C-n" . diff-restrict-view) - ("\C-c\C-s" . diff-split-hunk) - ("\C-c\C-t" . diff-test-hunk) - ("\C-c\C-r" . diff-reverse-direction) - ("\C-c\C-u" . diff-context->unified) - ;; `d' because it duplicates the context :-( --Stef - ("\C-c\C-d" . diff-unified->context) - ("\C-c\C-w" . diff-ignore-whitespace-hunk) - ("\C-c\C-b" . diff-refine-hunk) ;No reason for `b' :-( - ("\C-c\C-f" . next-error-follow-minor-mode)) - "Keymap for `diff-mode'. See also `diff-mode-shared-map'.") - -(easy-menu-define diff-mode-menu diff-mode-map - "Menu for `diff-mode'." - '("Diff" - ["Jump to Source" diff-goto-source - :help "Jump to the corresponding source line"] - ["Apply hunk" diff-apply-hunk - :help "Apply the current hunk to the source file and go to the next"] - ["Test applying hunk" diff-test-hunk - :help "See whether it's possible to apply the current hunk"] - ["Apply diff with Ediff" diff-ediff-patch - :help "Call `ediff-patch-file' on the current buffer"] - ["Create Change Log entries" diff-add-change-log-entries-other-window - :help "Create ChangeLog entries for the changes in the diff buffer"] - "-----" - ["Reverse direction" diff-reverse-direction - :help "Reverse the direction of the diffs"] - ["Context -> Unified" diff-context->unified - :help "Convert context diffs to unified diffs"] - ["Unified -> Context" diff-unified->context - :help "Convert unified diffs to context diffs"] - ;;["Fixup Headers" diff-fixup-modifs (not buffer-read-only)] - ["Show trailing whitespace" whitespace-mode - :style toggle :selected (bound-and-true-p whitespace-mode) - :help "Show trailing whitespace in modified lines"] - "-----" - ["Split hunk" diff-split-hunk - :active (diff-splittable-p) - :help "Split the current (unified diff) hunk at point into two hunks"] - ["Ignore whitespace changes" diff-ignore-whitespace-hunk - :help "Re-diff the current hunk, ignoring whitespace differences"] - ["Highlight fine changes" diff-refine-hunk - :help "Highlight changes of hunk at point at a finer granularity"] - ["Kill current hunk" diff-hunk-kill - :help "Kill current hunk"] - ["Kill current file's hunks" diff-file-kill - :help "Kill all current file's hunks"] - "-----" - ["Previous Hunk" diff-hunk-prev - :help "Go to the previous count'th hunk"] - ["Next Hunk" diff-hunk-next - :help "Go to the next count'th hunk"] - ["Previous File" diff-file-prev - :help "Go to the previous count'th file"] - ["Next File" diff-file-next - :help "Go to the next count'th file"] - )) - -(defcustom diff-minor-mode-prefix "\C-c=" - "Prefix key for `diff-minor-mode' commands." - :type '(choice (string "\e") (string "C-c=") string) - :group 'diff-mode) - -(easy-mmode-defmap diff-minor-mode-map - `((,diff-minor-mode-prefix . ,diff-mode-shared-map)) - "Keymap for `diff-minor-mode'. See also `diff-mode-shared-map'.") - -(define-minor-mode diff-auto-refine-mode - "Automatically highlight changes in detail as the user visits hunks. -When transitioning from disabled to enabled, -try to refine the current hunk, as well." - :group 'diff-mode :init-value t :lighter nil ;; " Auto-Refine" - (when diff-auto-refine-mode - (condition-case-no-debug nil (diff-refine-hunk) (error nil)))) - -;;;; -;;;; font-lock support -;;;; - -(defface diff-header - '((((class color) (min-colors 88) (background light)) - :background "grey80") - (((class color) (min-colors 88) (background dark)) - :background "grey45") - (((class color) (background light)) - :foreground "blue1" :weight bold) - (((class color) (background dark)) - :foreground "green" :weight bold) - (t :weight bold)) - "`diff-mode' face inherited by hunk and index header faces." - :group 'diff-mode) -(define-obsolete-face-alias 'diff-header-face 'diff-header "22.1") -(defvar diff-header-face 'diff-header) - -(defface diff-file-header - '((((class color) (min-colors 88) (background light)) - :background "grey70" :weight bold) - (((class color) (min-colors 88) (background dark)) - :background "grey60" :weight bold) - (((class color) (background light)) - :foreground "green" :weight bold) - (((class color) (background dark)) - :foreground "cyan" :weight bold) - (t :weight bold)) ; :height 1.3 - "`diff-mode' face used to highlight file header lines." - :group 'diff-mode) -(define-obsolete-face-alias 'diff-file-header-face 'diff-file-header "22.1") -(defvar diff-file-header-face 'diff-file-header) - -(defface diff-index - '((t :inherit diff-file-header)) - "`diff-mode' face used to highlight index header lines." - :group 'diff-mode) -(define-obsolete-face-alias 'diff-index-face 'diff-index "22.1") -(defvar diff-index-face 'diff-index) - -(defface diff-hunk-header - '((t :inherit diff-header)) - "`diff-mode' face used to highlight hunk header lines." - :group 'diff-mode) -(define-obsolete-face-alias 'diff-hunk-header-face 'diff-hunk-header "22.1") -(defvar diff-hunk-header-face 'diff-hunk-header) - -(defface diff-removed - '((t :inherit diff-changed)) - "`diff-mode' face used to highlight removed lines." - :group 'diff-mode) -(define-obsolete-face-alias 'diff-removed-face 'diff-removed "22.1") -(defvar diff-removed-face 'diff-removed) - -(defface diff-added - '((t :inherit diff-changed)) - "`diff-mode' face used to highlight added lines." - :group 'diff-mode) -(define-obsolete-face-alias 'diff-added-face 'diff-added "22.1") -(defvar diff-added-face 'diff-added) - -(defface diff-changed - '((((type tty pc) (class color) (background light)) - :foreground "magenta" :weight bold :slant italic) - (((type tty pc) (class color) (background dark)) - :foreground "yellow" :weight bold :slant italic)) - "`diff-mode' face used to highlight changed lines." - :group 'diff-mode) -(define-obsolete-face-alias 'diff-changed-face 'diff-changed "22.1") -(defvar diff-changed-face 'diff-changed) - -(defface diff-indicator-removed - '((t :inherit diff-removed)) - "`diff-mode' face used to highlight indicator of removed lines (-, <)." - :group 'diff-mode - :version "22.1") -(defvar diff-indicator-removed-face 'diff-indicator-removed) - -(defface diff-indicator-added - '((t :inherit diff-added)) - "`diff-mode' face used to highlight indicator of added lines (+, >)." - :group 'diff-mode - :version "22.1") -(defvar diff-indicator-added-face 'diff-indicator-added) - -(defface diff-indicator-changed - '((t :inherit diff-changed)) - "`diff-mode' face used to highlight indicator of changed lines." - :group 'diff-mode - :version "22.1") -(defvar diff-indicator-changed-face 'diff-indicator-changed) - -(defface diff-function - '((t :inherit diff-header)) - "`diff-mode' face used to highlight function names produced by \"diff -p\"." - :group 'diff-mode) -(define-obsolete-face-alias 'diff-function-face 'diff-function "22.1") -(defvar diff-function-face 'diff-function) - -(defface diff-context - '((((class color grayscale) (min-colors 88)) :inherit shadow)) - "`diff-mode' face used to highlight context and other side-information." - :group 'diff-mode) -(define-obsolete-face-alias 'diff-context-face 'diff-context "22.1") -(defvar diff-context-face 'diff-context) - -(defface diff-nonexistent - '((t :inherit diff-file-header)) - "`diff-mode' face used to highlight nonexistent files in recursive diffs." - :group 'diff-mode) -(define-obsolete-face-alias 'diff-nonexistent-face 'diff-nonexistent "22.1") -(defvar diff-nonexistent-face 'diff-nonexistent) - -(defconst diff-yank-handler '(diff-yank-function)) -(defun diff-yank-function (text) - ;; FIXME: the yank-handler is now called separately on each piece of text - ;; with a yank-handler property, so the next-single-property-change call - ;; below will always return nil :-( --stef - (let ((mixed (next-single-property-change 0 'yank-handler text)) - (start (point))) - ;; First insert the text. - (insert text) - ;; If the text does not include any diff markers and if we're not - ;; yanking back into a diff-mode buffer, get rid of the prefixes. - (unless (or mixed (derived-mode-p 'diff-mode)) - (undo-boundary) ; Just in case the user wanted the prefixes. - (let ((re (save-excursion - (if (re-search-backward "^[>][ \t]") - "^[ <>!+-]")))) - (save-excursion - (while (re-search-backward re start t) - (replace-match "" t t))))))) - -(defconst diff-hunk-header-re-unified - "^@@ -\\([0-9]+\\)\\(?:,\\([0-9]+\\)\\)? \\+\\([0-9]+\\)\\(?:,\\([0-9]+\\)\\)? @@") -(defconst diff-context-mid-hunk-header-re - "--- \\([0-9]+\\)\\(?:,\\([0-9]+\\)\\)? ----$") - -(defvar diff-font-lock-keywords - `((,(concat "\\(" diff-hunk-header-re-unified "\\)\\(.*\\)$") - (1 diff-hunk-header-face) (6 diff-function-face)) - ("^\\(\\*\\{15\\}\\)\\(.*\\)$" ;context - (1 diff-hunk-header-face) (2 diff-function-face)) - ("^\\*\\*\\* .+ \\*\\*\\*\\*". diff-hunk-header-face) ;context - (,diff-context-mid-hunk-header-re . diff-hunk-header-face) ;context - ("^[0-9,]+[acd][0-9,]+$" . diff-hunk-header-face) ;normal - ("^---$" . diff-hunk-header-face) ;normal - ;; For file headers, accept files with spaces, but be careful to rule - ;; out false-positives when matching hunk headers. - ("^\\(---\\|\\+\\+\\+\\|\\*\\*\\*\\) \\([^\t\n]+?\\)\\(?:\t.*\\| \\(\\*\\*\\*\\*\\|----\\)\\)?\n" - (0 diff-header-face) - (2 (if (not (match-end 3)) diff-file-header-face) prepend)) - ("^\\([-<]\\)\\(.*\n\\)" - (1 diff-indicator-removed-face) (2 diff-removed-face)) - ("^\\([+>]\\)\\(.*\n\\)" - (1 diff-indicator-added-face) (2 diff-added-face)) - ("^\\(!\\)\\(.*\n\\)" - (1 diff-indicator-changed-face) (2 diff-changed-face)) - ("^Index: \\(.+\\).*\n" - (0 diff-header-face) (1 diff-index-face prepend)) - ("^Only in .*\n" . diff-nonexistent-face) - ("^\\(#\\)\\(.*\\)" - (1 font-lock-comment-delimiter-face) - (2 font-lock-comment-face)) - ("^[^-=+*!<>#].*\n" (0 diff-context-face)))) - -(defconst diff-font-lock-defaults - '(diff-font-lock-keywords t nil nil nil (font-lock-multiline . nil))) - -(defvar diff-imenu-generic-expression - ;; Prefer second name as first is most likely to be a backup or - ;; version-control name. The [\t\n] at the end of the unidiff pattern - ;; catches Debian source diff files (which lack the trailing date). - '((nil "\\+\\+\\+\\ \\([^\t\n]+\\)[\t\n]" 1) ; unidiffs - (nil "^--- \\([^\t\n]+\\)\t.*\n\\*" 1))) ; context diffs - -;;;; -;;;; Movement -;;;; - -(defvar diff-valid-unified-empty-line t - "If non-nil, empty lines are valid in unified diffs. -Some versions of diff replace all-blank context lines in unified format with -empty lines. This makes the format less robust, but is tolerated. -See http://lists.gnu.org/archive/html/emacs-devel/2007-11/msg01990.html") - -(defconst diff-hunk-header-re - (concat "^\\(?:" diff-hunk-header-re-unified ".*\\|\\*\\{15\\}.*\n\\*\\*\\* .+ \\*\\*\\*\\*\\|[0-9]+\\(,[0-9]+\\)?[acd][0-9]+\\(,[0-9]+\\)?\\)$")) -(defconst diff-file-header-re (concat "^\\(--- .+\n\\+\\+\\+ \\|\\*\\*\\* .+\n--- \\|[^-+!<>0-9@* \n]\\).+\n" (substring diff-hunk-header-re 1))) -(defvar diff-narrowed-to nil) - -(defun diff-hunk-style (&optional style) - (when (looking-at diff-hunk-header-re) - (setq style (cdr (assq (char-after) '((?@ . unified) (?* . context))))) - (goto-char (match-end 0))) - style) - -(defun diff-end-of-hunk (&optional style donttrustheader) - (let (end) - (when (looking-at diff-hunk-header-re) - ;; Especially important for unified (because headers are ambiguous). - (setq style (diff-hunk-style style)) - (goto-char (match-end 0)) - (when (and (not donttrustheader) (match-end 2)) - (let* ((nold (string-to-number (or (match-string 2) "1"))) - (nnew (string-to-number (or (match-string 4) "1"))) - (endold - (save-excursion - (re-search-forward (if diff-valid-unified-empty-line - "^[- \n]" "^[- ]") - nil t nold) - (line-beginning-position 2))) - (endnew - ;; The hunk may end with a bunch of "+" lines, so the `end' is - ;; then further than computed above. - (save-excursion - (re-search-forward (if diff-valid-unified-empty-line - "^[+ \n]" "^[+ ]") - nil t nnew) - (line-beginning-position 2)))) - (setq end (max endold endnew))))) - ;; We may have a first evaluation of `end' thanks to the hunk header. - (unless end - (setq end (and (re-search-forward - (case style - (unified (concat (if diff-valid-unified-empty-line - "^[^-+# \\\n]\\|" "^[^-+# \\]\\|") - ;; A `unified' header is ambiguous. - diff-file-header-re)) - (context "^[^-+#! \\]") - (normal "^[^<>#\\]") - (t "^[^-+#!<> \\]")) - nil t) - (match-beginning 0))) - (when diff-valid-unified-empty-line - ;; While empty lines may be valid inside hunks, they are also likely - ;; to be unrelated to the hunk. - (goto-char (or end (point-max))) - (while (eq ?\n (char-before (1- (point)))) - (forward-char -1) - (setq end (point))))) - ;; The return value is used by easy-mmode-define-navigation. - (goto-char (or end (point-max))))) - -(defun diff-beginning-of-hunk (&optional try-harder) - "Move back to beginning of hunk. -If TRY-HARDER is non-nil, try to cater to the case where we're not in a hunk -but in the file header instead, in which case move forward to the first hunk." - (beginning-of-line) - (unless (looking-at diff-hunk-header-re) - (forward-line 1) - (condition-case () - (re-search-backward diff-hunk-header-re) - (error - (if (not try-harder) - (error "Can't find the beginning of the hunk") - (diff-beginning-of-file-and-junk) - (diff-hunk-next)))))) - -(defun diff-unified-hunk-p () - (save-excursion - (ignore-errors - (diff-beginning-of-hunk) - (looking-at "^@@")))) - -(defun diff-beginning-of-file () - (beginning-of-line) - (unless (looking-at diff-file-header-re) - (let ((start (point)) - res) - ;; diff-file-header-re may need to match up to 4 lines, so in case - ;; we're inside the header, we need to move up to 3 lines forward. - (forward-line 3) - (if (and (setq res (re-search-backward diff-file-header-re nil t)) - ;; Maybe the 3 lines forward were too much and we matched - ;; a file header after our starting point :-( - (or (<= (point) start) - (setq res (re-search-backward diff-file-header-re nil t)))) - res - (goto-char start) - (error "Can't find the beginning of the file"))))) - - -(defun diff-end-of-file () - (re-search-forward "^[-+#!<>0-9@* \\]" nil t) - (re-search-forward (concat "^[^-+#!<>0-9@* \\]\\|" diff-file-header-re) - nil 'move) - (if (match-beginning 1) - (goto-char (match-beginning 1)) - (beginning-of-line))) - -;; Define diff-{hunk,file}-{prev,next} -(easy-mmode-define-navigation - diff-hunk diff-hunk-header-re "hunk" diff-end-of-hunk diff-restrict-view - (if diff-auto-refine-mode - (condition-case-no-debug nil (diff-refine-hunk) (error nil)))) - -(easy-mmode-define-navigation - diff-file diff-file-header-re "file" diff-end-of-hunk) - -(defun diff-restrict-view (&optional arg) - "Restrict the view to the current hunk. -If the prefix ARG is given, restrict the view to the current file instead." - (interactive "P") - (save-excursion - (if arg (diff-beginning-of-file) (diff-beginning-of-hunk 'try-harder)) - (narrow-to-region (point) - (progn (if arg (diff-end-of-file) (diff-end-of-hunk)) - (point))) - (set (make-local-variable 'diff-narrowed-to) (if arg 'file 'hunk)))) - - -(defun diff-hunk-kill () - "Kill current hunk." - (interactive) - (diff-beginning-of-hunk) - (let* ((start (point)) - ;; Search the second match, since we're looking at the first. - (nexthunk (when (re-search-forward diff-hunk-header-re nil t 2) - (match-beginning 0))) - (firsthunk (ignore-errors - (goto-char start) - (diff-beginning-of-file) (diff-hunk-next) (point))) - (nextfile (ignore-errors (diff-file-next) (point))) - (inhibit-read-only t)) - (goto-char start) - (if (and firsthunk (= firsthunk start) - (or (null nexthunk) - (and nextfile (> nexthunk nextfile)))) - ;; It's the only hunk for this file, so kill the file. - (diff-file-kill) - (diff-end-of-hunk) - (kill-region start (point))))) - -;; "index ", "old mode", "new mode", "new file mode" and -;; "deleted file mode" are output by git-diff. -(defconst diff-file-junk-re - "diff \\|index \\|\\(?:deleted file\\|new\\(?: file\\)?\\|old\\) mode") - -(defun diff-beginning-of-file-and-junk () - "Go to the beginning of file-related diff-info. -This is like `diff-beginning-of-file' except it tries to skip back over leading -data such as \"Index: ...\" and such." - (let* ((orig (point)) - ;; Skip forward over what might be "leading junk" so as to get - ;; closer to the actual diff. - (_ (progn (beginning-of-line) - (while (looking-at diff-file-junk-re) - (forward-line 1)))) - (start (point)) - (prevfile (condition-case err - (save-excursion (diff-beginning-of-file) (point)) - (error err))) - (err (if (consp prevfile) prevfile)) - (nextfile (ignore-errors - (save-excursion - (goto-char start) (diff-file-next) (point)))) - ;; prevhunk is one of the limits. - (prevhunk (save-excursion - (ignore-errors - (if (numberp prevfile) (goto-char prevfile)) - (diff-hunk-prev) (point)))) - (previndex (save-excursion - (forward-line 1) ;In case we're looking at "Index:". - (re-search-backward "^Index: " prevhunk t)))) - ;; If we're in the junk, we should use nextfile instead of prevfile. - (if (and (numberp nextfile) - (or (not (numberp prevfile)) - (and previndex (> previndex prevfile)))) - (setq prevfile nextfile)) - (if (and previndex (numberp prevfile) (< previndex prevfile)) - (setq prevfile previndex)) - (if (and (numberp prevfile) (<= prevfile start)) - (progn - (goto-char prevfile) - ;; Now skip backward over the leading junk we may have before the - ;; diff itself. - (while (save-excursion - (and (zerop (forward-line -1)) - (looking-at diff-file-junk-re))) - (forward-line -1))) - ;; File starts *after* the starting point: we really weren't in - ;; a file diff but elsewhere. - (goto-char orig) - (signal (car err) (cdr err))))) - -(defun diff-file-kill () - "Kill current file's hunks." - (interactive) - (let ((orig (point)) - (start (progn (diff-beginning-of-file-and-junk) (point))) - (inhibit-read-only t)) - (diff-end-of-file) - (if (looking-at "^\n") (forward-char 1)) ;`tla' generates such diffs. - (if (> orig (point)) (error "Not inside a file diff")) - (kill-region start (point)))) - -(defun diff-kill-junk () - "Kill spurious empty diffs." - (interactive) - (save-excursion - (let ((inhibit-read-only t)) - (goto-char (point-min)) - (while (re-search-forward (concat "^\\(Index: .*\n\\)" - "\\([^-+!* <>].*\n\\)*?" - "\\(\\(Index:\\) \\|" - diff-file-header-re "\\)") - nil t) - (delete-region (if (match-end 4) (match-beginning 0) (match-end 1)) - (match-beginning 3)) - (beginning-of-line))))) - -(defun diff-count-matches (re start end) - (save-excursion - (let ((n 0)) - (goto-char start) - (while (re-search-forward re end t) (incf n)) - n))) - -(defun diff-splittable-p () - (save-excursion - (beginning-of-line) - (and (looking-at "^[-+ ]") - (progn (forward-line -1) (looking-at "^[-+ ]")) - (diff-unified-hunk-p)))) - -(defun diff-split-hunk () - "Split the current (unified diff) hunk at point into two hunks." - (interactive) - (beginning-of-line) - (let ((pos (point)) - (start (progn (diff-beginning-of-hunk) (point)))) - (unless (looking-at diff-hunk-header-re-unified) - (error "diff-split-hunk only works on unified context diffs")) - (forward-line 1) - (let* ((start1 (string-to-number (match-string 1))) - (start2 (string-to-number (match-string 3))) - (newstart1 (+ start1 (diff-count-matches "^[- \t]" (point) pos))) - (newstart2 (+ start2 (diff-count-matches "^[+ \t]" (point) pos))) - (inhibit-read-only t)) - (goto-char pos) - ;; Hopefully the after-change-function will not screw us over. - (insert "@@ -" (number-to-string newstart1) ",1 +" - (number-to-string newstart2) ",1 @@\n") - ;; Fix the original hunk-header. - (diff-fixup-modifs start pos)))) - - -;;;; -;;;; jump to other buffers -;;;; - -(defvar diff-remembered-files-alist nil) -(defvar diff-remembered-defdir nil) - -(defun diff-filename-drop-dir (file) - (when (string-match "/" file) (substring file (match-end 0)))) - -(defun diff-merge-strings (ancestor from to) - "Merge the diff between ANCESTOR and FROM into TO. -Returns the merged string if successful or nil otherwise. -The strings are assumed not to contain any \"\\n\" (i.e. end of line). -If ANCESTOR = FROM, returns TO. -If ANCESTOR = TO, returns FROM. -The heuristic is simplistic and only really works for cases -like \(diff-merge-strings \"b/foo\" \"b/bar\" \"/a/c/foo\")." - ;; Ideally, we want: - ;; AMB ANB CMD -> CND - ;; but that's ambiguous if `foo' or `bar' is empty: - ;; a/foo a/foo1 b/foo.c -> b/foo1.c but not 1b/foo.c or b/foo.c1 - (let ((str (concat ancestor "\n" from "\n" to))) - (when (and (string-match (concat - "\\`\\(.*?\\)\\(.*\\)\\(.*\\)\n" - "\\1\\(.*\\)\\3\n" - "\\(.*\\(\\2\\).*\\)\\'") str) - (equal to (match-string 5 str))) - (concat (substring str (match-beginning 5) (match-beginning 6)) - (match-string 4 str) - (substring str (match-end 6) (match-end 5)))))) - -(defun diff-tell-file-name (old name) - "Tell Emacs where the find the source file of the current hunk. -If the OLD prefix arg is passed, tell the file NAME of the old file." - (interactive - (let* ((old current-prefix-arg) - (fs (diff-hunk-file-names current-prefix-arg))) - (unless fs (error "No file name to look for")) - (list old (read-file-name (format "File for %s: " (car fs)) - nil (diff-find-file-name old 'noprompt) t)))) - (let ((fs (diff-hunk-file-names old))) - (unless fs (error "No file name to look for")) - (push (cons fs name) diff-remembered-files-alist))) - -(defun diff-hunk-file-names (&optional old) - "Give the list of file names textually mentioned for the current hunk." - (save-excursion - (unless (looking-at diff-file-header-re) - (or (ignore-errors (diff-beginning-of-file)) - (re-search-forward diff-file-header-re nil t))) - (let ((limit (save-excursion - (condition-case () - (progn (diff-hunk-prev) (point)) - (error (point-min))))) - (header-files - (if (looking-at "[-*][-*][-*] \\(\\S-+\\)\\(\\s-.*\\)?\n[-+][-+][-+] \\(\\S-+\\)") - (list (if old (match-string 1) (match-string 3)) - (if old (match-string 3) (match-string 1))) - (forward-line 1) nil))) - (delq nil - (append - (when (and (not old) - (save-excursion - (re-search-backward "^Index: \\(.+\\)" limit t))) - (list (match-string 1))) - header-files - (when (re-search-backward - "^diff \\(-\\S-+ +\\)*\\(\\S-+\\)\\( +\\(\\S-+\\)\\)?" - nil t) - (list (if old (match-string 2) (match-string 4)) - (if old (match-string 4) (match-string 2))))))))) - -(defun diff-find-file-name (&optional old noprompt prefix) - "Return the file corresponding to the current patch. -Non-nil OLD means that we want the old file. -Non-nil NOPROMPT means to prefer returning nil than to prompt the user. -PREFIX is only used internally: don't use it." - (unless (equal diff-remembered-defdir default-directory) - ;; Flush diff-remembered-files-alist if the default-directory is changed. - (set (make-local-variable 'diff-remembered-defdir) default-directory) - (set (make-local-variable 'diff-remembered-files-alist) nil)) - (save-excursion - (unless (looking-at diff-file-header-re) - (or (ignore-errors (diff-beginning-of-file)) - (re-search-forward diff-file-header-re nil t))) - (let ((fs (diff-hunk-file-names old))) - (if prefix (setq fs (mapcar (lambda (f) (concat prefix f)) fs))) - (or - ;; use any previously used preference - (cdr (assoc fs diff-remembered-files-alist)) - ;; try to be clever and use previous choices as an inspiration - (dolist (rf diff-remembered-files-alist) - (let ((newfile (diff-merge-strings (caar rf) (car fs) (cdr rf)))) - (if (and newfile (file-exists-p newfile)) (return newfile)))) - ;; look for each file in turn. If none found, try again but - ;; ignoring the first level of directory, ... - (do* ((files fs (delq nil (mapcar 'diff-filename-drop-dir files))) - (file nil nil)) - ((or (null files) - (setq file (do* ((files files (cdr files)) - (file (car files) (car files))) - ;; Use file-regular-p to avoid - ;; /dev/null, directories, etc. - ((or (null file) (file-regular-p file)) - file)))) - file)) - ;; .rej patches implicitly apply to - (and (string-match "\\.rej\\'" (or buffer-file-name "")) - (let ((file (substring buffer-file-name 0 (match-beginning 0)))) - (when (file-exists-p file) file))) - ;; If we haven't found the file, maybe it's because we haven't paid - ;; attention to the PCL-CVS hint. - (and (not prefix) - (boundp 'cvs-pcl-cvs-dirchange-re) - (save-excursion - (re-search-backward cvs-pcl-cvs-dirchange-re nil t)) - (diff-find-file-name old noprompt (match-string 1))) - ;; if all else fails, ask the user - (unless noprompt - (let ((file (read-file-name (format "Use file %s: " - (or (first fs) "")) - nil (first fs) t (first fs)))) - (set (make-local-variable 'diff-remembered-files-alist) - (cons (cons fs file) diff-remembered-files-alist)) - file)))))) - - -(defun diff-ediff-patch () - "Call `ediff-patch-file' on the current buffer." - (interactive) - (condition-case err - (ediff-patch-file nil (current-buffer)) - (wrong-number-of-arguments (ediff-patch-file)))) - -;;;; -;;;; Conversion functions -;;;; - -;;(defvar diff-inhibit-after-change nil -;; "Non-nil means inhibit `diff-mode's after-change functions.") - -(defun diff-unified->context (start end) - "Convert unified diffs to context diffs. -START and END are either taken from the region (if a prefix arg is given) or -else cover the whole buffer." - (interactive (if (or current-prefix-arg (and transient-mark-mode mark-active)) - (list (region-beginning) (region-end)) - (list (point-min) (point-max)))) - (unless (markerp end) (setq end (copy-marker end t))) - (let (;;(diff-inhibit-after-change t) - (inhibit-read-only t)) - (save-excursion - (goto-char start) - (while (and (re-search-forward - (concat "^\\(\\(---\\) .+\n\\(\\+\\+\\+\\) .+\\|" - diff-hunk-header-re-unified ".*\\)$") - nil t) - (< (point) end)) - (combine-after-change-calls - (if (match-beginning 2) - ;; we matched a file header - (progn - ;; use reverse order to make sure the indices are kept valid - (replace-match "---" t t nil 3) - (replace-match "***" t t nil 2)) - ;; we matched a hunk header - (let ((line1 (match-string 4)) - (lines1 (or (match-string 5) "1")) - (line2 (match-string 6)) - (lines2 (or (match-string 7) "1")) - ;; Variables to use the special undo function. - (old-undo buffer-undo-list) - (old-end (marker-position end)) - (start (match-beginning 0)) - (reversible t)) - (replace-match - (concat "***************\n*** " line1 "," - (number-to-string (+ (string-to-number line1) - (string-to-number lines1) - -1)) - " ****")) - (save-restriction - (narrow-to-region (line-beginning-position 2) - ;; Call diff-end-of-hunk from just before - ;; the hunk header so it can use the hunk - ;; header info. - (progn (diff-end-of-hunk 'unified) (point))) - (let ((hunk (buffer-string))) - (goto-char (point-min)) - (if (not (save-excursion (re-search-forward "^-" nil t))) - (delete-region (point) (point-max)) - (goto-char (point-max)) - (let ((modif nil) last-pt) - (while (progn (setq last-pt (point)) - (= (forward-line -1) 0)) - (case (char-after) - (?\s (insert " ") (setq modif nil) (backward-char 1)) - (?+ (delete-region (point) last-pt) (setq modif t)) - (?- (if (not modif) - (progn (forward-char 1) - (insert " ")) - (delete-char 1) - (insert "! ")) - (backward-char 2)) - (?\\ (when (save-excursion (forward-line -1) - (= (char-after) ?+)) - (delete-region (point) last-pt) (setq modif t))) - ;; diff-valid-unified-empty-line. - (?\n (insert " ") (setq modif nil) (backward-char 2)) - (t (setq modif nil)))))) - (goto-char (point-max)) - (save-excursion - (insert "--- " line2 "," - (number-to-string (+ (string-to-number line2) - (string-to-number lines2) - -1)) - " ----\n" hunk)) - ;;(goto-char (point-min)) - (forward-line 1) - (if (not (save-excursion (re-search-forward "^+" nil t))) - (delete-region (point) (point-max)) - (let ((modif nil) (delete nil)) - (if (save-excursion (re-search-forward "^\\+.*\n-" nil t)) - ;; Normally, lines in a substitution come with - ;; first the removals and then the additions, and - ;; the context->unified function follows this - ;; convention, of course. Yet, other alternatives - ;; are valid as well, but they preclude the use of - ;; context->unified as an undo command. - (setq reversible nil)) - (while (not (eobp)) - (case (char-after) - (?\s (insert " ") (setq modif nil) (backward-char 1)) - (?- (setq delete t) (setq modif t)) - (?+ (if (not modif) - (progn (forward-char 1) - (insert " ")) - (delete-char 1) - (insert "! ")) - (backward-char 2)) - (?\\ (when (save-excursion (forward-line 1) - (not (eobp))) - (setq delete t) (setq modif t))) - ;; diff-valid-unified-empty-line. - (?\n (insert " ") (setq modif nil) (backward-char 2) - (setq reversible nil)) - (t (setq modif nil))) - (let ((last-pt (point))) - (forward-line 1) - (when delete - (delete-region last-pt (point)) - (setq delete nil))))))) - (unless (or (not reversible) (eq buffer-undo-list t)) - ;; Drop the many undo entries and replace them with - ;; a single entry that uses diff-context->unified to do - ;; the work. - (setq buffer-undo-list - (cons (list 'apply (- old-end end) start (point-max) - 'diff-context->unified start (point-max)) - old-undo))))))))))) - -(defun diff-context->unified (start end &optional to-context) - "Convert context diffs to unified diffs. -START and END are either taken from the region -\(when it is highlighted) or else cover the whole buffer. -With a prefix argument, convert unified format to context format." - (interactive (if (and transient-mark-mode mark-active) - (list (region-beginning) (region-end) current-prefix-arg) - (list (point-min) (point-max) current-prefix-arg))) - (if to-context - (diff-unified->context start end) - (unless (markerp end) (setq end (copy-marker end t))) - (let ( ;;(diff-inhibit-after-change t) - (inhibit-read-only t)) - (save-excursion - (goto-char start) - (while (and (re-search-forward "^\\(\\(\\*\\*\\*\\) .+\n\\(---\\) .+\\|\\*\\{15\\}.*\n\\*\\*\\* \\([0-9]+\\),\\(-?[0-9]+\\) \\*\\*\\*\\*\\)$" nil t) - (< (point) end)) - (combine-after-change-calls - (if (match-beginning 2) - ;; we matched a file header - (progn - ;; use reverse order to make sure the indices are kept valid - (replace-match "+++" t t nil 3) - (replace-match "---" t t nil 2)) - ;; we matched a hunk header - (let ((line1s (match-string 4)) - (line1e (match-string 5)) - (pt1 (match-beginning 0)) - ;; Variables to use the special undo function. - (old-undo buffer-undo-list) - (old-end (marker-position end)) - (reversible t)) - (replace-match "") - (unless (re-search-forward - diff-context-mid-hunk-header-re nil t) - (error "Can't find matching `--- n1,n2 ----' line")) - (let ((line2s (match-string 1)) - (line2e (match-string 2)) - (pt2 (progn - (delete-region (progn (beginning-of-line) (point)) - (progn (forward-line 1) (point))) - (point-marker)))) - (goto-char pt1) - (forward-line 1) - (while (< (point) pt2) - (case (char-after) - (?! (delete-char 2) (insert "-") (forward-line 1)) - (?- (forward-char 1) (delete-char 1) (forward-line 1)) - (?\s ;merge with the other half of the chunk - (let* ((endline2 - (save-excursion - (goto-char pt2) (forward-line 1) (point)))) - (case (char-after pt2) - ((?! ?+) - (insert "+" - (prog1 (buffer-substring (+ pt2 2) endline2) - (delete-region pt2 endline2)))) - (?\s - (unless (= (- endline2 pt2) - (- (line-beginning-position 2) (point))) - ;; If the two lines we're merging don't have the - ;; same length (can happen with "diff -b"), then - ;; diff-unified->context will not properly undo - ;; this operation. - (setq reversible nil)) - (delete-region pt2 endline2) - (delete-char 1) - (forward-line 1)) - (?\\ (forward-line 1)) - (t (setq reversible nil) - (delete-char 1) (forward-line 1))))) - (t (setq reversible nil) (forward-line 1)))) - (while (looking-at "[+! ] ") - (if (/= (char-after) ?!) (forward-char 1) - (delete-char 1) (insert "+")) - (delete-char 1) (forward-line 1)) - (save-excursion - (goto-char pt1) - (insert "@@ -" line1s "," - (number-to-string (- (string-to-number line1e) - (string-to-number line1s) - -1)) - " +" line2s "," - (number-to-string (- (string-to-number line2e) - (string-to-number line2s) - -1)) " @@")) - (set-marker pt2 nil) - ;; The whole procedure succeeded, let's replace the myriad - ;; of undo elements with just a single special one. - (unless (or (not reversible) (eq buffer-undo-list t)) - (setq buffer-undo-list - (cons (list 'apply (- old-end end) pt1 (point) - 'diff-unified->context pt1 (point)) - old-undo))) - ))))))))) - -(defun diff-reverse-direction (start end) - "Reverse the direction of the diffs. -START and END are either taken from the region (if a prefix arg is given) or -else cover the whole buffer." - (interactive (if (or current-prefix-arg (and transient-mark-mode mark-active)) - (list (region-beginning) (region-end)) - (list (point-min) (point-max)))) - (unless (markerp end) (setq end (copy-marker end t))) - (let (;;(diff-inhibit-after-change t) - (inhibit-read-only t)) - (save-excursion - (goto-char start) - (while (and (re-search-forward "^\\(\\([-*][-*][-*] \\)\\(.+\\)\n\\([-+][-+][-+] \\)\\(.+\\)\\|\\*\\{15\\}.*\n\\*\\*\\* \\(.+\\) \\*\\*\\*\\*\\|@@ -\\([0-9,]+\\) \\+\\([0-9,]+\\) @@.*\\)$" nil t) - (< (point) end)) - (combine-after-change-calls - (cond - ;; a file header - ((match-beginning 2) (replace-match "\\2\\5\n\\4\\3" nil)) - ;; a context-diff hunk header - ((match-beginning 6) - (let ((pt-lines1 (match-beginning 6)) - (lines1 (match-string 6))) - (replace-match "" nil nil nil 6) - (forward-line 1) - (let ((half1s (point))) - (while (looking-at "[-! \\][ \t]\\|#") - (when (= (char-after) ?-) (delete-char 1) (insert "+")) - (forward-line 1)) - (let ((half1 (delete-and-extract-region half1s (point)))) - (unless (looking-at diff-context-mid-hunk-header-re) - (insert half1) - (error "Can't find matching `--- n1,n2 ----' line")) - (let* ((str1end (or (match-end 2) (match-end 1))) - (str1 (buffer-substring (match-beginning 1) str1end))) - (goto-char str1end) - (insert lines1) - (delete-region (match-beginning 1) str1end) - (forward-line 1) - (let ((half2s (point))) - (while (looking-at "[!+ \\][ \t]\\|#") - (when (= (char-after) ?+) (delete-char 1) (insert "-")) - (forward-line 1)) - (let ((half2 (delete-and-extract-region half2s (point)))) - (insert (or half1 "")) - (goto-char half1s) - (insert (or half2 "")))) - (goto-char pt-lines1) - (insert str1)))))) - ;; a unified-diff hunk header - ((match-beginning 7) - (replace-match "@@ -\\8 +\\7 @@" nil) - (forward-line 1) - (let ((c (char-after)) first last) - (while (case (setq c (char-after)) - (?- (setq first (or first (point))) - (delete-char 1) (insert "+") t) - (?+ (setq last (or last (point))) - (delete-char 1) (insert "-") t) - ((?\\ ?#) t) - (t (when (and first last (< first last)) - (insert (delete-and-extract-region first last))) - (setq first nil last nil) - (memq c (if diff-valid-unified-empty-line - '(?\s ?\n) '(?\s))))) - (forward-line 1)))))))))) - -(defun diff-fixup-modifs (start end) - "Fixup the hunk headers (in case the buffer was modified). -START and END are either taken from the region (if a prefix arg is given) or -else cover the whole buffer." - (interactive (if (or current-prefix-arg (and transient-mark-mode mark-active)) - (list (region-beginning) (region-end)) - (list (point-min) (point-max)))) - (let ((inhibit-read-only t)) - (save-excursion - (goto-char end) (diff-end-of-hunk nil 'donttrustheader) - (let ((plus 0) (minus 0) (space 0) (bang 0)) - (while (and (= (forward-line -1) 0) (<= start (point))) - (if (not (looking-at - (concat diff-hunk-header-re-unified - "\\|[-*][-*][-*] [0-9,]+ [-*][-*][-*][-*]$" - "\\|--- .+\n\\+\\+\\+ "))) - (case (char-after) - (?\s (incf space)) - (?+ (incf plus)) - (?- (incf minus)) - (?! (incf bang)) - ((?\\ ?#) nil) - (t (setq space 0 plus 0 minus 0 bang 0))) - (cond - ((looking-at diff-hunk-header-re-unified) - (let* ((old1 (match-string 2)) - (old2 (match-string 4)) - (new1 (number-to-string (+ space minus))) - (new2 (number-to-string (+ space plus)))) - (if old2 - (unless (string= new2 old2) (replace-match new2 t t nil 4)) - (goto-char (match-end 4)) (insert "," new2)) - (if old1 - (unless (string= new1 old1) (replace-match new1 t t nil 2)) - (goto-char (match-end 2)) (insert "," new1)))) - ((looking-at diff-context-mid-hunk-header-re) - (when (> (+ space bang plus) 0) - (let* ((old1 (match-string 1)) - (old2 (match-string 2)) - (new (number-to-string - (+ space bang plus -1 (string-to-number old1))))) - (unless (string= new old2) (replace-match new t t nil 2))))) - ((looking-at "\\*\\*\\* \\([0-9]+\\),\\(-?[0-9]*\\) \\*\\*\\*\\*$") - (when (> (+ space bang minus) 0) - (let* ((old (match-string 1)) - (new (format - (concat "%0" (number-to-string (length old)) "d") - (+ space bang minus -1 (string-to-number old))))) - (unless (string= new old) (replace-match new t t nil 2)))))) - (setq space 0 plus 0 minus 0 bang 0))))))) - -;;;; -;;;; Hooks -;;;; - -(defun diff-write-contents-hooks () - "Fixup hunk headers if necessary." - (if (buffer-modified-p) (diff-fixup-modifs (point-min) (point-max))) - nil) - -;; It turns out that making changes in the buffer from within an -;; *-change-function is asking for trouble, whereas making them -;; from a post-command-hook doesn't pose much problems -(defvar diff-unhandled-changes nil) -(defun diff-after-change-function (beg end len) - "Remember to fixup the hunk header. -See `after-change-functions' for the meaning of BEG, END and LEN." - ;; Ignoring changes when inhibit-read-only is set is strictly speaking - ;; incorrect, but it turns out that inhibit-read-only is normally not set - ;; inside editing commands, while it tends to be set when the buffer gets - ;; updated by an async process or by a conversion function, both of which - ;; would rather not be uselessly slowed down by this hook. - (when (and (not undo-in-progress) (not inhibit-read-only)) - (if diff-unhandled-changes - (setq diff-unhandled-changes - (cons (min beg (car diff-unhandled-changes)) - (max end (cdr diff-unhandled-changes)))) - (setq diff-unhandled-changes (cons beg end))))) - -(defun diff-post-command-hook () - "Fixup hunk headers if necessary." - (when (consp diff-unhandled-changes) - (ignore-errors - (save-excursion - (goto-char (car diff-unhandled-changes)) - ;; Maybe we've cut the end of the hunk before point. - (if (and (bolp) (not (bobp))) (backward-char 1)) - ;; We used to fixup modifs on all the changes, but it turns out that - ;; it's safer not to do it on big changes, e.g. when yanking a big - ;; diff, or when the user edits the header, since we might then - ;; screw up perfectly correct values. --Stef - (diff-beginning-of-hunk) - (let* ((style (if (looking-at "\\*\\*\\*") 'context)) - (start (line-beginning-position (if (eq style 'context) 3 2))) - (mid (if (eq style 'context) - (save-excursion - (re-search-forward diff-context-mid-hunk-header-re - nil t))))) - (when (and ;; Don't try to fixup changes in the hunk header. - (> (car diff-unhandled-changes) start) - ;; Don't try to fixup changes in the mid-hunk header either. - (or (not mid) - (< (cdr diff-unhandled-changes) (match-beginning 0)) - (> (car diff-unhandled-changes) (match-end 0))) - (save-excursion - (diff-end-of-hunk nil 'donttrustheader) - ;; Don't try to fixup changes past the end of the hunk. - (>= (point) (cdr diff-unhandled-changes)))) - (diff-fixup-modifs (point) (cdr diff-unhandled-changes))))) - (setq diff-unhandled-changes nil)))) - -(defun diff-next-error (arg reset) - ;; Select a window that displays the current buffer so that point - ;; movements are reflected in that window. Otherwise, the user might - ;; never see the hunk corresponding to the source she's jumping to. - (pop-to-buffer (current-buffer)) - (if reset (goto-char (point-min))) - (diff-hunk-next arg) - (diff-goto-source)) - -(defvar whitespace-style) -(defvar whitespace-trailing-regexp) - -;;;###autoload -(define-derived-mode diff-mode fundamental-mode "Diff" - "Major mode for viewing/editing context diffs. -Supports unified and context diffs as well as (to a lesser extent) -normal diffs. - -When the buffer is read-only, the ESC prefix is not necessary. -If you edit the buffer manually, diff-mode will try to update the hunk -headers for you on-the-fly. - -You can also switch between context diff and unified diff with \\[diff-context->unified], -or vice versa with \\[diff-unified->context] and you can also reverse the direction of -a diff with \\[diff-reverse-direction]. - - \\{diff-mode-map}" - - (set (make-local-variable 'font-lock-defaults) diff-font-lock-defaults) - (set (make-local-variable 'outline-regexp) diff-outline-regexp) - (set (make-local-variable 'imenu-generic-expression) - diff-imenu-generic-expression) - ;; These are not perfect. They would be better done separately for - ;; context diffs and unidiffs. - ;; (set (make-local-variable 'paragraph-start) - ;; (concat "@@ " ; unidiff hunk - ;; "\\|\\*\\*\\* " ; context diff hunk or file start - ;; "\\|--- [^\t]+\t")) ; context or unidiff file - ;; ; start (first or second line) - ;; (set (make-local-variable 'paragraph-separate) paragraph-start) - ;; (set (make-local-variable 'page-delimiter) "--- [^\t]+\t") - ;; compile support - (set (make-local-variable 'next-error-function) 'diff-next-error) - - (set (make-local-variable 'beginning-of-defun-function) - 'diff-beginning-of-file-and-junk) - (set (make-local-variable 'end-of-defun-function) - 'diff-end-of-file) - - ;; Set up `whitespace-mode' so that turning it on will show trailing - ;; whitespace problems on the modified lines of the diff. - (set (make-local-variable 'whitespace-style) '(trailing)) - (set (make-local-variable 'whitespace-trailing-regexp) - "^[-\+!<>].*?\\([\t ]+\\)$") - - (setq buffer-read-only diff-default-read-only) - ;; setup change hooks - (if (not diff-update-on-the-fly) - (add-hook 'write-contents-functions 'diff-write-contents-hooks nil t) - (make-local-variable 'diff-unhandled-changes) - (add-hook 'after-change-functions 'diff-after-change-function nil t) - (add-hook 'post-command-hook 'diff-post-command-hook nil t)) - ;; Neat trick from Dave Love to add more bindings in read-only mode: - (lexical-let ((ro-bind (cons 'buffer-read-only diff-mode-shared-map))) - (add-to-list 'minor-mode-overriding-map-alist ro-bind) - ;; Turn off this little trick in case the buffer is put in view-mode. - (add-hook 'view-mode-hook - (lambda () - (setq minor-mode-overriding-map-alist - (delq ro-bind minor-mode-overriding-map-alist))) - nil t)) - ;; add-log support - (set (make-local-variable 'add-log-current-defun-function) - 'diff-current-defun) - (set (make-local-variable 'add-log-buffer-file-name-function) - (lambda () (diff-find-file-name nil 'noprompt))) - (unless (buffer-file-name) - (hack-dir-local-variables-non-file-buffer))) - -;;;###autoload -(define-minor-mode diff-minor-mode - "Minor mode for viewing/editing context diffs. -\\{diff-minor-mode-map}" - :group 'diff-mode :lighter " Diff" - ;; FIXME: setup font-lock - ;; setup change hooks - (if (not diff-update-on-the-fly) - (add-hook 'write-contents-functions 'diff-write-contents-hooks nil t) - (make-local-variable 'diff-unhandled-changes) - (add-hook 'after-change-functions 'diff-after-change-function nil t) - (add-hook 'post-command-hook 'diff-post-command-hook nil t))) - -;;; Handy hook functions ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; - -(defun diff-delete-if-empty () - ;; An empty diff file means there's no more diffs to integrate, so we - ;; can just remove the file altogether. Very handy for .rej files if we - ;; remove hunks as we apply them. - (when (and buffer-file-name - (eq 0 (nth 7 (file-attributes buffer-file-name)))) - (delete-file buffer-file-name))) - -(defun diff-delete-empty-files () - "Arrange for empty diff files to be removed." - (add-hook 'after-save-hook 'diff-delete-if-empty nil t)) - -(defun diff-make-unified () - "Turn context diffs into unified diffs if applicable." - (if (save-excursion - (goto-char (point-min)) - (and (looking-at diff-hunk-header-re) (eq (char-after) ?*))) - (let ((mod (buffer-modified-p))) - (unwind-protect - (diff-context->unified (point-min) (point-max)) - (restore-buffer-modified-p mod))))) - -;;; -;;; Misc operations that have proved useful at some point. -;;; - -(defun diff-next-complex-hunk () - "Jump to the next \"complex\" hunk. -\"Complex\" is approximated by \"the hunk changes the number of lines\". -Only works for unified diffs." - (interactive) - (while - (and (re-search-forward diff-hunk-header-re-unified nil t) - (equal (match-string 2) (match-string 4))))) - -(defun diff-sanity-check-context-hunk-half (lines) - (let ((count lines)) - (while - (cond - ((and (memq (char-after) '(?\s ?! ?+ ?-)) - (memq (char-after (1+ (point))) '(?\s ?\t))) - (decf count) t) - ((or (zerop count) (= count lines)) nil) - ((memq (char-after) '(?! ?+ ?-)) - (if (not (and (eq (char-after (1+ (point))) ?\n) - (y-or-n-p "Try to auto-fix whitespace loss damage? "))) - (error "End of hunk ambiguously marked") - (forward-char 1) (insert " ") (forward-line -1) t)) - ((< lines 0) - (error "End of hunk ambiguously marked")) - ((not (y-or-n-p "Try to auto-fix whitespace loss and word-wrap damage? ")) - (error "Abort!")) - ((eolp) (insert " ") (forward-line -1) t) - (t (insert " ") (delete-region (- (point) 2) (- (point) 1)) t)) - (forward-line)))) - -(defun diff-sanity-check-hunk () - (let (;; Every modification is protected by a y-or-n-p, so it's probably - ;; OK to override a read-only setting. - (inhibit-read-only t)) - (save-excursion - (cond - ((not (looking-at diff-hunk-header-re)) - (error "Not recognizable hunk header")) - - ;; A context diff. - ((eq (char-after) ?*) - (if (not (looking-at "\\*\\{15\\}\\(?: .*\\)?\n\\*\\*\\* \\([0-9]+\\)\\(?:,\\([0-9]+\\)\\)? \\*\\*\\*\\*")) - (error "Unrecognized context diff first hunk header format") - (forward-line 2) - (diff-sanity-check-context-hunk-half - (if (match-end 2) - (1+ (- (string-to-number (match-string 2)) - (string-to-number (match-string 1)))) - 1)) - (if (not (looking-at diff-context-mid-hunk-header-re)) - (error "Unrecognized context diff second hunk header format") - (forward-line) - (diff-sanity-check-context-hunk-half - (if (match-end 2) - (1+ (- (string-to-number (match-string 2)) - (string-to-number (match-string 1)))) - 1))))) - - ;; A unified diff. - ((eq (char-after) ?@) - (if (not (looking-at diff-hunk-header-re-unified)) - (error "Unrecognized unified diff hunk header format") - (let ((before (string-to-number (or (match-string 2) "1"))) - (after (string-to-number (or (match-string 4) "1")))) - (forward-line) - (while - (case (char-after) - (?\s (decf before) (decf after) t) - (?- - (if (and (looking-at diff-file-header-re) - (zerop before) (zerop after)) - ;; No need to query: this is a case where two patches - ;; are concatenated and only counting the lines will - ;; give the right result. Let's just add an empty - ;; line so that our code which doesn't count lines - ;; will not get confused. - (progn (save-excursion (insert "\n")) nil) - (decf before) t)) - (?+ (decf after) t) - (t - (cond - ((and diff-valid-unified-empty-line - ;; Not just (eolp) so we don't infloop at eob. - (eq (char-after) ?\n) - (> before 0) (> after 0)) - (decf before) (decf after) t) - ((and (zerop before) (zerop after)) nil) - ((or (< before 0) (< after 0)) - (error (if (or (zerop before) (zerop after)) - "End of hunk ambiguously marked" - "Hunk seriously messed up"))) - ((not (y-or-n-p (concat "Try to auto-fix " (if (eolp) "whitespace loss" "word-wrap damage") "? "))) - (error "Abort!")) - ((eolp) (insert " ") (forward-line -1) t) - (t (insert " ") - (delete-region (- (point) 2) (- (point) 1)) t)))) - (forward-line))))) - - ;; A plain diff. - (t - ;; TODO. - ))))) - -(defun diff-hunk-text (hunk destp char-offset) - "Return the literal source text from HUNK as (TEXT . OFFSET). -If DESTP is nil, TEXT is the source, otherwise the destination text. -CHAR-OFFSET is a char-offset in HUNK, and OFFSET is the corresponding -char-offset in TEXT." - (with-temp-buffer - (insert hunk) - (goto-char (point-min)) - (let ((src-pos nil) - (dst-pos nil) - (divider-pos nil) - (num-pfx-chars 2)) - ;; Set the following variables: - ;; SRC-POS buffer pos of the source part of the hunk or nil if none - ;; DST-POS buffer pos of the destination part of the hunk or nil - ;; DIVIDER-POS buffer pos of any divider line separating the src & dst - ;; NUM-PFX-CHARS number of line-prefix characters used by this format" - (cond ((looking-at "^@@") - ;; unified diff - (setq num-pfx-chars 1) - (forward-line 1) - (setq src-pos (point) dst-pos (point))) - ((looking-at "^\\*\\*") - ;; context diff - (forward-line 2) - (setq src-pos (point)) - (re-search-forward diff-context-mid-hunk-header-re nil t) - (forward-line 0) - (setq divider-pos (point)) - (forward-line 1) - (setq dst-pos (point))) - ((looking-at "^[0-9]+a[0-9,]+$") - ;; normal diff, insert - (forward-line 1) - (setq dst-pos (point))) - ((looking-at "^[0-9,]+d[0-9]+$") - ;; normal diff, delete - (forward-line 1) - (setq src-pos (point))) - ((looking-at "^[0-9,]+c[0-9,]+$") - ;; normal diff, change - (forward-line 1) - (setq src-pos (point)) - (re-search-forward "^---$" nil t) - (forward-line 0) - (setq divider-pos (point)) - (forward-line 1) - (setq dst-pos (point))) - (t - (error "Unknown diff hunk type"))) - - (if (if destp (null dst-pos) (null src-pos)) - ;; Implied empty text - (if char-offset '("" . 0) "") - - ;; For context diffs, either side can be empty, (if there's only - ;; added or only removed text). We should then use the other side. - (cond ((equal src-pos divider-pos) (setq src-pos dst-pos)) - ((equal dst-pos (point-max)) (setq dst-pos src-pos))) - - (when char-offset (goto-char (+ (point-min) char-offset))) - - ;; Get rid of anything except the desired text. - (save-excursion - ;; Delete unused text region - (let ((keep (if destp dst-pos src-pos))) - (when (and divider-pos (> divider-pos keep)) - (delete-region divider-pos (point-max))) - (delete-region (point-min) keep)) - ;; Remove line-prefix characters, and unneeded lines (unified diffs). - (let ((kill-char (if destp ?- ?+))) - (goto-char (point-min)) - (while (not (eobp)) - (if (eq (char-after) kill-char) - (delete-region (point) (progn (forward-line 1) (point))) - (delete-char num-pfx-chars) - (forward-line 1))))) - - (let ((text (buffer-substring-no-properties (point-min) (point-max)))) - (if char-offset (cons text (- (point) (point-min))) text)))))) - - -(defun diff-find-text (text) - "Return the buffer position (BEG . END) of the nearest occurrence of TEXT. -If TEXT isn't found, nil is returned." - (let* ((orig (point)) - (forw (and (search-forward text nil t) - (cons (match-beginning 0) (match-end 0)))) - (back (and (goto-char (+ orig (length text))) - (search-backward text nil t) - (cons (match-beginning 0) (match-end 0))))) - ;; Choose the closest match. - (if (and forw back) - (if (> (- (car forw) orig) (- orig (car back))) back forw) - (or back forw)))) - -(defun diff-find-approx-text (text) - "Return the buffer position (BEG . END) of the nearest occurrence of TEXT. -Whitespace differences are ignored." - (let* ((orig (point)) - (re (concat "^[ \t\n ]*" - (mapconcat 'regexp-quote (split-string text) "[ \t\n ]+") - "[ \t\n ]*\n")) - (forw (and (re-search-forward re nil t) - (cons (match-beginning 0) (match-end 0)))) - (back (and (goto-char (+ orig (length text))) - (re-search-backward re nil t) - (cons (match-beginning 0) (match-end 0))))) - ;; Choose the closest match. - (if (and forw back) - (if (> (- (car forw) orig) (- orig (car back))) back forw) - (or back forw)))) - -(defsubst diff-xor (a b) (if a (if (not b) a) b)) - -(defun diff-find-source-location (&optional other-file reverse noprompt) - "Find out (BUF LINE-OFFSET POS SRC DST SWITCHED). -BUF is the buffer corresponding to the source file. -LINE-OFFSET is the offset between the expected and actual positions - of the text of the hunk or nil if the text was not found. -POS is a pair (BEG . END) indicating the position of the text in the buffer. -SRC and DST are the two variants of text as returned by `diff-hunk-text'. - SRC is the variant that was found in the buffer. -SWITCHED is non-nil if the patch is already applied. -NOPROMPT, if non-nil, means not to prompt the user." - (save-excursion - (let* ((other (diff-xor other-file diff-jump-to-old-file)) - (char-offset (- (point) (progn (diff-beginning-of-hunk 'try-harder) - (point)))) - ;; Check that the hunk is well-formed. Otherwise diff-mode and - ;; the user may disagree on what constitutes the hunk - ;; (e.g. because an empty line truncates the hunk mid-course), - ;; leading to potentially nasty surprises for the user. - ;; - ;; Suppress check when NOPROMPT is non-nil (Bug#3033). - (_ (unless noprompt (diff-sanity-check-hunk))) - (hunk (buffer-substring - (point) (save-excursion (diff-end-of-hunk) (point)))) - (old (diff-hunk-text hunk reverse char-offset)) - (new (diff-hunk-text hunk (not reverse) char-offset)) - ;; Find the location specification. - (line (if (not (looking-at "\\(?:\\*\\{15\\}.*\n\\)?[-@* ]*\\([0-9,]+\\)\\([ acd+]+\\([0-9,]+\\)\\)?")) - (error "Can't find the hunk header") - (if other (match-string 1) - (if (match-end 3) (match-string 3) - (unless (re-search-forward - diff-context-mid-hunk-header-re nil t) - (error "Can't find the hunk separator")) - (match-string 1))))) - (file (or (diff-find-file-name other noprompt) - (error "Can't find the file"))) - (buf (find-file-noselect file))) - ;; Update the user preference if he so wished. - (when (> (prefix-numeric-value other-file) 8) - (setq diff-jump-to-old-file other)) - (with-current-buffer buf - (goto-char (point-min)) (forward-line (1- (string-to-number line))) - (let* ((orig-pos (point)) - (switched nil) - ;; FIXME: Check for case where both OLD and NEW are found. - (pos (or (diff-find-text (car old)) - (progn (setq switched t) (diff-find-text (car new))) - (progn (setq switched nil) - (condition-case nil - (diff-find-approx-text (car old)) - (invalid-regexp nil))) ;Regex too big. - (progn (setq switched t) - (condition-case nil - (diff-find-approx-text (car new)) - (invalid-regexp nil))) ;Regex too big. - (progn (setq switched nil) nil)))) - (nconc - (list buf) - (if pos - (list (count-lines orig-pos (car pos)) pos) - (list nil (cons orig-pos (+ orig-pos (length (car old)))))) - (if switched (list new old t) (list old new)))))))) - - -(defun diff-hunk-status-msg (line-offset reversed dry-run) - (let ((msg (if dry-run - (if reversed "already applied" "not yet applied") - (if reversed "undone" "applied")))) - (message (cond ((null line-offset) "Hunk text not found") - ((= line-offset 0) "Hunk %s") - ((= line-offset 1) "Hunk %s at offset %d line") - (t "Hunk %s at offset %d lines")) - msg line-offset))) - -(defvar diff-apply-hunk-to-backup-file nil) - -(defun diff-apply-hunk (&optional reverse) - "Apply the current hunk to the source file and go to the next. -By default, the new source file is patched, but if the variable -`diff-jump-to-old-file' is non-nil, then the old source file is -patched instead (some commands, such as `diff-goto-source' can change -the value of this variable when given an appropriate prefix argument). - -With a prefix argument, REVERSE the hunk." - (interactive "P") - (destructuring-bind (buf line-offset pos old new &optional switched) - ;; Sometimes we'd like to have the following behavior: if REVERSE go - ;; to the new file, otherwise go to the old. But that means that by - ;; default we use the old file, which is the opposite of the default - ;; for diff-goto-source, and is thus confusing. Also when you don't - ;; know about it it's pretty surprising. - ;; TODO: make it possible to ask explicitly for this behavior. - ;; - ;; This is duplicated in diff-test-hunk. - (diff-find-source-location nil reverse) - (cond - ((null line-offset) - (error "Can't find the text to patch")) - ((with-current-buffer buf - (and buffer-file-name - (backup-file-name-p buffer-file-name) - (not diff-apply-hunk-to-backup-file) - (not (set (make-local-variable 'diff-apply-hunk-to-backup-file) - (yes-or-no-p (format "Really apply this hunk to %s? " - (file-name-nondirectory - buffer-file-name))))))) - (error "%s" - (substitute-command-keys - (format "Use %s\\[diff-apply-hunk] to apply it to the other file" - (if (not reverse) "\\[universal-argument] "))))) - ((and switched - ;; A reversed patch was detected, perhaps apply it in reverse. - (not (save-window-excursion - (pop-to-buffer buf) - (goto-char (+ (car pos) (cdr old))) - (y-or-n-p - (if reverse - "Hunk hasn't been applied yet; apply it now? " - "Hunk has already been applied; undo it? "))))) - (message "(Nothing done)")) - (t - ;; Apply the hunk - (with-current-buffer buf - (goto-char (car pos)) - (delete-region (car pos) (cdr pos)) - (insert (car new))) - ;; Display BUF in a window - (set-window-point (display-buffer buf) (+ (car pos) (cdr new))) - (diff-hunk-status-msg line-offset (diff-xor switched reverse) nil) - (when diff-advance-after-apply-hunk - (diff-hunk-next)))))) - - -(defun diff-test-hunk (&optional reverse) - "See whether it's possible to apply the current hunk. -With a prefix argument, try to REVERSE the hunk." - (interactive "P") - (destructuring-bind (buf line-offset pos src dst &optional switched) - (diff-find-source-location nil reverse) - (set-window-point (display-buffer buf) (+ (car pos) (cdr src))) - (diff-hunk-status-msg line-offset (diff-xor reverse switched) t))) - - -(defalias 'diff-mouse-goto-source 'diff-goto-source) - -(defun diff-goto-source (&optional other-file event) - "Jump to the corresponding source line. -`diff-jump-to-old-file' (or its opposite if the OTHER-FILE prefix arg -is given) determines whether to jump to the old or the new file. -If the prefix arg is bigger than 8 (for example with \\[universal-argument] \\[universal-argument]) -then `diff-jump-to-old-file' is also set, for the next invocations." - (interactive (list current-prefix-arg last-input-event)) - ;; When pointing at a removal line, we probably want to jump to - ;; the old location, and else to the new (i.e. as if reverting). - ;; This is a convenient detail when using smerge-diff. - (if event (posn-set-point (event-end event))) - (let ((rev (not (save-excursion (beginning-of-line) (looking-at "[-<]"))))) - (destructuring-bind (buf line-offset pos src dst &optional switched) - (diff-find-source-location other-file rev) - (pop-to-buffer buf) - (goto-char (+ (car pos) (cdr src))) - (diff-hunk-status-msg line-offset (diff-xor rev switched) t)))) - - -(defun diff-current-defun () - "Find the name of function at point. -For use in `add-log-current-defun-function'." - ;; Kill change-log-default-name so it gets recomputed each time, since - ;; each hunk may belong to another file which may belong to another - ;; directory and hence have a different ChangeLog file. - (kill-local-variable 'change-log-default-name) - (save-excursion - (when (looking-at diff-hunk-header-re) - (forward-line 1) - (re-search-forward "^[^ ]" nil t)) - (destructuring-bind (&optional buf line-offset pos src dst switched) - ;; Use `noprompt' since this is used in which-func-mode and such. - (ignore-errors ;Signals errors in place of prompting. - (diff-find-source-location nil nil 'noprompt)) - (when buf - (beginning-of-line) - (or (when (memq (char-after) '(?< ?-)) - ;; Cursor is pointing at removed text. This could be a removed - ;; function, in which case, going to the source buffer will - ;; not help since the function is now removed. Instead, - ;; try to figure out the function name just from the - ;; code-fragment. - (let ((old (if switched dst src))) - (with-temp-buffer - (insert (car old)) - (funcall (buffer-local-value 'major-mode buf)) - (goto-char (+ (point-min) (cdr old))) - (add-log-current-defun)))) - (with-current-buffer buf - (goto-char (+ (car pos) (cdr src))) - (add-log-current-defun))))))) - -(defun diff-ignore-whitespace-hunk () - "Re-diff the current hunk, ignoring whitespace differences." - (interactive) - (let* ((char-offset (- (point) (progn (diff-beginning-of-hunk 'try-harder) - (point)))) - (opts (case (char-after) (?@ "-bu") (?* "-bc") (t "-b"))) - (line-nb (and (or (looking-at "[^0-9]+\\([0-9]+\\)") - (error "Can't find line number")) - (string-to-number (match-string 1)))) - (inhibit-read-only t) - (hunk (delete-and-extract-region - (point) (save-excursion (diff-end-of-hunk) (point)))) - (lead (make-string (1- line-nb) ?\n)) ;Line nums start at 1. - (file1 (make-temp-file "diff1")) - (file2 (make-temp-file "diff2")) - (coding-system-for-read buffer-file-coding-system) - old new) - (unwind-protect - (save-excursion - (setq old (diff-hunk-text hunk nil char-offset)) - (setq new (diff-hunk-text hunk t char-offset)) - (write-region (concat lead (car old)) nil file1 nil 'nomessage) - (write-region (concat lead (car new)) nil file2 nil 'nomessage) - (with-temp-buffer - (let ((status - (call-process diff-command nil t nil - opts file1 file2))) - (case status - (0 nil) ;Nothing to reformat. - (1 (goto-char (point-min)) - ;; Remove the file-header. - (when (re-search-forward diff-hunk-header-re nil t) - (delete-region (point-min) (match-beginning 0)))) - (t (goto-char (point-max)) - (unless (bolp) (insert "\n")) - (insert hunk))) - (setq hunk (buffer-string)) - (unless (memq status '(0 1)) - (error "Diff returned: %s" status))))) - ;; Whatever happens, put back some equivalent text: either the new - ;; one or the original one in case some error happened. - (insert hunk) - (delete-file file1) - (delete-file file2)))) - -;;; Fine change highlighting. - -(defface diff-refine-change - '((((class color) (min-colors 88) (background light)) - :background "grey85") - (((class color) (min-colors 88) (background dark)) - :background "grey60") - (((class color) (background light)) - :background "yellow") - (((class color) (background dark)) - :background "green") - (t :weight bold)) - "Face used for char-based changes shown by `diff-refine-hunk'." - :group 'diff-mode) - -(defun diff-refine-preproc () - (while (re-search-forward "^[+>]" nil t) - ;; Remove spurious changes due to the fact that one side of the hunk is - ;; marked with leading + or > and the other with leading - or <. - ;; We used to replace all the prefix chars with " " but this only worked - ;; when we did char-based refinement (or when using - ;; smerge-refine-weight-hack) since otherwise, the `forward' motion done - ;; in chopup do not necessarily do the same as the ones in highlight - ;; since the "_" is not treated the same as " ". - (replace-match (cdr (assq (char-before) '((?+ . "-") (?> . "<")))))) - ) - -(defun diff-refine-hunk () - "Highlight changes of hunk at point at a finer granularity." - (interactive) - (eval-and-compile (require 'smerge-mode)) - (save-excursion - (diff-beginning-of-hunk 'try-harder) - (let* ((style (diff-hunk-style)) ;Skips the hunk header as well. - (beg (point)) - (props '((diff-mode . fine) (face diff-refine-change))) - (end (progn (diff-end-of-hunk) (point)))) - - (remove-overlays beg end 'diff-mode 'fine) - - (goto-char beg) - (case style - (unified - (while (re-search-forward "^\\(?:-.*\n\\)+\\(\\)\\(?:\\+.*\n\\)+" - end t) - (smerge-refine-subst (match-beginning 0) (match-end 1) - (match-end 1) (match-end 0) - props 'diff-refine-preproc))) - (context - (let* ((middle (save-excursion (re-search-forward "^---"))) - (other middle)) - (while (re-search-forward "^\\(?:!.*\n\\)+" middle t) - (smerge-refine-subst (match-beginning 0) (match-end 0) - (save-excursion - (goto-char other) - (re-search-forward "^\\(?:!.*\n\\)+" end) - (setq other (match-end 0)) - (match-beginning 0)) - other - props 'diff-refine-preproc)))) - (t ;; Normal diffs. - (let ((beg1 (1+ (point)))) - (when (re-search-forward "^---.*\n" end t) - ;; It's a combined add&remove, so there's something to do. - (smerge-refine-subst beg1 (match-beginning 0) - (match-end 0) end - props 'diff-refine-preproc)))))))) - - -(defun diff-add-change-log-entries-other-window () - "Iterate through the current diff and create ChangeLog entries. -I.e. like `add-change-log-entry-other-window' but applied to all hunks." - (interactive) - ;; XXX: Currently add-change-log-entry-other-window is only called - ;; once per hunk. Some hunks have multiple changes, it would be - ;; good to call it for each change. - (save-excursion - (goto-char (point-min)) - (let ((orig-buffer (current-buffer))) - (condition-case nil - ;; Call add-change-log-entry-other-window for each hunk in - ;; the diff buffer. - (while (progn - (diff-hunk-next) - ;; Move to where the changes are, - ;; `add-change-log-entry-other-window' works better in - ;; that case. - (re-search-forward - (concat "\n[!+-<>]" - ;; If the hunk is a context hunk with an empty first - ;; half, recognize the "--- NNN,MMM ----" line - "\\(-- [0-9]+\\(,[0-9]+\\)? ----\n" - ;; and skip to the next non-context line. - "\\( .*\n\\)*[+]\\)?") - nil t)) - (save-excursion - ;; FIXME: this pops up windows of all the buffers. - (add-change-log-entry nil nil t nil t))) - ;; When there's no more hunks, diff-hunk-next signals an error. - (error nil))))) - -;; provide the package -(provide 'diff-mode) - -;;; Old Change Log from when diff-mode wasn't part of Emacs: -;; Revision 1.11 1999/10/09 23:38:29 monnier -;; (diff-mode-load-hook): dropped. -;; (auto-mode-alist): also catch *.diffs. -;; (diff-find-file-name, diff-mode): add smarts to find the right file -;; for *.rej files (that lack any file name indication). -;; -;; Revision 1.10 1999/09/30 15:32:11 monnier -;; added support for "\ No newline at end of file". -;; -;; Revision 1.9 1999/09/15 00:01:13 monnier -;; - added basic `compile' support. -;; - have diff-kill-hunk call diff-kill-file if it's the only hunk. -;; - diff-kill-file now tries to kill the leading garbage as well. -;; -;; Revision 1.8 1999/09/13 21:10:09 monnier -;; - don't use CL in the autoloaded code -;; - accept diffs using -T -;; -;; Revision 1.7 1999/09/05 20:53:03 monnier -;; interface to ediff-patch -;; -;; Revision 1.6 1999/09/01 20:55:13 monnier -;; (ediff=patch-file): add bindings to call ediff-patch. -;; (diff-find-file-name): taken out of diff-goto-source. -;; (diff-unified->context, diff-context->unified, diff-reverse-direction, -;; diff-fixup-modifs): only use the region if a prefix arg is given. -;; -;; Revision 1.5 1999/08/31 19:18:52 monnier -;; (diff-beginning-of-file, diff-prev-file): fixed wrong parenthesis. -;; -;; Revision 1.4 1999/08/31 13:01:44 monnier -;; use `combine-after-change-calls' to minimize the slowdown of font-lock. -;; - -;; arch-tag: 2571d7ff-bc28-4cf9-8585-42e21890be66 -;;; diff-mode.el ends here diff -r 681cd08dc0f7 -r e93288477c43 lisp/diff.el --- a/lisp/diff.el Thu Jun 10 22:43:47 2010 +0000 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,205 +0,0 @@ -;;; diff.el --- run `diff' in compilation-mode - -;; Copyright (C) 1992, 1994, 1996, 2001, 2002, 2003, 2004, 2005, 2006, -;; 2007, 2008, 2009, 2010 Free Software Foundation, Inc. - -;; Author: Frank Bresz -;; (according to authors.el) -;; Maintainer: FSF -;; Keywords: unix, tools - -;; 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 3 of the License, 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. If not, see . - -;;; Commentary: - -;; This package helps you explore differences between files, using the -;; UNIX command diff(1). The commands are `diff' and `diff-backup'. -;; You can specify options with `diff-switches'. - -;;; Code: - -(defgroup diff nil - "Comparing files with `diff'." - :group 'tools) - -;;;###autoload -(defcustom diff-switches (purecopy "-c") - "A string or list of strings specifying switches to be passed to diff." - :type '(choice string (repeat string)) - :group 'diff) - -;;;###autoload -(defcustom diff-command (purecopy "diff") - "The command to use to run diff." - :type 'string - :group 'diff) - -(defvar diff-old-temp-file nil - "This is the name of a temp file to be deleted after diff finishes.") -(defvar diff-new-temp-file nil - "This is the name of a temp file to be deleted after diff finishes.") - -;; prompt if prefix arg present -(defun diff-switches () - (if current-prefix-arg - (read-string "Diff switches: " - (if (stringp diff-switches) - diff-switches - (mapconcat 'identity diff-switches " "))))) - -(defun diff-sentinel (code) - "Code run when the diff process exits. -CODE is the exit code of the process. It should be 0 only if no diffs -were found." - (if diff-old-temp-file (delete-file diff-old-temp-file)) - (if diff-new-temp-file (delete-file diff-new-temp-file)) - (save-excursion - (goto-char (point-max)) - (let ((inhibit-read-only t)) - (insert (format "\nDiff finished%s. %s\n" - (cond ((equal 0 code) " (no differences)") - ((equal 2 code) " (diff error)") - (t "")) - (current-time-string)))))) - -(defvar diff-old-file nil) -(defvar diff-new-file nil) -(defvar diff-extra-args nil) - -;;;###autoload -(defun diff (old new &optional switches no-async) - "Find and display the differences between OLD and NEW files. -When called interactively, read OLD and NEW using the minibuffer; -the default for NEW is the current buffer's file name, and the -default for OLD is a backup file for NEW, if one exists. -If NO-ASYNC is non-nil, call diff synchronously. - -When called interactively with a prefix argument, prompt -interactively for diff switches. Otherwise, the switches -specified in `diff-switches' are passed to the diff command." - (interactive - (let (oldf newf) - (setq newf (buffer-file-name) - newf (if (and newf (file-exists-p newf)) - (read-file-name - (concat "Diff new file (default " - (file-name-nondirectory newf) "): ") - nil newf t) - (read-file-name "Diff new file: " nil nil t))) - (setq oldf (file-newest-backup newf) - oldf (if (and oldf (file-exists-p oldf)) - (read-file-name - (concat "Diff original file (default " - (file-name-nondirectory oldf) "): ") - (file-name-directory oldf) oldf t) - (read-file-name "Diff original file: " - (file-name-directory newf) nil t))) - (list oldf newf (diff-switches)))) - (setq new (expand-file-name new) - old (expand-file-name old)) - (or switches (setq switches diff-switches)) ; If not specified, use default. - (let* ((old-alt (file-local-copy old)) - (new-alt (file-local-copy new)) - (command - (mapconcat 'identity - `(,diff-command - ;; Use explicitly specified switches - ,@(if (listp switches) switches (list switches)) - ,@(if (or old-alt new-alt) - (list "-L" old "-L" new)) - ,(shell-quote-argument (or old-alt old)) - ,(shell-quote-argument (or new-alt new))) - " ")) - (buf (get-buffer-create "*Diff*")) - (thisdir default-directory) - proc) - (save-excursion - (display-buffer buf) - (set-buffer buf) - (setq buffer-read-only nil) - (buffer-disable-undo (current-buffer)) - (let ((inhibit-read-only t)) - (erase-buffer)) - (buffer-enable-undo (current-buffer)) - (diff-mode) - ;; Use below 2 vars for backward-compatibility. - (set (make-local-variable 'diff-old-file) old) - (set (make-local-variable 'diff-new-file) new) - (set (make-local-variable 'diff-extra-args) (list switches no-async)) - (set (make-local-variable 'revert-buffer-function) - (lambda (ignore-auto noconfirm) - (apply 'diff diff-old-file diff-new-file diff-extra-args))) - (set (make-local-variable 'diff-old-temp-file) old-alt) - (set (make-local-variable 'diff-new-temp-file) new-alt) - (setq default-directory thisdir) - (let ((inhibit-read-only t)) - (insert command "\n")) - (if (and (not no-async) (fboundp 'start-process)) - (progn - (setq proc (start-process "Diff" buf shell-file-name - shell-command-switch command)) - (set-process-filter proc 'diff-process-filter) - (set-process-sentinel - proc (lambda (proc msg) - (with-current-buffer (process-buffer proc) - (diff-sentinel (process-exit-status proc)))))) - ;; Async processes aren't available. - (let ((inhibit-read-only t)) - (diff-sentinel - (call-process shell-file-name nil buf nil - shell-command-switch command))))) - buf)) - -(defun diff-process-filter (proc string) - (with-current-buffer (process-buffer proc) - (let ((moving (= (point) (process-mark proc)))) - (save-excursion - ;; Insert the text, advancing the process marker. - (goto-char (process-mark proc)) - (let ((inhibit-read-only t)) - (insert string)) - (set-marker (process-mark proc) (point))) - (if moving (goto-char (process-mark proc)))))) - -;;;###autoload -(defun diff-backup (file &optional switches) - "Diff this file with its backup file or vice versa. -Uses the latest backup, if there are several numerical backups. -If this file is a backup, diff it with its original. -The backup file is the first file given to `diff'. -With prefix arg, prompt for diff switches." - (interactive (list (read-file-name "Diff (file with backup): ") - (diff-switches))) - (let (bak ori) - (if (backup-file-name-p file) - (setq bak file - ori (file-name-sans-versions file)) - (setq bak (or (diff-latest-backup-file file) - (error "No backup found for %s" file)) - ori file)) - (diff bak ori switches))) - -(defun diff-latest-backup-file (fn) ; actually belongs into files.el - "Return the latest existing backup of FILE, or nil." - (let ((handler (find-file-name-handler fn 'diff-latest-backup-file))) - (if handler - (funcall handler 'diff-latest-backup-file fn) - (file-newest-backup fn)))) - -(provide 'diff) - -;; arch-tag: 7de2c29b-7ea5-4b85-9b9d-72dd860de2bd -;;; diff.el ends here diff -r 681cd08dc0f7 -r e93288477c43 lisp/ediff-diff.el --- a/lisp/ediff-diff.el Thu Jun 10 22:43:47 2010 +0000 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,1536 +0,0 @@ -;;; ediff-diff.el --- diff-related utilities - -;; Copyright (C) 1994, 1995, 1996, 1997, 1998, 1999, 2000, 2001, -;; 2002, 2003, 2004, 2005, 2006, 2007, 2008, 2009, 2010 Free Software Foundation, Inc. - -;; Author: Michael Kifer - -;; 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 3 of the License, 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. If not, see . - -;;; Commentary: - -;;; Code: - - -(provide 'ediff-diff) - -(eval-when-compile - (require 'ediff-util)) - -(require 'ediff-init) - -(defgroup ediff-diff nil - "Diff related utilities." - :prefix "ediff-" - :group 'ediff) - -(defcustom ediff-diff-program "diff" - "Program to use for generating the differential of the two files." - :type 'string - :group 'ediff-diff) -(defcustom ediff-diff3-program "diff3" - "Program to be used for three-way comparison. -Must produce output compatible with Unix's diff3 program." - :type 'string - :group 'ediff-diff) - - -;; The following functions must precede all defcustom-defined variables. - -(fset 'ediff-set-actual-diff-options '(lambda () nil)) - -(defcustom ediff-shell - (cond ((eq system-type 'emx) "cmd") ; OS/2 - ((memq system-type '(ms-dos windows-nt windows-95)) - shell-file-name) ; no standard name on MS-DOS - (t "sh")) ; UNIX - "The shell used to run diff and patch. -If user's .profile or .cshrc files are set up correctly, any shell -will do. However, some people set $prompt or other things -incorrectly, which leads to undesirable output messages. These may -cause Ediff to fail. In such a case, set `ediff-shell' to a shell that -you are not using or, better, fix your shell's startup file." - :type 'string - :group 'ediff-diff) - -(defcustom ediff-cmp-program "cmp" - "Utility to use to determine if two files are identical. -It must return code 0, if its arguments are identical files." - :type 'string - :group 'ediff-diff) - -(defcustom ediff-cmp-options nil - "Options to pass to `ediff-cmp-program'. -If GNU diff is used as `ediff-cmp-program', then the most useful options -are `-I REGEXP', to ignore changes whose lines match the REGEXP." - :type '(repeat string) - :group 'ediff-diff) - -(defun ediff-set-diff-options (symbol value) - (set symbol value) - (ediff-set-actual-diff-options)) - -(defcustom ediff-diff-options - (if (memq system-type '(ms-dos windows-nt windows-95)) "--binary" "") - "Options to pass to `ediff-diff-program'. -If Unix diff is used as `ediff-diff-program', -then a useful option is `-w', to ignore space. -Options `-c', `-u', and `-i' are not allowed. Case sensitivity can be -toggled interactively using \\[ediff-toggle-ignore-case]. - -Do not remove the default options. If you need to change this variable, add new -options after the default ones. - -This variable is not for customizing the look of the differences produced by -the command \\[ediff-show-diff-output]. Use the variable -`ediff-custom-diff-options' for that." - :set 'ediff-set-diff-options - :type 'string - :group 'ediff-diff) - -(ediff-defvar-local ediff-ignore-case nil - "*If t, skip over difference regions that differ only in letter case. -This variable can be set either in .emacs or toggled interactively. -Use `setq-default' if setting it in .emacs") - -(defcustom ediff-ignore-case-option "-i" - "Option that causes the diff program to ignore case of letters." - :type 'string - :group 'ediff-diff) - -(defcustom ediff-ignore-case-option3 "" - "Option that causes the diff3 program to ignore case of letters. -GNU diff3 doesn't have such an option." - :type 'string - :group 'ediff-diff) - -;; the actual options used in comparison -(ediff-defvar-local ediff-actual-diff-options ediff-diff-options "") - -(defcustom ediff-custom-diff-program ediff-diff-program - "Program to use for generating custom diff output for saving it in a file. -This output is not used by Ediff internally." - :type 'string - :group 'ediff-diff) -(defcustom ediff-custom-diff-options "-c" - "Options to pass to `ediff-custom-diff-program'." - :type 'string - :group 'ediff-diff) - -;;; Support for diff3 - -(defvar ediff-match-diff3-line "^====\\(.?\\)\C-m?$" - "Pattern to match lines produced by diff3 that describe differences.") -(defcustom ediff-diff3-options "" - "Options to pass to `ediff-diff3-program'." - :set 'ediff-set-diff-options - :type 'string - :group 'ediff-diff) - -;; the actual options used in comparison -(ediff-defvar-local ediff-actual-diff3-options ediff-diff3-options "") - -(defcustom ediff-diff3-ok-lines-regexp - "^\\([1-3]:\\|====\\| \\|.*Warning *:\\|.*No newline\\|.*missing newline\\|^\C-m$\\)" - "Regexp that matches normal output lines from `ediff-diff3-program'. -Lines that do not match are assumed to be error messages." - :type 'regexp - :group 'ediff-diff) - -;; keeps the status of the current diff in 3-way jobs. -;; the status can be =diff(A), =diff(B), or =diff(A+B) -(ediff-defvar-local ediff-diff-status "" "") - - -;;; Fine differences - -(ediff-defvar-local ediff-auto-refine (if (ediff-has-face-support-p) 'on 'nix) - "If `on', Ediff auto-highlights fine diffs for the current diff region. -If `off', auto-highlighting is not used. If `nix', no fine diffs are shown -at all, unless the user force-refines the region by hitting `*'. - -This variable can be set either in .emacs or toggled interactively. -Use `setq-default' if setting it in .emacs") - -(ediff-defvar-local ediff-ignore-similar-regions nil - "*If t, skip over difference regions that differ only in the white space and line breaks. -This variable can be set either in .emacs or toggled interactively. -Use `setq-default' if setting it in .emacs") - -(ediff-defvar-local ediff-auto-refine-limit 14000 - "*Auto-refine only the regions of this size \(in bytes\) or less.") - -;;; General - -(defvar ediff-diff-ok-lines-regexp - (concat - "^\\(" - "[0-9,]+[acd][0-9,]+\C-m?$" - "\\|[<>] " - "\\|---" - "\\|.*Warning *:" - "\\|.*No +newline" - "\\|.*missing +newline" - "\\|^\C-m?$" - "\\)") - "Regexp that matches normal output lines from `ediff-diff-program'. -This is mostly lifted from Emerge, except that Ediff also considers -warnings and `Missing newline'-type messages to be normal output. -Lines that do not match are assumed to be error messages.") - -(defvar ediff-match-diff-line - (let ((x "\\([0-9]+\\)\\(\\|,\\([0-9]+\\)\\)")) - (concat "^" x "\\([acd]\\)" x "\C-m?$")) - "Pattern to match lines produced by diff that describe differences.") - -(ediff-defvar-local ediff-setup-diff-regions-function nil - "value is a function symbol depending on the kind of job is to be done. -For 2-way jobs and for ediff-merge, it should be `ediff-setup-diff-regions'. -For jobs requiring diff3, it should be `ediff-setup-diff-regions3'. - -The function should take three mandatory arguments, file-A, file-B, and -file-C. It may ignore file C for diff2 jobs. It should also take -one optional arguments, diff-number to refine.") - - -;;; Functions - -;; Generate the difference vector and overlays for the two files -;; With optional arg REG-TO-REFINE, refine this region. -;; File-C argument is not used here. It is there just because -;; ediff-setup-diff-regions is called via a funcall to -;; ediff-setup-diff-regions-function, which can also have the value -;; ediff-setup-diff-regions3, which takes 4 arguments. -(defun ediff-setup-diff-regions (file-A file-B file-C) - ;; looking for '-c', '-i', '-u', or 'c', 'i', 'u' among clustered non-long options - (if (string-match "^-[ciu]\\| -[ciu]\\|\\(^\\| \\)-[^- ]+[ciu]" - ediff-diff-options) - (error "Options `-c', `-u', and `-i' are not allowed in `ediff-diff-options'")) - - ;; create, if it doesn't exist - (or (ediff-buffer-live-p ediff-diff-buffer) - (setq ediff-diff-buffer - (get-buffer-create (ediff-unique-buffer-name "*ediff-diff" "*")))) - (ediff-make-diff2-buffer ediff-diff-buffer file-A file-B) - (ediff-prepare-error-list ediff-diff-ok-lines-regexp ediff-diff-buffer) - (ediff-convert-diffs-to-overlays - (ediff-extract-diffs - ediff-diff-buffer ediff-word-mode ediff-narrow-bounds))) - -;; Run the diff program on FILE1 and FILE2 and put the output in DIFF-BUFFER -;; Return the size of DIFF-BUFFER -;; The return code isn't used in the program at present. -(defun ediff-make-diff2-buffer (diff-buffer file1 file2) - (let ((file1-size (ediff-file-size file1)) - (file2-size (ediff-file-size file2))) - (cond ((not (numberp file1-size)) - (message "Can't find file: %s" - (ediff-abbreviate-file-name file1)) - (sit-for 2) - ;; 1 is an error exit code - 1) - ((not (numberp file2-size)) - (message "Can't find file: %s" - (ediff-abbreviate-file-name file2)) - (sit-for 2) - ;; 1 is an error exit code - 1) - (t (message "Computing differences between %s and %s ..." - (file-name-nondirectory file1) - (file-name-nondirectory file2)) - ;; this erases the diff buffer automatically - (ediff-exec-process ediff-diff-program - diff-buffer - 'synchronize - ediff-actual-diff-options file1 file2) - (message "") - (ediff-with-current-buffer diff-buffer - (buffer-size)))))) - - - -;; If file-A/B/C is nil, do 2-way comparison with the non-nil buffers -;; This function works for diff3 and diff2 jobs -(defun ediff-setup-fine-diff-regions (file-A file-B file-C reg-num) - (or (ediff-buffer-live-p ediff-fine-diff-buffer) - (setq ediff-fine-diff-buffer - (get-buffer-create - (ediff-unique-buffer-name "*ediff-fine-diff" "*")))) - - (let (diff3-job diff-program diff-options ok-regexp diff-list) - (setq diff3-job ediff-3way-job - diff-program (if diff3-job ediff-diff3-program ediff-diff-program) - diff-options (if diff3-job - ediff-actual-diff3-options - ediff-actual-diff-options) - ok-regexp (if diff3-job - ediff-diff3-ok-lines-regexp - ediff-diff-ok-lines-regexp)) - - (ediff-message-if-verbose "Refining difference region %d ..." (1+ reg-num)) - (ediff-exec-process diff-program ediff-fine-diff-buffer 'synchronize - diff-options - ;; The shuffle below is because we can compare 3-way - ;; or in several 2-way fashions, like fA fC, fA fB, - ;; or fB fC. - (if file-A file-A file-B) - (if file-B file-B file-A) - (if diff3-job - (if file-C file-C file-B)) - ) ; exec process - - (ediff-prepare-error-list ok-regexp ediff-fine-diff-buffer) - (ediff-message-if-verbose - "") - ;; "Refining difference region %d ... done" (1+ reg-num)) - - (setq diff-list - (if diff3-job - (ediff-extract-diffs3 - ediff-fine-diff-buffer '3way-comparison 'word-mode) - (ediff-extract-diffs ediff-fine-diff-buffer 'word-mode))) - ;; fixup diff-list - (if diff3-job - (cond ((not file-A) - (mapc (lambda (elt) - (aset elt 0 nil) - (aset elt 1 nil)) - (cdr diff-list))) - ((not file-B) - (mapc (lambda (elt) - (aset elt 2 nil) - (aset elt 3 nil)) - (cdr diff-list))) - ((not file-C) - (mapc (lambda (elt) - (aset elt 4 nil) - (aset elt 5 nil)) - (cdr diff-list))) - )) - - (ediff-convert-fine-diffs-to-overlays diff-list reg-num) - )) - - -(defun ediff-prepare-error-list (ok-regexp diff-buff) - (or (ediff-buffer-live-p ediff-error-buffer) - (setq ediff-error-buffer - (get-buffer-create (ediff-unique-buffer-name - "*ediff-errors" "*")))) - (ediff-with-current-buffer ediff-error-buffer - (setq buffer-undo-list t) - (erase-buffer) - (insert (ediff-with-current-buffer diff-buff (buffer-string))) - (goto-char (point-min)) - (delete-matching-lines ok-regexp)) - ;; If diff reports errors, show them then quit. - (if (/= 0 (ediff-with-current-buffer ediff-error-buffer (buffer-size))) - (let ((ctl-buf ediff-control-buffer) - (error-buf ediff-error-buffer)) - (ediff-skip-unsuitable-frames) - (switch-to-buffer error-buf) - (ediff-kill-buffer-carefully ctl-buf) - (error "Errors in diff output. Diff output is in %S" diff-buff)))) - -;; BOUNDS specifies visibility bounds to use. -;; WORD-MODE tells whether we are in the word-mode or not. -;; If WORD-MODE, also construct vector of diffs using word numbers. -;; Else, use point values. -;; This function handles diff-2 jobs including the case of -;; merging buffers and files without ancestor. -(defun ediff-extract-diffs (diff-buffer word-mode &optional bounds) - (let ((A-buffer ediff-buffer-A) - (B-buffer ediff-buffer-B) - (C-buffer ediff-buffer-C) - (a-prev 1) ; this is needed to set the first diff line correctly - (a-prev-pt nil) - (b-prev 1) - (b-prev-pt nil) - (c-prev 1) - (c-prev-pt nil) - diff-list shift-A shift-B - ) - - ;; diff list contains word numbers, unless changed later - (setq diff-list (cons (if word-mode 'words 'points) - diff-list)) - ;; we don't use visibility bounds for buffer C when merging - (if bounds - (setq shift-A - (ediff-overlay-start - (ediff-get-value-according-to-buffer-type 'A bounds)) - shift-B - (ediff-overlay-start - (ediff-get-value-according-to-buffer-type 'B bounds)))) - - ;; reset point in buffers A/B/C - (ediff-with-current-buffer A-buffer - (goto-char (if shift-A shift-A (point-min)))) - (ediff-with-current-buffer B-buffer - (goto-char (if shift-B shift-B (point-min)))) - (if (ediff-buffer-live-p C-buffer) - (ediff-with-current-buffer C-buffer - (goto-char (point-min)))) - - (ediff-with-current-buffer diff-buffer - (goto-char (point-min)) - (while (re-search-forward ediff-match-diff-line nil t) - (let* ((a-begin (string-to-number (buffer-substring (match-beginning 1) - (match-end 1)))) - (a-end (let ((b (match-beginning 3)) - (e (match-end 3))) - (if b - (string-to-number (buffer-substring b e)) - a-begin))) - (diff-type (buffer-substring (match-beginning 4) (match-end 4))) - (b-begin (string-to-number (buffer-substring (match-beginning 5) - (match-end 5)))) - (b-end (let ((b (match-beginning 7)) - (e (match-end 7))) - (if b - (string-to-number (buffer-substring b e)) - b-begin))) - a-begin-pt a-end-pt b-begin-pt b-end-pt - c-begin c-end c-begin-pt c-end-pt) - ;; fix the beginning and end numbers, because diff is somewhat - ;; strange about how it numbers lines - (if (string-equal diff-type "a") - (setq b-end (1+ b-end) - a-begin (1+ a-begin) - a-end a-begin) - (if (string-equal diff-type "d") - (setq a-end (1+ a-end) - b-begin (1+ b-begin) - b-end b-begin) - ;; (string-equal diff-type "c") - (setq a-end (1+ a-end) - b-end (1+ b-end)))) - - (if (eq ediff-default-variant 'default-B) - (setq c-begin b-begin - c-end b-end) - (setq c-begin a-begin - c-end a-end)) - - ;; compute main diff vector - (if word-mode - ;; make diff-list contain word numbers - (setq diff-list - (nconc diff-list - (list - (if (ediff-buffer-live-p C-buffer) - (vector (- a-begin a-prev) (- a-end a-begin) - (- b-begin b-prev) (- b-end b-begin) - (- c-begin c-prev) (- c-end c-begin) - nil nil ; dummy ancestor - nil ; state of diff - nil ; state of merge - nil ; state of ancestor - ) - (vector (- a-begin a-prev) (- a-end a-begin) - (- b-begin b-prev) (- b-end b-begin) - nil nil ; dummy buf C - nil nil ; dummy ancestor - nil ; state of diff - nil ; state of merge - nil ; state of ancestor - )) - )) - a-prev a-end - b-prev b-end - c-prev c-end) - ;; else convert lines to points - (ediff-with-current-buffer A-buffer - (let ((longlines-mode-val - (if (and (boundp 'longlines-mode) longlines-mode) 1 0))) - ;; we must disable and then restore longlines-mode - (if (eq longlines-mode-val 1) - (longlines-mode 0)) - (goto-char (or a-prev-pt shift-A (point-min))) - (forward-line (- a-begin a-prev)) - (setq a-begin-pt (point)) - (forward-line (- a-end a-begin)) - (setq a-end-pt (point) - a-prev a-end - a-prev-pt a-end-pt) - (if (eq longlines-mode-val 1) - (longlines-mode longlines-mode-val)) - )) - (ediff-with-current-buffer B-buffer - (let ((longlines-mode-val - (if (and (boundp 'longlines-mode) longlines-mode) 1 0))) - (if (eq longlines-mode-val 1) - (longlines-mode 0)) - (goto-char (or b-prev-pt shift-B (point-min))) - (forward-line (- b-begin b-prev)) - (setq b-begin-pt (point)) - (forward-line (- b-end b-begin)) - (setq b-end-pt (point) - b-prev b-end - b-prev-pt b-end-pt) - (if (eq longlines-mode-val 1) - (longlines-mode longlines-mode-val)) - )) - (if (ediff-buffer-live-p C-buffer) - (ediff-with-current-buffer C-buffer - (let ((longlines-mode-val - (if (and (boundp 'longlines-mode) longlines-mode) 1 0))) - (if (eq longlines-mode-val 1) - (longlines-mode 0)) - (goto-char (or c-prev-pt (point-min))) - (forward-line (- c-begin c-prev)) - (setq c-begin-pt (point)) - (forward-line (- c-end c-begin)) - (setq c-end-pt (point) - c-prev c-end - c-prev-pt c-end-pt) - (if (eq longlines-mode-val 1) - (longlines-mode longlines-mode-val)) - ))) - (setq diff-list - (nconc - diff-list - (list - (if (ediff-buffer-live-p C-buffer) - (vector - a-begin-pt a-end-pt b-begin-pt b-end-pt - c-begin-pt c-end-pt - nil nil ; dummy ancestor - ;; state of diff - ;; shows which buff is different from the other two - (if (eq ediff-default-variant 'default-B) 'A 'B) - ediff-default-variant ; state of merge - nil ; state of ancestor - ) - (vector a-begin-pt a-end-pt - b-begin-pt b-end-pt - nil nil ; dummy buf C - nil nil ; dummy ancestor - nil nil ; dummy state of diff & merge - nil ; dummy state of ancestor - ))) - ))) - - ))) ; end ediff-with-current-buffer - diff-list - )) - - -(defun ediff-convert-diffs-to-overlays (diff-list) - (ediff-set-diff-overlays-in-one-buffer 'A diff-list) - (ediff-set-diff-overlays-in-one-buffer 'B diff-list) - (if ediff-3way-job - (ediff-set-diff-overlays-in-one-buffer 'C diff-list)) - (if ediff-merge-with-ancestor-job - (ediff-set-diff-overlays-in-one-buffer 'Ancestor diff-list)) - ;; set up vector showing the status of merge regions - (if ediff-merge-job - (setq ediff-state-of-merge - (vconcat - (mapcar (lambda (elt) - (let ((state-of-merge (aref elt 9)) - (state-of-ancestor (aref elt 10))) - (vector - ;; state of merge: prefers/default-A/B or combined - (if state-of-merge (format "%S" state-of-merge)) - ;; whether the ancestor region is empty - state-of-ancestor))) - ;; the first elt designates type of list - (cdr diff-list)) - ))) - (message "Processing difference regions ... done")) - - -(defun ediff-set-diff-overlays-in-one-buffer (buf-type diff-list) - (let* ((current-diff -1) - (buff (ediff-get-buffer buf-type)) - (ctl-buf ediff-control-buffer) - ;; ediff-extract-diffs puts the type of diff-list as the first elt - ;; of this list. The type is either 'points or 'words - (diff-list-type (car diff-list)) - (shift (ediff-overlay-start - (ediff-get-value-according-to-buffer-type - buf-type ediff-narrow-bounds))) - (limit (ediff-overlay-end - (ediff-get-value-according-to-buffer-type - buf-type ediff-narrow-bounds))) - diff-overlay-list list-element total-diffs - begin end pt-saved overlay state-of-diff) - - (setq diff-list (cdr diff-list)) ; discard diff list type - (setq total-diffs (length diff-list)) - - ;; shift, if necessary - (ediff-with-current-buffer buff (setq pt-saved shift)) - - (while diff-list - (setq current-diff (1+ current-diff) - list-element (car diff-list) - begin (aref list-element (cond ((eq buf-type 'A) 0) - ((eq buf-type 'B) 2) - ((eq buf-type 'C) 4) - (t 6))) ; Ancestor - end (aref list-element (cond ((eq buf-type 'A) 1) - ((eq buf-type 'B) 3) - ((eq buf-type 'C) 5) - (t 7))) ; Ancestor - state-of-diff (aref list-element 8) - ) - - (cond ((and (not (eq buf-type state-of-diff)) - (not (eq buf-type 'Ancestor)) - (memq state-of-diff '(A B C))) - (setq state-of-diff - (car (delq buf-type (delq state-of-diff (list 'A 'B 'C))))) - (setq state-of-diff (format "=diff(%S)" state-of-diff)) - ) - (t (setq state-of-diff nil))) - - ;; Put overlays at appropriate places in buffer - ;; convert word numbers to points, if necessary - (if (eq diff-list-type 'words) - (progn - (ediff-with-current-buffer buff (goto-char pt-saved)) - (ediff-with-current-buffer ctl-buf - (setq begin (ediff-goto-word (1+ begin) buff) - end (ediff-goto-word end buff 'end))) - (if (> end limit) (setq end limit)) - (if (> begin end) (setq begin end)) - (setq pt-saved (ediff-with-current-buffer buff (point))))) - (setq overlay (ediff-make-bullet-proof-overlay begin end buff)) - - (ediff-overlay-put overlay 'priority ediff-shadow-overlay-priority) - (ediff-overlay-put overlay 'ediff-diff-num current-diff) - (if (and (ediff-has-face-support-p) - ediff-use-faces ediff-highlight-all-diffs) - (ediff-set-overlay-face - overlay (ediff-background-face buf-type current-diff))) - - (if (= 0 (mod current-diff 10)) - (message "Buffer %S: Processing difference region %d of %d" - buf-type current-diff total-diffs)) - ;; Record all overlays for this difference. - ;; The 2-d elt, nil, is a place holder for the fine diff vector. - ;; The 3-d elt, nil, is a place holder for no-fine-diffs flag. - ;; The 4-th elt says which diff region is different from the other two - ;; (3-way jobs only). - (setq diff-overlay-list - (nconc - diff-overlay-list - (list (vector overlay nil nil state-of-diff))) - diff-list - (cdr diff-list)) - ) ; while - - (set (ediff-get-symbol-from-alist buf-type ediff-difference-vector-alist) - (vconcat diff-overlay-list)) - )) - -;; `n' is the diff region to work on. Default is ediff-current-difference. -;; if `flag' is 'noforce then make fine-diffs only if this region's fine -;; diffs have not been computed before. -;; if `flag' is 'skip then don't compute fine diffs for this region. -(defun ediff-make-fine-diffs (&optional n flag) - (or n (setq n ediff-current-difference)) - - (if (< ediff-number-of-differences 1) - (error ediff-NO-DIFFERENCES)) - - (if ediff-word-mode - (setq flag 'skip - ediff-auto-refine 'nix)) - - (or (< n 0) - (>= n ediff-number-of-differences) - ;; n is within the range - (let ((tmp-buffer (get-buffer-create ediff-tmp-buffer)) - (file-A ediff-temp-file-A) - (file-B ediff-temp-file-B) - (file-C ediff-temp-file-C) - (empty-A (ediff-empty-diff-region-p n 'A)) - (empty-B (ediff-empty-diff-region-p n 'B)) - (empty-C (ediff-empty-diff-region-p n 'C)) - (whitespace-A (ediff-whitespace-diff-region-p n 'A)) - (whitespace-B (ediff-whitespace-diff-region-p n 'B)) - (whitespace-C (ediff-whitespace-diff-region-p n 'C)) - cumulative-fine-diff-length) - - (cond ;; If one of the regions is empty (or 2 in 3way comparison) - ;; then don't refine. - ;; If the region happens to be entirely whitespace or empty then - ;; mark as such. - ((> (length (delq nil (list empty-A empty-B empty-C))) 1) - (if (and (ediff-looks-like-combined-merge n) - ediff-merge-job) - (ediff-set-fine-overlays-in-one-buffer 'C nil n)) - (if ediff-3way-comparison-job - (ediff-message-if-verbose - "Region %d is empty in all buffers but %S" - (1+ n) - (cond ((not empty-A) 'A) - ((not empty-B) 'B) - ((not empty-C) 'C))) - (ediff-message-if-verbose - "Region %d in buffer %S is empty" - (1+ n) - (cond (empty-A 'A) - (empty-B 'B) - (empty-C 'C))) - ) - ;; if all regions happen to be whitespace - (if (and whitespace-A whitespace-B whitespace-C) - ;; mark as space only - (ediff-mark-diff-as-space-only n t) - ;; if some regions are white and others don't, then mark as - ;; non-white-space-only - (ediff-mark-diff-as-space-only n nil))) - - ;; don't compute fine diffs if diff vector exists - ((and (eq flag 'noforce) (ediff-get-fine-diff-vector n 'A)) - (if (ediff-no-fine-diffs-p n) - (message - "Only white-space differences in region %d %s" - (1+ n) - (cond ((eq (ediff-no-fine-diffs-p n) 'A) - "in buffers B & C") - ((eq (ediff-no-fine-diffs-p n) 'B) - "in buffers A & C") - ((eq (ediff-no-fine-diffs-p n) 'C) - "in buffers A & B") - (t ""))))) - ;; don't compute fine diffs for this region - ((eq flag 'skip) - (or (ediff-get-fine-diff-vector n 'A) - (memq ediff-auto-refine '(off nix)) - (ediff-message-if-verbose - "Region %d exceeds the auto-refinement limit. Type `%s' to refine" - (1+ n) - (substitute-command-keys - "\\[ediff-make-or-kill-fine-diffs]") - ))) - (t - ;; recompute fine diffs - (ediff-wordify - (ediff-get-diff-posn 'A 'beg n) - (ediff-get-diff-posn 'A 'end n) - ediff-buffer-A - tmp-buffer - ediff-control-buffer) - (setq file-A - (ediff-make-temp-file tmp-buffer "fineDiffA" file-A)) - - (ediff-wordify - (ediff-get-diff-posn 'B 'beg n) - (ediff-get-diff-posn 'B 'end n) - ediff-buffer-B - tmp-buffer - ediff-control-buffer) - (setq file-B - (ediff-make-temp-file tmp-buffer "fineDiffB" file-B)) - - (if ediff-3way-job - (progn - (ediff-wordify - (ediff-get-diff-posn 'C 'beg n) - (ediff-get-diff-posn 'C 'end n) - ediff-buffer-C - tmp-buffer - ediff-control-buffer) - (setq file-C - (ediff-make-temp-file - tmp-buffer "fineDiffC" file-C)))) - - ;; save temp file names. - (setq ediff-temp-file-A file-A - ediff-temp-file-B file-B - ediff-temp-file-C file-C) - - ;; set the new vector of fine diffs, if none exists - (cond ((and ediff-3way-job whitespace-A) - (ediff-setup-fine-diff-regions nil file-B file-C n)) - ((and ediff-3way-job whitespace-B) - (ediff-setup-fine-diff-regions file-A nil file-C n)) - ((and ediff-3way-job - ;; In merge-jobs, whitespace-C is t, since - ;; ediff-empty-diff-region-p returns t in this case - whitespace-C) - (ediff-setup-fine-diff-regions file-A file-B nil n)) - (t - (ediff-setup-fine-diff-regions file-A file-B file-C n))) - - (setq cumulative-fine-diff-length - (+ (length (ediff-get-fine-diff-vector n 'A)) - (length (ediff-get-fine-diff-vector n 'B)) - ;; in merge jobs, the merge buffer is never refined - (if (and file-C (not ediff-merge-job)) - (length (ediff-get-fine-diff-vector n 'C)) - 0))) - - (cond ((or - ;; all regions are white space - (and whitespace-A whitespace-B whitespace-C) - ;; none is white space and no fine diffs detected - (and (not whitespace-A) - (not whitespace-B) - (not (and ediff-3way-job whitespace-C)) - (eq cumulative-fine-diff-length 0))) - (ediff-mark-diff-as-space-only n t) - (ediff-message-if-verbose - "Only white-space differences in region %d" (1+ n))) - ((eq cumulative-fine-diff-length 0) - (ediff-message-if-verbose - "Only white-space differences in region %d %s" - (1+ n) - (cond (whitespace-A (ediff-mark-diff-as-space-only n 'A) - "in buffers B & C") - (whitespace-B (ediff-mark-diff-as-space-only n 'B) - "in buffers A & C") - (whitespace-C (ediff-mark-diff-as-space-only n 'C) - "in buffers A & B")))) - (t - (ediff-mark-diff-as-space-only n nil))) - ) - ) ; end cond - (ediff-set-fine-diff-properties n) - ))) - -;; Interface to ediff-make-fine-diffs. Checks for auto-refine limit, etc. -(defun ediff-install-fine-diff-if-necessary (n) - (cond ((and (eq ediff-auto-refine 'on) - ediff-use-faces - (not (eq ediff-highlighting-style 'off)) - (not (eq ediff-highlighting-style 'ascii))) - (if (and - (> ediff-auto-refine-limit - (- (ediff-get-diff-posn 'A 'end n) - (ediff-get-diff-posn 'A 'beg n))) - (> ediff-auto-refine-limit - (- (ediff-get-diff-posn 'B 'end n) - (ediff-get-diff-posn 'B 'beg n)))) - (ediff-make-fine-diffs n 'noforce) - (ediff-make-fine-diffs n 'skip))) - - ;; highlight if fine diffs already exist - ((eq ediff-auto-refine 'off) - (ediff-make-fine-diffs n 'skip)))) - - -;; if fine diff vector is not set for diff N, then do nothing -(defun ediff-set-fine-diff-properties (n &optional default) - (or (not (ediff-has-face-support-p)) - (< n 0) - (>= n ediff-number-of-differences) - ;; when faces are supported, set faces and priorities of fine overlays - (progn - (ediff-set-fine-diff-properties-in-one-buffer 'A n default) - (ediff-set-fine-diff-properties-in-one-buffer 'B n default) - (if ediff-3way-job - (ediff-set-fine-diff-properties-in-one-buffer 'C n default))))) - -(defun ediff-set-fine-diff-properties-in-one-buffer (buf-type - n &optional default) - (let ((fine-diff-vector (ediff-get-fine-diff-vector n buf-type)) - (face (if default - 'default - (ediff-get-symbol-from-alist - buf-type ediff-fine-diff-face-alist) - )) - (priority (if default - 0 - (1+ (or (ediff-overlay-get - (symbol-value - (ediff-get-symbol-from-alist - buf-type - ediff-current-diff-overlay-alist)) - 'priority) - 0))))) - (mapcar (lambda (overl) - (ediff-set-overlay-face overl face) - (ediff-overlay-put overl 'priority priority)) - fine-diff-vector))) - -;; Set overlays over the regions that denote delimiters -(defun ediff-set-fine-overlays-for-combined-merge (diff-list reg-num) - (let (overlay overlay-list) - (while diff-list - (condition-case nil - (setq overlay - (ediff-make-bullet-proof-overlay - (nth 0 diff-list) (nth 1 diff-list) ediff-buffer-C)) - (error "")) - (setq overlay-list (cons overlay overlay-list)) - (if (> (length diff-list) 1) - (setq diff-list (cdr (cdr diff-list))) - (error "ediff-set-fine-overlays-for-combined-merge: corrupt list of -delimiter regions")) - ) - (setq overlay-list (reverse overlay-list)) - (ediff-set-fine-diff-vector - reg-num 'C (apply 'vector overlay-list)) - )) - - -;; Convert diff list to overlays for a given DIFF-REGION -;; in buffer of type BUF-TYPE -(defun ediff-set-fine-overlays-in-one-buffer (buf-type diff-list region-num) - (let* ((current-diff -1) - (reg-start (ediff-get-diff-posn buf-type 'beg region-num)) - (buff (ediff-get-buffer buf-type)) - (ctl-buf ediff-control-buffer) - combined-merge-diff-list - diff-overlay-list list-element - begin end overlay) - - (ediff-clear-fine-differences-in-one-buffer region-num buf-type) - (setq diff-list (cdr diff-list)) ; discard list type (words or points) - (ediff-with-current-buffer buff (goto-char reg-start)) - - ;; if it is a combined merge then set overlays in buff C specially - (if (and ediff-merge-job (eq buf-type 'C) - (setq combined-merge-diff-list - (ediff-looks-like-combined-merge region-num))) - (ediff-set-fine-overlays-for-combined-merge - combined-merge-diff-list region-num) - ;; regular fine diff - (while diff-list - (setq current-diff (1+ current-diff) - list-element (car diff-list) - begin (aref list-element (cond ((eq buf-type 'A) 0) - ((eq buf-type 'B) 2) - (t 4))) ; buf C - end (aref list-element (cond ((eq buf-type 'A) 1) - ((eq buf-type 'B) 3) - (t 5)))) ; buf C - (if (not (or begin end)) - () ; skip this diff - ;; Put overlays at appropriate places in buffers - ;; convert lines to points, if necessary - (ediff-with-current-buffer ctl-buf - (setq begin (ediff-goto-word (1+ begin) buff) - end (ediff-goto-word end buff 'end))) - (setq overlay (ediff-make-bullet-proof-overlay begin end buff)) - ;; record all overlays for this difference region - (setq diff-overlay-list (nconc diff-overlay-list (list overlay)))) - - (setq diff-list (cdr diff-list)) - ) ; while - ;; convert the list of difference information into a vector - ;; for fast access - (ediff-set-fine-diff-vector - region-num buf-type (vconcat diff-overlay-list)) - ))) - - -(defun ediff-convert-fine-diffs-to-overlays (diff-list region-num) - (ediff-set-fine-overlays-in-one-buffer 'A diff-list region-num) - (ediff-set-fine-overlays-in-one-buffer 'B diff-list region-num) - (if ediff-3way-job - (ediff-set-fine-overlays-in-one-buffer 'C diff-list region-num) - )) - - -;; Stolen from emerge.el -(defun ediff-get-diff3-group (file) - ;; This save-excursion allows ediff-get-diff3-group to be called for the - ;; various groups of lines (1, 2, 3) in any order, and for the lines to - ;; appear in any order. The reason this is necessary is that Gnu diff3 - ;; can produce the groups in the order 1, 2, 3 or 1, 3, 2. - (save-excursion - (re-search-forward - (concat "^" file ":\\([0-9]+\\)\\(,\\([0-9]+\\)\\)?\\([ac]\\)\C-m?$")) - (beginning-of-line 2) - ;; treatment depends on whether it is an "a" group or a "c" group - (if (string-equal (buffer-substring (match-beginning 4) (match-end 4)) "c") - ;; it is a "c" group - (if (match-beginning 2) - ;; it has two numbers - (list (string-to-number - (buffer-substring (match-beginning 1) (match-end 1))) - (1+ (string-to-number - (buffer-substring (match-beginning 3) (match-end 3))))) - ;; it has one number - (let ((x (string-to-number - (buffer-substring (match-beginning 1) (match-end 1))))) - (list x (1+ x)))) - ;; it is an "a" group - (let ((x (1+ (string-to-number - (buffer-substring (match-beginning 1) (match-end 1)))))) - (list x x))))) - - -;; If WORD-MODE, construct vector of diffs using word numbers. -;; Else, use point values. -;; WORD-MODE also tells if we are in the word-mode or not. -;; If THREE-WAY-COMP, then it is a 3-way comparison. Else, it is merging -;; with ancestor, in which case buffer-C contents is identical to buffer-A/B, -;; contents (unless buffer-A is narrowed) depending on ediff-default-variant's -;; value. -;; BOUNDS specifies visibility bounds to use. -(defun ediff-extract-diffs3 (diff-buffer word-mode three-way-comp - &optional bounds) - (let ((A-buffer ediff-buffer-A) - (B-buffer ediff-buffer-B) - (C-buffer ediff-buffer-C) - (anc-buffer ediff-ancestor-buffer) - (a-prev 1) ; needed to set the first diff line correctly - (a-prev-pt nil) - (b-prev 1) - (b-prev-pt nil) - (c-prev 1) - (c-prev-pt nil) - (anc-prev 1) - diff-list shift-A shift-B shift-C - ) - - ;; diff list contains word numbers or points, depending on word-mode - (setq diff-list (cons (if word-mode 'words 'points) - diff-list)) - (if bounds - (setq shift-A - (ediff-overlay-start - (ediff-get-value-according-to-buffer-type 'A bounds)) - shift-B - (ediff-overlay-start - (ediff-get-value-according-to-buffer-type 'B bounds)) - shift-C - (if three-way-comp - (ediff-overlay-start - (ediff-get-value-according-to-buffer-type 'C bounds))))) - - ;; reset point in buffers A, B, C - (ediff-with-current-buffer A-buffer - (goto-char (if shift-A shift-A (point-min)))) - (ediff-with-current-buffer B-buffer - (goto-char (if shift-B shift-B (point-min)))) - (if three-way-comp - (ediff-with-current-buffer C-buffer - (goto-char (if shift-C shift-C (point-min))))) - (if (ediff-buffer-live-p anc-buffer) - (ediff-with-current-buffer anc-buffer - (goto-char (point-min)))) - - (ediff-with-current-buffer diff-buffer - (goto-char (point-min)) - (while (re-search-forward ediff-match-diff3-line nil t) - ;; leave point after matched line - (beginning-of-line 2) - (let ((agreement (buffer-substring (match-beginning 1) (match-end 1)))) - ;; if the files A and B are the same and not 3way-comparison, - ;; ignore the difference - (if (or three-way-comp (not (string-equal agreement "3"))) - (let* ((a-begin (car (ediff-get-diff3-group "1"))) - (a-end (nth 1 (ediff-get-diff3-group "1"))) - (b-begin (car (ediff-get-diff3-group "2"))) - (b-end (nth 1 (ediff-get-diff3-group "2"))) - (c-or-anc-begin (car (ediff-get-diff3-group "3"))) - (c-or-anc-end (nth 1 (ediff-get-diff3-group "3"))) - (state-of-merge - (cond ((string-equal agreement "1") 'prefer-A) - ((string-equal agreement "2") 'prefer-B) - (t ediff-default-variant))) - (state-of-diff-merge - (if (memq state-of-merge '(default-A prefer-A)) 'B 'A)) - (state-of-diff-comparison - (cond ((string-equal agreement "1") 'A) - ((string-equal agreement "2") 'B) - ((string-equal agreement "3") 'C))) - state-of-ancestor - c-begin c-end - a-begin-pt a-end-pt - b-begin-pt b-end-pt - c-begin-pt c-end-pt - anc-begin-pt anc-end-pt) - - (setq state-of-ancestor - (= c-or-anc-begin c-or-anc-end)) - - (cond (three-way-comp - (setq c-begin c-or-anc-begin - c-end c-or-anc-end)) - ((eq ediff-default-variant 'default-B) - (setq c-begin b-begin - c-end b-end)) - (t - (setq c-begin a-begin - c-end a-end))) - - ;; compute main diff vector - (if word-mode - ;; make diff-list contain word numbers - (setq diff-list - (nconc diff-list - (list (vector - (- a-begin a-prev) (- a-end a-begin) - (- b-begin b-prev) (- b-end b-begin) - (- c-begin c-prev) (- c-end c-begin) - nil nil ; dummy ancestor - nil ; state of diff - nil ; state of merge - nil ; state of ancestor - ))) - a-prev a-end - b-prev b-end - c-prev c-end) - ;; else convert lines to points - (ediff-with-current-buffer A-buffer - (let ((longlines-mode-val - (if (and (boundp 'longlines-mode) longlines-mode) 1 0))) - ;; we must disable and then restore longlines-mode - (if (eq longlines-mode-val 1) - (longlines-mode 0)) - (goto-char (or a-prev-pt shift-A (point-min))) - (forward-line (- a-begin a-prev)) - (setq a-begin-pt (point)) - (forward-line (- a-end a-begin)) - (setq a-end-pt (point) - a-prev a-end - a-prev-pt a-end-pt) - (if (eq longlines-mode-val 1) - (longlines-mode longlines-mode-val)) - )) - (ediff-with-current-buffer B-buffer - (let ((longlines-mode-val - (if (and (boundp 'longlines-mode) longlines-mode) 1 0))) - (if (eq longlines-mode-val 1) - (longlines-mode 0)) - (goto-char (or b-prev-pt shift-B (point-min))) - (forward-line (- b-begin b-prev)) - (setq b-begin-pt (point)) - (forward-line (- b-end b-begin)) - (setq b-end-pt (point) - b-prev b-end - b-prev-pt b-end-pt) - (if (eq longlines-mode-val 1) - (longlines-mode longlines-mode-val)) - )) - (ediff-with-current-buffer C-buffer - (let ((longlines-mode-val - (if (and (boundp 'longlines-mode) longlines-mode) 1 0))) - (if (eq longlines-mode-val 1) - (longlines-mode 0)) - (goto-char (or c-prev-pt shift-C (point-min))) - (forward-line (- c-begin c-prev)) - (setq c-begin-pt (point)) - (forward-line (- c-end c-begin)) - (setq c-end-pt (point) - c-prev c-end - c-prev-pt c-end-pt) - (if (eq longlines-mode-val 1) - (longlines-mode longlines-mode-val)) - )) - (if (ediff-buffer-live-p anc-buffer) - (ediff-with-current-buffer anc-buffer - (let ((longlines-mode-val - (if (and (boundp 'longlines-mode) longlines-mode) 1 0))) - (if (eq longlines-mode-val 1) - (longlines-mode 0)) - (forward-line (- c-or-anc-begin anc-prev)) - (setq anc-begin-pt (point)) - (forward-line (- c-or-anc-end c-or-anc-begin)) - (setq anc-end-pt (point) - anc-prev c-or-anc-end) - (if (eq longlines-mode-val 1) - (longlines-mode longlines-mode-val)) - ))) - (setq diff-list - (nconc - diff-list - ;; if comparing with ancestor, then there also is a - ;; state-of-difference marker - (if three-way-comp - (list (vector - a-begin-pt a-end-pt - b-begin-pt b-end-pt - c-begin-pt c-end-pt - nil nil ; ancestor begin/end - state-of-diff-comparison - nil ; state of merge - nil ; state of ancestor - )) - (list (vector a-begin-pt a-end-pt - b-begin-pt b-end-pt - c-begin-pt c-end-pt - anc-begin-pt anc-end-pt - state-of-diff-merge - state-of-merge - state-of-ancestor - ))) - ))) - )) - - ))) ; end ediff-with-current-buffer - diff-list - )) - -;; Generate the difference vector and overlays for three files -;; File-C is either the third file to compare (in case of 3-way comparison) -;; or it is the ancestor file. -(defun ediff-setup-diff-regions3 (file-A file-B file-C) - ;; looking for '-i' or a 'i' among clustered non-long options - (if (string-match "^-i\\| -i\\|\\(^\\| \\)-[^- ]+i" ediff-diff-options) - (error "Option `-i' is not allowed in `ediff-diff3-options'")) - - (or (ediff-buffer-live-p ediff-diff-buffer) - (setq ediff-diff-buffer - (get-buffer-create (ediff-unique-buffer-name "*ediff-diff" "*")))) - - (message "Computing differences ...") - (ediff-exec-process ediff-diff3-program ediff-diff-buffer 'synchronize - ediff-actual-diff3-options file-A file-B file-C) - - (ediff-prepare-error-list ediff-diff3-ok-lines-regexp ediff-diff-buffer) - ;;(message "Computing differences ... done") - (ediff-convert-diffs-to-overlays - (ediff-extract-diffs3 - ediff-diff-buffer - ediff-word-mode ediff-3way-comparison-job ediff-narrow-bounds) - )) - - -;; Execute PROGRAM asynchronously, unless OS/2, Windows-*, or DOS, or unless -;; SYNCH is non-nil. BUFFER must be a buffer object, and must be alive. The -;; OPTIONS arg is a list of options to pass to PROGRAM. It may be a blank -;; string. All elements in FILES must be strings. We also delete nil from -;; args. -(defun ediff-exec-process (program buffer synch options &rest files) - (let ((data (match-data)) - ;; If this is a buffer job, we are diffing temporary files - ;; produced by Emacs with ediff-coding-system-for-write, so - ;; use the same encoding to read the results. - (coding-system-for-read - (if (string-match "buffer" (symbol-name ediff-job-name)) - ediff-coding-system-for-write - ediff-coding-system-for-read)) - args) - (setq args (append (split-string options) files)) - (setq args (delete "" (delq nil args))) ; delete nil and "" from arguments - ;; the --binary option, if present, should be used only for buffer jobs - ;; or for refining the differences - (or (string-match "buffer" (symbol-name ediff-job-name)) - (eq buffer ediff-fine-diff-buffer) - (setq args (delete "--binary" args))) - (unwind-protect - (let ((directory default-directory) - proc) - (with-current-buffer buffer - (erase-buffer) - (setq default-directory directory) - (if (or (memq system-type '(emx ms-dos windows-nt windows-95)) - synch) - ;; In OS/2 (emx) do it synchronously, since OS/2 doesn't let us - ;; delete files used by other processes. Thus, in ediff-buffers - ;; and similar functions, we can't delete temp files because - ;; they might be used by the asynch process that computes - ;; custom diffs. So, we have to wait till custom diff - ;; subprocess is done. - ;; Similarly for Windows-* - ;; In DOS, must synchronize because DOS doesn't have - ;; asynchronous processes. - (apply 'call-process program nil buffer nil args) - ;; On other systems, do it asynchronously. - (setq proc (get-buffer-process buffer)) - (if proc (kill-process proc)) - (setq proc - (apply 'start-process "Custom Diff" buffer program args)) - (setq mode-line-process '(":%s")) - (set-process-sentinel proc 'ediff-process-sentinel) - (set-process-filter proc 'ediff-process-filter) - ))) - (store-match-data data)))) - -;; This is shell-command-filter from simple.el in Emacs. -;; Copied here because XEmacs doesn't have it. -(defun ediff-process-filter (proc string) - ;; Do save-excursion by hand so that we can leave point numerically unchanged - ;; despite an insertion immediately after it. - (let* ((obuf (current-buffer)) - (buffer (process-buffer proc)) - opoint - (window (get-buffer-window buffer)) - (pos (window-start window))) - (unwind-protect - (progn - (set-buffer buffer) - (or (= (point) (point-max)) - (setq opoint (point))) - (goto-char (point-max)) - (insert-before-markers string)) - ;; insert-before-markers moved this marker: set it back. - (set-window-start window pos) - ;; Finish our save-excursion. - (if opoint - (goto-char opoint)) - (set-buffer obuf)))) - -;; like shell-command-sentinel but doesn't print an exit status message -;; we do this because diff always exits with status 1, if diffs are found -;; so shell-command-sentinel displays a confusing message to the user -(defun ediff-process-sentinel (process signal) - (if (and (memq (process-status process) '(exit signal)) - (buffer-name (process-buffer process))) - (progn - (with-current-buffer (process-buffer process) - (setq mode-line-process nil)) - (delete-process process)))) - - -;;; Word functions used to refine the current diff - -(defvar ediff-forward-word-function 'ediff-forward-word - "*Function to call to move to the next word. -Used for splitting difference regions into individual words.") -(make-variable-buffer-local 'ediff-forward-word-function) - -;; \240 is unicode symbol for nonbreakable whitespace -(defvar ediff-whitespace " \n\t\f\r\240" - "*Characters constituting white space. -These characters are ignored when differing regions are split into words.") -(make-variable-buffer-local 'ediff-whitespace) - -(defvar ediff-word-1 - (if (featurep 'xemacs) "a-zA-Z---_" "-[:word:]_") - "*Characters that constitute words of type 1. -More precisely, [ediff-word-1] is a regexp that matches type 1 words. -See `ediff-forward-word' for more details.") -(make-variable-buffer-local 'ediff-word-1) - -(defvar ediff-word-2 "0-9.," - "*Characters that constitute words of type 2. -More precisely, [ediff-word-2] is a regexp that matches type 2 words. -See `ediff-forward-word' for more details.") -(make-variable-buffer-local 'ediff-word-2) - -(defvar ediff-word-3 "`'?!:;\"{}[]()" - "*Characters that constitute words of type 3. -More precisely, [ediff-word-3] is a regexp that matches type 3 words. -See `ediff-forward-word' for more details.") -(make-variable-buffer-local 'ediff-word-3) - -(defvar ediff-word-4 - (concat "^" ediff-word-1 ediff-word-2 ediff-word-3 ediff-whitespace) - "*Characters that constitute words of type 4. -More precisely, [ediff-word-4] is a regexp that matches type 4 words. -See `ediff-forward-word' for more details.") -(make-variable-buffer-local 'ediff-word-4) - -;; Split region along word boundaries. Each word will be on its own line. -;; Output to buffer out-buffer. -(defun ediff-forward-word () - "Move point one word forward. -There are four types of words, each of which consists entirely of -characters in `ediff-word-1', `ediff-word-2', `ediff-word-3', or -`ediff-word-4'. Words are recognized by passing these one after another as -arguments to `skip-chars-forward'." - (or (> (+ (skip-chars-forward ediff-word-1) - (skip-syntax-forward "w")) - 0) - (> (skip-chars-forward ediff-word-2) 0) - (> (skip-chars-forward ediff-word-3) 0) - (> (skip-chars-forward ediff-word-4) 0) - )) - - -(defun ediff-wordify (beg end in-buffer out-buffer &optional control-buf) - (let ((forward-word-function - ;; eval in control buf to let user create local versions for - ;; different invocations - (if control-buf - (ediff-with-current-buffer control-buf - ediff-forward-word-function) - ediff-forward-word-function)) - inbuf-syntax-tbl sv-point diff-string) - (with-current-buffer in-buffer - (setq inbuf-syntax-tbl - (if control-buf - (ediff-with-current-buffer control-buf - ediff-syntax-table) - (syntax-table))) - (setq diff-string (buffer-substring-no-properties beg end)) - - (set-buffer out-buffer) - ;; Make sure that temp buff syntax table is the same as the original buf - ;; syntax tbl, because we use ediff-forward-word in both and - ;; ediff-forward-word depends on the syntax classes of characters. - (set-syntax-table inbuf-syntax-tbl) - (erase-buffer) - (insert diff-string) - (goto-char (point-min)) - (skip-chars-forward ediff-whitespace) - (delete-region (point-min) (point)) - - (while (not (eobp)) - (funcall forward-word-function) - (setq sv-point (point)) - (skip-chars-forward ediff-whitespace) - (delete-region sv-point (point)) - (insert "\n"))))) - -;; copy string specified as BEG END from IN-BUF to OUT-BUF -(defun ediff-copy-to-buffer (beg end in-buffer out-buffer) - (with-current-buffer out-buffer - (erase-buffer) - (insert-buffer-substring in-buffer beg end) - (goto-char (point-min)))) - - -;; goto word #n starting at current position in buffer `buf' -;; For ediff, a word is determined by ediff-forward-word-function -;; If `flag' is non-nil, goto the end of the n-th word. -(defun ediff-goto-word (n buf &optional flag) - ;; remember val ediff-forward-word-function has in ctl buf - (let ((fwd-word-fun ediff-forward-word-function) - (syntax-tbl ediff-syntax-table)) - (ediff-with-current-buffer buf - (skip-chars-forward ediff-whitespace) - (ediff-with-syntax-table syntax-tbl - (while (> n 1) - (funcall fwd-word-fun) - (skip-chars-forward ediff-whitespace) - (setq n (1- n))) - (if (and flag (> n 0)) - (funcall fwd-word-fun))) - (point)))) - -(defun ediff-same-file-contents (f1 f2) - "Return t if files F1 and F2 have identical contents." - (if (and (not (file-directory-p f1)) - (not (file-directory-p f2))) - (let ((res - (apply 'call-process ediff-cmp-program nil nil nil - (append ediff-cmp-options (list (expand-file-name f1) - (expand-file-name f2)))) - )) - (and (numberp res) (eq res 0))) - )) - - -(defun ediff-same-contents (d1 d2 &optional filter-re) - "Return t if D1 and D2 have the same content. -D1 and D2 can either be both directories or both regular files. -Symlinks and the likes are not handled. -If FILTER-RE is non-nil, recursive checking in directories -affects only files whose names match the expression." - ;; Normalize empty filter RE to nil. - (unless (> (length filter-re) 0) (setq filter-re nil)) - ;; Indicate progress - (message "Comparing '%s' and '%s' modulo '%s'" d1 d2 filter-re) - (cond - ;; D1 & D2 directories => recurse - ((and (file-directory-p d1) - (file-directory-p d2)) - (if (null ediff-recurse-to-subdirectories) - (if (y-or-n-p "Compare subdirectories recursively? ") - (setq ediff-recurse-to-subdirectories 'yes) - (setq ediff-recurse-to-subdirectories 'no))) - (if (eq ediff-recurse-to-subdirectories 'yes) - (let* ((all-entries-1 (directory-files d1 t filter-re)) - (all-entries-2 (directory-files d2 t filter-re)) - (entries-1 (ediff-delete-all-matches "^\\.\\.?$" all-entries-1)) - (entries-2 (ediff-delete-all-matches "^\\.\\.?$" all-entries-2)) - ) - - (ediff-same-file-contents-lists entries-1 entries-2 filter-re) - )) - ) ; end of the directories case - ;; D1 & D2 are both files => compare directly - ((and (file-regular-p d1) - (file-regular-p d2)) - (ediff-same-file-contents d1 d2)) - ;; Otherwise => false: unequal contents - ) - ) - -;; If lists have the same length and names of files are pairwise equal -;; (removing the directories) then compare contents pairwise. -;; True if all contents are the same; false otherwise -(defun ediff-same-file-contents-lists (entries-1 entries-2 filter-re) - ;; First, check only the names (works quickly and ensures a - ;; precondition for subsequent code) - (if (and (= (length entries-1) (length entries-2)) - (equal (mapcar 'file-name-nondirectory entries-1) - (mapcar 'file-name-nondirectory entries-2))) - ;; With name equality established, compare the entries - ;; through recursion. - (let ((continue t)) - (while (and entries-1 continue) - (if (ediff-same-contents - (car entries-1) (car entries-2) filter-re) - (setq entries-1 (cdr entries-1) - entries-2 (cdr entries-2)) - (setq continue nil)) - ) - ;; if reached the end then lists are equal - (null entries-1)) - ) - ) - - -;; ARG1 is a regexp, ARG2 is a list of full-filenames -;; Delete all entries that match the regexp -(defun ediff-delete-all-matches (regex file-list-list) - (let (result elt) - (while file-list-list - (setq elt (car file-list-list)) - (or (string-match regex (file-name-nondirectory elt)) - (setq result (cons elt result))) - (setq file-list-list (cdr file-list-list))) - (reverse result))) - - -(defun ediff-set-actual-diff-options () - (if ediff-ignore-case - (setq ediff-actual-diff-options - (concat ediff-diff-options " " ediff-ignore-case-option) - ediff-actual-diff3-options - (concat ediff-diff3-options " " ediff-ignore-case-option3)) - (setq ediff-actual-diff-options ediff-diff-options - ediff-actual-diff3-options ediff-diff3-options) - ) - (setq-default ediff-actual-diff-options ediff-actual-diff-options - ediff-actual-diff3-options ediff-actual-diff3-options) - ) - - -;; Ignore case handling - some ideas from drew.adams@@oracle.com -(defun ediff-toggle-ignore-case () - (interactive) - (ediff-barf-if-not-control-buffer) - (setq ediff-ignore-case (not ediff-ignore-case)) - (ediff-set-actual-diff-options) - (if ediff-ignore-case - (message "Ignoring regions that differ only in case") - (message "Ignoring case differences turned OFF")) - (cond (ediff-merge-job - (message "Ignoring letter case is too dangerous in merge jobs")) - ((and ediff-diff3-job (string= ediff-ignore-case-option3 "")) - (message "Ignoring letter case is not supported by this diff3 program")) - ((and (not ediff-3way-job) (string= ediff-ignore-case-option "")) - (message "Ignoring letter case is not supported by this diff program")) - (t - (sit-for 1) - (ediff-update-diffs))) - ) - - - -;; 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: - -;; arch-tag: a86d448e-58d7-4572-a1d9-fdedfa22f648 -;;; ediff-diff.el ends here diff -r 681cd08dc0f7 -r e93288477c43 lisp/ediff-help.el --- a/lisp/ediff-help.el Thu Jun 10 22:43:47 2010 +0000 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,321 +0,0 @@ -;;; ediff-help.el --- Code related to the contents of Ediff help buffers - -;; Copyright (C) 1996, 1997, 1998, 1999, 2000, 2001, 2002, 2003, -;; 2004, 2005, 2006, 2007, 2008, 2009, 2010 Free Software Foundation, Inc. - -;; Author: Michael Kifer - -;; 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 3 of the License, 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. If not, see . - -;;; Commentary: - -;;; Code: - - -;; Compiler pacifier start -(defvar ediff-multiframe) -;; end pacifier - -(require 'ediff-init) - -;; Help messages - -(defconst ediff-long-help-message-head - " Move around | Toggle features | Manipulate -=====================|===========================|=============================" - "The head of the full help message.") -(defconst ediff-long-help-message-tail - "=====================|===========================|============================= - R -show registry | = -compare regions | M -show session group - D -diff output | E -browse Ediff manual| G -send bug report - i -status info | ? -help off | z/q -suspend/quit -------------------------------------------------------------------------------- -For help on a specific command: Click Button 2 over it; or - Put the cursor over it and type RET." - "The tail of the full-help message.") - -(defconst ediff-long-help-message-compare3 - " -p,DEL -previous diff | | -vert/horiz split | xy -copy buf X's region to Y -n,SPC -next diff | h -hilighting | rx -restore buf X's old diff - j -jump to diff | @ -auto-refinement | * -refine current region - gx -goto X's point| ## -ignore whitespace | ! -update diff regions - C-l -recenter | #c -ignore case | - v/V -scroll up/dn | #f/#h -focus/hide regions | wx -save buf X - -scroll lt/rt | X -read-only in buf X | wd -save diff output - ~ -rotate buffers| m -wide display | -" - "Help message usually used for 3-way comparison. -Normally, not a user option. See `ediff-help-message' for details.") - -(defconst ediff-long-help-message-compare2 - " -p,DEL -previous diff | | -vert/horiz split |a/b -copy A/B's region to B/A -n,SPC -next diff | h -hilighting | rx -restore buf X's old diff - j -jump to diff | @ -auto-refinement | * -refine current region - gx -goto X's point| ## -ignore whitespace | ! -update diff regions - C-l -recenter | #c -ignore case | - v/V -scroll up/dn | #f/#h -focus/hide regions | wx -save buf X - -scroll lt/rt | X -read-only in buf X | wd -save diff output - ~ -swap variants | m -wide display | -" - "Help message usually used for 2-way comparison. -Normally, not a user option. See `ediff-help-message' for details.") - -(defconst ediff-long-help-message-narrow2 - " -p,DEL -previous diff | | -vert/horiz split |a/b -copy A/B's region to B/A -n,SPC -next diff | h -hilighting | rx -restore buf X's old diff - j -jump to diff | @ -auto-refinement | * -refine current region - gx -goto X's point| ## -ignore whitespace | ! -update diff regions - C-l -recenter | #c -ignore case | % -narrow/widen buffs - v/V -scroll up/dn | #f/#h -focus/hide regions | wx -save buf X - -scroll lt/rt | X -read-only in buf X | wd -save diff output - ~ -swap variants | m -wide display | -" - "Help message when comparing windows or regions line-by-line. -Normally, not a user option. See `ediff-help-message' for details.") - -(defconst ediff-long-help-message-word-mode - " -p,DEL -previous diff | | -vert/horiz split | xy -copy buf X's region to Y -n,SPC -next diff | h -hilighting | rx -restore buf X's old diff - j -jump to diff | | - gx -goto X's point| % -narrow/widen buffs | ! -recompute diffs - C-l -recenter | #c -ignore case | - v/V -scroll up/dn | #f/#h -focus/hide regions | wx -save buf X - -scroll lt/rt | X -read-only in buf X | wd -save diff output - ~ -swap variants | m -wide display | -" - "Help message when comparing windows or regions word-by-word. -Normally, not a user option. See `ediff-help-message' for details.") - -(defconst ediff-long-help-message-merge - " -p,DEL -previous diff | | -vert/horiz split | x -copy buf X's region to C -n,SPC -next diff | h -hilighting | r -restore buf C's old diff - j -jump to diff | @ -auto-refinement | * -refine current region - gx -goto X's point| ## -ignore whitespace | ! -update diff regions - C-l -recenter | #f/#h -focus/hide regions | + -combine diff regions - v/V -scroll up/dn | X -read-only in buf X | wx -save buf X - -scroll lt/rt | m -wide display | wd -save diff output - ~ -swap variants | s -shrink window C | / -show ancestor buff - | $$ -show clashes only | & -merge w/new default - | $* -skip changed regions | -" - "Help message for merge sessions. -Normally, not a user option. See `ediff-help-message' for details.") - -;; The actual long help message. -(ediff-defvar-local ediff-long-help-message "" - "Normally, not a user option. See `ediff-help-message' for details.") - -(defconst ediff-brief-message-string - " Type ? for help" - "Contents of the brief help message.") -;; The actual brief help message -(ediff-defvar-local ediff-brief-help-message "" - "Normally, not a user option. See `ediff-help-message' for details.") - -(ediff-defvar-local ediff-brief-help-message-function nil - "The brief help message that the user can customize. -If the user sets this to a parameter-less function, Ediff will use it to -produce the brief help message. This function must return a string.") -(ediff-defvar-local ediff-long-help-message-function nil - "The long help message that the user can customize. -See `ediff-brief-help-message-function' for more.") - -(defcustom ediff-use-long-help-message nil - "If t, Ediff displays a long help message. Short help message otherwise." - :type 'boolean - :group 'ediff-window) - -;; The actual help message. -(ediff-defvar-local ediff-help-message "" - "The actual help message. -Normally, the user shouldn't touch this. However, if you want Ediff to -start up with different help messages for different jobs, you can change -the value of this variable and the variables `ediff-help-message-*' in -`ediff-startup-hook'.") - - -;; the keymap that defines clicks over the quick help regions -(defvar ediff-help-region-map (make-sparse-keymap)) - -(define-key - ediff-help-region-map - (if (featurep 'emacs) [mouse-2] [button2]) - 'ediff-help-for-quick-help) - -;; runs in the control buffer -(defun ediff-set-help-overlays () - (goto-char (point-min)) - (let (overl beg end cmd) - (while (re-search-forward " *\\([^ \t\n|]+\\||\\) +-[^|\n]+" nil 'noerror) - (setq beg (match-beginning 0) - end (match-end 0) - cmd (buffer-substring (match-beginning 1) (match-end 1))) - (setq overl (ediff-make-overlay beg end)) - (if (featurep 'emacs) - (ediff-overlay-put overl 'mouse-face 'highlight) - (ediff-overlay-put overl 'highlight t)) - (ediff-overlay-put overl 'ediff-help-info cmd)))) - - -(defun ediff-help-for-quick-help () - "Explain Ediff commands in more detail." - (interactive) - (ediff-barf-if-not-control-buffer) - (let ((pos (ediff-event-point last-command-event)) - overl cmd) - - (if (featurep 'xemacs) - (setq overl (extent-at pos (current-buffer) 'ediff-help-info) - cmd (ediff-overlay-get overl 'ediff-help-info)) - (setq cmd (car (mapcar (lambda (elt) - (overlay-get elt 'ediff-help-info)) - (overlays-at pos))))) - - (if (not (stringp cmd)) - (error "Hmm... I don't see an Ediff command around here...")) - - (ediff-documentation "Quick Help Commands") - - (let (case-fold-search) - (cond ((string= cmd "?") (re-search-forward "^`\\?'")) - ((string= cmd "G") (re-search-forward "^`G'")) - ((string= cmd "E") (re-search-forward "^`E'")) - ((string= cmd "wd") (re-search-forward "^`wd'")) - ((string= cmd "wx") (re-search-forward "^`wa'")) - ((string= cmd "a/b") (re-search-forward "^`a'")) - ((string= cmd "x") (re-search-forward "^`a'")) - ((string= cmd "xy") (re-search-forward "^`ab'")) - ((string= cmd "p,DEL") (re-search-forward "^`p'")) - ((string= cmd "n,SPC") (re-search-forward "^`n'")) - ((string= cmd "j") (re-search-forward "^`j'")) - ((string= cmd "gx") (re-search-forward "^`ga'")) - ((string= cmd "!") (re-search-forward "^`!'")) - ((string= cmd "*") (re-search-forward "^`\\*'")) - ((string= cmd "m") (re-search-forward "^`m'")) - ((string= cmd "|") (re-search-forward "^`|'")) - ((string= cmd "@") (re-search-forward "^`@'")) - ((string= cmd "h") (re-search-forward "^`h'")) - ((string= cmd "r") (re-search-forward "^`r'")) - ((string= cmd "rx") (re-search-forward "^`ra'")) - ((string= cmd "##") (re-search-forward "^`##'")) - ((string= cmd "#c") (re-search-forward "^`#c'")) - ((string= cmd "#f/#h") (re-search-forward "^`#f'")) - ((string= cmd "X") (re-search-forward "^`A'")) - ((string= cmd "v/V") (re-search-forward "^`v'")) - ((string= cmd "") (re-search-forward "^`<'")) - ((string= cmd "~") (re-search-forward "^`~'")) - ((string= cmd "i") (re-search-forward "^`i'")) - ((string= cmd "D") (re-search-forward "^`D'")) - ((string= cmd "R") (re-search-forward "^`R'")) - ((string= cmd "M") (re-search-forward "^`M'")) - ((string= cmd "z/q") (re-search-forward "^`z'")) - ((string= cmd "%") (re-search-forward "^`%'")) - ((string= cmd "C-l") (re-search-forward "^`C-l'")) - ((string= cmd "$$") (re-search-forward "^`\\$\\$'")) - ((string= cmd "$*") (re-search-forward "^`\\$\\*'")) - ((string= cmd "/") (re-search-forward "^`/'")) - ((string= cmd "&") (re-search-forward "^`&'")) - ((string= cmd "s") (re-search-forward "^`s'")) - ((string= cmd "+") (re-search-forward "^`\\+'")) - ((string= cmd "=") (re-search-forward "^`='")) - (t (error "Undocumented command! Type `G' in Ediff Control Panel to drop a note to the Ediff maintainer"))) - ) ; let case-fold-search - )) - - -;; assuming we are in control window, calculate length of the first line in -;; help message -(defun ediff-help-message-line-length () - (save-excursion - (goto-char (point-min)) - (if ediff-use-long-help-message - (forward-line 1)) - (end-of-line) - (current-column))) - - -(defun ediff-indent-help-message () - (let* ((shift (/ (max 0 (- (window-width (selected-window)) - (ediff-help-message-line-length))) - 2)) - (str (make-string shift ?\ ))) - (save-excursion - (goto-char (point-min)) - (while (< (point) (point-max)) - (insert str) - (beginning-of-line) - (forward-line 1))))) - - -;; compose the help message as a string -(defun ediff-set-help-message () - (setq ediff-long-help-message - (cond ((and ediff-long-help-message-function - (or (symbolp ediff-long-help-message-function) - (consp ediff-long-help-message-function))) - (funcall ediff-long-help-message-function)) - (ediff-word-mode - (concat ediff-long-help-message-head - ediff-long-help-message-word-mode - ediff-long-help-message-tail)) - (ediff-narrow-job - (concat ediff-long-help-message-head - ediff-long-help-message-narrow2 - ediff-long-help-message-tail)) - (ediff-merge-job - (concat ediff-long-help-message-head - ediff-long-help-message-merge - ediff-long-help-message-tail)) - (ediff-diff3-job - (concat ediff-long-help-message-head - ediff-long-help-message-compare3 - ediff-long-help-message-tail)) - (t - (concat ediff-long-help-message-head - ediff-long-help-message-compare2 - ediff-long-help-message-tail)))) - (setq ediff-brief-help-message - (cond ((and ediff-brief-help-message-function - (or (symbolp ediff-brief-help-message-function) - (consp ediff-brief-help-message-function))) - (funcall ediff-brief-help-message-function)) - ((stringp ediff-brief-help-message-function) - ediff-brief-help-message-function) - ((ediff-multiframe-setup-p) ediff-brief-message-string) - (t ; long brief msg, not multiframe --- put in the middle - ediff-brief-message-string) - )) - (setq ediff-help-message (if ediff-use-long-help-message - ediff-long-help-message - ediff-brief-help-message)) - (run-hooks 'ediff-display-help-hook)) - -;;;###autoload -(defun ediff-customize () - (interactive) - (customize-group "ediff")) - - -(provide 'ediff-help) - - -;; arch-tag: 05659813-7fcf-4274-964f-d2f577431a9d -;;; ediff-help.el ends here diff -r 681cd08dc0f7 -r e93288477c43 lisp/ediff-hook.el --- a/lisp/ediff-hook.el Thu Jun 10 22:43:47 2010 +0000 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,263 +0,0 @@ -;;; ediff-hook.el --- setup for Ediff's menus and autoloads - -;; Copyright (C) 1995, 1996, 1997, 1998, 1999, 2000, 2001, 2002, 2003, -;; 2004, 2005, 2006, 2007, 2008, 2009, 2010 Free Software Foundation, Inc. - -;; Author: Michael Kifer - -;; 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 3 of the License, 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. If not, see . - -;;; Commentary: - -;;; Code: - -;;; These must be placed in menu-bar.el in Emacs -;; -;; (define-key menu-bar-tools-menu [ediff-misc] -;; '("Ediff Miscellanea" . menu-bar-ediff-misc-menu)) -;; (define-key menu-bar-tools-menu [epatch] -;; '("Apply Patch" . menu-bar-epatch-menu)) -;; (define-key menu-bar-tools-menu [ediff-merge] -;; '("Merge" . menu-bar-ediff-merge-menu)) -;; (define-key menu-bar-tools-menu [ediff] -;; '("Compare" . menu-bar-ediff-menu)) - -;; Compiler pacifier -(defvar ediff-menu) -(defvar ediff-merge-menu) -(defvar epatch-menu) -(defvar ediff-misc-menu) -;; end pacifier - -;; allow menus to be set up without ediff-wind.el being loaded -(defvar ediff-window-setup-function) - -;; This autoload is useless in Emacs because ediff-hook.el is dumped with -;; emacs, but it is needed in XEmacs -;;;###autoload -(if (featurep 'xemacs) - (progn - (defun ediff-xemacs-init-menus () - (when (featurep 'menubar) - (add-submenu - '("Tools") ediff-menu "OO-Browser...") - (add-submenu - '("Tools") ediff-merge-menu "OO-Browser...") - (add-submenu - '("Tools") epatch-menu "OO-Browser...") - (add-submenu - '("Tools") ediff-misc-menu "OO-Browser...") - (add-menu-button - '("Tools") "-------" "OO-Browser...") - )) - (defvar ediff-menu - '("Compare" - ["Two Files..." ediff-files t] - ["Two Buffers..." ediff-buffers t] - ["Three Files..." ediff-files3 t] - ["Three Buffers..." ediff-buffers3 t] - "---" - ["Two Directories..." ediff-directories t] - ["Three Directories..." ediff-directories3 t] - "---" - ["File with Revision..." ediff-revision t] - ["Directory Revisions..." ediff-directory-revisions t] - "---" - ["Windows Word-by-word..." ediff-windows-wordwise t] - ["Windows Line-by-line..." ediff-windows-linewise t] - "---" - ["Regions Word-by-word..." ediff-regions-wordwise t] - ["Regions Line-by-line..." ediff-regions-linewise t] - )) - (defvar ediff-merge-menu - '("Merge" - ["Files..." ediff-merge-files t] - ["Files with Ancestor..." ediff-merge-files-with-ancestor t] - ["Buffers..." ediff-merge-buffers t] - ["Buffers with Ancestor..." - ediff-merge-buffers-with-ancestor t] - "---" - ["Directories..." ediff-merge-directories t] - ["Directories with Ancestor..." - ediff-merge-directories-with-ancestor t] - "---" - ["Revisions..." ediff-merge-revisions t] - ["Revisions with Ancestor..." - ediff-merge-revisions-with-ancestor t] - ["Directory Revisions..." ediff-merge-directory-revisions t] - ["Directory Revisions with Ancestor..." - ediff-merge-directory-revisions-with-ancestor t] - )) - (defvar epatch-menu - '("Apply Patch" - ["To a file..." ediff-patch-file t] - ["To a buffer..." ediff-patch-buffer t] - )) - (defvar ediff-misc-menu - '("Ediff Miscellanea" - ["Ediff Manual" ediff-documentation t] - ["Customize Ediff" ediff-customize t] - ["List Ediff Sessions" ediff-show-registry t] - ["Use separate frame for Ediff control buffer" - ediff-toggle-multiframe - :style toggle - :selected (if (and (featurep 'ediff-util) - (boundp 'ediff-window-setup-function)) - (eq ediff-window-setup-function - 'ediff-setup-windows-multiframe))] - ["Use a toolbar with Ediff control buffer" - ediff-toggle-use-toolbar - :style toggle - :selected (if (featurep 'ediff-tbar) - (ediff-use-toolbar-p))])) - - ;; put these menus before Object-Oriented-Browser in Tools menu - (if (and (featurep 'menubar) (not (featurep 'infodock)) - (not (featurep 'ediff-hook))) - (ediff-xemacs-init-menus))) - ;; Emacs - ;; initialize menu bar keymaps - (defvar menu-bar-ediff-misc-menu - (make-sparse-keymap "Ediff Miscellanea")) - (fset 'menu-bar-ediff-misc-menu - (symbol-value 'menu-bar-ediff-misc-menu)) - (defvar menu-bar-epatch-menu (make-sparse-keymap "Apply Patch")) - (fset 'menu-bar-epatch-menu (symbol-value 'menu-bar-epatch-menu)) - (defvar menu-bar-ediff-merge-menu (make-sparse-keymap "Merge")) - (fset 'menu-bar-ediff-merge-menu - (symbol-value 'menu-bar-ediff-merge-menu)) - (defvar menu-bar-ediff-menu (make-sparse-keymap "Compare")) - (fset 'menu-bar-ediff-menu (symbol-value 'menu-bar-ediff-menu)) - - ;; define ediff compare menu - (define-key menu-bar-ediff-menu [ediff-misc] - `(menu-item ,(purecopy "Ediff Miscellanea") menu-bar-ediff-misc-menu)) - (define-key menu-bar-ediff-menu [separator-ediff-misc] menu-bar-separator) - (define-key menu-bar-ediff-menu [window] - `(menu-item ,(purecopy "This Window and Next Window") compare-windows - :help ,(purecopy "Compare the current window and the next window"))) - (define-key menu-bar-ediff-menu [ediff-windows-linewise] - `(menu-item ,(purecopy "Windows Line-by-line...") ediff-windows-linewise - :help ,(purecopy "Compare windows line-wise"))) - (define-key menu-bar-ediff-menu [ediff-windows-wordwise] - `(menu-item ,(purecopy "Windows Word-by-word...") ediff-windows-wordwise - :help ,(purecopy "Compare windows word-wise"))) - (define-key menu-bar-ediff-menu [separator-ediff-windows] menu-bar-separator) - (define-key menu-bar-ediff-menu [ediff-regions-linewise] - `(menu-item ,(purecopy "Regions Line-by-line...") ediff-regions-linewise - :help ,(purecopy "Compare regions line-wise"))) - (define-key menu-bar-ediff-menu [ediff-regions-wordwise] - `(menu-item ,(purecopy "Regions Word-by-word...") ediff-regions-wordwise - :help ,(purecopy "Compare regions word-wise"))) - (define-key menu-bar-ediff-menu [separator-ediff-regions] menu-bar-separator) - (define-key menu-bar-ediff-menu [ediff-dir-revision] - `(menu-item ,(purecopy "Directory Revisions...") ediff-directory-revisions - :help ,(purecopy "Compare directory files with their older versions"))) - (define-key menu-bar-ediff-menu [ediff-revision] - `(menu-item ,(purecopy "File with Revision...") ediff-revision - :help ,(purecopy "Compare file with its older versions"))) - (define-key menu-bar-ediff-menu [separator-ediff-directories] menu-bar-separator) - (define-key menu-bar-ediff-menu [ediff-directories3] - `(menu-item ,(purecopy "Three Directories...") ediff-directories3 - :help ,(purecopy "Compare files common to three directories simultaneously"))) - (define-key menu-bar-ediff-menu [ediff-directories] - `(menu-item ,(purecopy "Two Directories...") ediff-directories - :help ,(purecopy "Compare files common to two directories simultaneously"))) - (define-key menu-bar-ediff-menu [separator-ediff-files] menu-bar-separator) - (define-key menu-bar-ediff-menu [ediff-buffers3] - `(menu-item ,(purecopy "Three Buffers...") ediff-buffers3 - :help ,(purecopy "Compare three buffers simultaneously"))) - (define-key menu-bar-ediff-menu [ediff-files3] - `(menu-item ,(purecopy "Three Files...") ediff-files3 - :help ,(purecopy "Compare three files simultaneously"))) - (define-key menu-bar-ediff-menu [ediff-buffers] - `(menu-item ,(purecopy "Two Buffers...") ediff-buffers - :help ,(purecopy "Compare two buffers simultaneously"))) - (define-key menu-bar-ediff-menu [ediff-files] - `(menu-item ,(purecopy "Two Files...") ediff-files - :help ,(purecopy "Compare two files simultaneously"))) - - ;; define ediff merge menu - (define-key - menu-bar-ediff-merge-menu [ediff-merge-dir-revisions-with-ancestor] - `(menu-item ,(purecopy "Directory Revisions with Ancestor...") - ediff-merge-directory-revisions-with-ancestor - :help ,(purecopy "Merge versions of the files in the same directory by comparing the files with common ancestors"))) - (define-key - menu-bar-ediff-merge-menu [ediff-merge-dir-revisions] - `(menu-item ,(purecopy "Directory Revisions...") ediff-merge-directory-revisions - :help ,(purecopy "Merge versions of the files in the same directory (without using ancestor information)"))) - (define-key - menu-bar-ediff-merge-menu [ediff-merge-revisions-with-ancestor] - `(menu-item ,(purecopy "Revisions with Ancestor...") - ediff-merge-revisions-with-ancestor - :help ,(purecopy "Merge versions of the same file by comparing them with a common ancestor"))) - (define-key menu-bar-ediff-merge-menu [ediff-merge-revisions] - `(menu-item ,(purecopy "Revisions...") ediff-merge-revisions - :help ,(purecopy "Merge versions of the same file (without using ancestor information)"))) - (define-key menu-bar-ediff-merge-menu [separator-ediff-merge] menu-bar-separator) - (define-key - menu-bar-ediff-merge-menu [ediff-merge-directories-with-ancestor] - `(menu-item ,(purecopy "Directories with Ancestor...") - ediff-merge-directories-with-ancestor - :help ,(purecopy "Merge files common to a pair of directories by comparing the files with common ancestors"))) - (define-key menu-bar-ediff-merge-menu [ediff-merge-directories] - `(menu-item ,(purecopy "Directories...") ediff-merge-directories - :help ,(purecopy "Merge files common to a pair of directories"))) - (define-key - menu-bar-ediff-merge-menu [separator-ediff-merge-dirs] menu-bar-separator) - (define-key - menu-bar-ediff-merge-menu [ediff-merge-buffers-with-ancestor] - `(menu-item ,(purecopy "Buffers with Ancestor...") ediff-merge-buffers-with-ancestor - :help ,(purecopy "Merge buffers by comparing their contents with a common ancestor"))) - (define-key menu-bar-ediff-merge-menu [ediff-merge-buffers] - `(menu-item ,(purecopy "Buffers...") ediff-merge-buffers - :help ,(purecopy "Merge buffers (without using ancestor information)"))) - (define-key menu-bar-ediff-merge-menu [ediff-merge-files-with-ancestor] - `(menu-item ,(purecopy "Files with Ancestor...") ediff-merge-files-with-ancestor - :help ,(purecopy "Merge files by comparing them with a common ancestor"))) - (define-key menu-bar-ediff-merge-menu [ediff-merge-files] - `(menu-item ,(purecopy "Files...") ediff-merge-files - :help ,(purecopy "Merge files (without using ancestor information)"))) - - ;; define epatch menu - (define-key menu-bar-epatch-menu [ediff-patch-buffer] - `(menu-item ,(purecopy "To a Buffer...") ediff-patch-buffer - :help ,(purecopy "Apply a patch to the contents of a buffer"))) - (define-key menu-bar-epatch-menu [ediff-patch-file] - `(menu-item ,(purecopy "To a File...") ediff-patch-file - :help ,(purecopy "Apply a patch to a file"))) - - ;; define ediff miscellanea - (define-key menu-bar-ediff-misc-menu [emultiframe] - `(menu-item ,(purecopy "Use separate control buffer frame") - ediff-toggle-multiframe - :help ,(purecopy "Switch between the single-frame presentation mode and the multi-frame mode"))) - (define-key menu-bar-ediff-misc-menu [eregistry] - `(menu-item ,(purecopy "List Ediff Sessions") ediff-show-registry - :help ,(purecopy "List all active Ediff sessions; it is a convenient way to find and resume such a session"))) - (define-key menu-bar-ediff-misc-menu [ediff-cust] - `(menu-item ,(purecopy "Customize Ediff") ediff-customize - :help ,(purecopy "Change some of the parameters that govern the behavior of Ediff"))) - (define-key menu-bar-ediff-misc-menu [ediff-doc] - `(menu-item ,(purecopy "Ediff Manual") ediff-documentation - :help ,(purecopy "Bring up the Ediff manual")))) - -(provide 'ediff-hook) - - -;; arch-tag: 512f8656-8a4b-4789-af5d-5c6144498df3 -;;; ediff-hook.el ends here diff -r 681cd08dc0f7 -r e93288477c43 lisp/ediff-init.el --- a/lisp/ediff-init.el Thu Jun 10 22:43:47 2010 +0000 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,1821 +0,0 @@ -;;; ediff-init.el --- Macros, variables, and defsubsts used by Ediff - -;; Copyright (C) 1994, 1995, 1996, 1997, 1998, 1999, 2000, 2001, 2002, -;; 2003, 2004, 2005, 2006, 2007, 2008, 2009, 2010 Free Software Foundation, Inc. - -;; Author: Michael Kifer - -;; 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 3 of the License, 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. If not, see . - -;;; Commentary: - -;;; Code: - -;; Start compiler pacifier -(defvar ediff-metajob-name) -(defvar ediff-meta-buffer) -(defvar ediff-grab-mouse) -(defvar ediff-mouse-pixel-position) -(defvar ediff-mouse-pixel-threshold) -(defvar ediff-whitespace) -(defvar ediff-multiframe) -(defvar ediff-use-toolbar-p) -(defvar mswindowsx-bitmap-file-path) -;; end pacifier - -(defvar ediff-force-faces nil - "If t, Ediff will think that it is running on a display that supports faces. -This is provided as a temporary relief for users of face-capable displays -that Ediff doesn't know about.") - -;; Are we running as a window application or on a TTY? -(defsubst ediff-device-type () - (if (featurep 'xemacs) - (device-type (selected-device)) - window-system)) - -;; in XEmacs: device-type is tty on tty and stream in batch. -(defun ediff-window-display-p () - (and (ediff-device-type) (not (memq (ediff-device-type) '(tty pc stream))))) - -;; test if supports faces -(defun ediff-has-face-support-p () - (cond ((ediff-window-display-p)) - (ediff-force-faces) - ((ediff-color-display-p)) - ((featurep 'emacs) (memq (ediff-device-type) '(pc))) - ((featurep 'xemacs) (memq (ediff-device-type) '(tty pc))) - )) - -;; toolbar support for emacs hasn't been implemented in ediff -(defun ediff-has-toolbar-support-p () - (if (featurep 'xemacs) - (if (featurep 'toolbar) (console-on-window-system-p)))) - - -(defun ediff-has-gutter-support-p () - (if (featurep 'xemacs) - (if (featurep 'gutter) (console-on-window-system-p)))) - -(defun ediff-use-toolbar-p () - (and (ediff-has-toolbar-support-p) ;Can it do it ? - (boundp 'ediff-use-toolbar-p) - ediff-use-toolbar-p)) ;Does the user want it ? - -;; Defines VAR as an advertised local variable. -;; Performs a defvar, then executes `make-variable-buffer-local' on -;; the variable. Also sets the `permanent-local' property, -;; so that `kill-all-local-variables' (called by major-mode setting -;; commands) won't destroy Ediff control variables. -;; -;; Plagiarised from `emerge-defvar-local' for XEmacs. -(defmacro ediff-defvar-local (var value doc) - "Defines VAR as a local variable." - (declare (indent defun)) - `(progn - (defvar ,var ,value ,doc) - (make-variable-buffer-local ',var) - (put ',var 'permanent-local t))) - - - -;; Variables that control each Ediff session---local to the control buffer. - -;; Mode variables -;; The buffer in which the A variant is stored. -(ediff-defvar-local ediff-buffer-A nil "") -;; The buffer in which the B variant is stored. -(ediff-defvar-local ediff-buffer-B nil "") -;; The buffer in which the C variant is stored or where the merge buffer lives. -(ediff-defvar-local ediff-buffer-C nil "") -;; Ancestor buffer -(ediff-defvar-local ediff-ancestor-buffer nil "") -;; The Ediff control buffer -(ediff-defvar-local ediff-control-buffer nil "") - -(ediff-defvar-local ediff-temp-indirect-buffer nil - "If t, the buffer is a temporary indirect buffer. -It needs to be killed when we quit the session.") - - -;; Association between buff-type and ediff-buffer-* -(defconst ediff-buffer-alist - '((?A . ediff-buffer-A) - (?B . ediff-buffer-B) - (?C . ediff-buffer-C))) - -;;; Macros -(defmacro ediff-odd-p (arg) - `(eq (logand ,arg 1) 1)) - -(defmacro ediff-buffer-live-p (buf) - `(and ,buf (get-buffer ,buf) (buffer-name (get-buffer ,buf)))) - -(defmacro ediff-get-buffer (arg) - `(cond ((eq ,arg 'A) ediff-buffer-A) - ((eq ,arg 'B) ediff-buffer-B) - ((eq ,arg 'C) ediff-buffer-C) - ((eq ,arg 'Ancestor) ediff-ancestor-buffer) - )) - -(defmacro ediff-get-value-according-to-buffer-type (buf-type list) - `(cond ((eq ,buf-type 'A) (nth 0 ,list)) - ((eq ,buf-type 'B) (nth 1 ,list)) - ((eq ,buf-type 'C) (nth 2 ,list)) - )) - -(defmacro ediff-char-to-buftype (arg) - `(cond ((memq ,arg '(?a ?A)) 'A) - ((memq ,arg '(?b ?B)) 'B) - ((memq ,arg '(?c ?C)) 'C) - )) - - -;; A-list is supposed to be of the form (A . symb) (B . symb)...) -;; where the first part of any association is a buffer type and the second is -;; an appropriate symbol. Given buffer-type, this function returns the -;; symbol. This is used to avoid using `intern' -(defsubst ediff-get-symbol-from-alist (buf-type alist) - (cdr (assoc buf-type alist))) - -(defconst ediff-difference-vector-alist - '((A . ediff-difference-vector-A) - (B . ediff-difference-vector-B) - (C . ediff-difference-vector-C) - (Ancestor . ediff-difference-vector-Ancestor))) - -(defmacro ediff-get-difference (n buf-type) - `(aref - (symbol-value - (ediff-get-symbol-from-alist - ,buf-type ediff-difference-vector-alist)) - ,n)) - -;; Tell if it has been previously determined that the region has -;; no diffs other than the white space and newlines -;; The argument, N, is the diff region number used by Ediff to index the -;; diff vector. It is 1 less than the number seen by the user. -;; Returns: -;; t if the diffs are whitespace in all buffers -;; 'A (in 3-buf comparison only) if there are only whitespace -;; diffs in bufs B and C -;; 'B (in 3-buf comparison only) if there are only whitespace -;; diffs in bufs A and C -;; 'C (in 3-buf comparison only) if there are only whitespace -;; diffs in bufs A and B -;; -;; A Difference Vector has the form: -;; [diff diff diff ...] -;; where each diff has the form: -;; [overlay fine-diff-vector no-fine-diffs-flag state-of-difference] -;; fine-diff-vector is a vector [fine-diff fine-diff fine-diff ...] -;; no-fine-diffs-flag says if there are fine differences. -;; state-of-difference is A, B, C, or nil, indicating which buffer is -;; different from the other two (used only in 3-way jobs). -(defmacro ediff-no-fine-diffs-p (n) - `(aref (ediff-get-difference ,n 'A) 2)) - -(defmacro ediff-get-diff-overlay-from-diff-record (diff-rec) - `(aref ,diff-rec 0)) - -(defmacro ediff-get-diff-overlay (n buf-type) - `(ediff-get-diff-overlay-from-diff-record - (ediff-get-difference ,n ,buf-type))) - -(defmacro ediff-get-fine-diff-vector-from-diff-record (diff-rec) - `(aref ,diff-rec 1)) - -(defmacro ediff-set-fine-diff-vector (n buf-type fine-vec) - `(aset (ediff-get-difference ,n ,buf-type) 1 ,fine-vec)) - -(defmacro ediff-get-state-of-diff (n buf-type) - `(if (ediff-buffer-live-p ediff-buffer-C) - (aref (ediff-get-difference ,n ,buf-type) 3))) -(defmacro ediff-set-state-of-diff (n buf-type val) - `(aset (ediff-get-difference ,n ,buf-type) 3 ,val)) - -(defmacro ediff-get-state-of-merge (n) - `(if ediff-state-of-merge - (aref (aref ediff-state-of-merge ,n) 0))) -(defmacro ediff-set-state-of-merge (n val) - `(if ediff-state-of-merge - (aset (aref ediff-state-of-merge ,n) 0 ,val))) - -(defmacro ediff-get-state-of-ancestor (n) - `(if ediff-state-of-merge - (aref (aref ediff-state-of-merge ,n) 1))) - -;; if flag is t, puts a mark on diff region saying that -;; the differences are in white space only. If flag is nil, -;; the region is marked as essential (i.e., differences are -;; not just in the white space and newlines.) -(defmacro ediff-mark-diff-as-space-only (n flag) - `(aset (ediff-get-difference ,n 'A) 2 ,flag)) - -(defmacro ediff-get-fine-diff-vector (n buf-type) - `(ediff-get-fine-diff-vector-from-diff-record - (ediff-get-difference ,n ,buf-type))) - -;; Macro to switch to BUFFER, evaluate BODY, returns to original buffer. -;; Doesn't save the point and mark. -;; This is `with-current-buffer' with the added test for live buffers." -(defmacro ediff-with-current-buffer (buffer &rest body) - "Evaluates BODY in BUFFER." - (declare (indent 1) (debug (form body))) - `(if (ediff-buffer-live-p ,buffer) - (save-current-buffer - (set-buffer ,buffer) - ,@body) - (or (eq this-command 'ediff-quit) - (error ediff-KILLED-VITAL-BUFFER)) - )) - - -(defsubst ediff-multiframe-setup-p () - (and (ediff-window-display-p) ediff-multiframe)) - -(defmacro ediff-narrow-control-frame-p () - `(and (ediff-multiframe-setup-p) - (equal ediff-help-message ediff-brief-message-string))) - -(defmacro ediff-3way-comparison-job () - `(memq - ediff-job-name - '(ediff-files3 ediff-buffers3))) -(ediff-defvar-local ediff-3way-comparison-job nil "") - -(defmacro ediff-merge-job () - `(memq - ediff-job-name - '(ediff-merge-files - ediff-merge-buffers - ediff-merge-files-with-ancestor - ediff-merge-buffers-with-ancestor - ediff-merge-revisions - ediff-merge-revisions-with-ancestor))) -(ediff-defvar-local ediff-merge-job nil "") - -(defmacro ediff-patch-job () - `(eq ediff-job-name 'epatch)) - -(defmacro ediff-merge-with-ancestor-job () - `(memq - ediff-job-name - '(ediff-merge-files-with-ancestor - ediff-merge-buffers-with-ancestor - ediff-merge-revisions-with-ancestor))) -(ediff-defvar-local ediff-merge-with-ancestor-job nil "") - -(defmacro ediff-3way-job () - `(or ediff-3way-comparison-job ediff-merge-job)) -(ediff-defvar-local ediff-3way-job nil "") - -;; A diff3 job is like a 3way job, but ediff-merge doesn't require the use -;; of diff3. -(defmacro ediff-diff3-job () - `(or ediff-3way-comparison-job - ediff-merge-with-ancestor-job)) -(ediff-defvar-local ediff-diff3-job nil "") - -(defmacro ediff-windows-job () - `(memq ediff-job-name '(ediff-windows-wordwise ediff-windows-linewise))) -(ediff-defvar-local ediff-windows-job nil "") - -(defmacro ediff-word-mode-job () - `(memq ediff-job-name '(ediff-windows-wordwise ediff-regions-wordwise))) -(ediff-defvar-local ediff-word-mode-job nil "") - -(defmacro ediff-narrow-job () - `(memq ediff-job-name '(ediff-windows-wordwise - ediff-regions-wordwise - ediff-windows-linewise - ediff-regions-linewise))) -(ediff-defvar-local ediff-narrow-job nil "") - -;; Note: ediff-merge-directory-revisions-with-ancestor is not treated as an -;; ancestor metajob, since it behaves differently. -(defsubst ediff-ancestor-metajob (&optional metajob) - (memq (or metajob ediff-metajob-name) - '(ediff-merge-directories-with-ancestor - ediff-merge-filegroups-with-ancestor))) -(defsubst ediff-revision-metajob (&optional metajob) - (memq (or metajob ediff-metajob-name) - '(ediff-directory-revisions - ediff-merge-directory-revisions - ediff-merge-directory-revisions-with-ancestor))) -(defsubst ediff-patch-metajob (&optional metajob) - (memq (or metajob ediff-metajob-name) - '(ediff-multifile-patch))) -;; metajob involving only one group of files, such as multipatch or directory -;; revision -(defsubst ediff-one-filegroup-metajob (&optional metajob) - (or (ediff-revision-metajob metajob) - (ediff-patch-metajob metajob) - ;; add more here - )) -;; jobs suitable for the operation of collecting diffs into a multifile patch -(defsubst ediff-collect-diffs-metajob (&optional metajob) - (memq (or metajob ediff-metajob-name) - '(ediff-directories - ediff-merge-directories - ediff-merge-directories-with-ancestor - ediff-directory-revisions - ediff-merge-directory-revisions - ediff-merge-directory-revisions-with-ancestor - ;; add more here - ))) -(defsubst ediff-merge-metajob (&optional metajob) - (memq (or metajob ediff-metajob-name) - '(ediff-merge-directories - ediff-merge-directories-with-ancestor - ediff-merge-directory-revisions - ediff-merge-directory-revisions-with-ancestor - ediff-merge-filegroups-with-ancestor - ;; add more here - ))) - -(defsubst ediff-metajob3 (&optional metajob) - (memq (or metajob ediff-metajob-name) - '(ediff-merge-directories-with-ancestor - ediff-merge-filegroups-with-ancestor - ediff-directories3 - ediff-filegroups3))) -(defsubst ediff-comparison-metajob3 (&optional metajob) - (memq (or metajob ediff-metajob-name) - '(ediff-directories3 ediff-filegroups3))) - -;; with no argument, checks if we are in ediff-control-buffer -;; with argument, checks if we are in ediff-meta-buffer -(defun ediff-in-control-buffer-p (&optional meta-buf-p) - (and (boundp 'ediff-control-buffer) - (eq (if meta-buf-p ediff-meta-buffer ediff-control-buffer) - (current-buffer)))) - -(defsubst ediff-barf-if-not-control-buffer (&optional meta-buf-p) - (or (ediff-in-control-buffer-p meta-buf-p) - (error "%S: This command runs in Ediff Control Buffer only!" - this-command))) - -(defgroup ediff-highlighting nil - "Hilighting of difference regions in Ediff." - :prefix "ediff-" - :group 'ediff) - -(defgroup ediff-merge nil - "Merging utilities." - :prefix "ediff-" - :group 'ediff) - -(defgroup ediff-hook nil - "Hooks run by Ediff." - :prefix "ediff-" - :group 'ediff) - -;; Hook variables - -(defcustom ediff-before-setup-hook nil - "Hooks to run before Ediff begins to set up windows and buffers. -This hook can be used to save the previous window config, which can be restored -on ediff-quit or ediff-suspend." - :type 'hook - :group 'ediff-hook) -(defcustom ediff-before-setup-windows-hook nil - "Hooks to run before Ediff sets its window configuration. -This hook is run every time when Ediff arranges its windows. -This happens each time Ediff detects that the windows were messed up by the -user." - :type 'hook - :group 'ediff-hook) -(defcustom ediff-after-setup-windows-hook nil - "Hooks to run after Ediff sets its window configuration. -This can be used to set up control window or icon in a desired place." - :type 'hook - :group 'ediff-hook) -(defcustom ediff-before-setup-control-frame-hook nil - "Hooks run before setting up the frame to display Ediff Control Panel. -Can be used to change control frame parameters to position it where it -is desirable." - :type 'hook - :group 'ediff-hook) -(defcustom ediff-after-setup-control-frame-hook nil - "Hooks run after setting up the frame to display Ediff Control Panel. -Can be used to move the frame where it is desired." - :type 'hook - :group 'ediff-hook) -(defcustom ediff-startup-hook nil - "Hooks to run in the control buffer after Ediff has been set up and is ready for the job." - :type 'hook - :group 'ediff-hook) -(defcustom ediff-select-hook nil - "Hooks to run after a difference has been selected." - :type 'hook - :group 'ediff-hook) -(defcustom ediff-unselect-hook nil - "Hooks to run after a difference has been unselected." - :type 'hook - :group 'ediff-hook) -(defcustom ediff-prepare-buffer-hook nil - "Hooks run after buffers A, B, and C are set up. -For each buffer, the hooks are run with that buffer made current." - :type 'hook - :group 'ediff-hook) -(defcustom ediff-load-hook nil - "Hook run after Ediff is loaded. Can be used to change defaults." - :type 'hook - :group 'ediff-hook) - -(defcustom ediff-mode-hook nil - "Hook run just after ediff-mode is set up in the control buffer. -This is done before any windows or frames are created. One can use it to -set local variables that determine how the display looks like." - :type 'hook - :group 'ediff-hook) -(defcustom ediff-keymap-setup-hook nil - "Hook run just after the default bindings in Ediff keymap are set up." - :type 'hook - :group 'ediff-hook) - -(defcustom ediff-display-help-hook nil - "Hooks run after preparing the help message." - :type 'hook - :group 'ediff-hook) - -(defcustom ediff-suspend-hook nil - "Hooks to run in the Ediff control buffer when Ediff is suspended." - :type 'hook - :group 'ediff-hook) -(defcustom ediff-quit-hook nil - "Hooks to run in the Ediff control buffer after finishing Ediff." - :type 'hook - :group 'ediff-hook) -(defcustom ediff-cleanup-hook nil - "Hooks to run on exiting Ediff but before killing the control and variant buffers." - :type 'hook - :group 'ediff-hook) - -;; Error messages -(defconst ediff-KILLED-VITAL-BUFFER - "You have killed a vital Ediff buffer---you must leave Ediff now!") -(defconst ediff-NO-DIFFERENCES - "Sorry, comparison of identical variants is not what I am made for...") -(defconst ediff-BAD-DIFF-NUMBER - ;; %S stands for this-command, %d - diff number, %d - max diff - "%S: Bad diff region number, %d. Valid numbers are 1 to %d") -(defconst ediff-BAD-INFO (format " -*** The Info file for Ediff, a part of the standard distribution -*** of %sEmacs, does not seem to be properly installed. -*** -*** Please contact your system administrator. " - (if (featurep 'xemacs) "X" ""))) - -;; Selective browsing - -(ediff-defvar-local ediff-skip-diff-region-function 'ediff-show-all-diffs - "Function that determines the next/previous diff region to show. -Should return t for regions to be ignored and nil otherwise. -This function gets a region number as an argument. The region number -is the one used internally by Ediff. It is 1 less than the number seen -by the user.") - -(ediff-defvar-local ediff-hide-regexp-matches-function - 'ediff-hide-regexp-matches - "Function to use in determining which regions to hide. -See the documentation string of `ediff-hide-regexp-matches' for details.") -(ediff-defvar-local ediff-focus-on-regexp-matches-function - 'ediff-focus-on-regexp-matches - "Function to use in determining which regions to focus on. -See the documentation string of `ediff-focus-on-regexp-matches' for details.") - -;; Regexp that determines buf A regions to focus on when skipping to diff -(ediff-defvar-local ediff-regexp-focus-A "" "") -;; Regexp that determines buf B regions to focus on when skipping to diff -(ediff-defvar-local ediff-regexp-focus-B "" "") -;; Regexp that determines buf C regions to focus on when skipping to diff -(ediff-defvar-local ediff-regexp-focus-C "" "") -;; connective that determines whether to focus regions that match both or -;; one of the regexps -(ediff-defvar-local ediff-focus-regexp-connective 'and "") - -;; Regexp that determines buf A regions to ignore when skipping to diff -(ediff-defvar-local ediff-regexp-hide-A "" "") -;; Regexp that determines buf B regions to ignore when skipping to diff -(ediff-defvar-local ediff-regexp-hide-B "" "") -;; Regexp that determines buf C regions to ignore when skipping to diff -(ediff-defvar-local ediff-regexp-hide-C "" "") -;; connective that determines whether to hide regions that match both or -;; one of the regexps -(ediff-defvar-local ediff-hide-regexp-connective 'and "") - - -;;; Copying difference regions between buffers. - -;; A list of killed diffs. -;; A diff is saved here if it is replaced by a diff -;; from another buffer. This alist has the form: -;; \((num (buff-object . diff) (buff-object . diff) (buff-object . diff)) ...), -;; where some buffer-objects may be missing. -(ediff-defvar-local ediff-killed-diffs-alist nil "") - -;; Syntax table to use in ediff-forward-word-function -;; This is chosen by a heuristic. The important thing is for all buffers to -;; have the same syntax table. Which is not too important. -(ediff-defvar-local ediff-syntax-table nil "") - - -;; Highlighting -(defcustom ediff-before-flag-bol (if (featurep 'xemacs) (make-glyph "->>") "->>") - "Flag placed before a highlighted block of differences, if block starts at beginning of a line." - :type 'string - :tag "Region before-flag at beginning of line" - :group 'ediff) - -(defcustom ediff-after-flag-eol (if (featurep 'xemacs) (make-glyph "<<-") "<<-") - "Flag placed after a highlighted block of differences, if block ends at end of a line." - :type 'string - :tag "Region after-flag at end of line" - :group 'ediff) - -(defcustom ediff-before-flag-mol (if (featurep 'xemacs) (make-glyph "->>") "->>") - "Flag placed before a highlighted block of differences, if block starts in mid-line." - :type 'string - :tag "Region before-flag in the middle of line" - :group 'ediff) -(defcustom ediff-after-flag-mol (if (featurep 'xemacs) (make-glyph "<<-") "<<-") - "Flag placed after a highlighted block of differences, if block ends in mid-line." - :type 'string - :tag "Region after-flag in the middle of line" - :group 'ediff) - - -(ediff-defvar-local ediff-use-faces t "") -(defcustom ediff-use-faces t - "If t, differences are highlighted using faces, if device supports faces. -If nil, differences are highlighted using ASCII flags, ediff-before-flag -and ediff-after-flag. On a non-window system, differences are always -highlighted using ASCII flags." - :type 'boolean - :group 'ediff-highlighting) - -;; this indicates that diff regions are word-size, so fine diffs are -;; permanently nixed; used in ediff-windows-wordwise and ediff-regions-wordwise -(ediff-defvar-local ediff-word-mode nil "") -;; Name of the job (ediff-files, ediff-windows, etc.) -(ediff-defvar-local ediff-job-name nil "") - -;; Narrowing and ediff-region/windows support -;; This is a list (overlay-A overlay-B overlay-C) -;; If set, Ediff compares only those parts of buffers A/B/C that lie within -;; the bounds of these overlays. -(ediff-defvar-local ediff-narrow-bounds nil "") - -;; List (overlay-A overlay-B overlay-C), where each overlay spans the -;; entire corresponding buffer. -(ediff-defvar-local ediff-wide-bounds nil "") - -;; Current visibility boundaries in buffers A, B, and C. -;; This is also a list of overlays. When the user toggles narrow/widen, -;; this list changes from ediff-wide-bounds to ediff-narrow-bounds. -;; and back. -(ediff-defvar-local ediff-visible-bounds nil "") - -(ediff-defvar-local ediff-start-narrowed t - "Non-nil means start narrowed, if doing ediff-windows-* or ediff-regions-*") -(ediff-defvar-local ediff-quit-widened t - "*Non-nil means: when finished, Ediff widens buffers A/B. -Actually, Ediff restores the scope of visibility that existed at startup.") - -(defcustom ediff-keep-variants t - "nil means prompt to remove unmodified buffers A/B/C at session end. -Supplying a prefix argument to the quit command `q' temporarily reverses the -meaning of this variable." - :type 'boolean - :group 'ediff) - -(ediff-defvar-local ediff-highlight-all-diffs t "") -(defcustom ediff-highlight-all-diffs t - "If nil, only the selected differences are highlighted. -Otherwise, all difference regions are highlighted, but the selected region is -shown in brighter colors." - :type 'boolean - :group 'ediff-highlighting) - - -;; The suffix of the control buffer name. -(ediff-defvar-local ediff-control-buffer-suffix nil "") -;; Same as ediff-control-buffer-suffix, but without <,>. -;; It's a number rather than string. -(ediff-defvar-local ediff-control-buffer-number nil "") - - -;; The original values of ediff-protected-variables for buffer A -(ediff-defvar-local ediff-buffer-values-orig-A nil "") -;; The original values of ediff-protected-variables for buffer B -(ediff-defvar-local ediff-buffer-values-orig-B nil "") -;; The original values of ediff-protected-variables for buffer C -(ediff-defvar-local ediff-buffer-values-orig-C nil "") -;; The original values of ediff-protected-variables for buffer Ancestor -(ediff-defvar-local ediff-buffer-values-orig-Ancestor nil "") - -;; association between buff-type and ediff-buffer-values-orig-* -(defconst ediff-buffer-values-orig-alist - '((A . ediff-buffer-values-orig-A) - (B . ediff-buffer-values-orig-B) - (C . ediff-buffer-values-orig-C) - (Ancestor . ediff-buffer-values-orig-Ancestor))) - -;; Buffer-local variables to be saved then restored during Ediff sessions -(defconst ediff-protected-variables '( - ;;buffer-read-only - mode-line-format)) - -;; Vector of differences between the variants. Each difference is -;; represented by a vector of two overlays plus a vector of fine diffs, -;; plus a no-fine-diffs flag. The first overlay spans the -;; difference region in the A buffer and the second overlays the diff in -;; the B buffer. If a difference section is empty, the corresponding -;; overlay's endpoints coincide. -;; -;; The precise form of a Difference Vector for one buffer is: -;; [diff diff diff ...] -;; where each diff has the form: -;; [diff-overlay fine-diff-vector no-fine-diffs-flag state-of-diff] -;; fine-diff-vector is a vector [fine-diff-overlay fine-diff-overlay ...] -;; no-fine-diffs-flag says if there are fine differences. -;; state-of-difference is A, B, C, or nil, indicating which buffer is -;; different from the other two (used only in 3-way jobs. -(ediff-defvar-local ediff-difference-vector-A nil "") -(ediff-defvar-local ediff-difference-vector-B nil "") -(ediff-defvar-local ediff-difference-vector-C nil "") -(ediff-defvar-local ediff-difference-vector-Ancestor nil "") -;; A-list of diff vector types associated with buffer types -(defconst ediff-difference-vector-alist - '((A . ediff-difference-vector-A) - (B . ediff-difference-vector-B) - (C . ediff-difference-vector-C) - (Ancestor . ediff-difference-vector-Ancestor))) - -;; [ status status status ...] -;; Each status: [state-of-merge state-of-ancestor] -;; state-of-merge is default-A, default-B, prefer-A, or prefer-B. It -;; indicates the way a diff region was created in buffer C. -;; state-of-ancestor says if the corresponding region in ancestor buffer is -;; empty. -(ediff-defvar-local ediff-state-of-merge nil "") - -;; The difference that is currently selected. -(ediff-defvar-local ediff-current-difference -1 "") -;; Number of differences found. -(ediff-defvar-local ediff-number-of-differences nil "") - -;; Buffer containing the output of diff, which is used by Ediff to step -;; through files. -(ediff-defvar-local ediff-diff-buffer nil "") -;; Like ediff-diff-buffer, but contains context diff. It is not used by -;; Ediff, but it is saved in a file, if user requests so. -(ediff-defvar-local ediff-custom-diff-buffer nil "") -;; Buffer used for diff-style fine differences between regions. -(ediff-defvar-local ediff-fine-diff-buffer nil "") -;; Temporary buffer used for computing fine differences. -(defconst ediff-tmp-buffer " *ediff-tmp*" "") -;; Buffer used for messages -(defconst ediff-msg-buffer " *ediff-message*" "") -;; Buffer containing the output of diff when diff returns errors. -(ediff-defvar-local ediff-error-buffer nil "") -;; Buffer to display debug info -(ediff-defvar-local ediff-debug-buffer "*ediff-debug*" "") - -;; List of ediff control panels associated with each buffer A/B/C/Ancestor. -;; Not used any more, but may be needed in the future. -(ediff-defvar-local ediff-this-buffer-ediff-sessions nil "") - -;; to be deleted in due time -;; List of difference overlays disturbed by working with the current diff. -(defvar ediff-disturbed-overlays nil "") - -;; Priority of non-selected overlays. -(defvar ediff-shadow-overlay-priority 100 "") - -(defcustom ediff-version-control-package 'vc - "Version control package used. -Currently, Ediff supports vc.el, rcs.el, pcl-cvs.el, and generic-sc.el. The -standard Emacs interface to RCS, CVS, SCCS, etc., is vc.el. However, some -people find the other two packages more convenient. Set this variable to the -appropriate symbol: `rcs', `pcl-cvs', or `generic-sc' if you so desire." - :type 'symbol - :group 'ediff) - -(defcustom ediff-coding-system-for-read 'raw-text - "The coding system for read to use when running the diff program as a subprocess. -In most cases, the default will do. However, under certain circumstances in -MS-Windows you might need to use something like 'raw-text-dos here. -So, if the output that your diff program sends to Emacs contains extra ^M's, -you might need to experiment here, if the default or 'raw-text-dos doesn't -work." - :type 'symbol - :group 'ediff) - -(defcustom ediff-coding-system-for-write (if (featurep 'xemacs) - 'escape-quoted - 'emacs-internal) - "The coding system for write to use when writing out difference regions -to temp files in buffer jobs and when Ediff needs to find fine differences." - :type 'symbol - :group 'ediff) - - -(defalias 'ediff-read-event - (if (featurep 'xemacs) 'next-command-event 'read-event)) - -(defalias 'ediff-overlayp - (if (featurep 'xemacs) 'extentp 'overlayp)) - -(defalias 'ediff-make-overlay - (if (featurep 'xemacs) 'make-extent 'make-overlay)) - -(defalias 'ediff-delete-overlay - (if (featurep 'xemacs) 'delete-extent 'delete-overlay)) - -;; Assumes that emacs-major-version and emacs-minor-version are defined. -(defun ediff-check-version (op major minor &optional type-of-emacs) - "Check the current version against MAJOR and MINOR version numbers. -The comparison uses operator OP, which may be any of: =, >, >=, <, <=. -TYPE-OF-EMACS is either 'xemacs or 'emacs." - (and (cond ((eq type-of-emacs 'xemacs) (featurep 'xemacs)) - ((eq type-of-emacs 'emacs) (featurep 'emacs)) - (t)) - (cond ((eq op '=) (and (= emacs-minor-version minor) - (= emacs-major-version major))) - ((memq op '(> >= < <=)) - (and (or (funcall op emacs-major-version major) - (= emacs-major-version major)) - (if (= emacs-major-version major) - (funcall op emacs-minor-version minor) - t))) - (t - (error "%S: Invalid op in ediff-check-version" op))))) - -;; ediff-check-version seems to be totally unused anyway. -(make-obsolete 'ediff-check-version 'version< "23.1") - -(defun ediff-color-display-p () - (condition-case nil - (if (featurep 'xemacs) - (eq (device-class (selected-device)) 'color) ; xemacs form - (display-color-p)) ; emacs form - (error nil))) - - -;; A var local to each control panel buffer. Indicates highlighting style -;; in effect for this buffer: `face', `ascii', -;; `off' -- turned off \(on a dumb terminal only\). -(ediff-defvar-local ediff-highlighting-style - (if (and (ediff-has-face-support-p) ediff-use-faces) 'face 'ascii) - "") - - -(if (ediff-has-face-support-p) - (if (featurep 'xemacs) - (progn - (defalias 'ediff-valid-color-p 'valid-color-name-p) - (defalias 'ediff-get-face 'get-face)) - (defalias 'ediff-valid-color-p (if (fboundp 'color-defined-p) - 'color-defined-p - 'x-color-defined-p)) - (defalias 'ediff-get-face 'internal-get-face))) - -(if (ediff-window-display-p) - (if (featurep 'xemacs) - (progn - (defalias 'ediff-display-pixel-width 'device-pixel-width) - (defalias 'ediff-display-pixel-height 'device-pixel-height)) - (defalias 'ediff-display-pixel-width - (if (fboundp 'display-pixel-width) - 'display-pixel-width - 'x-display-pixel-width)) - (defalias 'ediff-display-pixel-height - (if (fboundp 'display-pixel-height) - 'display-pixel-height - 'x-display-pixel-height)))) - -;; A-list of current-diff-overlay symbols associated with buf types -(defconst ediff-current-diff-overlay-alist - '((A . ediff-current-diff-overlay-A) - (B . ediff-current-diff-overlay-B) - (C . ediff-current-diff-overlay-C) - (Ancestor . ediff-current-diff-overlay-Ancestor))) - -;; A-list of current-diff-face-* symbols associated with buf types -(defconst ediff-current-diff-face-alist - '((A . ediff-current-diff-A) - (B . ediff-current-diff-B) - (C . ediff-current-diff-C) - (Ancestor . ediff-current-diff-Ancestor))) - - -(defun ediff-set-overlay-face (extent face) - (ediff-overlay-put extent 'face face) - (ediff-overlay-put extent 'help-echo 'ediff-region-help-echo)) - -(defun ediff-region-help-echo (extent-or-window &optional overlay point) - (unless overlay - (setq overlay extent-or-window)) - (let ((is-current (ediff-overlay-get overlay 'ediff)) - (face (ediff-overlay-get overlay 'face)) - (diff-num (ediff-overlay-get overlay 'ediff-diff-num)) - face-help) - - ;; This happens only for refinement overlays - (if (stringp face) - (setq face (intern face))) - (setq face-help (and face (get face 'ediff-help-echo))) - - (cond ((and is-current diff-num) ; current diff region - (format "Difference region %S -- current" (1+ diff-num))) - (face-help) ; refinement of current diff region - (diff-num ; non-current - (format "Difference region %S -- non-current" (1+ diff-num))) - (t "")) ; none - )) - - -(defun ediff-set-face-pixmap (face pixmap) - "Set face pixmap on a monochrome display." - (if (and (ediff-window-display-p) (not (ediff-color-display-p))) - (condition-case nil - (set-face-background-pixmap face pixmap) - (error - (message "Pixmap not found for %S: %s" (face-name face) pixmap) - (sit-for 1))))) - -(defun ediff-hide-face (face) - (if (and (ediff-has-face-support-p) - (boundp 'add-to-list) - (boundp 'facemenu-unlisted-faces)) - (add-to-list 'facemenu-unlisted-faces face))) - - - -(defface ediff-current-diff-A - (if (featurep 'emacs) - '((((class color) (min-colors 16)) - (:foreground "firebrick" :background "pale green")) - (((class color)) - (:foreground "blue3" :background "yellow3")) - (t (:inverse-video t))) - '((((type tty)) (:foreground "blue3" :background "yellow3")) - (((class color)) (:foreground "firebrick" :background "pale green")) - (t (:inverse-video t)))) - "Face for highlighting the selected difference in buffer A." - :group 'ediff-highlighting) -;; An internal variable. Ediff takes the face from here. When unhighlighting, -;; this variable is set to nil, then again to the appropriate face. -(defvar ediff-current-diff-face-A 'ediff-current-diff-A - "Face for highlighting the selected difference in buffer A. -DO NOT CHANGE this variable. Instead, use the customization -widget to customize the actual face object `ediff-current-diff-A' -this variable represents.") -(ediff-hide-face ediff-current-diff-face-A) -;; Until custom.el for XEmacs starts supporting :inverse-video we do this. -;; This means that some user customization may be trashed. -(and (featurep 'xemacs) - (ediff-has-face-support-p) - (not (ediff-color-display-p)) - (copy-face 'modeline ediff-current-diff-face-A)) - - - -(defface ediff-current-diff-B - (if (featurep 'emacs) - '((((class color) (min-colors 16)) - (:foreground "DarkOrchid" :background "Yellow")) - (((class color)) - (:foreground "magenta3" :background "yellow3" - :weight bold)) - (t (:inverse-video t))) - '((((type tty)) (:foreground "magenta3" :background "yellow3" - :weight bold)) - (((class color)) (:foreground "DarkOrchid" :background "Yellow")) - (t (:inverse-video t)))) - "Face for highlighting the selected difference in buffer B." - :group 'ediff-highlighting) -;; An internal variable. Ediff takes the face from here. When unhighlighting, -;; this variable is set to nil, then again to the appropriate face. -(defvar ediff-current-diff-face-B 'ediff-current-diff-B - "Face for highlighting the selected difference in buffer B. - this variable. Instead, use the customization -widget to customize the actual face `ediff-current-diff-B' -this variable represents.") -(ediff-hide-face ediff-current-diff-face-B) -;; Until custom.el for XEmacs starts supporting :inverse-video we do this. -;; This means that some user customization may be trashed. -(and (featurep 'xemacs) - (ediff-has-face-support-p) - (not (ediff-color-display-p)) - (copy-face 'modeline ediff-current-diff-face-B)) - - -(defface ediff-current-diff-C - (if (featurep 'emacs) - '((((class color) (min-colors 16)) - (:foreground "Navy" :background "Pink")) - (((class color)) - (:foreground "cyan3" :background "yellow3" :weight bold)) - (t (:inverse-video t))) - '((((type tty)) (:foreground "cyan3" :background "yellow3" :weight bold)) - (((class color)) (:foreground "Navy" :background "Pink")) - (t (:inverse-video t)))) - "Face for highlighting the selected difference in buffer C." - :group 'ediff-highlighting) -;; An internal variable. Ediff takes the face from here. When unhighlighting, -;; this variable is set to nil, then again to the appropriate face. -(defvar ediff-current-diff-face-C 'ediff-current-diff-C - "Face for highlighting the selected difference in buffer C. -DO NOT CHANGE this variable. Instead, use the customization -widget to customize the actual face object `ediff-current-diff-C' -this variable represents.") -(ediff-hide-face ediff-current-diff-face-C) -;; Until custom.el for XEmacs starts supporting :inverse-video we do this. -;; This means that some user customization may be trashed. -(and (featurep 'xemacs) - (ediff-has-face-support-p) - (not (ediff-color-display-p)) - (copy-face 'modeline ediff-current-diff-face-C)) - - -(defface ediff-current-diff-Ancestor - (if (featurep 'emacs) - '((((class color) (min-colors 16)) - (:foreground "Black" :background "VioletRed")) - (((class color)) - (:foreground "black" :background "magenta3")) - (t (:inverse-video t))) - '((((type tty)) (:foreground "black" :background "magenta3")) - (((class color)) (:foreground "Black" :background "VioletRed")) - (t (:inverse-video t)))) - "Face for highlighting the selected difference in buffer Ancestor." - :group 'ediff-highlighting) -;; An internal variable. Ediff takes the face from here. When unhighlighting, -;; this variable is set to nil, then again to the appropriate face. -(defvar ediff-current-diff-face-Ancestor 'ediff-current-diff-Ancestor - "Face for highlighting the selected difference in buffer Ancestor. -DO NOT CHANGE this variable. Instead, use the customization -widget to customize the actual face object `ediff-current-diff-Ancestor' -this variable represents.") -(ediff-hide-face ediff-current-diff-face-Ancestor) -;; Until custom.el for XEmacs starts supporting :inverse-video we do this. -;; This means that some user customization may be trashed. -(and (featurep 'xemacs) - (ediff-has-face-support-p) - (not (ediff-color-display-p)) - (copy-face 'modeline ediff-current-diff-face-Ancestor)) - - -(defface ediff-fine-diff-A - (if (featurep 'emacs) - '((((class color) (min-colors 16)) - (:foreground "Navy" :background "sky blue")) - (((class color)) - (:foreground "white" :background "sky blue" :weight bold)) - (t (:underline t :stipple "gray3"))) - '((((type tty)) (:foreground "white" :background "sky blue" :weight bold)) - (((class color)) (:foreground "Navy" :background "sky blue")) - (t (:underline t :stipple "gray3")))) - "Face for highlighting the refinement of the selected diff in buffer A." - :group 'ediff-highlighting) -;; An internal variable. Ediff takes the face from here. When unhighlighting, -;; this variable is set to nil, then again to the appropriate face. -(defvar ediff-fine-diff-face-A 'ediff-fine-diff-A - "Face for highlighting the fine differences in buffer A. -DO NOT CHANGE this variable. Instead, use the customization -widget to customize the actual face object `ediff-fine-diff-A' -this variable represents.") -(ediff-hide-face ediff-fine-diff-face-A) - -(defface ediff-fine-diff-B - (if (featurep 'emacs) - '((((class color) (min-colors 16)) - (:foreground "Black" :background "cyan")) - (((class color)) - (:foreground "magenta3" :background "cyan3")) - (t (:underline t :stipple "gray3"))) - '((((type tty)) (:foreground "magenta3" :background "cyan3")) - (((class color)) (:foreground "Black" :background "cyan")) - (t (:underline t :stipple "gray3")))) - "Face for highlighting the refinement of the selected diff in buffer B." - :group 'ediff-highlighting) -;; An internal variable. Ediff takes the face from here. When unhighlighting, -;; this variable is set to nil, then again to the appropriate face. -(defvar ediff-fine-diff-face-B 'ediff-fine-diff-B - "Face for highlighting the fine differences in buffer B. -DO NOT CHANGE this variable. Instead, use the customization -widget to customize the actual face object `ediff-fine-diff-B' -this variable represents.") -(ediff-hide-face ediff-fine-diff-face-B) - -(defface ediff-fine-diff-C - (if (featurep 'emacs) - '((((type pc)) - (:foreground "white" :background "Turquoise")) - (((class color) (min-colors 16)) - (:foreground "Black" :background "Turquoise")) - (((class color)) - (:foreground "yellow3" :background "Turquoise" - :weight bold)) - (t (:underline t :stipple "gray3"))) - '((((type tty)) (:foreground "yellow3" :background "Turquoise" - :weight bold)) - (((type pc)) (:foreground "white" :background "Turquoise")) - (((class color)) (:foreground "Black" :background "Turquoise")) - (t (:underline t :stipple "gray3")))) - "Face for highlighting the refinement of the selected diff in buffer C." - :group 'ediff-highlighting) -;; An internal variable. Ediff takes the face from here. When unhighlighting, -;; this variable is set to nil, then again to the appropriate face. -(defvar ediff-fine-diff-face-C 'ediff-fine-diff-C - "Face for highlighting the fine differences in buffer C. -DO NOT CHANGE this variable. Instead, use the customization -widget to customize the actual face object `ediff-fine-diff-C' -this variable represents.") -(ediff-hide-face ediff-fine-diff-face-C) - -(defface ediff-fine-diff-Ancestor - (if (featurep 'emacs) - '((((class color) (min-colors 16)) - (:foreground "Black" :background "Green")) - (((class color)) - (:foreground "red3" :background "green")) - (t (:underline t :stipple "gray3"))) - '((((type tty)) (:foreground "red3" :background "green")) - (((class color)) (:foreground "Black" :background "Green")) - (t (:underline t :stipple "gray3")))) - "Face for highlighting the refinement of the selected diff in the ancestor buffer. -At present, this face is not used and no fine differences are computed for the -ancestor buffer." - :group 'ediff-highlighting) -;; An internal variable. Ediff takes the face from here. When unhighlighting, -;; this variable is set to nil, then again to the appropriate face. -(defvar ediff-fine-diff-face-Ancestor 'ediff-fine-diff-Ancestor - "Face for highlighting the fine differences in buffer Ancestor. -DO NOT CHANGE this variable. Instead, use the customization -widget to customize the actual face object `ediff-fine-diff-Ancestor' -this variable represents.") -(ediff-hide-face ediff-fine-diff-face-Ancestor) - -;; Some installs don't have stipple or Stipple. So, try them in turn. -(defvar stipple-pixmap - (cond ((not (ediff-has-face-support-p)) nil) - ((and (boundp 'x-bitmap-file-path) - (locate-library "stipple" t x-bitmap-file-path)) "stipple") - ((and (boundp 'mswindowsx-bitmap-file-path) - (locate-library "stipple" t mswindowsx-bitmap-file-path)) "stipple") - (t "Stipple"))) - -(defface ediff-even-diff-A - (if (featurep 'emacs) - `((((type pc)) - (:foreground "green3" :background "light grey")) - (((class color) (min-colors 16)) - (:foreground "Black" :background "light grey")) - (((class color)) - (:foreground "red3" :background "light grey" - :weight bold)) - (t (:italic t :stipple ,stipple-pixmap))) - `((((type tty)) (:foreground "red3" :background "light grey" - :weight bold)) - (((type pc)) (:foreground "green3" :background "light grey")) - (((class color)) (:foreground "Black" :background "light grey")) - (t (:italic t :stipple ,stipple-pixmap)))) - "Face for highlighting even-numbered non-current differences in buffer A." - :group 'ediff-highlighting) -;; An internal variable. Ediff takes the face from here. When unhighlighting, -;; this variable is set to nil, then again to the appropriate face. -(defvar ediff-even-diff-face-A 'ediff-even-diff-A - "Face for highlighting even-numbered non-current differences in buffer A. -DO NOT CHANGE this variable. Instead, use the customization -widget to customize the actual face object `ediff-even-diff-A' -this variable represents.") -(ediff-hide-face ediff-even-diff-face-A) - -(defface ediff-even-diff-B - (if (featurep 'emacs) - `((((class color) (min-colors 16)) - (:foreground "White" :background "Grey")) - (((class color)) - (:foreground "blue3" :background "Grey" :weight bold)) - (t (:italic t :stipple ,stipple-pixmap))) - `((((type tty)) (:foreground "blue3" :background "Grey" :weight bold)) - (((class color)) (:foreground "White" :background "Grey")) - (t (:italic t :stipple ,stipple-pixmap)))) - "Face for highlighting even-numbered non-current differences in buffer B." - :group 'ediff-highlighting) -;; An internal variable. Ediff takes the face from here. When unhighlighting, -;; this variable is set to nil, then again to the appropriate face. -(defvar ediff-even-diff-face-B 'ediff-even-diff-B - "Face for highlighting even-numbered non-current differences in buffer B. -DO NOT CHANGE this variable. Instead, use the customization -widget to customize the actual face object `ediff-even-diff-B' -this variable represents.") -(ediff-hide-face ediff-even-diff-face-B) - -(defface ediff-even-diff-C - (if (featurep 'emacs) - `((((type pc)) - (:foreground "yellow3" :background "light grey")) - (((class color) (min-colors 16)) - (:foreground "Black" :background "light grey")) - (((class color)) - (:foreground "yellow3" :background "light grey" - :weight bold)) - (t (:italic t :stipple ,stipple-pixmap))) - `((((type tty)) (:foreground "yellow3" :background "light grey" - :weight bold)) - (((type pc)) (:foreground "yellow3" :background "light grey")) - (((class color)) (:foreground "Black" :background "light grey")) - (t (:italic t :stipple ,stipple-pixmap)))) - "Face for highlighting even-numbered non-current differences in buffer C." - :group 'ediff-highlighting) -;; An internal variable. Ediff takes the face from here. When unhighlighting, -;; this variable is set to nil, then again to the appropriate face. -(defvar ediff-even-diff-face-C 'ediff-even-diff-C - "Face for highlighting even-numbered non-current differences in buffer C. -DO NOT CHANGE this variable. Instead, use the customization -widget to customize the actual face object `ediff-even-diff-C' -this variable represents.") -(ediff-hide-face ediff-even-diff-face-C) - -(defface ediff-even-diff-Ancestor - (if (featurep 'emacs) - `((((type pc)) - (:foreground "cyan3" :background "light grey")) - (((class color) (min-colors 16)) - (:foreground "White" :background "Grey")) - (((class color)) - (:foreground "cyan3" :background "light grey" - :weight bold)) - (t (:italic t :stipple ,stipple-pixmap))) - `((((type tty)) (:foreground "cyan3" :background "light grey" - :weight bold)) - (((type pc)) (:foreground "cyan3" :background "light grey")) - (((class color)) (:foreground "White" :background "Grey")) - (t (:italic t :stipple ,stipple-pixmap)))) - "Face for highlighting even-numbered non-current differences in the ancestor buffer." - :group 'ediff-highlighting) -;; An internal variable. Ediff takes the face from here. When unhighlighting, -;; this variable is set to nil, then again to the appropriate face. -(defvar ediff-even-diff-face-Ancestor 'ediff-even-diff-Ancestor - "Face for highlighting even-numbered non-current differences in buffer Ancestor. -DO NOT CHANGE this variable. Instead, use the customization -widget to customize the actual face object `ediff-even-diff-Ancestor' -this variable represents.") -(ediff-hide-face ediff-even-diff-face-Ancestor) - -;; Association between buffer types and even-diff-face symbols -(defconst ediff-even-diff-face-alist - '((A . ediff-even-diff-A) - (B . ediff-even-diff-B) - (C . ediff-even-diff-C) - (Ancestor . ediff-even-diff-Ancestor))) - -(defface ediff-odd-diff-A - (if (featurep 'emacs) - '((((type pc)) - (:foreground "green3" :background "gray40")) - (((class color) (min-colors 16)) - (:foreground "White" :background "Grey")) - (((class color)) - (:foreground "red3" :background "black" :weight bold)) - (t (:italic t :stipple "gray1"))) - '((((type tty)) (:foreground "red3" :background "black" :weight bold)) - (((type pc)) (:foreground "green3" :background "gray40")) - (((class color)) (:foreground "White" :background "Grey")) - (t (:italic t :stipple "gray1")))) - "Face for highlighting odd-numbered non-current differences in buffer A." - :group 'ediff-highlighting) -;; An internal variable. Ediff takes the face from here. When unhighlighting, -;; this variable is set to nil, then again to the appropriate face. -(defvar ediff-odd-diff-face-A 'ediff-odd-diff-A - "Face for highlighting odd-numbered non-current differences in buffer A. -DO NOT CHANGE this variable. Instead, use the customization -widget to customize the actual face object `ediff-odd-diff-A' -this variable represents.") -(ediff-hide-face ediff-odd-diff-face-A) - - -(defface ediff-odd-diff-B - (if (featurep 'emacs) - '((((type pc)) - (:foreground "White" :background "gray40")) - (((class color) (min-colors 16)) - (:foreground "Black" :background "light grey")) - (((class color)) - (:foreground "cyan3" :background "black" :weight bold)) - (t (:italic t :stipple "gray1"))) - '((((type tty)) (:foreground "cyan3" :background "black" :weight bold)) - (((type pc)) (:foreground "White" :background "gray40")) - (((class color)) (:foreground "Black" :background "light grey")) - (t (:italic t :stipple "gray1")))) - "Face for highlighting odd-numbered non-current differences in buffer B." - :group 'ediff-highlighting) -;; An internal variable. Ediff takes the face from here. When unhighlighting, -;; this variable is set to nil, then again to the appropriate face. -(defvar ediff-odd-diff-face-B 'ediff-odd-diff-B - "Face for highlighting odd-numbered non-current differences in buffer B. -DO NOT CHANGE this variable. Instead, use the customization -widget to customize the actual face object `ediff-odd-diff-B' -this variable represents.") -(ediff-hide-face ediff-odd-diff-face-B) - -(defface ediff-odd-diff-C - (if (featurep 'emacs) - '((((type pc)) - (:foreground "yellow3" :background "gray40")) - (((class color) (min-colors 16)) - (:foreground "White" :background "Grey")) - (((class color)) - (:foreground "yellow3" :background "black" :weight bold)) - (t (:italic t :stipple "gray1"))) - '((((type tty)) (:foreground "yellow3" :background "black" :weight bold)) - (((type pc)) (:foreground "yellow3" :background "gray40")) - (((class color)) (:foreground "White" :background "Grey")) - (t (:italic t :stipple "gray1")))) - "Face for highlighting odd-numbered non-current differences in buffer C." - :group 'ediff-highlighting) -;; An internal variable. Ediff takes the face from here. When unhighlighting, -;; this variable is set to nil, then again to the appropriate face. -(defvar ediff-odd-diff-face-C 'ediff-odd-diff-C - "Face for highlighting odd-numbered non-current differences in buffer C. -DO NOT CHANGE this variable. Instead, use the customization -widget to customize the actual face object `ediff-odd-diff-C' -this variable represents.") -(ediff-hide-face ediff-odd-diff-face-C) - -(defface ediff-odd-diff-Ancestor - (if (featurep 'emacs) - '((((class color) (min-colors 16)) - (:foreground "cyan3" :background "gray40")) - (((class color)) - (:foreground "green3" :background "black" :weight bold)) - (t (:italic t :stipple "gray1"))) - '((((type tty)) (:foreground "green3" :background "black" :weight bold)) - (((class color)) (:foreground "cyan3" :background "gray40")) - (t (:italic t :stipple "gray1")))) - "Face for highlighting odd-numbered non-current differences in the ancestor buffer." - :group 'ediff-highlighting) -;; An internal variable. Ediff takes the face from here. When unhighlighting, -;; this variable is set to nil, then again to the appropriate face. -(defvar ediff-odd-diff-face-Ancestor 'ediff-odd-diff-Ancestor - "Face for highlighting odd-numbered non-current differences in buffer Ancestor. -DO NOT CHANGE this variable. Instead, use the customization -widget to customize the actual face object `ediff-odd-diff-Ancestor' -this variable represents.") -(ediff-hide-face ediff-odd-diff-face-Ancestor) - -;; Association between buffer types and odd-diff-face symbols -(defconst ediff-odd-diff-face-alist - '((A . ediff-odd-diff-A) - (B . ediff-odd-diff-B) - (C . ediff-odd-diff-C) - (Ancestor . ediff-odd-diff-Ancestor))) - -;; A-list of fine-diff face symbols associated with buffer types -(defconst ediff-fine-diff-face-alist - '((A . ediff-fine-diff-A) - (B . ediff-fine-diff-B) - (C . ediff-fine-diff-C) - (Ancestor . ediff-fine-diff-Ancestor))) - -;; Help echo -(put ediff-fine-diff-face-A 'ediff-help-echo - "A `refinement' of the current difference region") -(put ediff-fine-diff-face-B 'ediff-help-echo - "A `refinement' of the current difference region") -(put ediff-fine-diff-face-C 'ediff-help-echo - "A `refinement' of the current difference region") -(put ediff-fine-diff-face-Ancestor 'ediff-help-echo - "A `refinement' of the current difference region") - -(add-hook 'ediff-quit-hook 'ediff-cleanup-mess) -(add-hook 'ediff-suspend-hook 'ediff-default-suspend-function) - - -;;; Overlays - -(ediff-defvar-local ediff-current-diff-overlay-A nil - "Overlay for the current difference region in buffer A.") -(ediff-defvar-local ediff-current-diff-overlay-B nil - "Overlay for the current difference region in buffer B.") -(ediff-defvar-local ediff-current-diff-overlay-C nil - "Overlay for the current difference region in buffer C.") -(ediff-defvar-local ediff-current-diff-overlay-Ancestor nil - "Overlay for the current difference region in the ancestor buffer.") - -;; Compute priority of a current ediff overlay. -(defun ediff-highest-priority (start end buffer) - (let ((pos (max 1 (1- start))) - ovr-list) - (if (featurep 'xemacs) - (1+ ediff-shadow-overlay-priority) - (ediff-with-current-buffer buffer - (while (< pos (min (point-max) (1+ end))) - (setq ovr-list (append (overlays-at pos) ovr-list)) - (setq pos (next-overlay-change pos))) - (+ 1 ediff-shadow-overlay-priority - (apply 'max - (cons - 1 - (mapcar - (lambda (ovr) - (if (and ovr - ;; exclude ediff overlays from priority - ;; calculation, or else priority will keep - ;; increasing - (null (ediff-overlay-get ovr 'ediff)) - (null (ediff-overlay-get ovr 'ediff-diff-num))) - ;; use the overlay priority or 0 - (or (ediff-overlay-get ovr 'priority) 0) - 0)) - ovr-list)))))))) - - -(defvar ediff-toggle-read-only-function nil - "*Specifies the function to be used to toggle read-only. -If nil, Ediff tries to deduce the function from the binding of C-x C-q. -Normally, this is the `toggle-read-only' function, but, if version -control is used, it could be `vc-toggle-read-only' or `rcs-toggle-read-only'.") - -(defcustom ediff-make-buffers-readonly-at-startup nil - "Make all variant buffers read-only when Ediff starts up. -This property can be toggled interactively." - :type 'boolean - :group 'ediff) - - -;;; Misc - -;; if nil, this silences some messages -(defvar ediff-verbose-p t) - -(defcustom ediff-autostore-merges 'group-jobs-only - "Save the results of merge jobs automatically. -With value nil, don't save automatically. With value t, always -save. Anything else means save automatically only if the merge -job is part of a group of jobs, such as `ediff-merge-directory' -or `ediff-merge-directory-revisions'." - :type '(choice (const nil) (const t) (const group-jobs-only)) - :group 'ediff-merge) -(make-variable-buffer-local 'ediff-autostore-merges) - -;; file where the result of the merge is to be saved. used internally -(ediff-defvar-local ediff-merge-store-file nil "") - -(defcustom ediff-merge-filename-prefix "merge_" - "Prefix to be attached to saved merge buffers." - :type 'string - :group 'ediff-merge) - -(defcustom ediff-no-emacs-help-in-control-buffer nil - "Non-nil means C-h should not invoke Emacs help in control buffer. -Instead, C-h would jump to previous difference." - :type 'boolean - :group 'ediff) - -;; This is the same as temporary-file-directory from Emacs 20.3. -;; Copied over here because XEmacs doesn't have this variable. -(defcustom ediff-temp-file-prefix - (file-name-as-directory - (cond ((boundp 'temporary-file-directory) temporary-file-directory) - ((fboundp 'temp-directory) (temp-directory)) - (t "/tmp/"))) -;;; (file-name-as-directory -;;; (cond ((memq system-type '(ms-dos windows-nt)) -;;; (or (getenv "TEMP") (getenv "TMPDIR") (getenv "TMP") "c:/temp")) -;;; (t -;;; (or (getenv "TMPDIR") (getenv "TMP") (getenv "TEMP") "/tmp")))) - "Prefix to put on Ediff temporary file names. -Do not start with `~/' or `~USERNAME/'." - :type 'string - :group 'ediff) - -(defcustom ediff-temp-file-mode 384 ; u=rw only - "Mode for Ediff temporary files." - :type 'integer - :group 'ediff) - -;; Metacharacters that have to be protected from the shell when executing -;; a diff/diff3 command. -(defcustom ediff-metachars "[ \t\n!\"#$&'()*;<=>?[\\^`{|~]" - "Regexp that matches characters that must be quoted with `\\' in shell command line. -This default should work without changes." - :type 'string - :group 'ediff) - -;; needed to simulate frame-char-width in XEmacs. -(defvar ediff-H-glyph (if (featurep 'xemacs) (make-glyph "H"))) - - -;; Temporary file used for refining difference regions in buffer A. -(ediff-defvar-local ediff-temp-file-A nil "") -;; Temporary file used for refining difference regions in buffer B. -(ediff-defvar-local ediff-temp-file-B nil "") -;; Temporary file used for refining difference regions in buffer C. -(ediff-defvar-local ediff-temp-file-C nil "") - - -(defun ediff-file-remote-p (file-name) - (file-remote-p file-name)) - -;; File for which we can get attributes, such as size or date -(defun ediff-listable-file (file-name) - (let ((handler (find-file-name-handler file-name 'file-local-copy))) - (or (null handler) (eq handler 'dired-handler-fn)))) - - -(defsubst ediff-frame-unsplittable-p (frame) - (cdr (assq 'unsplittable (frame-parameters frame)))) - -(defsubst ediff-get-next-window (wind prev-wind) - (cond ((window-live-p wind) wind) - (prev-wind (next-window wind)) - (t (selected-window)) - )) - - -(defsubst ediff-kill-buffer-carefully (buf) - "Kill buffer BUF if it exists." - (if (ediff-buffer-live-p buf) - (kill-buffer (get-buffer buf)))) - -(defsubst ediff-background-face (buf-type dif-num) - ;; The value of dif-num is always 1- the one that user sees. - ;; This is why even face is used when dif-num is odd. - (ediff-get-symbol-from-alist - buf-type (if (ediff-odd-p dif-num) - ediff-even-diff-face-alist - ediff-odd-diff-face-alist) - )) - - -;; activate faces on diff regions in buffer -(defun ediff-paint-background-regions-in-one-buffer (buf-type unhighlight) - (let ((diff-vector - (eval (ediff-get-symbol-from-alist - buf-type ediff-difference-vector-alist))) - overl diff-num) - (mapcar (lambda (rec) - (setq overl (ediff-get-diff-overlay-from-diff-record rec) - diff-num (ediff-overlay-get overl 'ediff-diff-num)) - (if (ediff-overlay-buffer overl) - ;; only if overlay is alive - (ediff-set-overlay-face - overl - (if (not unhighlight) - (ediff-background-face buf-type diff-num)))) - ) - diff-vector))) - - -;; activate faces on diff regions in all buffers -(defun ediff-paint-background-regions (&optional unhighlight) - (ediff-paint-background-regions-in-one-buffer - 'A unhighlight) - (ediff-paint-background-regions-in-one-buffer - 'B unhighlight) - (ediff-paint-background-regions-in-one-buffer - 'C unhighlight) - (ediff-paint-background-regions-in-one-buffer - 'Ancestor unhighlight)) - - -;; arg is a record for a given diff in a difference vector -;; this record is itself a vector -(defsubst ediff-clear-fine-diff-vector (diff-record) - (if diff-record - (mapc 'ediff-delete-overlay - (ediff-get-fine-diff-vector-from-diff-record diff-record)))) - -(defsubst ediff-clear-fine-differences-in-one-buffer (n buf-type) - (ediff-clear-fine-diff-vector (ediff-get-difference n buf-type)) - (ediff-set-fine-diff-vector n buf-type nil)) - -(defsubst ediff-clear-fine-differences (n) - (ediff-clear-fine-differences-in-one-buffer n 'A) - (ediff-clear-fine-differences-in-one-buffer n 'B) - (if ediff-3way-job - (ediff-clear-fine-differences-in-one-buffer n 'C))) - - -(defsubst ediff-mouse-event-p (event) - (if (featurep 'xemacs) - (button-event-p event) - (string-match "mouse" (format "%S" (event-basic-type event))))) - - -(defsubst ediff-key-press-event-p (event) - (if (featurep 'xemacs) - (key-press-event-p event) - (or (char-or-string-p event) (symbolp event)))) - -(defun ediff-event-point (event) - (cond ((ediff-mouse-event-p event) - (if (featurep 'xemacs) - (event-point event) - (posn-point (event-start event)))) - ((ediff-key-press-event-p event) - (point)) - (t (error "Error")))) - -(defun ediff-event-buffer (event) - (cond ((ediff-mouse-event-p event) - (if (featurep 'xemacs) - (event-buffer event) - (window-buffer (posn-window (event-start event))))) - ((ediff-key-press-event-p event) - (current-buffer)) - (t (error "Error")))) - -(defun ediff-event-key (event-or-key) - (if (featurep 'xemacs) - ;;(if (eventp event-or-key) (event-key event-or-key) event-or-key) - (if (eventp event-or-key) (event-to-character event-or-key t t) event-or-key) - event-or-key)) - -(defun ediff-last-command-char () - (ediff-event-key last-command-event)) - - -(defsubst ediff-frame-iconified-p (frame) - (and (ediff-window-display-p) (frame-live-p frame) - (if (featurep 'xemacs) - (frame-iconified-p frame) - (eq (frame-visible-p frame) 'icon)))) - -(defsubst ediff-window-visible-p (wind) - ;; under TTY, window-live-p also means window is visible - (and (window-live-p wind) - (or (not (ediff-window-display-p)) - (frame-visible-p (window-frame wind))))) - - -(defsubst ediff-frame-char-width (frame) - (if (featurep 'xemacs) - (/ (frame-pixel-width frame) (frame-width frame)) - (frame-char-width frame))) - -(defun ediff-reset-mouse (&optional frame do-not-grab-mouse) - (or frame (setq frame (selected-frame))) - (if (ediff-window-display-p) - (let ((frame-or-wind frame)) - (if (featurep 'xemacs) - (setq frame-or-wind (frame-selected-window frame))) - (or do-not-grab-mouse - ;; don't set mouse if the user said to never do this - (not ediff-grab-mouse) - ;; Don't grab on quit, if the user doesn't want to. - ;; If ediff-grab-mouse = t, then mouse won't be grabbed for - ;; sessions that are not part of a group (this is done in - ;; ediff-recenter). The condition below affects only terminating - ;; sessions in session groups (in which case mouse is warped into - ;; a meta buffer). - (and (eq ediff-grab-mouse 'maybe) - (memq this-command '(ediff-quit ediff-update-diffs))) - (set-mouse-position frame-or-wind 1 0)) - ))) - -(defsubst ediff-spy-after-mouse () - (setq ediff-mouse-pixel-position (mouse-pixel-position))) - -;; It is not easy to find out when the user grabs the mouse, since emacs and -;; xemacs behave differently when mouse is not in any frame. Also, this is -;; sensitive to when the user grabbed mouse. Not used for now. -(defun ediff-user-grabbed-mouse () - (if ediff-mouse-pixel-position - (cond ((not (eq (car ediff-mouse-pixel-position) - (car (mouse-pixel-position))))) - ((and (car (cdr ediff-mouse-pixel-position)) - (car (cdr (mouse-pixel-position))) - (cdr (cdr ediff-mouse-pixel-position)) - (cdr (cdr (mouse-pixel-position)))) - (not (and (< (abs (- (car (cdr ediff-mouse-pixel-position)) - (car (cdr (mouse-pixel-position))))) - ediff-mouse-pixel-threshold) - (< (abs (- (cdr (cdr ediff-mouse-pixel-position)) - (cdr (cdr (mouse-pixel-position))))) - ediff-mouse-pixel-threshold)))) - (t nil)))) - -(defsubst ediff-frame-char-height (frame) - (if (featurep 'xemacs) - (glyph-height ediff-H-glyph (frame-selected-window frame)) - (frame-char-height frame))) - -;; Some overlay functions - -(defsubst ediff-overlay-start (overl) - (if (ediff-overlayp overl) - (if (featurep 'xemacs) - (extent-start-position overl) - (overlay-start overl)))) - -(defsubst ediff-overlay-end (overl) - (if (ediff-overlayp overl) - (if (featurep 'xemacs) - (extent-end-position overl) - (overlay-end overl)))) - -(defsubst ediff-empty-overlay-p (overl) - (= (ediff-overlay-start overl) (ediff-overlay-end overl))) - -;; like overlay-buffer in Emacs. In XEmacs, returns nil if the extent is -;; dead. Otherwise, works like extent-buffer -(defun ediff-overlay-buffer (overl) - (if (featurep 'xemacs) - (and (extent-live-p overl) (extent-object overl)) - (overlay-buffer overl))) - -;; like overlay-get in Emacs. In XEmacs, returns nil if the extent is -;; dead. Otherwise, like extent-property -(defun ediff-overlay-get (overl property) - (if (featurep 'xemacs) - (and (extent-live-p overl) (extent-property overl property)) - (overlay-get overl property))) - - -;; These two functions are here because XEmacs refuses to -;; handle overlays whose buffers were deleted. -(defun ediff-move-overlay (overlay beg end &optional buffer) - "Calls `move-overlay' in Emacs and `set-extent-endpoints' in Lemacs. -Checks if overlay's buffer exists before actually doing the move." - (let ((buf (and overlay (ediff-overlay-buffer overlay)))) - (if (ediff-buffer-live-p buf) - (if (featurep 'xemacs) - (set-extent-endpoints overlay beg end) - (move-overlay overlay beg end buffer)) - ;; buffer's dead - (if overlay - (ediff-delete-overlay overlay))))) - -(defun ediff-overlay-put (overlay prop value) - "Calls `overlay-put' or `set-extent-property' depending on Emacs version. -Checks if overlay's buffer exists." - (if (ediff-buffer-live-p (ediff-overlay-buffer overlay)) - (if (featurep 'xemacs) - (set-extent-property overlay prop value) - (overlay-put overlay prop value)) - (ediff-delete-overlay overlay))) - -;; temporarily uses DIR to abbreviate file name -;; if DIR is nil, use default-directory -(defun ediff-abbreviate-file-name (file &optional dir) - (cond ((stringp dir) - (let ((directory-abbrev-alist (list (cons dir "")))) - (abbreviate-file-name file))) - (t - (if (featurep 'xemacs) - ;; XEmacs requires addl argument - (abbreviate-file-name file t) - (abbreviate-file-name file))))) - -;; Takes a directory and returns the parent directory. -;; does nothing to `/'. If the ARG is a regular file, -;; strip the file AND the last dir. -(defun ediff-strip-last-dir (dir) - (if (not (stringp dir)) (setq dir default-directory)) - (setq dir (expand-file-name dir)) - (or (file-directory-p dir) (setq dir (file-name-directory dir))) - (let* ((pos (1- (length dir))) - (last-char (aref dir pos))) - (if (and (> pos 0) (= last-char ?/)) - (setq dir (substring dir 0 pos))) - (ediff-abbreviate-file-name (file-name-directory dir)))) - -(defun ediff-truncate-string-left (str newlen) - ;; leave space for ... on the left - (let ((len (length str)) - substr) - (if (<= len newlen) - str - (setq newlen (max 0 (- newlen 3))) - (setq substr (substring str (max 0 (- len 1 newlen)))) - (concat "..." substr)))) - -(defsubst ediff-nonempty-string-p (string) - (and (stringp string) (not (string= string "")))) - -(unless (fboundp 'subst-char-in-string) - (defun subst-char-in-string (fromchar tochar string &optional inplace) - "Replace FROMCHAR with TOCHAR in STRING each time it occurs. -Unless optional argument INPLACE is non-nil, return a new string." - (let ((i (length string)) - (newstr (if inplace string (copy-sequence string)))) - (while (> i 0) - (setq i (1- i)) - (if (eq (aref newstr i) fromchar) - (aset newstr i tochar))) - newstr))) - -(defun ediff-abbrev-jobname (jobname) - (cond ((eq jobname 'ediff-directories) - "Compare two directories") - ((eq jobname 'ediff-files) - "Compare two files") - ((eq jobname 'ediff-buffers) - "Compare two buffers") - ((eq jobname 'ediff-directories3) - "Compare three directories") - ((eq jobname 'ediff-files3) - "Compare three files") - ((eq jobname 'ediff-buffers3) - "Compare three buffers") - ((eq jobname 'ediff-revision) - "Compare file with a version") - ((eq jobname 'ediff-directory-revisions) - "Compare dir files with versions") - ((eq jobname 'ediff-merge-directory-revisions) - "Merge dir files with versions") - ((eq jobname 'ediff-merge-directory-revisions-with-ancestor) - "Merge dir versions via ancestors") - (t - (capitalize - (subst-char-in-string ?- ?\s (substring (symbol-name jobname) 6)))) - )) - - -;; If ediff modified mode line, strip the modification -(defsubst ediff-strip-mode-line-format () - (if (member (car mode-line-format) '(" A: " " B: " " C: " " Ancestor: ")) - (setq mode-line-format (nth 2 mode-line-format)))) - -;; Verify that we have a difference selected. -(defsubst ediff-valid-difference-p (&optional n) - (or n (setq n ediff-current-difference)) - (and (>= n 0) (< n ediff-number-of-differences))) - -(defsubst ediff-show-all-diffs (n) - "Don't skip difference regions." - nil) - -(defsubst ediff-message-if-verbose (string &rest args) - (if ediff-verbose-p - (apply 'message string args))) - -(defun ediff-file-attributes (filename attr-number) - (if (ediff-listable-file filename) - (nth attr-number (file-attributes filename)) - -1) - ) - -(defsubst ediff-file-size (filename) - (ediff-file-attributes filename 7)) -(defsubst ediff-file-modtime (filename) - (ediff-file-attributes filename 5)) - - -(defun ediff-convert-standard-filename (fname) - (if (fboundp 'convert-standard-filename) - (convert-standard-filename fname) - fname)) - -(if (featurep 'emacs) - (defalias 'ediff-with-syntax-table 'with-syntax-table) - (if (fboundp 'with-syntax-table) - (defalias 'ediff-with-syntax-table 'with-syntax-table) - ;; stolen from subr.el in emacs 21 - (defmacro ediff-with-syntax-table (table &rest body) - (let ((old-table (make-symbol "table")) - (old-buffer (make-symbol "buffer"))) - `(let ((,old-table (syntax-table)) - (,old-buffer (current-buffer))) - (unwind-protect - (progn - (set-syntax-table (copy-syntax-table ,table)) - ,@body) - (save-current-buffer - (set-buffer ,old-buffer) - (set-syntax-table ,old-table)))))))) - - -(provide 'ediff-init) - - - -;; 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: - -;; arch-tag: fa31d384-1e70-4d4b-82a7-3e96307c46f5 -;;; ediff-init.el ends here diff -r 681cd08dc0f7 -r e93288477c43 lisp/ediff-merg.el --- a/lisp/ediff-merg.el Thu Jun 10 22:43:47 2010 +0000 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,397 +0,0 @@ -;;; ediff-merg.el --- merging utilities - -;; Copyright (C) 1994, 1995, 1996, 1997, 1998, 1999, 2000, 2001, 2002, -;; 2003, 2004, 2005, 2006, 2007, 2008, 2009, 2010 Free Software Foundation, Inc. - -;; Author: Michael Kifer - -;; 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 3 of the License, 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. If not, see . - -;;; Commentary: - -;;; Code: - - -;; compiler pacifier -(defvar ediff-window-A) -(defvar ediff-window-B) -(defvar ediff-window-C) -(defvar ediff-merge-window-share) -(defvar ediff-window-config-saved) - -(eval-when-compile - (require 'ediff-util)) -;; end pacifier - -(require 'ediff-init) - -(defcustom ediff-quit-merge-hook 'ediff-maybe-save-and-delete-merge - "Hooks to run before quitting a merge job. -The most common use is to save and delete the merge buffer." - :type 'hook - :group 'ediff-merge) - - -(defcustom ediff-default-variant 'combined - "The variant to be used as a default for buffer C in merging. -Valid values are the symbols `default-A', `default-B', and `combined'." - :type '(radio (const default-A) (const default-B) (const combined)) - :group 'ediff-merge) - -(defcustom ediff-combination-pattern - '("<<<<<<< variant A" A ">>>>>>> variant B" B "####### Ancestor" Ancestor "======= end") - "Pattern to be used for combining difference regions in buffers A and B. -The value must be a list of the form -\(STRING1 bufspec1 STRING2 bufspec2 STRING3 bufspec3 STRING4) -where bufspec is the symbol A, B, or Ancestor. For instance, if the value is -'(STRING1 A STRING2 Ancestor STRING3 B STRING4) then the -combined text will look like this: - -STRING1 -diff region from variant A -STRING2 -diff region from the ancestor -STRING3 -diff region from variant B -STRING4 -" - :type '(choice (list string symbol string symbol string) - (list string symbol string symbol string symbol string)) - :group 'ediff-merge) - -(defcustom ediff-show-clashes-only nil - "If t, show only those diff regions where both buffers disagree with the ancestor. -This means that regions that have status prefer-A or prefer-B will be -skipped over. A value of nil means show all regions." - :type 'boolean - :group 'ediff-merge - ) -(make-variable-buffer-local 'ediff-show-clashes-only) - -(defcustom ediff-skip-merge-regions-that-differ-from-default nil - "If t, show only the regions that have not been changed by the user. -A region is considered to have been changed if it is different from the current -default (`default-A', `default-B', `combined') and it hasn't been marked as -`prefer-A' or `prefer-B'. -A region is considered to have been changed also when it is marked as -as `prefer-A', but is different from the corresponding difference region in -Buffer A or if it is marked as `prefer-B' and is different from the region in -Buffer B." - :type 'boolean - :group 'ediff-merge - ) -(make-variable-buffer-local 'ediff-skip-merge-regions-that-differ-from-default) - -;; check if there is no clash between the ancestor and one of the variants. -;; if it is not a merge job then return true -(defun ediff-merge-region-is-non-clash (n) - (if (ediff-merge-job) - (string-match "prefer" (or (ediff-get-state-of-merge n) "")) - t)) - -;; If ediff-show-clashes-only, check if there is no clash between the ancestor -;; and one of the variants. -(defun ediff-merge-region-is-non-clash-to-skip (n) - (and (ediff-merge-job) - ediff-show-clashes-only - (ediff-merge-region-is-non-clash n))) - -;; If ediff-skip-changed-regions, check if the merge region differs from -;; the current default. If a region is different from the default, it means -;; that the user has made determination as to how to merge for this particular -;; region. -(defun ediff-skip-merge-region-if-changed-from-default-p (n) - (and (ediff-merge-job) - ediff-skip-merge-regions-that-differ-from-default - (ediff-merge-changed-from-default-p n 'prefers-too))) - - -(defun ediff-get-combined-region (n) - (let ((pattern-list ediff-combination-pattern) - (combo-region "") - (err-msg - "ediff-combination-pattern: Invalid format. Please consult the documentation") - region-delim region-spec) - - (if (< (length pattern-list) 5) - (error err-msg)) - - (while (> (length pattern-list) 2) - (setq region-delim (nth 0 pattern-list) - region-spec (nth 1 pattern-list)) - (or (and (stringp region-delim) (memq region-spec '(A B Ancestor))) - (error err-msg)) - - (condition-case nil - (setq combo-region - (concat combo-region - region-delim "\n" - (ediff-get-region-contents - n region-spec ediff-control-buffer))) - (error "")) - (setq pattern-list (cdr (cdr pattern-list))) - ) - - (setq region-delim (nth 0 pattern-list)) - (or (stringp region-delim) - (error err-msg)) - (setq combo-region (concat combo-region region-delim "\n")) - )) - -;;(defsubst ediff-make-combined-diff (regA regB) -;; (concat (nth 0 ediff-combination-pattern) "\n" -;; regA -;; (nth 1 ediff-combination-pattern) "\n" -;; regB -;; (nth 2 ediff-combination-pattern) "\n")) - -(defsubst ediff-set-state-of-all-diffs-in-all-buffers (ctl-buf) - (let ((n 0)) - (while (< n ediff-number-of-differences) - (ediff-set-state-of-diff-in-all-buffers n ctl-buf) - (setq n (1+ n))))) - -(defun ediff-set-state-of-diff-in-all-buffers (n ctl-buf) - (let ((regA (ediff-get-region-contents n 'A ctl-buf)) - (regB (ediff-get-region-contents n 'B ctl-buf)) - (regC (ediff-get-region-contents n 'C ctl-buf))) - (cond ((and (string= regA regB) (string= regA regC)) - (ediff-set-state-of-diff n 'A "=diff(B)") - (ediff-set-state-of-diff n 'B "=diff(C)") - (ediff-set-state-of-diff n 'C "=diff(A)")) - ((string= regA regB) - (ediff-set-state-of-diff n 'A "=diff(B)") - (ediff-set-state-of-diff n 'B "=diff(A)") - (ediff-set-state-of-diff n 'C nil)) - ((string= regA regC) - (ediff-set-state-of-diff n 'A "=diff(C)") - (ediff-set-state-of-diff n 'C "=diff(A)") - (ediff-set-state-of-diff n 'B nil)) - ((string= regB regC) - (ediff-set-state-of-diff n 'C "=diff(B)") - (ediff-set-state-of-diff n 'B "=diff(C)") - (ediff-set-state-of-diff n 'A nil)) - ((string= regC (ediff-get-combined-region n)) - (ediff-set-state-of-diff n 'A nil) - (ediff-set-state-of-diff n 'B nil) - (ediff-set-state-of-diff n 'C "=diff(A+B)")) - (t (ediff-set-state-of-diff n 'A nil) - (ediff-set-state-of-diff n 'B nil) - (ediff-set-state-of-diff n 'C nil))) - )) - -(defun ediff-set-merge-mode () - (normal-mode t) - (remove-hook 'local-write-file-hooks 'ediff-set-merge-mode)) - - -;; Go over all diffs starting with DIFF-NUM and copy regions into buffer C -;; according to the state of the difference. -;; Since ediff-copy-diff refuses to copy identical diff regions, there is -;; no need to optimize ediff-do-merge any further. -;; -;; If re-merging, change state of merge in all diffs starting with -;; DIFF-NUM, except those where the state is prefer-* or where it is -;; `default-*' or `combined' but the buf C region appears to be modified -;; since last set by default. -(defun ediff-do-merge (diff-num &optional remerging) - (if (< diff-num 0) (setq diff-num 0)) - (let ((n diff-num) - ;;(default-state-of-merge (format "%S" ediff-default-variant)) - do-not-copy state-of-merge) - (while (< n ediff-number-of-differences) - (setq do-not-copy nil) ; reset after each cycle - (if (= (mod n 10) 0) - (message "%s buffers A & B into C ... region %d of %d" - (if remerging "Re-merging" "Merging") - n - ediff-number-of-differences)) - - (setq state-of-merge (ediff-get-state-of-merge n)) - - (if remerging - ;;(let ((reg-A (ediff-get-region-contents n 'A ediff-control-buffer)) - ;; (reg-B (ediff-get-region-contents n 'B ediff-control-buffer)) - ;; (reg-C (ediff-get-region-contents n 'C ediff-control-buffer))) - (progn - - ;; if region was edited since it was first set by default - (if (or (ediff-merge-changed-from-default-p n) - ;; was preferred - (string-match "prefer" state-of-merge)) - ;; then ignore - (setq do-not-copy t)) - - ;; change state of merge for this diff, if necessary - (if (and (string-match "\\(default\\|combined\\)" state-of-merge) - (not do-not-copy)) - (ediff-set-state-of-merge - n (format "%S" ediff-default-variant))) - )) - - ;; state-of-merge may have changed via ediff-set-state-of-merge, so - ;; check it once again - (setq state-of-merge (ediff-get-state-of-merge n)) - - (or do-not-copy - (if (string= state-of-merge "combined") - ;; use n+1 because ediff-combine-diffs works via user numbering - ;; of diffs, which is 1+ to what ediff uses internally - (ediff-combine-diffs (1+ n) 'batch) - (ediff-copy-diff - n (if (string-match "-A" state-of-merge) 'A 'B) 'C 'batch))) - (setq n (1+ n))) - (message "Merging buffers A & B into C ... Done") - )) - - -(defun ediff-re-merge () - "Remerge unmodified diff regions using a new default. Start with the current region." - (interactive) - (let* ((default-variant-alist - (list '("default-A") '("default-B") '("combined"))) - (actual-alist - (delete (list (symbol-name ediff-default-variant)) - default-variant-alist))) - (setq ediff-default-variant - (intern - (completing-read - (format "Current merge default is `%S'. New default: " - ediff-default-variant) - actual-alist nil 'must-match))) - (ediff-do-merge ediff-current-difference 'remerge) - (ediff-recenter) - )) - -(defun ediff-shrink-window-C (arg) - "Shrink window C to just one line. -With a prefix argument, returns window C to its normal size. -Used only for merging jobs." - (interactive "P") - (if (not ediff-merge-job) - (error "ediff-shrink-window-C can be used only for merging jobs")) - (cond ((eq arg '-) (setq arg -1)) - ((not (numberp arg)) (setq arg nil))) - (cond ((null arg) - (let ((ediff-merge-window-share - (if (< (window-height ediff-window-C) 3) - ediff-merge-window-share 0))) - (setq ediff-window-config-saved "") ; force redisplay - (ediff-recenter 'no-rehighlight))) - ((and (< arg 0) (> (window-height ediff-window-C) 2)) - (setq ediff-merge-window-share (* ediff-merge-window-share 0.9)) - (setq ediff-window-config-saved "") ; force redisplay - (ediff-recenter 'no-rehighlight)) - ((and (> arg 0) (> (window-height ediff-window-A) 2)) - (setq ediff-merge-window-share (* ediff-merge-window-share 1.1)) - (setq ediff-window-config-saved "") ; force redisplay - (ediff-recenter 'no-rehighlight)))) - - -;; N here is the user's region number. It is 1+ what Ediff uses internally. -(defun ediff-combine-diffs (n &optional batch-invocation) - "Combine Nth diff regions of buffers A and B and place the combination in C. -N is a prefix argument. If nil, combine the current difference regions. -Combining is done according to the specifications in variable -`ediff-combination-pattern'." - (interactive "P") - (setq n (if (numberp n) (1- n) ediff-current-difference)) - - (let (reg-combined) - ;;(setq regA (ediff-get-region-contents n 'A ediff-control-buffer) - ;; regB (ediff-get-region-contents n 'B ediff-control-buffer)) - ;;(setq reg-combined (ediff-make-combined-diff regA regB)) - (setq reg-combined (ediff-get-combined-region n)) - - (ediff-copy-diff n nil 'C batch-invocation reg-combined)) - (or batch-invocation (ediff-jump-to-difference (1+ n)))) - - -;; Checks if the region in buff C looks like a combination of the regions -;; in buffers A and B. Return a list (reg-a-beg reg-a-end reg-b-beg reg-b-end) -;; These refer to where the delimiters for region A, B, Ancestor start and end -;; in buffer C -(defun ediff-looks-like-combined-merge (region-num) - (if ediff-merge-job - (let ((combined (string-match (regexp-quote "(A+B)") - (or (ediff-get-state-of-diff region-num 'C) - ""))) - (mrgreg-beg (ediff-get-diff-posn 'C 'beg region-num)) - (mrgreg-end (ediff-get-diff-posn 'C 'end region-num)) - (pattern-list ediff-combination-pattern) - delim reg-beg reg-end delim-regs-list) - - (if combined - (ediff-with-current-buffer ediff-buffer-C - (while pattern-list - (goto-char mrgreg-beg) - (setq delim (nth 0 pattern-list)) - (search-forward delim mrgreg-end 'noerror) - (setq reg-beg (match-beginning 0)) - (setq reg-end (match-end 0)) - (if (and reg-beg reg-end) - (setq delim-regs-list - ;; in reverse - (cons reg-end (cons reg-beg delim-regs-list)))) - (if (> (length pattern-list) 1) - (setq pattern-list (cdr (cdr pattern-list))) - (setq pattern-list nil)) - ))) - - (reverse delim-regs-list) - ))) - -(defvar state-of-merge) ; dynamic var - -;; Check if the non-preferred merge has been modified since originally set. -;; This affects only the regions that are marked as default-A/B or combined. -;; If PREFERS-TOO is non-nil, then look at the regions marked as prefers-A/B as -;; well. -(defun ediff-merge-changed-from-default-p (diff-num &optional prefers-too) - (let ((reg-A (ediff-get-region-contents diff-num 'A ediff-control-buffer)) - (reg-B (ediff-get-region-contents diff-num 'B ediff-control-buffer)) - (reg-C (ediff-get-region-contents diff-num 'C ediff-control-buffer))) - - (setq state-of-merge (ediff-get-state-of-merge diff-num)) - - ;; if region was edited since it was first set by default - (or (and (string= state-of-merge "default-A") - (not (string= reg-A reg-C))) - (and (string= state-of-merge "default-B") - (not (string= reg-B reg-C))) - (and (string= state-of-merge "combined") - ;;(not (string= (ediff-make-combined-diff reg-A reg-B) reg-C))) - (not (string= (ediff-get-combined-region diff-num) reg-C))) - (and prefers-too - (string= state-of-merge "prefer-A") - (not (string= reg-A reg-C))) - (and prefers-too - (string= state-of-merge "prefer-B") - (not (string= reg-B reg-C))) - ))) - - -(provide 'ediff-merg) - - -;; 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: - -;; arch-tag: 9b798cf9-02ba-487f-a62e-b63aa823dbfb -;;; ediff-merg.el ends here diff -r 681cd08dc0f7 -r e93288477c43 lisp/ediff-mult.el --- a/lisp/ediff-mult.el Thu Jun 10 22:43:47 2010 +0000 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,2476 +0,0 @@ -;;; ediff-mult.el --- support for multi-file/multi-buffer processing in Ediff - -;; Copyright (C) 1995, 1996, 1997, 1998, 1999, 2000, 2001, 2002, -;; 2003, 2004, 2005, 2006, 2007, 2008, 2009, 2010 Free Software Foundation, Inc. - -;; Author: Michael Kifer - -;; 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 3 of the License, 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. If not, see . - -;;; 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 a 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) - -(defgroup ediff-mult nil - "Multi-file and multi-buffer processing in Ediff." - :prefix "ediff-" - :group 'ediff) - - -;; compiler pacifier -(eval-when-compile - (require 'ediff-ptch) - (require 'ediff)) -;; end pacifier - -(require 'ediff-init) - -;; meta-buffer -(ediff-defvar-local ediff-meta-buffer nil "") -(ediff-defvar-local ediff-parent-meta-buffer nil "") -;; the registry buffer -(defvar ediff-registry-buffer nil) - -(defconst ediff-meta-buffer-brief-message "Ediff Session Group Panel: %s - - Type ? to show useful commands in this buffer - -") - -(defconst ediff-meta-buffer-verbose-message "Ediff Session Group Panel: %s - -Useful commands (type ? to hide them and free up screen): - 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-local ediff-meta-buffer-map nil - "The keymap for the meta buffer.") -(defvar ediff-dir-diffs-buffer-map (make-sparse-keymap) - "The keymap to be installed in the buffer showing differences between -directories.") - -;; 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-local ediff-meta-action-function nil "") -;; Tells ediff-update-meta-buffer how to redraw it -(ediff-defvar-local ediff-meta-redraw-function nil "") -;; Tells ediff-filegroup-action and similar procedures how to invoke Ediff for -;; the sessions in a given session group -(ediff-defvar-local ediff-session-action-function nil "") - -(ediff-defvar-local ediff-metajob-name nil "") - -;; buffer used to collect custom diffs from individual sessions in the group -(ediff-defvar-local ediff-meta-diff-buffer nil "") - -;; t means recurse into subdirs when deciding which files have same contents -(ediff-defvar-local ediff-recurse-to-subdirectories nil "") - -;; history var to use for filtering groups of files -(defvar ediff-filtering-regexp-history nil "") - -(defcustom ediff-default-filtering-regexp nil - "The default regular expression used as a filename filter in multifile comparisons. -Should be a sexp. For instance (car ediff-filtering-regexp-history) or nil." - :type 'sexp - :group 'ediff-mult) - -;; 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-local ediff-meta-list nil "") - -(ediff-defvar-local ediff-meta-session-number nil "") - - -;; the difference list between directories in a directory session group -(ediff-defvar-local ediff-dir-difference-list nil "") -(ediff-defvar-local ediff-dir-diffs-buffer nil "") - -;; The registry of Ediff sessions. A list of control buffers. -(defvar ediff-session-registry nil) - -(defcustom ediff-meta-truncate-filenames t - "If non-nil, truncate long file names in the session group buffers. -This can be toggled with `ediff-toggle-filename-truncation'." - :type 'boolean - :group 'ediff-mult) - -(defcustom ediff-meta-mode-hook nil - "Hooks run just after setting up meta mode." - :type 'hook - :group 'ediff-mult) - -(defcustom ediff-registry-setup-hook nil - "Hooks run just after the registry control panel is set up." - :type 'hook - :group 'ediff-mult) - -(defcustom ediff-before-session-group-setup-hooks nil - "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 restored -on `ediff-quit', `ediff-suspend', or `ediff-quit-session-group-hook'." - :type 'hook - :group 'ediff-hook) -(defcustom ediff-after-session-group-setup-hook nil - "Hooks run just after a meta-buffer controlling a session group, such as -ediff-directories, is run." - :type 'hook - :group 'ediff-mult) -(defcustom ediff-quit-session-group-hook nil - "Hooks run just before exiting a session group." - :type 'hook - :group 'ediff-mult) -(defcustom ediff-show-registry-hook nil - "Hooks run just after the registry buffer is shown." - :type 'hook - :group 'ediff-mult) -(defcustom ediff-show-session-group-hook '(delete-other-windows) - "Hooks run just after a session group buffer is shown." - :type 'hook - :group 'ediff-mult) -(defcustom ediff-meta-buffer-keymap-setup-hook nil - "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 meta -buffers." - :type 'hook - :group 'ediff-mult) - -;; Buffer holding the multi-file patch. Local to the meta buffer -(ediff-defvar-local ediff-meta-patchbufer nil "") - -;;; 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 -(defsubst ediff-get-group-buffer (meta-list) - (nth 0 (car meta-list))) - -(defsubst ediff-get-group-regexp (meta-list) - (nth 1 (car meta-list))) -;; group objects -(defsubst ediff-get-group-objA (meta-list) - (nth 2 (car meta-list))) -(defsubst ediff-get-group-objB (meta-list) - (nth 3 (car meta-list))) -(defsubst ediff-get-group-objC (meta-list) - (nth 4 (car meta-list))) -(defsubst ediff-get-group-merge-autostore-dir (meta-list) - (nth 5 (car meta-list))) -(defsubst ediff-get-group-comparison-func (meta-list) - (nth 6 (car meta-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 -(defsubst ediff-get-session-buffer (elt) - (nth 0 elt)) -(defsubst ediff-get-session-status (elt) - (nth 1 elt)) -(defsubst ediff-set-session-status (session-info new-status) - (setcar (cdr session-info) new-status)) -;; session objects -(defsubst ediff-get-session-objA (elt) - (nth 2 elt)) -(defsubst ediff-get-session-objB (elt) - (nth 3 elt)) -(defsubst ediff-get-session-objC (elt) - (nth 4 elt)) -;; Take the "name" component of the object into acount. ObjA/C/B is of the form -;; (name . equality-indicator) -(defsubst ediff-get-session-objA-name (elt) - (car (nth 2 elt))) -(defsubst ediff-get-session-objB-name (elt) - (car (nth 3 elt))) -(defsubst ediff-get-session-objC-name (elt) - (car (nth 4 elt))) -;; equality indicators -(defsubst ediff-get-file-eqstatus (elt) - (nth 1 elt)) -(defsubst ediff-set-file-eqstatus (elt value) - (setcar (cdr elt) 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 `='. -;; Currently, the eq-indicator is used only for 2 and 3-file jobs. -(defun ediff-make-new-meta-list-element (obj1 obj2 obj3) - (list nil nil (list obj1 nil) (list obj2 nil) (list obj3 nil))) - -;; 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. -(defun ediff-make-new-meta-list-header (regexp - objA objB objC - merge-auto-store-dir - comparison-func) - (list regexp objA objB objC merge-auto-store-dir comparison-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 -(defun ediff-get-session-activity-marker (session) - (let ((session-buf (ediff-get-session-buffer session))) - (cond ((null session-buf) nil) ; virgin session - ((ediff-buffer-live-p session-buf) ?+) ;active session - (t ?-)))) - -;; checks if the session is a meta session -(defun ediff-meta-session-p (session-info) - (and (stringp (ediff-get-session-objA-name session-info)) - (file-directory-p (ediff-get-session-objA-name session-info)) - (stringp (ediff-get-session-objB-name session-info)) - (file-directory-p (ediff-get-session-objB-name session-info)) - (if (stringp (ediff-get-session-objC-name session-info)) - (file-directory-p (ediff-get-session-objC-name session-info)) t))) - - -(ediff-defvar-local ediff-verbose-help-enabled nil - "If t, display redundant help in ediff-directories and other meta buffers. -Toggled by ediff-toggle-verbose-help-meta-buffer" ) - -;; Toggle verbose help in meta-buffers -;; TODO: Someone who understands all this can make it better. -(defun ediff-toggle-verbose-help-meta-buffer () - "Toggle showing tediously verbose help in meta buffers." - (interactive) - (setq ediff-verbose-help-enabled (not ediff-verbose-help-enabled)) - (ediff-update-meta-buffer (current-buffer) 'must-redraw)) - -;; set up the keymap in the meta buffer -(defun ediff-setup-meta-map () - (setq ediff-meta-buffer-map (make-sparse-keymap)) - (suppress-keymap ediff-meta-buffer-map) - (define-key ediff-meta-buffer-map "?" 'ediff-toggle-verbose-help-meta-buffer) - (define-key ediff-meta-buffer-map "q" 'ediff-quit-meta-buffer) - (define-key ediff-meta-buffer-map "T" 'ediff-toggle-filename-truncation) - (define-key ediff-meta-buffer-map "R" 'ediff-show-registry) - (define-key ediff-meta-buffer-map "E" 'ediff-documentation) - (define-key ediff-meta-buffer-map "v" ediff-meta-action-function) - (define-key ediff-meta-buffer-map "\C-m" ediff-meta-action-function) - (define-key ediff-meta-buffer-map " " 'ediff-next-meta-item) - (define-key ediff-meta-buffer-map "n" 'ediff-next-meta-item) - (define-key ediff-meta-buffer-map "\C-?" 'ediff-previous-meta-item) - (define-key ediff-meta-buffer-map "p" 'ediff-previous-meta-item) - (define-key ediff-meta-buffer-map [delete] 'ediff-previous-meta-item) - (define-key ediff-meta-buffer-map [backspace] 'ediff-previous-meta-item) - - (let ((menu-map (make-sparse-keymap "Ediff-Meta"))) - (define-key ediff-meta-buffer-map [menu-bar ediff-meta-mode] - (cons "Ediff-Meta" menu-map)) - (define-key menu-map [ediff-quit-meta-buffer] - '(menu-item "Quit" ediff-quit-meta-buffer - :help "Quit the meta buffer")) - (define-key menu-map [ediff-toggle-filename-truncation] - '(menu-item "Truncate filenames" ediff-toggle-filename-truncation - :help "Toggle truncation of long file names in session group buffers" - :button (:toggle . ediff-meta-truncate-filenames))) - (define-key menu-map [ediff-show-registry] - '(menu-item "Display Ediff Registry" ediff-show-registry - :help "Display Ediff's registry")) - (define-key menu-map [ediff-documentation] - '(menu-item "Show Manual" ediff-documentation - :help "Display Ediff's manual")) - - (or (ediff-one-filegroup-metajob) - (progn - (define-key ediff-meta-buffer-map "=" nil) - (define-key ediff-meta-buffer-map "==" 'ediff-meta-mark-equal-files) - (define-key ediff-meta-buffer-map "=m" 'ediff-meta-mark-equal-files) - (define-key ediff-meta-buffer-map "=h" 'ediff-meta-mark-equal-files))) - - - (define-key menu-map [ediff-next-meta-item] - '(menu-item "Next" ediff-next-meta-item - :help "Move to the next item in Ediff registry or session group buffer")) - (define-key menu-map [ediff-previous-meta-item] - '(menu-item "Previous" ediff-previous-meta-item - :help "Move to the previous item in Ediff registry or session group buffer"))) - - - (if ediff-no-emacs-help-in-control-buffer - (define-key ediff-meta-buffer-map "\C-h" 'ediff-previous-meta-item)) - (if (featurep 'emacs) - (define-key ediff-meta-buffer-map [mouse-2] ediff-meta-action-function) - (define-key ediff-meta-buffer-map [button2] ediff-meta-action-function)) - - (use-local-map ediff-meta-buffer-map) - ;; modify ediff-meta-buffer-map here - (run-hooks 'ediff-meta-buffer-keymap-setup-hook)) - - -(defun ediff-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) - (setq major-mode 'ediff-meta-mode) - (setq mode-name "MetaEdiff") - ;; don't use run-mode-hooks here! - (run-hooks 'ediff-meta-mode-hook)) - - -;; the keymap for the buffer showing directory differences -(suppress-keymap ediff-dir-diffs-buffer-map) -(define-key ediff-dir-diffs-buffer-map "q" 'ediff-bury-dir-diffs-buffer) -(define-key ediff-dir-diffs-buffer-map " " 'next-line) -(define-key ediff-dir-diffs-buffer-map "n" 'next-line) -(define-key ediff-dir-diffs-buffer-map "\C-?" 'previous-line) -(define-key ediff-dir-diffs-buffer-map "p" 'previous-line) -(define-key ediff-dir-diffs-buffer-map "C" 'ediff-dir-diff-copy-file) -(if (featurep 'emacs) - (define-key ediff-dir-diffs-buffer-map [mouse-2] 'ediff-dir-diff-copy-file) - (define-key ediff-dir-diffs-buffer-map [button2] 'ediff-dir-diff-copy-file)) -(define-key ediff-dir-diffs-buffer-map [delete] 'previous-line) -(define-key ediff-dir-diffs-buffer-map [backspace] 'previous-line) - -(defun ediff-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") - (or count (setq count 1)) - (let (overl) - (while (< 0 count) - (setq count (1- count)) - (ediff-next-meta-item1) - (setq overl (ediff-get-meta-overlay-at-pos (point))) - ;; skip invisible ones - (while (and overl (ediff-overlay-get overl 'invisible)) - (ediff-next-meta-item1) - (setq overl (ediff-get-meta-overlay-at-pos (point))))))) - -;; Move to the next meta item -(defun ediff-next-meta-item1 () - (let (pos) - (setq pos (ediff-next-meta-overlay-start (point))) - (if pos (goto-char pos)) - (if (eq ediff-metajob-name 'ediff-registry) - (if (and (ediff-get-meta-info (current-buffer) pos 'noerror) - (search-forward "*Ediff" nil t)) - (skip-chars-backward "a-zA-Z*")) - (if (> (skip-chars-forward "-+?H* \t0-9") 0) - (backward-char 1))))) - - -(defun ediff-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") - (or count (setq count 1)) - (let (overl) - (while (< 0 count) - (setq count (1- count)) - (ediff-previous-meta-item1) - (setq overl (ediff-get-meta-overlay-at-pos (point))) - ;; skip invisible ones - (while (and overl (ediff-overlay-get overl 'invisible)) - (ediff-previous-meta-item1) - (setq overl (ediff-get-meta-overlay-at-pos (point))))))) - -(defun ediff-previous-meta-item1 () - (let (pos) - (setq pos (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))) - - (if pos (goto-char pos)) - (if (eq ediff-metajob-name 'ediff-registry) - (if (and (ediff-get-meta-info (current-buffer) pos 'noerror) - (search-forward "*Ediff" nil t)) - (skip-chars-backward "a-zA-Z*")) - (if (> (skip-chars-forward "-+?H* \t0-9") 0) - (backward-char 1))) - )) - -(defsubst ediff-add-slash-if-directory (dir file) - (if (file-directory-p (concat dir file)) - (file-name-as-directory file) - file)) - -(defun ediff-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 default -behavior." - (interactive) - (setq ediff-meta-truncate-filenames (not ediff-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 -(defvar ediff-membership-code1 2) -(defvar ediff-membership-code2 3) -(defvar ediff-membership-code3 5) -(defvar ediff-product-of-memcodes (* ediff-membership-code1 - ediff-membership-code2 - ediff-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 nil or a filter regexp; only file names that match the regexp -;; are considered. -;; 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. -;; -(defun ediff-intersect-directories (jobname - regexp dir1 dir2 - &optional - dir3 merge-autostore-dir comparison-func) - (setq comparison-func (or comparison-func 'string=)) - (let (lis1 lis2 lis3 common auxdir1 auxdir2 auxdir3 common-part difflist) - - (setq auxdir1 (file-name-as-directory dir1) - lis1 (directory-files auxdir1 nil regexp) - lis1 (delete "." lis1) - lis1 (delete ".." lis1) - lis1 (mapcar - (lambda (elt) - (ediff-add-slash-if-directory auxdir1 elt)) - lis1) - auxdir2 (file-name-as-directory dir2) - lis2 (directory-files auxdir2 nil regexp) - lis2 (delete "." lis2) - lis2 (delete ".." lis2) - lis2 (mapcar - (lambda (elt) - (ediff-add-slash-if-directory auxdir2 elt)) - lis2)) - - (if (stringp dir3) - (setq auxdir3 (file-name-as-directory dir3) - lis3 (directory-files auxdir3 nil regexp) - lis3 (delete "." lis3) - lis3 (delete ".." lis3) - lis3 (mapcar - (lambda (elt) - (ediff-add-slash-if-directory auxdir3 elt)) - lis3))) - - (if (ediff-nonempty-string-p merge-autostore-dir) - (setq merge-autostore-dir - (file-name-as-directory merge-autostore-dir))) - (setq common (ediff-intersection lis1 lis2 comparison-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 (and lis3 (ediff-comparison-metajob3 jobname)) - (setq common (ediff-intersection common lis3 comparison-func))) - - ;; copying is needed because sort sorts via side effects - (setq common (sort (ediff-copy-list common) 'string-lessp)) - - ;; compute difference list - (setq difflist (ediff-set-difference - (ediff-union (ediff-union lis1 lis2 comparison-func) - lis3 - comparison-func) - common - comparison-func) - difflist (delete "." difflist) - ;; copying is needed because sort sorts via side effects - difflist (sort (ediff-copy-list (delete ".." difflist)) - 'string-lessp)) - - (setq difflist (mapcar (lambda (elt) (cons elt 1)) 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. - (mapc (lambda (elt) - (if (member (car elt) lis1) - (setcdr elt (* (cdr elt) ediff-membership-code1))) - (if (member (car elt) lis2) - (setcdr elt (* (cdr elt) ediff-membership-code2))) - (if (member (car elt) lis3) - (setcdr elt (* (cdr elt) ediff-membership-code3))) - ) - difflist) - (setq difflist (cons - ;; diff metalist header - (ediff-make-new-meta-list-header regexp - auxdir1 auxdir2 auxdir3 - merge-autostore-dir - comparison-func) - difflist)) - - (setq common-part - (cons - ;; metalist header - (ediff-make-new-meta-list-header regexp - auxdir1 auxdir2 auxdir3 - merge-autostore-dir - comparison-func) - (mapcar - (lambda (elt) - (ediff-make-new-meta-list-element - (expand-file-name (concat auxdir1 elt)) - (expand-file-name (concat auxdir2 elt)) - (if lis3 - (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. - (setq elt (ediff-add-slash-if-directory auxdir3 elt)) - (if (file-exists-p (concat auxdir3 elt)) - (expand-file-name (concat auxdir3 elt))))))) - common))) - ;; return result - (cons common-part difflist) - )) - -;; 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. -(defun ediff-get-directory-files-under-revision (jobname - regexp dir1 - &optional merge-autostore-dir) - (let (lis1 elt common auxdir1) - (setq auxdir1 (file-name-as-directory dir1) - lis1 (directory-files auxdir1 nil regexp)) - - (if (ediff-nonempty-string-p merge-autostore-dir) - (setq merge-autostore-dir - (file-name-as-directory merge-autostore-dir))) - - (while lis1 - (setq elt (car lis1) - lis1 (cdr lis1)) - ;; take files under revision control - (cond ((file-directory-p (concat auxdir1 elt)) - (setq common - (cons (ediff-add-slash-if-directory auxdir1 elt) common))) - ((and (featurep 'vc-hooks) (vc-backend (concat auxdir1 elt))) - (setq common (cons elt common))) - ;; The following two are needed only if vc-hooks isn't loaded. - ;; They won't recognize CVS files. - ((file-exists-p (concat auxdir1 elt ",v")) - (setq common (cons elt common))) - ((file-exists-p (concat auxdir1 "RCS/" elt ",v")) - (setq common (cons elt common))) - ) ; cond - ) ; while - - (setq common (delete "./" common) - common (delete "../" common) - common (delete "RCS" common) - common (delete "CVS" common) - ) - - ;; copying is needed because sort sorts via side effects - (setq common (sort (ediff-copy-list common) 'string-lessp)) - - ;; return result - (cons - ;; header -- has 6 elements. Meta buffer is prepended later by - ;; ediff-prepare-meta-buffer - (ediff-make-new-meta-list-header regexp - auxdir1 nil nil - merge-autostore-dir nil) - (mapcar (lambda (elt) (ediff-make-new-meta-list-element - (expand-file-name (concat auxdir1 elt)) nil nil)) - 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. -(defun ediff-prepare-meta-buffer (action-func meta-list - meta-buffer-name redraw-function - jobname &optional startup-hooks) - (let* ((meta-buffer-name - (ediff-unique-buffer-name meta-buffer-name "*")) - (meta-buffer (get-buffer-create meta-buffer-name))) - (ediff-with-current-buffer meta-buffer - - ;; comes first - (ediff-meta-mode) - - (setq ediff-meta-action-function action-func - ediff-meta-redraw-function redraw-function - ediff-metajob-name jobname - ediff-meta-buffer meta-buffer) - - ;; comes after ediff-meta-action-function is set - (ediff-setup-meta-map) - - (if (eq ediff-metajob-name 'ediff-registry) - (progn - (setq ediff-registry-buffer meta-buffer - ediff-meta-list meta-list) - ;; this func is used only from registry buffer, not from other - ;; meta-buffs. - (define-key - ediff-meta-buffer-map "M" 'ediff-show-meta-buff-from-registry)) - ;; Initialize the meta list -- we don't do this for registry. - (setq ediff-meta-list - ;; add meta-buffer to the list header - (cons (cons meta-buffer (car meta-list)) - (cdr meta-list)))) - - (or (eq meta-buffer ediff-registry-buffer) - (setq ediff-session-registry - (cons meta-buffer ediff-session-registry))) - - ;; redraw-function uses ediff-meta-list - (funcall redraw-function ediff-meta-list) - - ;; set read-only/non-modified - (setq buffer-read-only t) - (set-buffer-modified-p nil) - - (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 (eq action-func 'ediff-filegroup-action) - (progn - ;; put meta buffer in (car ediff-dir-difference-list) - (setq ediff-dir-difference-list - (cons (cons meta-buffer (car ediff-dir-difference-list)) - (cdr ediff-dir-difference-list))) - - (or (ediff-one-filegroup-metajob jobname) - (ediff-draw-dir-diffs ediff-dir-difference-list)) - (define-key - ediff-meta-buffer-map "h" 'ediff-mark-for-hiding-at-pos) - (define-key ediff-meta-buffer-map "x" 'ediff-hide-marked-sessions) - (define-key - ediff-meta-buffer-map "m" 'ediff-mark-for-operation-at-pos) - (define-key ediff-meta-buffer-map "u" nil) - (define-key - ediff-meta-buffer-map "um" 'ediff-unmark-all-for-operation) - (define-key - ediff-meta-buffer-map "uh" 'ediff-unmark-all-for-hiding) - - (define-key ediff-meta-buffer-map - [menu-bar ediff-meta-mode ediff-hide-marked-sessions] - '(menu-item "Hide marked" ediff-hide-marked-sessions - :help "Hide marked sessions. With prefix arg, unhide")) - - (define-key ediff-meta-buffer-map - [menu-bar ediff-meta-mode ediff-mark-for-hiding-at-pos] - '(menu-item "Mark for hiding" ediff-mark-for-hiding-at-pos - :help "Mark session for hiding. With prefix arg, unmark")) - - (define-key ediff-meta-buffer-map - [menu-bar ediff-meta-mode ediff-mark-for-operation-at-pos] - '(menu-item "Mark for group operation" ediff-mark-for-operation-at-pos - :help "Mark session for a group operation. With prefix arg, unmark")) - - (define-key ediff-meta-buffer-map - [menu-bar ediff-meta-mode ediff-unmark-all-for-hiding] - '(menu-item "Unmark all for hiding" ediff-unmark-all-for-hiding - :help "Unmark all sessions marked for hiding")) - - (define-key ediff-meta-buffer-map - [menu-bar ediff-meta-mode ediff-unmark-all-for-operation] - '(menu-item "Unmark all for group operation" ediff-unmark-all-for-operation - :help "Unmark all sessions marked for operation")) - - (cond ((ediff-collect-diffs-metajob jobname) - (define-key ediff-meta-buffer-map - [menu-bar ediff-meta-mode ediff-collect-custom-diffs] - '(menu-item "Collect diffs" ediff-collect-custom-diffs - :help "Collect custom diffs of marked sessions in buffer `*Ediff Multifile Diffs*'")) - (define-key - ediff-meta-buffer-map "P" 'ediff-collect-custom-diffs)) - ((ediff-patch-metajob jobname) - (define-key ediff-meta-buffer-map - [menu-bar ediff-meta-mode ediff-meta-show-patch] - '(menu-item "Show multi-file patch" ediff-meta-show-patch - :help "Show the multi-file patch associated with this group session")) - (define-key - ediff-meta-buffer-map "P" 'ediff-meta-show-patch))) - (define-key ediff-meta-buffer-map "^" 'ediff-up-meta-hierarchy) - (define-key ediff-meta-buffer-map "D" 'ediff-show-dir-diffs) - - (define-key ediff-meta-buffer-map - [menu-bar ediff-meta-mode ediff-up-meta-hierarchy] - '(menu-item "Go to parent session" ediff-up-meta-hierarchy - :help "Go to the parent session group buffer")) - - (define-key ediff-meta-buffer-map - [menu-bar ediff-meta-mode ediff-show-dir-diffs] - '(menu-item "Diff directories" ediff-show-dir-diffs - :help "Display differences among the directories involved in session group")))) - - (if (eq ediff-metajob-name 'ediff-registry) - (run-hooks 'ediff-registry-setup-hook) - (run-hooks 'ediff-after-session-group-setup-hook)) - ) ; eval in meta-buffer - meta-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) -(defun ediff-insert-session-activity-marker-in-meta-buffer (session) - (insert - (cond ((ediff-get-session-activity-marker session)) - ;; 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) -(defun ediff-insert-session-status-in-meta-buffer (session) - (insert - (cond ((ediff-get-session-status session)) ; 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 -(defun ediff-replace-session-activity-marker-in-meta-buffer (point new-marker) - (let* ((overl (ediff-get-meta-overlay-at-pos point)) - (session-info (ediff-overlay-get overl 'ediff-meta-info)) - (activity-marker (ediff-get-session-activity-marker session-info)) - buffer-read-only) - (or new-marker activity-marker (setq new-marker ?\s)) - (goto-char (ediff-overlay-start overl)) - (if (eq (char-after (point)) new-marker) - () ; if marker shown in buffer is the same as new-marker, do nothing - (insert new-marker) - (delete-char 1) - (set-buffer-modified-p nil)))) - -;; If NEW-STATUS is non-nil, use it to substitute the current status marker in -;; the meta buffer. If nil, use SPC -(defun ediff-replace-session-status-in-meta-buffer (point new-status) - (let* ((overl (ediff-get-meta-overlay-at-pos point)) - (session-info (ediff-overlay-get overl 'ediff-meta-info)) - (status (ediff-get-session-status session-info)) - buffer-read-only) - (setq new-status (or new-status status ?\s)) - (goto-char (ediff-overlay-start overl)) - (forward-char 1) ; 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 - (insert new-status) - (delete-char 1) - (set-buffer-modified-p nil)))) - -;; insert all file info in meta buffer for a given session -(defun ediff-insert-session-info-in-meta-buffer (session-info sessionNum) - (let ((f1 (ediff-get-session-objA session-info)) - (f2 (ediff-get-session-objB session-info)) - (f3 (ediff-get-session-objC session-info)) - (pt (point)) - (hidden (eq (ediff-get-session-status session-info) ?I))) - ;; insert activity marker, i.e., SPC, - or + - (ediff-insert-session-activity-marker-in-meta-buffer session-info) - ;; insert session status, i.e., *, H - (ediff-insert-session-status-in-meta-buffer session-info) - (insert " Session " (int-to-string sessionNum) ":\n") - (ediff-meta-insert-file-info1 f1) - (ediff-meta-insert-file-info1 f2) - (ediff-meta-insert-file-info1 f3) - (ediff-set-meta-overlay pt (point) session-info sessionNum hidden))) - - -;; this is a setup function for ediff-directories -;; must return meta-buffer -(defun ediff-redraw-directory-group-buffer (meta-list) - ;; extract directories - (let ((meta-buf (ediff-get-group-buffer meta-list)) - (empty t) - (sessionNum 0) - regexp elt merge-autostore-dir - point tmp-list buffer-read-only) - (ediff-with-current-buffer meta-buf - (setq point (point)) - (erase-buffer) - ;; delete phony overlays that used to represent sessions before the buff - ;; was redrawn - (if (featurep 'xemacs) - (map-extents 'delete-extent) - (mapc 'delete-overlay (overlays-in 1 1))) - - (setq regexp (ediff-get-group-regexp meta-list) - merge-autostore-dir - (ediff-get-group-merge-autostore-dir meta-list)) - - (if ediff-verbose-help-enabled - (progn - (insert (format ediff-meta-buffer-verbose-message - (ediff-abbrev-jobname ediff-metajob-name))) - - (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 sessions for hiding\n" - " =m:\tlike ==, but also marks sessions for operation\n\n"))) - (insert (format ediff-meta-buffer-brief-message - (ediff-abbrev-jobname ediff-metajob-name)))) - - (insert "\n") - (if (and (stringp regexp) (> (length regexp) 0)) - (insert - (format "*** Filter-through regular expression: %s\n" regexp))) - (ediff-insert-dirs-in-meta-buffer meta-list) - (if (and ediff-autostore-merges (ediff-merge-metajob) - (ediff-nonempty-string-p merge-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 - (setq meta-list (cdr meta-list) - tmp-list meta-list) - (while (and tmp-list empty) - (if (and (car tmp-list) - (not (eq (ediff-get-session-status (car tmp-list)) ?I))) - (setq empty nil)) - (setq tmp-list (cdr tmp-list))) - - (if empty - (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. - (while meta-list - (setq elt (car meta-list) - meta-list (cdr meta-list) - sessionNum (1+ sessionNum)) - (if (eq (ediff-get-session-status elt) ?I) - () - (ediff-insert-session-info-in-meta-buffer elt sessionNum))) - (set-buffer-modified-p nil) - (goto-char point) - meta-buf))) - -(defun ediff-update-markers-in-dir-meta-buffer (meta-list) - (let ((meta-buf (ediff-get-group-buffer meta-list)) - session-info point overl buffer-read-only) - (ediff-with-current-buffer meta-buf - (setq point (point)) - (goto-char (point-min)) - (ediff-next-meta-item1) - (while (not (bobp)) - (setq session-info (ediff-get-meta-info meta-buf (point) 'no-error) - overl (ediff-get-meta-overlay-at-pos (point))) - (if session-info - (progn - (cond ((eq (ediff-get-session-status session-info) ?I) - ;; Do hiding - (if overl (ediff-overlay-put overl 'invisible t))) - ((and (eq (ediff-get-session-status session-info) ?H) - overl (ediff-overlay-get overl 'invisible)) - ;; Do unhiding - (ediff-overlay-put overl 'invisible nil)) - (t (ediff-replace-session-activity-marker-in-meta-buffer - (point) - (ediff-get-session-activity-marker session-info)) - (ediff-replace-session-status-in-meta-buffer - (point) - (ediff-get-session-status session-info)))))) - (ediff-next-meta-item1) ; advance to the next item - ) ; end while - (set-buffer-modified-p nil) - (goto-char point)) - meta-buf)) - -(defun ediff-update-session-marker-in-dir-meta-buffer (session-num) - (let (buffer-meta-overlays session-info overl buffer-read-only) - (setq overl - (if (featurep 'xemacs) - (map-extents - (lambda (ext maparg) - (if (and - (ediff-overlay-get ext 'ediff-meta-info) - (eq (ediff-overlay-get ext 'ediff-meta-session-number) - session-num)) - ext))) - ;; Emacs doesn't have map-extents, so try harder - ;; Splice overlay lists to get all buffer overlays - (setq buffer-meta-overlays (overlay-lists) - buffer-meta-overlays (append (car buffer-meta-overlays) - (cdr buffer-meta-overlays))) - (car - (delq nil - (mapcar - (lambda (overl) - (if (and - (ediff-overlay-get overl 'ediff-meta-info) - (eq (ediff-overlay-get - overl 'ediff-meta-session-number) - session-num)) - overl)) - buffer-meta-overlays))))) - (or overl - (error - "Bug in ediff-update-session-marker-in-dir-meta-buffer: no overlay with given number %S" - session-num)) - (setq session-info (ediff-overlay-get overl 'ediff-meta-info)) - (goto-char (ediff-overlay-start overl)) - (ediff-replace-session-activity-marker-in-meta-buffer - (point) - (ediff-get-session-activity-marker session-info)) - (ediff-replace-session-status-in-meta-buffer - (point) - (ediff-get-session-status session-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 -(defun ediff-problematic-session-p (session) - (let ((f1 (ediff-get-session-objA-name session)) - (f2 (ediff-get-session-objB-name session)) - (f3 (ediff-get-session-objC-name session))) - (cond ((and (stringp f1) (not (file-directory-p f1)) - (stringp f2) (not (file-directory-p f2)) - ;; either invalid file name or a directory - (or (not (stringp f3)) (file-directory-p f3)) - (ediff-ancestor-metajob)) - ;; more may be added later - 'ancestor-is-dir) - (t nil)))) - -(defun ediff-meta-insert-file-info1 (fileinfo) - (let ((fname (car fileinfo)) - (feq (ediff-get-file-eqstatus fileinfo)) - (max-filename-width (if ediff-meta-truncate-filenames - (- (window-width) 41) - 500)) - file-modtime file-size) - (cond ((not (stringp fname)) (setq file-size -2)) ; file doesn't exits - ((ediff-listable-file fname) - (if (file-exists-p fname) - ;; set real size and modtime - (setq file-size (ediff-file-size fname) - file-modtime (ediff-file-modtime fname)) - (setq file-size -2))) ; file doesn't exist - ( t (setq file-size -1))) ; remote file - (if (stringp fname) - (insert - (format - "%s %s %-20s %s\n" - (if feq "=" " ") ; equality indicator - (format "%10s" (cond ((= file-size -1) "--") - ((< file-size -1) "--") - (t file-size))) - (cond ((= file-size -1) "*remote file*") - ((< file-size -1) "*file doesn't exist*") - (t (ediff-format-date (decode-time file-modtime)))) - - ;; dir names in meta lists have training slashes, so we just - ;; abbreviate the file name, if file exists - (if (and (not (stringp fname)) (< file-size -1)) - "-------" ; file doesn't exist - (ediff-truncate-string-left - (ediff-abbreviate-file-name fname) - max-filename-width))))))) - -(defconst ediff-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 -(defsubst ediff-fill-leading-zero (num) - (if (< num 10) - (format "0%d" num) - (number-to-string num))) - -;; TIME is like the output of decode-time -(defun ediff-format-date (time) - (format "%s %2d %4d %s:%s:%s" - (cdr (assoc (nth 4 time) ediff-months)) ; month - (nth 3 time) ; day - (nth 5 time) ; year - (ediff-fill-leading-zero (nth 2 time)) ; hour - (ediff-fill-leading-zero (nth 1 time)) ; min - (ediff-fill-leading-zero (nth 0 time)) ; sec - )) - -;; Draw the directories -(defun ediff-insert-dirs-in-meta-buffer (meta-list) - (let* ((dir1 (ediff-abbreviate-file-name (ediff-get-group-objA meta-list))) - (dir2 (ediff-get-group-objB meta-list)) - (dir2 (if (stringp dir2) (ediff-abbreviate-file-name dir2))) - (dir3 (ediff-get-group-objC meta-list)) - (dir3 (if (stringp dir3) (ediff-abbreviate-file-name dir3)))) - (insert "*** Directory A: " dir1 "\n") - (if dir2 (insert "*** Directory B: " dir2 "\n")) - (if dir3 (insert "*** Directory C: " dir3 "\n")) - (insert "\n"))) - -(defun ediff-draw-dir-diffs (diff-list &optional buf-name) - (if (null diff-list) (error "Lost difference info on these directories")) - (setq buf-name - (or buf-name - (ediff-unique-buffer-name "*Ediff File Group Differences" "*"))) - (let* ((regexp (ediff-get-group-regexp diff-list)) - (dir1 (ediff-abbreviate-file-name (ediff-get-group-objA diff-list))) - (dir2 (ediff-abbreviate-file-name (ediff-get-group-objB diff-list))) - (dir3 (ediff-get-group-objC diff-list)) - (dir3 (if (stringp dir3) (ediff-abbreviate-file-name dir3))) - (meta-buf (ediff-get-group-buffer diff-list)) - (underline (make-string 26 ?-)) - file membership-code saved-point - buffer-read-only) - ;; skip the directory part - (setq diff-list (cdr diff-list)) - (setq ediff-dir-diffs-buffer (get-buffer-create buf-name)) - (ediff-with-current-buffer ediff-dir-diffs-buffer - (setq saved-point (point)) - (use-local-map ediff-dir-diffs-buffer-map) - (erase-buffer) - (setq ediff-meta-buffer meta-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)) - (if dir2 (insert (format "*** Directory B: %s\n" dir2))) - (if dir3 (insert (format "*** Directory C: %s\n" dir3))) - (if (and (stringp regexp) (> (length regexp) 0)) - (insert - (format "*** Filter-through regular expression: %s\n" regexp))) - (insert "\n") - (insert (format "\n%-27s%-26s" "Directory A" "Directory B")) - (if dir3 - (insert (format " %-25s\n" "Directory C")) - (insert "\n")) - (insert (format "%s%s" underline underline)) - (if (stringp dir3) - (insert (format "%s\n\n" underline)) - (insert "\n\n")) - - (if (null diff-list) - (insert "\n\t*** No differences ***\n")) - - (while diff-list - (setq file (car (car diff-list)) - membership-code (cdr (car diff-list)) - diff-list (cdr diff-list)) - (if (= (mod membership-code ediff-membership-code1) 0) ; dir1 - (let ((beg (point))) - (insert (format "%-27s" - (ediff-truncate-string-left - (ediff-abbreviate-file-name - (if (file-directory-p (concat dir1 file)) - (file-name-as-directory file) - file)) - 24))) - ;; format of meta info in the dir-diff-buffer: - ;; (filename-tail filename-full otherdir1 otherdir2 otherdir3) - (ediff-set-meta-overlay - beg (point) - (list meta-buf file (concat dir1 file) dir1 dir2 dir3))) - (insert (format "%-27s" "---"))) - (if (= (mod membership-code ediff-membership-code2) 0) ; dir2 - (let ((beg (point))) - (insert (format "%-26s" - (ediff-truncate-string-left - (ediff-abbreviate-file-name - (if (file-directory-p (concat dir2 file)) - (file-name-as-directory file) - file)) - 24))) - (ediff-set-meta-overlay - beg (point) - (list meta-buf file (concat dir2 file) dir1 dir2 dir3))) - (insert (format "%-26s" "---"))) - (if (stringp dir3) - (if (= (mod membership-code ediff-membership-code3) 0) ; dir3 - (let ((beg (point))) - (insert (format " %-25s" - (ediff-truncate-string-left - (ediff-abbreviate-file-name - (if (file-directory-p (concat dir3 file)) - (file-name-as-directory file) - file)) - 24))) - (ediff-set-meta-overlay - beg (point) - (list meta-buf file (concat dir3 file) dir1 dir2 dir3))) - (insert (format " %-25s" "---")))) - (insert "\n")) - (setq buffer-read-only t) - (set-buffer-modified-p nil) - (goto-char saved-point)) ; end eval in diff buffer - )) - -(defun ediff-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 ((buf ediff-meta-buffer) - wind) - (ediff-kill-buffer-carefully ediff-dir-diffs-buffer) - (if (setq wind (ediff-get-visible-buffer-window buf)) - (select-window wind) - (set-window-buffer (selected-window) buf)))) - -;; executes in dir session group buffer -;; show buffer differences -(defun ediff-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-p ediff-dir-diffs-buffer) - (ediff-draw-dir-diffs ediff-dir-difference-list)) - (let ((buf ediff-dir-diffs-buffer)) - (other-window 1) - (set-window-buffer (selected-window) buf) - (goto-char (point-min)))) - -;; Format of meta info in dir-diff-buffer: -;; (filename-tail filename-full otherdir1 otherdir2) -(defun ediff-dir-diff-copy-file () - "Copy file described at point to directories where this file is missing." - (interactive) - (let* ((pos (ediff-event-point last-command-event)) - (info (ediff-get-meta-info (current-buffer) pos 'noerror)) - (meta-buf (car info)) - (file-tail (nth 1 info)) - (file-abs (nth 2 info)) - (otherdir1 (nth 3 info)) - (otherfile1 (if otherdir1 (concat otherdir1 file-tail))) - (otherdir2 (nth 4 info)) - (otherfile2 (if otherdir2 (concat otherdir2 file-tail))) - (otherdir3 (nth 5 info)) - (otherfile3 (if otherdir3 (concat otherdir3 file-tail))) - meta-list dir-diff-list - ) - (if (null info) - (error "No file suitable for copying described at this location")) - (ediff-with-current-buffer meta-buf - (setq meta-list ediff-meta-list - dir-diff-list ediff-dir-difference-list)) - - ;; copy file to directories where it doesn't exist, update - ;; ediff-dir-difference-list and redisplay - (mapc - (lambda (otherfile-struct) - (let ((otherfile (car otherfile-struct)) - (file-mem-code (cdr otherfile-struct))) - (if otherfile - (or (file-exists-p otherfile) - (if (y-or-n-p - (format "Copy %s to %s? " file-abs otherfile)) - (let* ((file-diff-record (assoc file-tail dir-diff-list)) - (new-mem-code - (* (cdr file-diff-record) file-mem-code))) - (copy-file file-abs otherfile) - (setcdr file-diff-record new-mem-code) - (ediff-draw-dir-diffs dir-diff-list (buffer-name)) - (sit-for 0) - ;; 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-code ediff-product-of-memcodes) - (and (> new-mem-code ediff-membership-code3) - (null otherfile3))) - (delq file-diff-record dir-diff-list)) - )))) - )) - ;; 2,3,5 are numbers used to encode membership of a file in - ;; dir1/2/3. See ediff-intersect-directories. - (list (cons otherfile1 2) (cons otherfile2 3) (cons otherfile3 5))) - - (if (and (file-exists-p otherfile1) - (file-exists-p otherfile2) - (or (not otherfile3) (file-exists-p otherfile3))) - ;; update ediff-meta-list by direct modification - (nconc meta-list - (list (ediff-make-new-meta-list-element - (expand-file-name otherfile1) - (expand-file-name otherfile2) - (if otherfile3 - (expand-file-name otherfile3))))) - ) - (ediff-update-meta-buffer meta-buf 'must-redraw) - )) - -(defun ediff-up-meta-hierarchy () - "Go to the parent session group buffer." - (interactive) - (if (ediff-buffer-live-p ediff-parent-meta-buffer) - (ediff-show-meta-buffer - ediff-parent-meta-buffer ediff-meta-session-number) - (error "This session group has no parent"))) - - -;; argument is ignored -(defun ediff-redraw-registry-buffer (&optional ignore) - (ediff-with-current-buffer ediff-registry-buffer - (let ((point (point)) - elt bufAname bufBname bufCname cur-diff total-diffs pt - job-name meta-list registry-list buffer-read-only) - (erase-buffer) - ;; delete phony overlays that used to represent sessions before the buff - ;; was redrawn - (if (featurep 'xemacs) - (map-extents 'delete-extent) - (mapc 'delete-overlay (overlays-in 1 1))) - - (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 - (mapc (lambda (elt) - (if (not (ediff-buffer-live-p elt)) - (setq ediff-session-registry - (delq elt ediff-session-registry)))) - ediff-session-registry) - - (if (null ediff-session-registry) - (insert " ******* No active Ediff sessions *******\n")) - - (setq registry-list ediff-session-registry) - (while registry-list - (setq elt (car registry-list) - registry-list (cdr registry-list)) - - (if (ediff-buffer-live-p elt) - (if (ediff-with-current-buffer elt - (setq job-name ediff-metajob-name - meta-list ediff-meta-list) - (and ediff-metajob-name - (not (eq ediff-metajob-name 'ediff-registry)))) - (progn - (setq pt (point)) - (insert (format " *group*\t%s: %s\n" - (buffer-name elt) - (ediff-abbrev-jobname job-name))) - (insert (format "\t\t %s %s %s\n" - (ediff-abbreviate-file-name - (ediff-get-group-objA meta-list)) - (ediff-abbreviate-file-name - (if (stringp - (ediff-get-group-objB meta-list)) - (ediff-get-group-objB meta-list) - "")) - (ediff-abbreviate-file-name - (if (stringp - (ediff-get-group-objC meta-list)) - (ediff-get-group-objC meta-list) - "")))) - (ediff-set-meta-overlay pt (point) elt)) - (progn - (ediff-with-current-buffer elt - (setq bufAname (if (ediff-buffer-live-p ediff-buffer-A) - (buffer-name ediff-buffer-A) - "!!!killed buffer!!!") - bufBname (if (ediff-buffer-live-p ediff-buffer-B) - (buffer-name ediff-buffer-B) - "!!!killed buffer!!!") - bufCname (cond ((not (ediff-3way-job)) - "") - ((ediff-buffer-live-p ediff-buffer-C) - (buffer-name ediff-buffer-C)) - (t "!!!killed buffer!!!"))) - (setq total-diffs (format "%-4d" ediff-number-of-differences) - cur-diff - (cond ((= ediff-current-difference -1) " _") - ((= ediff-current-difference - ediff-number-of-differences) - " $") - (t (format - "%4d" (1+ ediff-current-difference)))) - job-name ediff-job-name)) - ;; back in the meta buf - (setq pt (point)) - (insert cur-diff "/" total-diffs "\t" - (buffer-name elt) - (format ": %s" (ediff-abbrev-jobname job-name))) - (insert - "\n\t\t " bufAname " " bufBname " " bufCname "\n") - (ediff-set-meta-overlay pt (point) elt)))) - ) ; while - (set-buffer-modified-p nil) - (goto-char point) - ))) - -;; 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) -(defun ediff-set-meta-overlay (b e prop &optional session-number hidden) - (let (overl) - (setq overl (ediff-make-overlay b e)) - (if (featurep 'emacs) - (ediff-overlay-put overl 'mouse-face 'highlight) - (ediff-overlay-put overl 'highlight t)) - (ediff-overlay-put overl 'ediff-meta-info prop) - (ediff-overlay-put overl 'invisible hidden) - (ediff-overlay-put overl 'follow-link t) - (if (numberp session-number) - (ediff-overlay-put overl 'ediff-meta-session-number session-number)))) - -(defun ediff-mark-for-hiding-at-pos (unmark) - "Mark session for hiding. With prefix arg, unmark." - (interactive "P") - (let* ((pos (ediff-event-point last-command-event)) - (meta-buf (ediff-event-buffer last-command-event)) - ;; ediff-get-meta-info gives error if meta-buf or pos are invalid - (info (ediff-get-meta-info meta-buf pos)) - (session-number (ediff-get-session-number-at-pos pos))) - (ediff-mark-session-for-hiding info unmark) - (ediff-next-meta-item 1) - (save-excursion - (ediff-update-meta-buffer meta-buf nil session-number)) - )) - -;; Returns whether session was marked or unmarked -(defun ediff-mark-session-for-hiding (info unmark) - (let ((session-buf (ediff-get-session-buffer info)) - ignore) - (cond ((eq unmark 'mark) (setq unmark nil)) - ((eq (ediff-get-session-status info) ?H) (setq unmark t)) - (unmark ; says unmark, but the marker is different from H - (setq ignore t))) - (cond (ignore) - (unmark (ediff-set-session-status info nil)) -;;; (if (ediff-buffer-live-p session-buf) -;;; (error "Can't hide active session, %s" (buffer-name session-buf))) - (t (ediff-set-session-status info ?H)))) - unmark) - - -(defun ediff-mark-for-operation-at-pos (unmark) - "Mark session for a group operation. With prefix arg, unmark." - (interactive "P") - (let* ((pos (ediff-event-point last-command-event)) - (meta-buf (ediff-event-buffer last-command-event)) - ;; ediff-get-meta-info gives error if meta-buf or pos are invalid - (info (ediff-get-meta-info meta-buf pos)) - (session-number (ediff-get-session-number-at-pos pos))) - (ediff-mark-session-for-operation info unmark) - (ediff-next-meta-item 1) - (save-excursion - (ediff-update-meta-buffer meta-buf nil session-number)) - )) - - -;; returns whether session was unmarked. -;; remember: this is a toggle op -(defun ediff-mark-session-for-operation (info unmark) - (let (ignore) - (cond ((eq unmark 'mark) (setq unmark nil)) - ((eq (ediff-get-session-status info) ?*) (setq unmark t)) - (unmark ; says unmark, but the marker is different from * - (setq ignore t))) - (cond (ignore) - (unmark (ediff-set-session-status info nil)) - (t (ediff-set-session-status info ?*)))) - unmark) - - -(defun ediff-hide-marked-sessions (unhide) - "Hide marked sessions. With prefix arg, unhide." - (interactive "P") - (let ((grp-buf (ediff-get-group-buffer ediff-meta-list)) - (meta-list (cdr ediff-meta-list)) - (from (if unhide ?I ?H)) - (to (if unhide ?H ?I)) - (numMarked 0) - active-sessions-exist session-buf elt) - (while meta-list - (setq elt (car meta-list) - meta-list (cdr meta-list) - session-buf (ediff-get-session-buffer elt)) - - (if (eq (ediff-get-session-status elt) from) - (progn - (setq numMarked (1+ numMarked)) - (if (and (eq to ?I) (buffer-live-p session-buf)) - ;; shouldn't hide active sessions - (setq active-sessions-exist t) - (ediff-set-session-status elt to))))) - (if (> numMarked 0) - (ediff-update-meta-buffer grp-buf 'must-redraw) - (beep) - (if unhide - (message "Nothing to reveal...") - (message "Nothing to hide..."))) - (if active-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 -(defun ediff-operate-on-marked-sessions (operation) - (let ((grp-buf (ediff-get-group-buffer ediff-meta-list)) - (meta-list (cdr ediff-meta-list)) - (marksym ?*) - (numMarked 0) - (sessionNum 0) - (diff-buffer ediff-meta-diff-buffer) - session-buf elt) - (while meta-list - (setq elt (car meta-list) - meta-list (cdr meta-list) - sessionNum (1+ sessionNum)) - (cond ((eq (ediff-get-session-status elt) marksym) - (save-excursion - (setq numMarked (1+ numMarked)) - (funcall operation elt sessionNum))) - ;; The following goes into a session represented by a subdirectory - ;; and applies operation to marked sessions there - ((and (ediff-meta-session-p elt) - (ediff-buffer-live-p - (setq session-buf (ediff-get-session-buffer elt)))) - (setq numMarked - (+ numMarked - (ediff-with-current-buffer session-buf - ;; pass meta-diff along - (setq ediff-meta-diff-buffer diff-buffer) - ;; collect diffs in child group - (ediff-operate-on-marked-sessions operation))))))) - (ediff-update-meta-buffer grp-buf 'must-redraw) ; just in case - numMarked - )) - -(defun ediff-append-custom-diff (session sessionNum) - (or (ediff-collect-diffs-metajob) - (error "Can't compute multifile patch in this context")) - (let ((session-buf (ediff-get-session-buffer session)) - (meta-diff-buff ediff-meta-diff-buffer) - (metajob ediff-metajob-name) - tmp-buf custom-diff-buf) - (if (ediff-buffer-live-p session-buf) - (ediff-with-current-buffer session-buf - (if (eq ediff-control-buffer session-buf) ; individual session - (progn - (ediff-compute-custom-diffs-maybe) - (setq custom-diff-buf ediff-custom-diff-buffer))))) - - (or (ediff-buffer-live-p meta-diff-buff) - (error "Ediff: something wrong--killed multiple diff's buffer")) - - (cond ((ediff-buffer-live-p custom-diff-buf) - ;; for live session buffers we do them first because the user may - ;; have changed them with respect to the underlying files - (with-current-buffer meta-diff-buff - (goto-char (point-max)) - (insert-buffer-substring custom-diff-buf) - (insert "\n"))) - ;; if ediff session is not live, run diff directly on the files - ((memq metajob '(ediff-directories - ediff-merge-directories - ediff-merge-directories-with-ancestor)) - ;; get diffs by calling shell command on ediff-custom-diff-program - (with-current-buffer - (setq tmp-buf (get-buffer-create ediff-tmp-buffer)) - (erase-buffer) - (shell-command - (format - "%s %s %s %s" - (shell-quote-argument ediff-custom-diff-program) - ediff-custom-diff-options - (shell-quote-argument (ediff-get-session-objA-name session)) - (shell-quote-argument (ediff-get-session-objB-name session)) - ) - t) - ) - (with-current-buffer meta-diff-buff - (goto-char (point-max)) - (insert-buffer-substring tmp-buf) - (insert "\n"))) - (t - (ediff-kill-buffer-carefully meta-diff-buff) - (error "Session %d compares versions of file. Such session must be active to enable multifile patch collection" sessionNum ))) - )) - -(defun ediff-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 produce -multifile patches. For `ediff-directory-revisions', we insist that -all marked sessions must be active." - (interactive) - (let ((coding-system-for-read ediff-coding-system-for-read)) - (or (ediff-buffer-live-p ediff-meta-diff-buffer) - (setq ediff-meta-diff-buffer - (get-buffer-create - (ediff-unique-buffer-name "*Ediff Multifile Diffs" "*")))) - (ediff-with-current-buffer ediff-meta-diff-buffer - (setq buffer-read-only nil) - (erase-buffer)) - (if (> (ediff-operate-on-marked-sessions 'ediff-append-custom-diff) 0) - ;; did something - (progn - (display-buffer ediff-meta-diff-buffer 'not-this-window) - (ediff-with-current-buffer ediff-meta-diff-buffer - (set-buffer-modified-p nil) - (setq buffer-read-only t)) - (if (fboundp 'diff-mode) - (with-current-buffer ediff-meta-diff-buffer - (diff-mode)))) - (beep) - (message "No marked sessions found")))) - -(defun ediff-meta-show-patch () - "Show the multi-file patch associated with this group session." - (interactive) - (let* ((pos (ediff-event-point last-command-event)) - (meta-buf (ediff-event-buffer last-command-event)) - (info (ediff-get-meta-info meta-buf pos 'noerror)) - (patchbuffer ediff-meta-patchbufer)) - (if (ediff-buffer-live-p patchbuffer) - (ediff-with-current-buffer patchbuffer - (save-restriction - (if (not info) - (widen) - (narrow-to-region - (ediff-get-session-objB-name info) - (ediff-get-session-objC-name info))) - (set-buffer (get-buffer-create ediff-tmp-buffer)) - (erase-buffer) - (insert-buffer-substring patchbuffer) - (goto-char (point-min)) - (display-buffer ediff-tmp-buffer 'not-this-window) - )) - (error "The patch buffer wasn't found")))) - - -;; This function executes in meta buffer. It knows where event happened. -(defun ediff-filegroup-action () - "Execute appropriate action for a selected session." - (interactive) - (let* ((pos (ediff-event-point last-command-event)) - (meta-buf (ediff-event-buffer last-command-event)) - ;; ediff-get-meta-info gives error if meta-buf or pos are invalid - (info (ediff-get-meta-info meta-buf pos)) - (session-buf (ediff-get-session-buffer info)) - (session-number (ediff-get-session-number-at-pos pos meta-buf)) - (default-regexp (eval ediff-default-filtering-regexp)) - merge-autostore-dir file1 file2 file3 regexp) - - (setq file1 (ediff-get-session-objA-name info) - file2 (ediff-get-session-objB-name info) - file3 (ediff-get-session-objC-name info)) - - ;; make sure we don't start on hidden sessions - ;; ?H means marked for hiding. ?I means invalid (hidden). - (if (memq (ediff-get-session-status info) '(?I)) - (progn - (beep) - (if (y-or-n-p "This session is marked as hidden, unmark? ") - (progn - (ediff-set-session-status info nil) - (ediff-update-meta-buffer meta-buf nil session-number)) - (error "Aborted")))) - - (ediff-with-current-buffer meta-buf - (setq merge-autostore-dir - (ediff-get-group-merge-autostore-dir ediff-meta-list)) - (goto-char pos) ; 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-p info) - ;; do ediff/ediff-merge on subdirectories - (if (ediff-buffer-live-p session-buf) - (ediff-show-meta-buffer session-buf) - (setq regexp - (read-string - (if (stringp default-regexp) - (format - "Filter through regular expression (default %s): " - default-regexp) - "Filter through regular expression: ") - nil - 'ediff-filtering-regexp-history - (eval ediff-default-filtering-regexp))) - (ediff-directories-internal - file1 file2 file3 regexp - ediff-session-action-function - ediff-metajob-name - ;; make it update (car info) after startup - `(list (lambda () - ;; child session group should know its parent - (setq ediff-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-p file1)) - (if (ediff-buffer-live-p session-buf) - (ediff-show-meta-buffer session-buf) - (setq regexp (read-string "Filter through regular expression: " - nil 'ediff-filtering-regexp-history)) - (ediff-directory-revisions-internal - file1 regexp - ediff-session-action-function ediff-metajob-name - ;; make it update (car info) after startup - `(list (lambda () - ;; child session group should know its parent and - ;; its number - (setq ediff-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-p session-buf) - (ediff-with-current-buffer session-buf - (setq ediff-mouse-pixel-position (mouse-pixel-position)) - (ediff-recenter 'no-rehighlight))) - - ((ediff-problematic-session-p info) - (beep) - (if (y-or-n-p - "This session has no ancestor. Merge without the ancestor? ") - (ediff-merge-files - file1 file2 - ;; 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) - (setq ediff-meta-buffer ,(current-buffer) - ediff-meta-session-number - ,session-number) - (setq ediff-merge-store-file - ,(if (ediff-nonempty-string-p - merge-autostore-dir) - (concat - merge-autostore-dir - ediff-merge-filename-prefix - (file-name-nondirectory file1)) - )) - ;; 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 - (funcall ediff-session-action-function - file1 - ;; 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) - (setq ediff-meta-buffer ,(current-buffer) - ediff-meta-session-number - ,session-number) - (setq ediff-merge-store-file - ,(if (ediff-nonempty-string-p - merge-autostore-dir) - (concat - merge-autostore-dir - ediff-merge-filename-prefix - (file-name-nondirectory file1))) ) - ;; 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 - (funcall ediff-session-action-function - file1 file2 - ;; 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) - (setq ediff-meta-buffer ,(current-buffer) - ediff-meta-session-number - ,session-number) - (setq ediff-merge-store-file - ,(if (ediff-nonempty-string-p - merge-autostore-dir) - (concat - merge-autostore-dir - ediff-merge-filename-prefix - (file-name-nondirectory file1))) ) - ;; 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 - (funcall ediff-session-action-function - file1 file2 file3 - ;; 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) - (setq ediff-merge-store-file - ,(if (ediff-nonempty-string-p - merge-autostore-dir) - (concat - merge-autostore-dir - ediff-merge-filename-prefix - (file-name-nondirectory file1))) ) - (setq ediff-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 - )) - -(defun ediff-registry-action () - "Switch to a selected session." - (interactive) - (let* ((pos (ediff-event-point last-command-event)) - (buf (ediff-event-buffer last-command-event)) - (ctl-buf (ediff-get-meta-info buf pos))) - - (if (ediff-buffer-live-p ctl-buf) - ;; check if this is ediff-control-buffer or ediff-meta-buffer - (if (ediff-with-current-buffer ctl-buf - (eq (key-binding "q") 'ediff-quit-meta-buffer)) - ;; it's a meta-buffer -- last action should just display it - (ediff-show-meta-buffer ctl-buf t) - ;; it's a session buffer -- invoke go back to session - (ediff-with-current-buffer ctl-buf - (setq ediff-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-buffer buf - (goto-char pos)) - )) - - -;; If session number is t, means don't update meta buffer -(defun ediff-show-meta-buffer (&optional meta-buf session-number) - "Show the session group buffer." - (interactive) - (run-hooks 'ediff-before-directory-setup-hooks) - (let (wind frame silent) - (if meta-buf (setq silent t)) - - (setq meta-buf (or meta-buf ediff-meta-buffer)) - (cond ((not (bufferp meta-buf)) - (error "This Ediff session is not part of a session group")) - ((not (ediff-buffer-live-p meta-buf)) - (error - "Can't find this session's group panel -- session itself is ok"))) - - (cond ((numberp session-number) - (ediff-update-meta-buffer meta-buf nil session-number)) - ;; if session-number is t, don't update - (session-number) - (t (ediff-cleanup-meta-buffer meta-buf))) - - (ediff-with-current-buffer meta-buf - (save-excursion - (cond ((setq wind (ediff-get-visible-buffer-window meta-buf)) - (or silent - (message - "Already showing the group panel for this session")) - (set-window-buffer wind meta-buf) - (select-window wind)) - ((window-live-p (setq wind ediff-window-C)) ;in merge--merge buf - (set-window-buffer ediff-window-C meta-buf) - (select-window wind)) - ((window-live-p (setq wind ediff-window-A)) - (set-window-buffer ediff-window-A meta-buf) - (select-window wind)) - ((window-live-p (setq wind ediff-window-B)) - (set-window-buffer ediff-window-B meta-buf) - (select-window wind)) - ((and - (setq wind - (ediff-get-visible-buffer-window ediff-registry-buffer)) - (ediff-window-display-p)) - (select-window wind) - (other-window 1) - (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 - (setq wind (ediff-get-visible-buffer-window meta-buf)))) - (progn - (setq frame (window-frame wind)) - (raise-frame frame) - (ediff-reset-mouse frame))) - (sit-for 0) ; 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) - )) - -(defun ediff-show-current-session-meta-buffer () - (interactive) - (ediff-show-meta-buffer nil ediff-meta-session-number)) - -(defun ediff-show-meta-buff-from-registry () - "Display the session group buffer for a selected session group." - (interactive) - (let* ((pos (ediff-event-point last-command-event)) - (meta-buf (ediff-event-buffer last-command-event)) - (info (ediff-get-meta-info meta-buf pos)) - (meta-or-session-buf info)) - (ediff-with-current-buffer meta-or-session-buf - (ediff-show-meta-buffer nil t)))) - -;;;###autoload -(defun ediff-show-registry () - "Display Ediff's registry." - (interactive) - (ediff-update-registry) - (if (not (ediff-buffer-live-p ediff-registry-buffer)) - (error "No active Ediff sessions or corrupted session registry")) - (let (wind frame) - ;; for some reason, point moves in ediff-registry-buffer, so we preserve it - ;; explicitly - (ediff-with-current-buffer ediff-registry-buffer - (save-excursion - (cond ((setq wind - (ediff-get-visible-buffer-window ediff-registry-buffer)) - (message "Already showing the registry") - (set-window-buffer wind ediff-registry-buffer) - (select-window wind)) - ((window-live-p ediff-window-C) - (set-window-buffer ediff-window-C ediff-registry-buffer) - (select-window ediff-window-C)) - ((window-live-p ediff-window-A) - (set-window-buffer ediff-window-A ediff-registry-buffer) - (select-window ediff-window-A)) - ((window-live-p ediff-window-B) - (set-window-buffer ediff-window-B ediff-registry-buffer) - (select-window ediff-window-B)) - ((and (setq wind - (ediff-get-visible-buffer-window ediff-meta-buffer)) - (ediff-window-display-p)) - (select-window wind) - (other-window 1) - (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 - (setq frame - (window-frame - (ediff-get-visible-buffer-window ediff-registry-buffer))) - (raise-frame frame) - (ediff-reset-mouse frame))) - (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. -(defun ediff-update-meta-buffer (meta-buf &optional must-redraw session-number) - (if (ediff-buffer-live-p meta-buf) - (ediff-with-current-buffer meta-buf - (let (overl) - (cond (must-redraw ; completely redraw the meta buffer - (funcall ediff-meta-redraw-function ediff-meta-list)) - ((numberp session-number) ; redraw only for the given session - (ediff-update-session-marker-in-dir-meta-buffer - session-number)) - (t ; update what changed only, but scan the entire meta buffer - (ediff-update-markers-in-dir-meta-buffer ediff-meta-list))) - (setq overl (ediff-get-meta-overlay-at-pos (point))) - ;; skip the invisible sessions - (while (and overl (ediff-overlay-get overl 'invisible)) - (ediff-next-meta-item1) - (setq overl (ediff-get-meta-overlay-at-pos (point)))) - )))) - -(defun ediff-update-registry () - (ediff-with-current-buffer (current-buffer) - (if (ediff-buffer-live-p ediff-registry-buffer) - (ediff-redraw-registry-buffer) - (ediff-prepare-meta-buffer - 'ediff-registry-action - ediff-session-registry - "*Ediff Registry" - 'ediff-redraw-registry-buffer - 'ediff-registry)) - )) - -;; If meta-buf exists, it is redrawn along with parent. -;; Otherwise, nothing happens. -(defun ediff-cleanup-meta-buffer (meta-buffer) - (if (ediff-buffer-live-p meta-buffer) - (ediff-with-current-buffer meta-buffer - (ediff-update-meta-buffer meta-buffer) - (if (ediff-buffer-live-p ediff-parent-meta-buffer) - (ediff-update-meta-buffer - ediff-parent-meta-buffer nil ediff-meta-session-number))))) - -;; t if no session is in progress -(defun ediff-safe-to-quit (meta-buffer) - (if (ediff-buffer-live-p meta-buffer) - (let ((lis ediff-meta-list) - (cont t) - buffer-read-only) - ;;(ediff-update-meta-buffer meta-buffer) - (ediff-with-current-buffer meta-buffer - (setq lis (cdr lis)) ; discard the description part of meta-list - (while (and cont lis) - (if (ediff-buffer-live-p - (ediff-get-group-buffer lis)) ; in progress - (setq cont nil)) - (setq lis (cdr lis))) - cont)))) - -(defun ediff-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-buffer ediff-dir-diffs-buffer) - (meta-diff-buffer ediff-meta-diff-buffer) - (session-number ediff-meta-session-number) - (parent-buf ediff-parent-meta-buffer) - (dont-show-registry (eq buf ediff-registry-buffer))) - (if dont-show-registry - (bury-buffer) - ;;(ediff-cleanup-meta-buffer buf) - (cond ((and (ediff-safe-to-quit buf) - (y-or-n-p "Quit this session group? ")) - (run-hooks 'ediff-quit-session-group-hook) - (message "") - (ediff-dispose-of-meta-buffer buf)) - ((ediff-safe-to-quit buf) - (bury-buffer)) - (t - (error - "This session group has active sessions---cannot exit"))) - (ediff-update-meta-buffer parent-buf nil session-number) - (ediff-kill-buffer-carefully dir-diffs-buffer) - (ediff-kill-buffer-carefully meta-diff-buffer) - (if (ediff-buffer-live-p parent-buf) - (progn - (setq dont-show-registry t) - (ediff-show-meta-buffer parent-buf session-number))) - ) - (or dont-show-registry - (ediff-show-registry)))) - -(defun ediff-dispose-of-meta-buffer (buf) - (setq ediff-session-registry (delq buf ediff-session-registry)) - (ediff-with-current-buffer buf - (if (ediff-buffer-live-p ediff-dir-diffs-buffer) - (kill-buffer ediff-dir-diffs-buffer))) - (kill-buffer buf)) - - -;; 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. -(defun ediff-get-meta-info (buf point &optional noerror) - (let (result olist tmp) - (if (and point (ediff-buffer-live-p buf)) - (ediff-with-current-buffer buf - (if (featurep 'xemacs) - (setq result - (if (setq tmp (extent-at point buf 'ediff-meta-info)) - (ediff-overlay-get tmp 'ediff-meta-info))) - (setq olist - (mapcar (lambda (elt) - (unless (overlay-get elt 'invisible) - (overlay-get elt 'ediff-meta-info))) - (overlays-at point))) - (while (and olist (null (car olist))) - (setq olist (cdr olist))) - (setq result (car olist))))) - (or result - (unless noerror - (ediff-update-registry) - (error "No session info in this line"))))) - - -(defun ediff-get-meta-overlay-at-pos (point) - (if (featurep 'xemacs) - (extent-at point (current-buffer) 'ediff-meta-info) - (let* ((overl-list (overlays-at point)) - (overl (car overl-list))) - (while (and overl (null (overlay-get overl 'ediff-meta-info))) - (setq overl-list (cdr overl-list) - overl (car overl-list))) - overl))) - -(defun ediff-get-session-number-at-pos (point &optional meta-buffer) - (setq meta-buffer (if (ediff-buffer-live-p meta-buffer) - meta-buffer - (current-buffer))) - (ediff-with-current-buffer meta-buffer - (ediff-overlay-get - (ediff-get-meta-overlay-at-pos point) 'ediff-meta-session-number))) - - -;; Return location of the next meta overlay after point -(defun ediff-next-meta-overlay-start (point) - (if (eobp) - (goto-char (point-min)) - (let ((overl (ediff-get-meta-overlay-at-pos point))) - (if (featurep 'xemacs) - (progn ; xemacs - (if overl - (setq overl (next-extent overl)) - (setq overl (next-extent (current-buffer)))) - (if overl - (extent-start-position overl) - (point-max))) - ;; emacs - (if overl - ;; note: end of current overlay is the beginning of the next one - (overlay-end overl) - (next-overlay-change point)))))) - - -(defun ediff-previous-meta-overlay-start (point) - (if (bobp) - (goto-char (point-max)) - (let ((overl (ediff-get-meta-overlay-at-pos point))) - (if (featurep 'xemacs) - (progn - (if overl - (setq overl (previous-extent overl)) - (setq overl (previous-extent (current-buffer)))) - (if overl - (extent-start-position overl) - (point-min))) - (if overl (setq point (overlay-start overl))) - ;; 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 - (setq point (1- point))) - (setq point (previous-overlay-change point)) - ;; 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-at point)) - (setq point (point-min))) - point)))) - -;; this is the action invoked when the user selects a patch from the meta -;; buffer. -(defun ediff-patch-file-form-meta (file &optional startup-hooks) - (let* ((pos (ediff-event-point last-command-event)) - (meta-buf (ediff-event-buffer last-command-event)) - ;; ediff-get-meta-info gives error if meta-buf or pos are invalid - (info (ediff-get-meta-info meta-buf pos)) - (meta-patchbuf ediff-meta-patchbufer) - session-buf beg-marker end-marker) - - (if (or (file-directory-p file) (string-match "/dev/null" file)) - (error "`%s' is not an ordinary file" (file-name-as-directory file))) - (setq session-buf (ediff-get-session-buffer info) - beg-marker (ediff-get-session-objB-name info) - end-marker (ediff-get-session-objC-name info)) - - (or (ediff-buffer-live-p session-buf) ; either an active patch session - (null session-buf) ; or it is a virgin session - (error - "Patch has already been applied to this file -- can't repeat!")) - - (ediff-with-current-buffer meta-patchbuf - (save-restriction - (widen) - (narrow-to-region beg-marker end-marker) - (ediff-patch-file-internal meta-patchbuf file startup-hooks))))) - - -(defun ediff-unmark-all-for-operation () - "Unmark all sessions marked for operation." - (interactive) - (let ((list (cdr ediff-meta-list)) - elt) - (while (setq elt (car list)) - (ediff-mark-session-for-operation elt 'unmark) - (setq list (cdr list)))) - (ediff-update-meta-buffer (current-buffer) 'must-redraw)) - -(defun ediff-unmark-all-for-hiding () - "Unmark all sessions marked for hiding." - (interactive) - (let ((list (cdr ediff-meta-list)) - elt) - (while (setq elt (car list)) - (ediff-mark-session-for-hiding elt 'unmark) - (setq list (cdr list)))) - (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 -(defun ediff-meta-mark-equal-files (&optional action) - "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, mark -for operation, or simply indicate which are equal files. If it is nil, then -`(ediff-last-command-char)' is used to decide which action to take." - (interactive) - (if (null action) - (setq action (ediff-last-command-char))) - (let ((list (cdr ediff-meta-list)) - marked1 marked2 marked3 - fileinfo1 fileinfo2 fileinfo3 elt) - (message "Comparing files...") - (while (setq elt (car list)) - (setq fileinfo1 (ediff-get-session-objA elt) - fileinfo2 (ediff-get-session-objB elt) - fileinfo3 (ediff-get-session-objC elt)) - (ediff-set-file-eqstatus fileinfo1 nil) - (ediff-set-file-eqstatus fileinfo2 nil) - (ediff-set-file-eqstatus fileinfo3 nil) - - (setq marked1 t - marked2 t - marked3 t) - (or (ediff-mark-if-equal fileinfo1 fileinfo2) - (setq marked1 nil)) - (if (ediff-metajob3) - (progn - (or (ediff-mark-if-equal fileinfo1 fileinfo3) - (setq marked2 nil)) - (or (ediff-mark-if-equal fileinfo2 fileinfo3) - (setq marked3 nil)))) - (if (and marked1 marked2 marked3) - (cond ((eq action ?h) - (ediff-mark-session-for-hiding elt 'mark)) - ((eq action ?m) - (ediff-mark-session-for-operation elt 'mark)) - )) - (setq list (cdr list))) - (message "Comparing files... Done")) - (setq ediff-recurse-to-subdirectories nil) - (ediff-update-meta-buffer (current-buffer) 'must-redraw)) - -;; mark files 1 and 2 as equal, if they are. -;; returns t, if something was marked -(defun ediff-mark-if-equal (fileinfo1 fileinfo2) - (let ((f1 (car fileinfo1)) - (f2 (car fileinfo2))) - (if (and (stringp f1) (stringp f2) (ediff-same-contents f1 f2)) - (progn - (ediff-set-file-eqstatus fileinfo1 t) - (ediff-set-file-eqstatus fileinfo2 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: - -;; arch-tag: c8a76898-f96f-4d9c-be9d-129134017188 -;;; ediff-mult.el ends here diff -r 681cd08dc0f7 -r e93288477c43 lisp/ediff-ptch.el --- a/lisp/ediff-ptch.el Thu Jun 10 22:43:47 2010 +0000 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,844 +0,0 @@ -;;; ediff-ptch.el --- Ediff's patch support - -;; Copyright (C) 1996, 1997, 1998, 1999, 2000, 2001, 2002, -;; 2003, 2004, 2005, 2006, 2007, 2008, 2009, 2010 Free Software Foundation, Inc. - -;; Author: Michael Kifer - -;; 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 3 of the License, 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. If not, see . - -;;; Commentary: - -;;; Code: - - -(provide 'ediff-ptch) - -(defgroup ediff-ptch nil - "Ediff patch support." - :tag "Patch" - :prefix "ediff-" - :group 'ediff) - -;; compiler pacifier -(eval-when-compile - (require 'ediff)) -;; end pacifier - -(require 'ediff-init) - -(defcustom ediff-patch-program "patch" - "Name of the program that applies patches. -It is recommended to use GNU-compatible versions." - :type 'string - :group 'ediff-ptch) -(defcustom ediff-patch-options "-f" - "Options to pass to ediff-patch-program. - -Note: the `-b' option should be specified in `ediff-backup-specs'. - -It is recommended to pass the `-f' option to the patch program, so it won't ask -questions. However, some implementations don't accept this option, in which -case the default value for this variable should be changed." - :type 'string - :group 'ediff-ptch) - -(defvar ediff-last-dir-patch nil - "Last directory used by an Ediff command for file to patch.") - -;; the default backup extension -(defconst ediff-default-backup-extension - (if (memq system-type '(emx ms-dos)) - "_orig" ".orig")) - - -(defcustom ediff-backup-extension ediff-default-backup-extension - "Backup extension used by the patch program. -See also `ediff-backup-specs'." - :type 'string - :group 'ediff-ptch) - -(defun ediff-test-patch-utility () - (condition-case nil - (cond ((eq 0 (call-process ediff-patch-program nil nil nil "-z." "-b")) - ;; GNU `patch' v. >= 2.2 - 'gnu) - ((eq 0 (call-process ediff-patch-program nil nil nil "-b")) - 'posix) - (t 'traditional)) - (file-error nil))) - -(defcustom ediff-backup-specs - (let ((type (ediff-test-patch-utility))) - (cond ((eq type 'gnu) - ;; GNU `patch' v. >= 2.2 - (format "-z%s -b" ediff-backup-extension)) - ((eq type 'posix) - ;; POSIX `patch' -- ediff-backup-extension must be ".orig" - (setq ediff-backup-extension ediff-default-backup-extension) - "-b") - (t - ;; traditional `patch' - (format "-b %s" ediff-backup-extension)))) - "Backup directives to pass to the patch program. -Ediff requires that the old version of the file \(before applying the patch\) -be saved in a file named `the-patch-file.extension'. Usually `extension' is -`.orig', but this can be changed by the user and may depend on the system. -Therefore, Ediff needs to know the backup extension used by the patch program. - -Some versions of the patch program let you specify `-b backup-extension'. -Other versions only permit `-b', which assumes the extension `.orig' -\(in which case ediff-backup-extension MUST be also `.orig'\). The latest -versions of GNU patch require `-b -z backup-extension'. - -Note that both `ediff-backup-extension' and `ediff-backup-specs' -must be set properly. If your patch program takes the option `-b', -but not `-b extension', the variable `ediff-backup-extension' must -still be set so Ediff will know which extension to use. - -Ediff tries to guess the appropriate value for this variables. It is believed -to be working for `traditional' patch, all versions of GNU patch, and for POSIX -patch. So, don't change these variables, unless the default doesn't work." - :type 'string - :group 'ediff-ptch) - - -(defcustom ediff-patch-default-directory nil - "Default directory to look for patches." - :type '(choice (const nil) string) - :group 'ediff-ptch) - -;; This context diff does not recognize spaces inside files, but removing ' ' -;; from [^ \t] breaks normal patches for some reason -(defcustom ediff-context-diff-label-regexp - (concat "\\(" ; context diff 2-liner - "^\\*\\*\\* +\\([^ \t]+\\)[^*]+[\t ]*\n--- +\\([^ \t]+\\)" - "\\|" ; unified format diff 2-liner - "^--- +\\([^ \t]+\\).*\n\\+\\+\\+ +\\([^ \t]+\\)" - "\\)") - "Regexp matching filename 2-liners at the start of each context diff. -You probably don't want to change that, unless you are using an obscure patch -program." - :type 'regexp - :group 'ediff-ptch) - -;; The buffer of the patch file. Local to control buffer. -(ediff-defvar-local ediff-patchbufer nil "") - -;; The buffer where patch displays its diagnostics. -(ediff-defvar-local ediff-patch-diagnostics nil "") - -;; Map of patch buffer. Has the form: -;; ((filename1 marker1 marker2) (filename2 marker1 marker2) ...) -;; where filenames are files to which patch would have applied the patch; -;; marker1 delimits the beginning of the corresponding patch and marker2 does -;; it for the end. -(ediff-defvar-local ediff-patch-map nil "") - -;; strip prefix from filename -;; returns /dev/null, if can't strip prefix -(defsubst ediff-file-name-sans-prefix (filename prefix) - (if prefix - (save-match-data - (if (string-match (concat "^" (if (stringp prefix) - (regexp-quote prefix) - "")) - filename) - (substring filename (match-end 0)) - (concat "/null/" filename))) - filename) - ) - - - -;; no longer used -;; return the number of matches of regexp in buf starting from the beginning -(defun ediff-count-matches (regexp buf) - (ediff-with-current-buffer buf - (let ((count 0) opoint) - (save-excursion - (goto-char (point-min)) - (while (and (not (eobp)) - (progn (setq opoint (point)) - (re-search-forward regexp nil t))) - (if (= opoint (point)) - (forward-char 1) - (setq count (1+ count))))) - count))) - -;; Scan BUF (which is supposed to contain a patch) and make a list of the form -;; ((nil nil filename-spec1 marker1 marker2) -;; (nil nil filename-spec2 marker1 marker2) ...) -;; where filename-spec[12] are files to which the `patch' program would -;; have applied the patch. -;; nin, nil are placeholders. See ediff-make-new-meta-list-element in -;; ediff-meta.el for the explanations. -;; In the beginning we don't know exactly which files need to be patched. -;; We usually come up with two candidates and ediff-file-name-sans-prefix -;; resolves this later. -;; -;; The marker `marker1' delimits the beginning of the corresponding patch and -;; `marker2' does it for the end. -;; The result of ediff-map-patch-buffer is a list, which is then assigned -;; to ediff-patch-map. -;; The function returns the number of elements in the list ediff-patch-map -(defun ediff-map-patch-buffer (buf) - (ediff-with-current-buffer buf - (let ((count 0) - (mark1 (move-marker (make-marker) (point-min))) - (mark1-end (point-min)) - (possible-file-names '("/dev/null" . "/dev/null")) - mark2-end mark2 filenames - beg1 beg2 end1 end2 - patch-map opoint) - (save-excursion - (goto-char (point-min)) - (setq opoint (point)) - (while (and (not (eobp)) - (re-search-forward ediff-context-diff-label-regexp nil t)) - (if (= opoint (point)) - (forward-char 1) ; ensure progress towards the end - (setq mark2 (move-marker (make-marker) (match-beginning 0)) - mark2-end (match-end 0) - beg1 (or (match-beginning 2) (match-beginning 4)) - end1 (or (match-end 2) (match-end 4)) - beg2 (or (match-beginning 3) (match-beginning 5)) - end2 (or (match-end 3) (match-end 5))) - ;; possible-file-names is holding the new file names until we - ;; insert the old file name in the patch map - ;; It is a pair - ;; (filename-from-1st-header-line . filename-from-2nd-line) - (setq possible-file-names - (cons (if (and beg1 end1) - (buffer-substring beg1 end1) - "/dev/null") - (if (and beg2 end2) - (buffer-substring beg2 end2) - "/dev/null"))) - ;; check for any `Index:' or `Prereq:' lines, but don't use them - (if (re-search-backward "^Index:" mark1-end 'noerror) - (move-marker mark2 (match-beginning 0))) - (if (re-search-backward "^Prereq:" mark1-end 'noerror) - (move-marker mark2 (match-beginning 0))) - - (goto-char mark2-end) - - (if filenames - (setq patch-map - (cons (ediff-make-new-meta-list-element - filenames mark1 mark2) - patch-map))) - (setq mark1 mark2 - mark1-end mark2-end - filenames possible-file-names)) - (setq opoint (point) - count (1+ count)))) - (setq mark2 (point-max-marker) - patch-map (cons (ediff-make-new-meta-list-element - possible-file-names mark1 mark2) - patch-map)) - (setq ediff-patch-map (nreverse patch-map)) - count))) - -;; Fix up the file names in the list using the argument FILENAME -;; Algorithm: find the files' directories in the patch and, if a directory is -;; absolute, cut it out from the corresponding file name in the patch. -;; Relative directories are not cut out. -;; Prepend the directory of FILENAME to each resulting file (which came -;; originally from the patch). -;; In addition, the first file in the patch document is replaced by FILENAME. -;; Each file is actually a pair of files found in the context diff header -;; In the end, for each pair, we ask the user which file to patch. -;; Note: Ediff doesn't recognize multi-file patches that are separated -;; with the `Index:' line. It treats them as a single-file patch. -;; -;; Executes inside the patch buffer -(defun ediff-fixup-patch-map (filename) - (setq filename (expand-file-name filename)) - (let ((actual-dir (if (file-directory-p filename) - ;; directory part of filename - (file-name-as-directory filename) - (file-name-directory filename))) - ;; In case 2 files are possible patch targets, the user will be offered - ;; to choose file1 or file2. In a multifile patch, if the user chooses - ;; 1 or 2, this choice is preserved to decide future alternatives. - chosen-alternative - ) - - ;; chop off base-dirs - (mapc (lambda (session-info) - (let* ((proposed-file-names - ;; Filename-spec is objA; it is represented as - ;; (file1 . file2). Get it using ediff-get-session-objA. - (ediff-get-session-objA-name session-info)) - ;; base-dir1 is the dir part of the 1st file in the patch - (base-dir1 - (or (file-name-directory (car proposed-file-names)) - "")) - ;; directory part of the 2nd file in the patch - (base-dir2 - (or (file-name-directory (cdr proposed-file-names)) - "")) - ) - ;; If both base-dir1 and base-dir2 are relative and exist, - ;; assume that - ;; these dirs lead to the actual files starting at the present - ;; directory. So, we don't strip these relative dirs from the - ;; file names. This is a heuristic intended to improve guessing - (let ((default-directory (file-name-directory filename))) - (unless (or (file-name-absolute-p base-dir1) - (file-name-absolute-p base-dir2) - (not (file-exists-p base-dir1)) - (not (file-exists-p base-dir2))) - (setq base-dir1 "" - base-dir2 ""))) - (or (string= (car proposed-file-names) "/dev/null") - (setcar proposed-file-names - (ediff-file-name-sans-prefix - (car proposed-file-names) base-dir1))) - (or (string= - (cdr proposed-file-names) "/dev/null") - (setcdr proposed-file-names - (ediff-file-name-sans-prefix - (cdr proposed-file-names) base-dir2))) - )) - ediff-patch-map) - - ;; take the given file name into account - (or (file-directory-p filename) - (string= "/dev/null" filename) - (setcar (ediff-get-session-objA (car ediff-patch-map)) - (cons (file-name-nondirectory filename) - (file-name-nondirectory filename)))) - - ;; prepend actual-dir - (mapc (lambda (session-info) - (let ((proposed-file-names - (ediff-get-session-objA-name session-info))) - (if (and (string-match "^/null/" (car proposed-file-names)) - (string-match "^/null/" (cdr proposed-file-names))) - ;; couldn't intuit the file name to patch, so - ;; something is amiss - (progn - (with-output-to-temp-buffer ediff-msg-buffer - (ediff-with-current-buffer standard-output - (fundamental-mode)) - (princ - (format " -The patch file contains a context diff for - %s - %s -However, Ediff cannot infer the name of the actual file -to be patched on your system. If you know the correct file name, -please enter it now. - -If you don't know and still would like to apply patches to -other files, enter /dev/null -" - (substring (car proposed-file-names) 6) - (substring (cdr proposed-file-names) 6)))) - (let ((directory t) - user-file) - (while directory - (setq user-file - (read-file-name - "Please enter file name: " - actual-dir actual-dir t)) - (if (not (file-directory-p user-file)) - (setq directory nil) - (setq directory t) - (beep) - (message "%s is a directory" user-file) - (sit-for 2))) - (setcar (ediff-get-session-objA session-info) - (cons user-file user-file)))) - (setcar proposed-file-names - (expand-file-name - (concat actual-dir (car proposed-file-names)))) - (setcdr proposed-file-names - (expand-file-name - (concat actual-dir (cdr proposed-file-names))))) - )) - ediff-patch-map) - ;; Check for the existing files in each pair and discard the nonexisting - ;; ones. If both exist, ask the user. - (mapcar (lambda (session-info) - (let* ((file1 (car (ediff-get-session-objA-name session-info))) - (file2 (cdr (ediff-get-session-objA-name session-info))) - (session-file-object - (ediff-get-session-objA session-info)) - (f1-exists (file-exists-p file1)) - (f2-exists (file-exists-p file2))) - (cond - ((and - ;; The patch program prefers the shortest file as the patch - ;; target. However, this is a questionable heuristic. In an - ;; interactive program, like ediff, we can offer the user a - ;; choice. - ;; (< (length file2) (length file1)) - (not f1-exists) - f2-exists) - ;; replace file-pair with the winning file2 - (setcar session-file-object file2)) - ((and - ;; (< (length file1) (length file2)) - (not f2-exists) - f1-exists) - ;; replace file-pair with the winning file1 - (setcar session-file-object file1)) - ((and f1-exists f2-exists - (string= file1 file2)) - (setcar session-file-object file1)) - ((and f1-exists f2-exists (eq chosen-alternative 1)) - (setcar session-file-object file1)) - ((and f1-exists f2-exists (eq chosen-alternative 2)) - (setcar session-file-object file2)) - ((and f1-exists f2-exists) - (with-output-to-temp-buffer ediff-msg-buffer - (ediff-with-current-buffer standard-output - (fundamental-mode)) - (princ (format " -Ediff has inferred that - %s - %s -are two possible targets for applying the patch. -Both files seem to be plausible alternatives. - -Please advice: - Type `y' to use %s as the target; - Type `n' to use %s as the target. -" - file1 file2 file1 file2))) - (setcar session-file-object - (if (y-or-n-p (format "Use %s ? " file1)) - (progn - (setq chosen-alternative 1) - file1) - (setq chosen-alternative 2) - file2)) - ) - (f2-exists (setcar session-file-object file2)) - (f1-exists (setcar session-file-object file1)) - (t - (with-output-to-temp-buffer ediff-msg-buffer - (ediff-with-current-buffer standard-output - (fundamental-mode)) - (princ "\nEdiff has inferred that") - (if (string= file1 file2) - (princ (format " - %s -is assumed to be the target for this patch. However, this file does not exist." - file1)) - (princ (format " - %s - %s -are two possible targets for this patch. However, these files do not exist." - file1 file2))) - (princ " -\nPlease enter an alternative patch target ...\n")) - (let ((directory t) - target) - (while directory - (setq target (read-file-name - "Please enter a patch target: " - actual-dir actual-dir t)) - (if (not (file-directory-p target)) - (setq directory nil) - (beep) - (message "%s is a directory" target) - (sit-for 2))) - (setcar session-file-object target)))))) - ediff-patch-map) - )) - -(defun ediff-show-patch-diagnostics () - (interactive) - (cond ((window-live-p ediff-window-A) - (set-window-buffer ediff-window-A ediff-patch-diagnostics)) - ((window-live-p ediff-window-B) - (set-window-buffer ediff-window-B ediff-patch-diagnostics)) - (t (display-buffer ediff-patch-diagnostics 'not-this-window)))) - -;; prompt for file, get the buffer -(defun ediff-prompt-for-patch-file () - (let ((dir (cond (ediff-use-last-dir ediff-last-dir-patch) - (ediff-patch-default-directory) ; try patch default dir - (t default-directory))) - (coding-system-for-read ediff-coding-system-for-read) - patch-file-name) - (setq patch-file-name - (read-file-name - (format "Patch is in file%s: " - (cond ((and buffer-file-name - (equal (expand-file-name dir) - (file-name-directory buffer-file-name))) - (concat - " (default " - (file-name-nondirectory buffer-file-name) - ")")) - (t ""))) - dir buffer-file-name 'must-match)) - (if (file-directory-p patch-file-name) - (error "Patch file cannot be a directory: %s" patch-file-name) - (find-file-noselect patch-file-name)) - )) - - -;; Try current buffer, then the other window's buffer. Else, give up. -(defun ediff-prompt-for-patch-buffer () - (get-buffer - (read-buffer - "Buffer that holds the patch: " - (cond ((save-excursion - (goto-char (point-min)) - (re-search-forward ediff-context-diff-label-regexp nil t)) - (current-buffer)) - ((save-window-excursion - (other-window 1) - (save-excursion - (goto-char (point-min)) - (and (re-search-forward ediff-context-diff-label-regexp nil t) - (current-buffer))))) - ((save-window-excursion - (other-window -1) - (save-excursion - (goto-char (point-min)) - (and (re-search-forward ediff-context-diff-label-regexp nil t) - (current-buffer))))) - (t (ediff-other-buffer (current-buffer)))) - 'must-match))) - - -(defun ediff-get-patch-buffer (&optional arg patch-buf) - "Obtain patch buffer. If patch is already in a buffer---use it. -Else, read patch file into a new buffer. If patch buffer is passed as an -optional argument, then use it." - (let ((last-nonmenu-event t) ; Emacs: don't use dialog box - last-command-event) ; XEmacs: don't use dialog box - - (cond ((ediff-buffer-live-p patch-buf)) - ;; even prefix arg: patch in buffer - ((and (integerp arg) (eq 0 (mod arg 2))) - (setq patch-buf (ediff-prompt-for-patch-buffer))) - ;; odd prefix arg: get patch from a file - ((and (integerp arg) (eq 1 (mod arg 2))) - (setq patch-buf (ediff-prompt-for-patch-file))) - (t (setq patch-buf - (if (y-or-n-p "Is the patch already in a buffer? ") - (ediff-prompt-for-patch-buffer) - (ediff-prompt-for-patch-file))))) - - (ediff-with-current-buffer patch-buf - (goto-char (point-min)) - (or (ediff-get-visible-buffer-window patch-buf) - (progn - (pop-to-buffer patch-buf 'other-window) - (select-window (previous-window))))) - (ediff-map-patch-buffer patch-buf) - patch-buf)) - -;; Dispatch the right patch file function: regular or meta-level, -;; depending on how many patches are in the patch file. -;; At present, there is no support for meta-level patches. -;; Should return either the ctl buffer or the meta-buffer -(defun ediff-dispatch-file-patching-job (patch-buf filename - &optional startup-hooks) - (ediff-with-current-buffer patch-buf - ;; relativize names in the patch with respect to source-file - (ediff-fixup-patch-map filename) - (if (< (length ediff-patch-map) 2) - (ediff-patch-file-internal - patch-buf - (if (and ediff-patch-map - (not (string-match - "^/dev/null" - ;; this is the file to patch - (ediff-get-session-objA-name (car ediff-patch-map)))) - (> (length - (ediff-get-session-objA-name (car ediff-patch-map))) - 1)) - (ediff-get-session-objA-name (car ediff-patch-map)) - filename) - startup-hooks) - (ediff-multi-patch-internal patch-buf startup-hooks)) - )) - - -;; When patching a buffer, never change the orig file. Instead, create a new -;; buffer, ***_patched, even if the buff visits a file. -;; Users who want to actually patch the buffer should use -;; ediff-patch-file, not ediff-patch-buffer. -(defun ediff-patch-buffer-internal (patch-buf - buf-to-patch-name - &optional startup-hooks) - (let* ((buf-to-patch (get-buffer buf-to-patch-name)) - (visited-file (if buf-to-patch (buffer-file-name buf-to-patch))) - (buf-mod-status (buffer-modified-p buf-to-patch)) - (multifile-patch-p (> (length (ediff-with-current-buffer patch-buf - ediff-patch-map)) 1)) - default-dir file-name ctl-buf) - (if multifile-patch-p - (error - "To apply multi-file patches, please use `ediff-patch-file'")) - - ;; create a temp file to patch - (ediff-with-current-buffer buf-to-patch - (setq default-dir default-directory) - (setq file-name (ediff-make-temp-file buf-to-patch)) - ;; temporarily switch visited file name, if any - (set-visited-file-name file-name) - ;; don't create auto-save file, if buff was visiting a file - (or visited-file - (setq buffer-auto-save-file-name nil)) - ;; don't confuse the user with a new bufname - (rename-buffer buf-to-patch-name) - (set-buffer-modified-p nil) - (set-visited-file-modtime) ; sync buffer and temp file - (setq default-directory default-dir) - ) - - ;; dispatch a patch function - (setq ctl-buf (ediff-dispatch-file-patching-job - patch-buf file-name startup-hooks)) - - (ediff-with-current-buffer ctl-buf - (delete-file (buffer-file-name ediff-buffer-A)) - (delete-file (buffer-file-name ediff-buffer-B)) - (ediff-with-current-buffer ediff-buffer-A - (if default-dir (setq default-directory default-dir)) - (set-visited-file-name visited-file) ; visited-file might be nil - (rename-buffer buf-to-patch-name) - (set-buffer-modified-p buf-mod-status)) - (ediff-with-current-buffer ediff-buffer-B - (setq buffer-auto-save-file-name nil) ; don't create auto-save file - (if default-dir (setq default-directory default-dir)) - (set-visited-file-name nil) - (rename-buffer (ediff-unique-buffer-name - (concat buf-to-patch-name "_patched") "")) - (set-buffer-modified-p t))) - )) - - -;; Traditional patch has weird return codes. -;; GNU and Posix return 1 if some hanks failed and 2 in case of trouble. -;; 0 is a good code in all cases. -;; We'll do the concervative thing. -(defun ediff-patch-return-code-ok (code) - (eq code 0)) -;;; (if (eq (ediff-test-patch-utility) 'traditional) -;;; (eq code 0) -;;; (not (eq code 2)))) - -(defun ediff-patch-file-internal (patch-buf source-filename - &optional startup-hooks) - (setq source-filename (expand-file-name source-filename)) - - (let* ((shell-file-name ediff-shell) - (patch-diagnostics (get-buffer-create "*ediff patch diagnostics*")) - ;; ediff-find-file may use a temp file to do the patch - ;; so, we save source-filename and true-source-filename as a var - ;; that initially is source-filename but may be changed to a temp - ;; file for the purpose of patching. - (true-source-filename source-filename) - (target-filename source-filename) - ;; this ensures that the patch process gets patch buffer in the - ;; encoding that Emacs thinks is right for that type of text - (coding-system-for-write - (if (boundp 'buffer-file-coding-system) buffer-file-coding-system)) - target-buf buf-to-patch file-name-magic-p - patch-return-code ctl-buf backup-style aux-wind) - - (if (string-match "V" ediff-patch-options) - (error - "Ediff doesn't take the -V option in `ediff-patch-options'--sorry")) - - ;; Make a temp file, if source-filename has a magic file handler (or if - ;; it is handled via auto-mode-alist and similar magic). - ;; Check if there is a buffer visiting source-filename and if they are in - ;; sync; arrange for the deletion of temp file. - (ediff-find-file 'true-source-filename 'buf-to-patch - 'ediff-last-dir-patch 'startup-hooks) - - ;; Check if source file name has triggered black magic, such as file name - ;; handlers or auto mode alist, and make a note of it. - ;; true-source-filename should be either the original name or a - ;; temporary file where we put the after-product of the file handler. - (setq file-name-magic-p (not (equal (file-truename true-source-filename) - (file-truename source-filename)))) - - ;; Checkout orig file, if necessary, so that the patched file - ;; could be checked back in. - (ediff-maybe-checkout buf-to-patch) - - (ediff-with-current-buffer patch-diagnostics - (insert-buffer-substring patch-buf) - (message "Applying patch ... ") - ;; fix environment for gnu patch, so it won't make numbered extensions - (setq backup-style (getenv "VERSION_CONTROL")) - (setenv "VERSION_CONTROL" nil) - (setq patch-return-code - (call-process-region - (point-min) (point-max) - shell-file-name - t ; delete region (which contains the patch - t ; insert output (patch diagnostics) in current buffer - nil ; don't redisplay - shell-command-switch ; usually -c - (format "%s %s %s %s" - ediff-patch-program - ediff-patch-options - ediff-backup-specs - (expand-file-name true-source-filename)) - )) - - ;; restore environment for gnu patch - (setenv "VERSION_CONTROL" backup-style)) - - (message "Applying patch ... done") - (message "") - - (switch-to-buffer patch-diagnostics) - (sit-for 0) ; synchronize - let the user see diagnostics - - (or (and (ediff-patch-return-code-ok patch-return-code) - (file-exists-p - (concat true-source-filename ediff-backup-extension))) - (progn - (with-output-to-temp-buffer ediff-msg-buffer - (ediff-with-current-buffer standard-output - (fundamental-mode)) - (princ (format - "Patch program has failed due to a bad patch file, -it couldn't apply all hunks, OR -it couldn't create the backup for the file being patched. - -The former could be caused by a corrupt patch file or because the %S -program doesn't understand the format of the patch file in use. - -The second problem might be due to an incompatibility among these settings: - ediff-patch-program = %S ediff-patch-options = %S - ediff-backup-extension = %S ediff-backup-specs = %S - -See Ediff on-line manual for more details on these variables. -In particular, check the documentation for `ediff-backup-specs'. - -In any of the above cases, Ediff doesn't compare files automatically. -However, if the patch was applied partially and the backup file was created, -you can still examine the changes via M-x ediff-files" - ediff-patch-program - ediff-patch-program - ediff-patch-options - ediff-backup-extension - ediff-backup-specs - ))) - (beep 1) - (if (setq aux-wind (get-buffer-window ediff-msg-buffer)) - (progn - (select-window aux-wind) - (goto-char (point-max)))) - (switch-to-buffer-other-window patch-diagnostics) - (error "Patch appears to have failed"))) - - ;; If black magic is involved, apply patch to a temp copy of the - ;; file. Otherwise, apply patch to the orig copy. If patch is applied - ;; to temp copy, we name the result old-name_patched for local files - ;; and temp-copy_patched for remote files. The orig file name isn't - ;; changed, and the temp copy of the original is later deleted. - ;; Without magic, the original file is renamed (usually into - ;; old-name_orig) and the result of patching will have the same name as - ;; the original. - (if (not file-name-magic-p) - (ediff-with-current-buffer buf-to-patch - (set-visited-file-name - (concat source-filename ediff-backup-extension)) - (set-buffer-modified-p nil)) - - ;; Black magic in effect. - ;; If orig file was remote, put the patched file in the temp directory. - ;; If orig file is local, put the patched file in the directory of - ;; the orig file. - (setq target-filename - (concat - (if (ediff-file-remote-p (file-truename source-filename)) - true-source-filename - source-filename) - "_patched")) - - (rename-file true-source-filename target-filename t) - - ;; arrange that the temp copy of orig will be deleted - (rename-file (concat true-source-filename ediff-backup-extension) - true-source-filename t)) - - ;; make orig buffer read-only - (setq startup-hooks - (cons 'ediff-set-read-only-in-buf-A startup-hooks)) - - ;; set up a buf for the patched file - (setq target-buf (find-file-noselect target-filename)) - - (setq ctl-buf - (ediff-buffers-internal - buf-to-patch target-buf nil - startup-hooks 'epatch)) - (ediff-with-current-buffer ctl-buf - (setq ediff-patchbufer patch-buf - ediff-patch-diagnostics patch-diagnostics)) - - (bury-buffer patch-diagnostics) - (message "Type `P', if you need to see patch diagnostics") - ctl-buf)) - -(defun ediff-multi-patch-internal (patch-buf &optional startup-hooks) - (let (meta-buf) - (setq startup-hooks - ;; this sets various vars in the meta buffer inside - ;; ediff-prepare-meta-buffer - (cons `(lambda () - ;; tell what to do if the user clicks on a session record - (setq ediff-session-action-function - 'ediff-patch-file-form-meta - ediff-meta-patchbufer patch-buf) ) - startup-hooks)) - (setq meta-buf (ediff-prepare-meta-buffer - 'ediff-filegroup-action - (ediff-with-current-buffer patch-buf - (cons (ediff-make-new-meta-list-header - nil ; regexp - (format "%S" patch-buf) ; obj A - nil nil ; objects B,C - nil ; merge-auto-store-dir - nil ; comparison-func - ) - ediff-patch-map)) - "*Ediff Session Group Panel" - 'ediff-redraw-directory-group-buffer - 'ediff-multifile-patch - startup-hooks)) - (ediff-show-meta-buffer meta-buf) - )) - - - - -;; 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: - -;; arch-tag: 2fe2161e-e116-469b-90fa-5cbb44c1bd1b -;;; ediff-ptch.el ends here diff -r 681cd08dc0f7 -r e93288477c43 lisp/ediff-util.el --- a/lisp/ediff-util.el Thu Jun 10 22:43:47 2010 +0000 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,4291 +0,0 @@ -;;; ediff-util.el --- the core commands and utilities of ediff - -;; Copyright (C) 1994, 1995, 1996, 1997, 1998, 1999, 2000, 2001, 2002, -;; 2003, 2004, 2005, 2006, 2007, 2008, 2009, 2010 Free Software Foundation, Inc. - -;; Author: Michael Kifer - -;; 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 3 of the License, 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. If not, see . - -;;; Commentary: - -;;; Code: - - -(provide 'ediff-util) - -;; Compiler pacifier -(defvar ediff-use-toolbar-p) -(defvar ediff-toolbar-height) -(defvar ediff-toolbar) -(defvar ediff-toolbar-3way) -(defvar bottom-toolbar) -(defvar bottom-toolbar-visible-p) -(defvar bottom-toolbar-height) -(defvar mark-active) - -(defvar ediff-after-quit-hook-internal nil) - -(eval-and-compile - (unless (fboundp 'declare-function) (defmacro declare-function (&rest r)))) - -(eval-when-compile - (require 'ediff)) - -;; end pacifier - - -(require 'ediff-init) -(require 'ediff-help) -(require 'ediff-mult) -(require 'ediff-wind) -(require 'ediff-diff) -(require 'ediff-merg) -;; for compatibility with current stable version of xemacs -(if (featurep 'xemacs) - (require 'ediff-tbar)) - - -;;; Functions - -(defun ediff-mode () - "Ediff mode controls all operations in a single Ediff session. -This mode is entered through one of the following commands: - `ediff' - `ediff-files' - `ediff-buffers' - `ebuffers' - `ediff3' - `ediff-files3' - `ediff-buffers3' - `ebuffers3' - `ediff-merge' - `ediff-merge-files' - `ediff-merge-files-with-ancestor' - `ediff-merge-buffers' - `ediff-merge-buffers-with-ancestor' - `ediff-merge-revisions' - `ediff-merge-revisions-with-ancestor' - `ediff-windows-wordwise' - `ediff-windows-linewise' - `ediff-regions-wordwise' - `ediff-regions-linewise' - `epatch' - `ediff-patch-file' - `ediff-patch-buffer' - `epatch-buffer' - `erevision' - `ediff-revision' - -Commands: -\\{ediff-mode-map}" - (kill-all-local-variables) - (setq major-mode 'ediff-mode) - (setq mode-name "Ediff") - ;; We use run-hooks instead of run-mode-hooks for two reasons. - ;; The ediff control buffer is read-only and it is not supposed to be - ;; modified by minor modes and such. So, run-mode-hooks doesn't do anything - ;; useful here on top of what run-hooks does. - ;; Second, changing run-hooks to run-mode-hooks would require an - ;; if-statement, since XEmacs doesn't have this. - (run-hooks 'ediff-mode-hook)) - - - -;;; Build keymaps - -(ediff-defvar-local ediff-mode-map nil - "Local keymap used in Ediff mode. -This is local to each Ediff Control Panel, so they may vary from invocation -to invocation.") - -;; Set up the keymap in the control buffer -(defun ediff-set-keys () - "Set up Ediff keymap, if necessary." - (if (null ediff-mode-map) - (ediff-setup-keymap)) - (use-local-map ediff-mode-map)) - -;; Reload Ediff keymap. For debugging only. -(defun ediff-reload-keymap () - (interactive) - (setq ediff-mode-map nil) - (ediff-set-keys)) - - -(defun ediff-setup-keymap () - "Set up the keymap used in the control buffer of Ediff." - (setq ediff-mode-map (make-sparse-keymap)) - (suppress-keymap ediff-mode-map) - - (define-key ediff-mode-map - (if (featurep 'emacs) [mouse-2] [button2]) 'ediff-help-for-quick-help) - (define-key ediff-mode-map "\C-m" 'ediff-help-for-quick-help) - - (define-key ediff-mode-map "p" 'ediff-previous-difference) - (define-key ediff-mode-map "\C-?" 'ediff-previous-difference) - (define-key ediff-mode-map [delete] 'ediff-previous-difference) - (define-key ediff-mode-map "\C-h" (if ediff-no-emacs-help-in-control-buffer - 'ediff-previous-difference nil)) - ;; must come after C-h, or else C-h wipes out backspace's binding in XEmacs - (define-key ediff-mode-map [backspace] 'ediff-previous-difference) - (define-key ediff-mode-map "n" 'ediff-next-difference) - (define-key ediff-mode-map " " 'ediff-next-difference) - (define-key ediff-mode-map "j" 'ediff-jump-to-difference) - (define-key ediff-mode-map "g" nil) - (define-key ediff-mode-map "ga" 'ediff-jump-to-difference-at-point) - (define-key ediff-mode-map "gb" 'ediff-jump-to-difference-at-point) - (define-key ediff-mode-map "q" 'ediff-quit) - (define-key ediff-mode-map "D" 'ediff-show-diff-output) - (define-key ediff-mode-map "z" 'ediff-suspend) - (define-key ediff-mode-map "\C-l" 'ediff-recenter) - (define-key ediff-mode-map "|" 'ediff-toggle-split) - (define-key ediff-mode-map "h" 'ediff-toggle-hilit) - (or ediff-word-mode - (define-key ediff-mode-map "@" 'ediff-toggle-autorefine)) - (if ediff-narrow-job - (define-key ediff-mode-map "%" 'ediff-toggle-narrow-region)) - (define-key ediff-mode-map "~" 'ediff-swap-buffers) - (define-key ediff-mode-map "v" 'ediff-scroll-vertically) - (define-key ediff-mode-map "\C-v" 'ediff-scroll-vertically) - (define-key ediff-mode-map "^" 'ediff-scroll-vertically) - (define-key ediff-mode-map "\M-v" 'ediff-scroll-vertically) - (define-key ediff-mode-map "V" 'ediff-scroll-vertically) - (define-key ediff-mode-map "<" 'ediff-scroll-horizontally) - (define-key ediff-mode-map ">" 'ediff-scroll-horizontally) - (define-key ediff-mode-map "i" 'ediff-status-info) - (define-key ediff-mode-map "E" 'ediff-documentation) - (define-key ediff-mode-map "?" 'ediff-toggle-help) - (define-key ediff-mode-map "!" 'ediff-update-diffs) - (define-key ediff-mode-map "M" 'ediff-show-current-session-meta-buffer) - (define-key ediff-mode-map "R" 'ediff-show-registry) - (or ediff-word-mode - (define-key ediff-mode-map "*" 'ediff-make-or-kill-fine-diffs)) - (define-key ediff-mode-map "a" nil) - (define-key ediff-mode-map "b" nil) - (define-key ediff-mode-map "r" nil) - (cond (ediff-merge-job - ;; Will barf if no ancestor - (define-key ediff-mode-map "/" 'ediff-show-ancestor) - ;; In merging, we allow only A->C and B->C copying. - (define-key ediff-mode-map "a" 'ediff-copy-A-to-C) - (define-key ediff-mode-map "b" 'ediff-copy-B-to-C) - (define-key ediff-mode-map "r" 'ediff-restore-diff-in-merge-buffer) - (define-key ediff-mode-map "s" 'ediff-shrink-window-C) - (define-key ediff-mode-map "+" 'ediff-combine-diffs) - (define-key ediff-mode-map "$" nil) - (define-key ediff-mode-map "$$" 'ediff-toggle-show-clashes-only) - (define-key ediff-mode-map "$*" 'ediff-toggle-skip-changed-regions) - (define-key ediff-mode-map "&" 'ediff-re-merge)) - (ediff-3way-comparison-job - (define-key ediff-mode-map "ab" 'ediff-copy-A-to-B) - (define-key ediff-mode-map "ba" 'ediff-copy-B-to-A) - (define-key ediff-mode-map "ac" 'ediff-copy-A-to-C) - (define-key ediff-mode-map "bc" 'ediff-copy-B-to-C) - (define-key ediff-mode-map "c" nil) - (define-key ediff-mode-map "ca" 'ediff-copy-C-to-A) - (define-key ediff-mode-map "cb" 'ediff-copy-C-to-B) - (define-key ediff-mode-map "ra" 'ediff-restore-diff) - (define-key ediff-mode-map "rb" 'ediff-restore-diff) - (define-key ediff-mode-map "rc" 'ediff-restore-diff) - (define-key ediff-mode-map "C" 'ediff-toggle-read-only)) - (t ; 2-way comparison - (define-key ediff-mode-map "a" 'ediff-copy-A-to-B) - (define-key ediff-mode-map "b" 'ediff-copy-B-to-A) - (define-key ediff-mode-map "ra" 'ediff-restore-diff) - (define-key ediff-mode-map "rb" 'ediff-restore-diff)) - ) ; cond - (define-key ediff-mode-map "G" 'ediff-submit-report) - (define-key ediff-mode-map "#" nil) - (define-key ediff-mode-map "#h" 'ediff-toggle-regexp-match) - (define-key ediff-mode-map "#f" 'ediff-toggle-regexp-match) - (define-key ediff-mode-map "#c" 'ediff-toggle-ignore-case) - (or ediff-word-mode - (define-key ediff-mode-map "##" 'ediff-toggle-skip-similar)) - (define-key ediff-mode-map "o" nil) - (define-key ediff-mode-map "A" 'ediff-toggle-read-only) - (define-key ediff-mode-map "B" 'ediff-toggle-read-only) - (define-key ediff-mode-map "w" nil) - (define-key ediff-mode-map "wa" 'ediff-save-buffer) - (define-key ediff-mode-map "wb" 'ediff-save-buffer) - (define-key ediff-mode-map "wd" 'ediff-save-buffer) - (define-key ediff-mode-map "=" 'ediff-inferior-compare-regions) - (if (and (fboundp 'ediff-show-patch-diagnostics) (ediff-patch-job)) - (define-key ediff-mode-map "P" 'ediff-show-patch-diagnostics)) - (if ediff-3way-job - (progn - (define-key ediff-mode-map "wc" 'ediff-save-buffer) - (define-key ediff-mode-map "gc" 'ediff-jump-to-difference-at-point) - )) - - (define-key ediff-mode-map "m" 'ediff-toggle-wide-display) - - ;; Allow ediff-mode-map to be referenced indirectly - (fset 'ediff-mode-map ediff-mode-map) - (run-hooks 'ediff-keymap-setup-hook)) - - -;;; Setup functions - -;; Common startup entry for all Ediff functions It now returns control buffer -;; so other functions can do post-processing SETUP-PARAMETERS is a list of the -;; form ((param .val) (param . val)...) This serves a similar purpose to -;; STARTUP-HOOKS, but these parameters are set in the new control buffer right -;; after this buf is created and before any windows are set and such. -(defun ediff-setup (buffer-A file-A buffer-B file-B buffer-C file-C - startup-hooks setup-parameters - &optional merge-buffer-file) - (run-hooks 'ediff-before-setup-hook) - ;; ediff-convert-standard-filename puts file names in the form appropriate - ;; for the OS at hand. - (setq file-A (ediff-convert-standard-filename (expand-file-name file-A))) - (setq file-B (ediff-convert-standard-filename (expand-file-name file-B))) - (if (stringp file-C) - (setq file-C - (ediff-convert-standard-filename (expand-file-name file-C)))) - (if (stringp merge-buffer-file) - (progn - (setq merge-buffer-file - (ediff-convert-standard-filename - (expand-file-name merge-buffer-file))) - ;; check the directory exists - (or (file-exists-p (file-name-directory merge-buffer-file)) - (error "Directory %s given as place to save the merge doesn't exist" - (abbreviate-file-name - (file-name-directory merge-buffer-file)))) - (if (and (file-exists-p merge-buffer-file) - (file-directory-p merge-buffer-file)) - (error "The merge buffer file %s must not be a directory" - (abbreviate-file-name merge-buffer-file))) - )) - (let* ((control-buffer-name - (ediff-unique-buffer-name "*Ediff Control Panel" "*")) - (control-buffer (ediff-with-current-buffer buffer-A - (get-buffer-create control-buffer-name)))) - (ediff-with-current-buffer control-buffer - (ediff-mode) - - (make-local-variable 'ediff-use-long-help-message) - (make-local-variable 'ediff-prefer-iconified-control-frame) - (make-local-variable 'ediff-split-window-function) - (make-local-variable 'ediff-default-variant) - (make-local-variable 'ediff-merge-window-share) - (make-local-variable 'ediff-window-setup-function) - (make-local-variable 'ediff-keep-variants) - - (make-local-variable 'window-min-height) - (setq window-min-height 2) - - (if (featurep 'xemacs) - (make-local-hook 'ediff-after-quit-hook-internal)) - - ;; unwrap set up parameters passed as argument - (while setup-parameters - (set (car (car setup-parameters)) (cdr (car setup-parameters))) - (setq setup-parameters (cdr setup-parameters))) - - ;; set variables classifying the current ediff job - ;; must come AFTER setup-parameters - (setq ediff-3way-comparison-job (ediff-3way-comparison-job) - ediff-merge-job (ediff-merge-job) - ediff-merge-with-ancestor-job (ediff-merge-with-ancestor-job) - ediff-3way-job (ediff-3way-job) - ediff-diff3-job (ediff-diff3-job) - ediff-narrow-job (ediff-narrow-job) - ediff-windows-job (ediff-windows-job) - ediff-word-mode-job (ediff-word-mode-job)) - - ;; Don't delete variants in case of ediff-buffer-* jobs without asking. - ;; This is because one may loose work---dangerous. - (if (string-match "buffer" (symbol-name ediff-job-name)) - (setq ediff-keep-variants t)) - - (if (featurep 'xemacs) - (make-local-hook 'pre-command-hook)) - - (if (ediff-window-display-p) - (add-hook 'pre-command-hook 'ediff-spy-after-mouse nil 'local)) - (setq ediff-mouse-pixel-position (mouse-pixel-position)) - - ;; adjust for merge jobs - (if ediff-merge-job - (let ((buf - ;; If default variant is `combined', the right stuff is - ;; inserted by ediff-do-merge - ;; Note: at some point, we tried to put ancestor buffer here - ;; (which is currently buffer C. This didn't work right - ;; because the merge buffer will contain lossage: diff regions - ;; in the ancestor, which correspond to revisions that agree - ;; in both buf A and B. - (cond ((eq ediff-default-variant 'default-B) - buffer-B) - (t buffer-A)))) - - (setq ediff-split-window-function - ediff-merge-split-window-function) - - ;; remember the ancestor buffer, if any - (setq ediff-ancestor-buffer buffer-C) - - (setq buffer-C - (get-buffer-create - (ediff-unique-buffer-name "*ediff-merge" "*"))) - (with-current-buffer buffer-C - (insert-buffer-substring buf) - (goto-char (point-min)) - (funcall (ediff-with-current-buffer buf major-mode)) - (widen) ; merge buffer is always widened - (add-hook 'local-write-file-hooks 'ediff-set-merge-mode nil t) - ))) - (setq buffer-read-only nil - ediff-buffer-A buffer-A - ediff-buffer-B buffer-B - ediff-buffer-C buffer-C - ediff-control-buffer control-buffer) - - (ediff-choose-syntax-table) - - (setq ediff-control-buffer-suffix - (if (string-match "<[0-9]*>" control-buffer-name) - (substring control-buffer-name - (match-beginning 0) (match-end 0)) - "") - ediff-control-buffer-number - (max - 0 - (1- - (string-to-number - (substring - ediff-control-buffer-suffix - (or - (string-match "[0-9]+" ediff-control-buffer-suffix) - 0)))))) - - (setq ediff-error-buffer - (get-buffer-create (ediff-unique-buffer-name "*ediff-errors" "*"))) - - (with-current-buffer ediff-error-buffer - (setq buffer-undo-list t)) - - (ediff-with-current-buffer buffer-A (ediff-strip-mode-line-format)) - (ediff-with-current-buffer buffer-B (ediff-strip-mode-line-format)) - (if ediff-3way-job - (ediff-with-current-buffer buffer-C (ediff-strip-mode-line-format))) - (if (ediff-buffer-live-p ediff-ancestor-buffer) - (ediff-with-current-buffer ediff-ancestor-buffer - (ediff-strip-mode-line-format))) - - (ediff-save-protected-variables) ; save variables to be restored on exit - - ;; ediff-setup-diff-regions-function must be set after setup - ;; parameters are processed. - (setq ediff-setup-diff-regions-function - (if ediff-diff3-job - 'ediff-setup-diff-regions3 - 'ediff-setup-diff-regions)) - - (setq ediff-wide-bounds - (list (ediff-make-bullet-proof-overlay - '(point-min) '(point-max) ediff-buffer-A) - (ediff-make-bullet-proof-overlay - '(point-min) '(point-max) ediff-buffer-B) - (ediff-make-bullet-proof-overlay - '(point-min) '(point-max) ediff-buffer-C))) - - ;; This has effect only on ediff-windows/regions - ;; In all other cases, ediff-visible-region sets visibility bounds to - ;; ediff-wide-bounds, and ediff-narrow-bounds are ignored. - (if ediff-start-narrowed - (setq ediff-visible-bounds ediff-narrow-bounds) - (setq ediff-visible-bounds ediff-wide-bounds)) - - (ediff-set-keys) ; comes after parameter setup - - ;; set up ediff-narrow-bounds, if not set - (or ediff-narrow-bounds - (setq ediff-narrow-bounds ediff-wide-bounds)) - - ;; All these must be inside ediff-with-current-buffer control-buffer, - ;; since these vars are local to control-buffer - ;; These won't run if there are errors in diff - (ediff-with-current-buffer ediff-buffer-A - (ediff-nuke-selective-display) - (run-hooks 'ediff-prepare-buffer-hook) - (if (ediff-with-current-buffer control-buffer ediff-merge-job) - (setq buffer-read-only t)) - ;; add control-buffer to the list of sessions--no longer used, but may - ;; be used again in the future - (or (memq control-buffer ediff-this-buffer-ediff-sessions) - (setq ediff-this-buffer-ediff-sessions - (cons control-buffer ediff-this-buffer-ediff-sessions))) - (if ediff-make-buffers-readonly-at-startup - (setq buffer-read-only t)) - ) - - (ediff-with-current-buffer ediff-buffer-B - (ediff-nuke-selective-display) - (run-hooks 'ediff-prepare-buffer-hook) - (if (ediff-with-current-buffer control-buffer ediff-merge-job) - (setq buffer-read-only t)) - ;; add control-buffer to the list of sessions - (or (memq control-buffer ediff-this-buffer-ediff-sessions) - (setq ediff-this-buffer-ediff-sessions - (cons control-buffer ediff-this-buffer-ediff-sessions))) - (if ediff-make-buffers-readonly-at-startup - (setq buffer-read-only t)) - ) - - (if ediff-3way-job - (ediff-with-current-buffer ediff-buffer-C - (ediff-nuke-selective-display) - ;; the merge bufer should never be narrowed - ;; (it can happen if it is on rmail-mode or similar) - (if (ediff-with-current-buffer control-buffer ediff-merge-job) - (widen)) - (run-hooks 'ediff-prepare-buffer-hook) - ;; add control-buffer to the list of sessions - (or (memq control-buffer ediff-this-buffer-ediff-sessions) - (setq ediff-this-buffer-ediff-sessions - (cons control-buffer - ediff-this-buffer-ediff-sessions))) - (if ediff-make-buffers-readonly-at-startup - (setq buffer-read-only t) - (setq buffer-read-only nil)) - )) - - (if (ediff-buffer-live-p ediff-ancestor-buffer) - (ediff-with-current-buffer ediff-ancestor-buffer - (ediff-nuke-selective-display) - (setq buffer-read-only t) - (run-hooks 'ediff-prepare-buffer-hook) - (or (memq control-buffer ediff-this-buffer-ediff-sessions) - (setq ediff-this-buffer-ediff-sessions - (cons control-buffer - ediff-this-buffer-ediff-sessions))) - )) - - ;; the following must be after setting up ediff-narrow-bounds AND after - ;; nuking selective display - (funcall ediff-setup-diff-regions-function file-A file-B file-C) - (setq ediff-number-of-differences (length ediff-difference-vector-A)) - (setq ediff-current-difference -1) - - (ediff-make-current-diff-overlay 'A) - (ediff-make-current-diff-overlay 'B) - (if ediff-3way-job - (ediff-make-current-diff-overlay 'C)) - (if ediff-merge-with-ancestor-job - (ediff-make-current-diff-overlay 'Ancestor)) - - (ediff-setup-windows buffer-A buffer-B buffer-C control-buffer) - - (let ((shift-A (ediff-overlay-start - (ediff-get-value-according-to-buffer-type - 'A ediff-narrow-bounds))) - (shift-B (ediff-overlay-start - (ediff-get-value-according-to-buffer-type - 'B ediff-narrow-bounds))) - (shift-C (ediff-overlay-start - (ediff-get-value-according-to-buffer-type - 'C ediff-narrow-bounds)))) - ;; position point in buf A - (save-excursion - (select-window ediff-window-A) - (goto-char shift-A)) - ;; position point in buf B - (save-excursion - (select-window ediff-window-B) - (goto-char shift-B)) - (if ediff-3way-job - (save-excursion - (select-window ediff-window-C) - (goto-char shift-C))) - ) - - (select-window ediff-control-window) - (ediff-visible-region) - - (run-hooks 'startup-hooks) - (ediff-arrange-autosave-in-merge-jobs merge-buffer-file) - - (ediff-refresh-mode-lines) - (setq buffer-read-only t) - (setq ediff-session-registry - (cons control-buffer ediff-session-registry)) - (ediff-update-registry) - (if (ediff-buffer-live-p ediff-meta-buffer) - (ediff-update-meta-buffer - ediff-meta-buffer nil ediff-meta-session-number)) - (run-hooks 'ediff-startup-hook) - ) ; eval in control-buffer - control-buffer)) - - -;; This function assumes that we are in the window where control buffer is -;; to reside. -(defun ediff-setup-control-buffer (ctl-buf) - "Set up window for control buffer." - (if (window-dedicated-p (selected-window)) - (set-buffer ctl-buf) ; we are in control frame but just in case - (switch-to-buffer ctl-buf)) - (let ((window-min-height 2)) - (erase-buffer) - (ediff-set-help-message) - (insert ediff-help-message) - (shrink-window-if-larger-than-buffer) - (or (ediff-multiframe-setup-p) - (ediff-indent-help-message)) - (ediff-set-help-overlays) - - (set-buffer-modified-p nil) - (ediff-refresh-mode-lines) - (setq ediff-control-window (selected-window)) - (setq ediff-window-config-saved - (format "%S%S%S%S%S%S%S" - ediff-control-window - ediff-window-A - ediff-window-B - ediff-window-C - ediff-split-window-function - (ediff-multiframe-setup-p) - ediff-wide-display-p)) - - (set-window-dedicated-p (selected-window) t) - ;; In multiframe, toolbar is set in ediff-setup-control-frame - (if (not (ediff-multiframe-setup-p)) - (ediff-make-bottom-toolbar)) ; this checks if toolbar is requested - (goto-char (point-min)) - (skip-chars-forward ediff-whitespace))) - -;; This executes in control buffer and sets auto-save, visited file name, etc, -;; in the merge buffer -(defun ediff-arrange-autosave-in-merge-jobs (merge-buffer-file) - (if (not ediff-merge-job) - () - (if (stringp merge-buffer-file) - (setq ediff-autostore-merges t - ediff-merge-store-file merge-buffer-file)) - (if (stringp ediff-merge-store-file) - (progn - ;; save before leaving ctl buffer - (ediff-verify-file-merge-buffer ediff-merge-store-file) - (setq merge-buffer-file ediff-merge-store-file) - (ediff-with-current-buffer ediff-buffer-C - (set-visited-file-name merge-buffer-file)))) - (ediff-with-current-buffer ediff-buffer-C - (setq buffer-offer-save t) ; ask before killing buffer - ;; make sure the contents is auto-saved - (auto-save-mode 1)) - )) - - -;;; Commands for working with Ediff - -(defun ediff-update-diffs () - "Recompute difference regions in buffers A, B, and C. -Buffers are not synchronized with their respective files, so changes done -to these buffers are not saved at this point---the user can do this later, -if necessary." - (interactive) - (ediff-barf-if-not-control-buffer) - (if (and (ediff-buffer-live-p ediff-ancestor-buffer) - (not - (y-or-n-p - "Ancestor buffer will not be used. Recompute diffs anyway? "))) - (error "Recomputation of differences canceled")) - - (let ((point-A (ediff-with-current-buffer ediff-buffer-A (point))) - ;;(point-B (ediff-with-current-buffer ediff-buffer-B (point))) - (tmp-buffer (get-buffer-create ediff-tmp-buffer)) - (buf-A-file-name (buffer-file-name ediff-buffer-A)) - (buf-B-file-name (buffer-file-name ediff-buffer-B)) - ;; (null ediff-buffer-C) is no problem, as we later check if - ;; ediff-buffer-C is alive - (buf-C-file-name (buffer-file-name ediff-buffer-C)) - (overl-A (ediff-get-value-according-to-buffer-type - 'A ediff-narrow-bounds)) - (overl-B (ediff-get-value-according-to-buffer-type - 'B ediff-narrow-bounds)) - (overl-C (ediff-get-value-according-to-buffer-type - 'C ediff-narrow-bounds)) - beg-A end-A beg-B end-B beg-C end-C - file-A file-B file-C) - - (if (stringp buf-A-file-name) - (setq buf-A-file-name (file-name-nondirectory buf-A-file-name))) - (if (stringp buf-B-file-name) - (setq buf-B-file-name (file-name-nondirectory buf-B-file-name))) - (if (stringp buf-C-file-name) - (setq buf-C-file-name (file-name-nondirectory buf-C-file-name))) - - (ediff-unselect-and-select-difference -1) - - (setq beg-A (ediff-overlay-start overl-A) - beg-B (ediff-overlay-start overl-B) - beg-C (ediff-overlay-start overl-C) - end-A (ediff-overlay-end overl-A) - end-B (ediff-overlay-end overl-B) - end-C (ediff-overlay-end overl-C)) - - (if ediff-word-mode - (progn - (ediff-wordify beg-A end-A ediff-buffer-A tmp-buffer) - (setq file-A (ediff-make-temp-file tmp-buffer "regA")) - (ediff-wordify beg-B end-B ediff-buffer-B tmp-buffer) - (setq file-B (ediff-make-temp-file tmp-buffer "regB")) - (if ediff-3way-job - (progn - (ediff-wordify beg-C end-C ediff-buffer-C tmp-buffer) - (setq file-C (ediff-make-temp-file tmp-buffer "regC")))) - ) - ;; not word-mode - (setq file-A (ediff-make-temp-file ediff-buffer-A buf-A-file-name)) - (setq file-B (ediff-make-temp-file ediff-buffer-B buf-B-file-name)) - (if ediff-3way-job - (setq file-C (ediff-make-temp-file ediff-buffer-C buf-C-file-name))) - ) - - (ediff-clear-diff-vector 'ediff-difference-vector-A 'fine-diffs-also) - (ediff-clear-diff-vector 'ediff-difference-vector-B 'fine-diffs-also) - (ediff-clear-diff-vector 'ediff-difference-vector-C 'fine-diffs-also) - (ediff-clear-diff-vector - 'ediff-difference-vector-Ancestor 'fine-diffs-also) - ;; let them garbage collect. we can't use the ancestor after recomputing - ;; the diffs. - (setq ediff-difference-vector-Ancestor nil - ediff-ancestor-buffer nil - ediff-state-of-merge nil) - - (setq ediff-killed-diffs-alist nil) ; invalidate saved killed diff regions - - ;; In case of merge job, fool it into thinking that it is just doing - ;; comparison - (let ((ediff-setup-diff-regions-function ediff-setup-diff-regions-function) - (ediff-3way-comparison-job ediff-3way-comparison-job) - (ediff-merge-job ediff-merge-job) - (ediff-merge-with-ancestor-job ediff-merge-with-ancestor-job) - (ediff-job-name ediff-job-name)) - (if ediff-merge-job - (setq ediff-setup-diff-regions-function 'ediff-setup-diff-regions3 - ediff-3way-comparison-job t - ediff-merge-job nil - ediff-merge-with-ancestor-job nil - ediff-job-name 'ediff-files3)) - (funcall ediff-setup-diff-regions-function file-A file-B file-C)) - - (setq ediff-number-of-differences (length ediff-difference-vector-A)) - (delete-file file-A) - (delete-file file-B) - (if file-C - (delete-file file-C)) - - (if ediff-3way-job - (ediff-set-state-of-all-diffs-in-all-buffers ediff-control-buffer)) - - (ediff-jump-to-difference (ediff-diff-at-point 'A point-A)) - (message "") - )) - -;; Not bound to any key---to dangerous. A user can do it if necessary. -(defun ediff-revert-buffers-then-recompute-diffs (noconfirm) - "Revert buffers A, B and C. Then rerun Ediff on file A and file B." - (interactive "P") - (ediff-barf-if-not-control-buffer) - (let ((bufA ediff-buffer-A) - (bufB ediff-buffer-B) - (bufC ediff-buffer-C) - (ctl-buf ediff-control-buffer) - (keep-variants ediff-keep-variants) - (ancestor-buf ediff-ancestor-buffer) - (ancestor-job ediff-merge-with-ancestor-job) - (merge ediff-merge-job) - (comparison ediff-3way-comparison-job)) - (ediff-with-current-buffer bufA - (revert-buffer t noconfirm)) - (ediff-with-current-buffer bufB - (revert-buffer t noconfirm)) - ;; this should only be executed in a 3way comparison, not in merge - (if comparison - (ediff-with-current-buffer bufC - (revert-buffer t noconfirm))) - (if merge - (progn - (set-buffer ctl-buf) - ;; the argument says whether to reverse the meaning of - ;; ediff-keep-variants, i.e., ediff-really-quit runs here with - ;; variants kept. - (ediff-really-quit (not keep-variants)) - (kill-buffer bufC) - (if ancestor-job - (ediff-merge-buffers-with-ancestor bufA bufB ancestor-buf) - (ediff-merge-buffers bufA bufB))) - (ediff-update-diffs)))) - - -;; optional NO-REHIGHLIGHT says to not rehighlight buffers -(defun ediff-recenter (&optional no-rehighlight) - "Bring the highlighted region of all buffers being compared into view. -Reestablish the default three-window display." - (interactive) - (ediff-barf-if-not-control-buffer) - (let (buffer-read-only) - (if (and (ediff-buffer-live-p ediff-buffer-A) - (ediff-buffer-live-p ediff-buffer-B) - (or (not ediff-3way-job) - (ediff-buffer-live-p ediff-buffer-C))) - (ediff-setup-windows - ediff-buffer-A ediff-buffer-B ediff-buffer-C ediff-control-buffer) - (or (eq this-command 'ediff-quit) - (message ediff-KILLED-VITAL-BUFFER - (beep 1))) - )) - - ;; set visibility range appropriate to this invocation of Ediff. - (ediff-visible-region) - ;; raise - (if (and (ediff-window-display-p) - (symbolp this-command) - (symbolp last-command) - ;; Either one of the display-changing commands - (or (memq this-command - '(ediff-recenter - ediff-dir-action ediff-registry-action - ediff-patch-action - ediff-toggle-wide-display ediff-toggle-multiframe)) - ;; Or one of the movement cmds and prev cmd was an Ediff cmd - ;; This avoids raising frames unnecessarily. - (and (memq this-command - '(ediff-next-difference - ediff-previous-difference - ediff-jump-to-difference - ediff-jump-to-difference-at-point)) - (not (string-match "^ediff-" (symbol-name last-command))) - ))) - (progn - (if (window-live-p ediff-window-A) - (raise-frame (window-frame ediff-window-A))) - (if (window-live-p ediff-window-B) - (raise-frame (window-frame ediff-window-B))) - (if (window-live-p ediff-window-C) - (raise-frame (window-frame ediff-window-C))))) - (if (and (ediff-window-display-p) - (frame-live-p ediff-control-frame) - (not ediff-use-long-help-message) - (not (ediff-frame-iconified-p ediff-control-frame))) - (raise-frame ediff-control-frame)) - - ;; Redisplay whatever buffers are showing, if there is a selected difference - (let ((control-frame ediff-control-frame) - (control-buf ediff-control-buffer)) - (if (and (ediff-buffer-live-p ediff-buffer-A) - (ediff-buffer-live-p ediff-buffer-B) - (or (not ediff-3way-job) - (ediff-buffer-live-p ediff-buffer-C))) - (progn - (or no-rehighlight - (ediff-select-difference ediff-current-difference)) - - (ediff-recenter-one-window 'A) - (ediff-recenter-one-window 'B) - (if ediff-3way-job - (ediff-recenter-one-window 'C)) - - (ediff-with-current-buffer control-buf - (ediff-recenter-ancestor) ; check if ancestor is alive - - (if (and (ediff-multiframe-setup-p) - (not ediff-use-long-help-message) - (not (ediff-frame-iconified-p ediff-control-frame))) - ;; never grab mouse on quit in this place - (ediff-reset-mouse - control-frame - (eq this-command 'ediff-quit)))) - )) - - (or no-rehighlight - (ediff-restore-highlighting)) - (ediff-with-current-buffer control-buf (ediff-refresh-mode-lines)) - )) - -;; this function returns to the window it was called from -;; (which was the control window) -(defun ediff-recenter-one-window (buf-type) - (if (ediff-valid-difference-p) - ;; context must be saved before switching to windows A/B/C - (let* ((ctl-wind (selected-window)) - (shift (ediff-overlay-start - (ediff-get-value-according-to-buffer-type - buf-type ediff-narrow-bounds))) - (job-name ediff-job-name) - (control-buf ediff-control-buffer) - (window-name (ediff-get-symbol-from-alist - buf-type ediff-window-alist)) - (window (if (window-live-p (symbol-value window-name)) - (symbol-value window-name)))) - - (if (and window ediff-windows-job) - (set-window-start window shift)) - (if window - (progn - (select-window window) - (ediff-deactivate-mark) - (ediff-position-region - (ediff-get-diff-posn buf-type 'beg nil control-buf) - (ediff-get-diff-posn buf-type 'end nil control-buf) - (ediff-get-diff-posn buf-type 'beg nil control-buf) - job-name - ))) - (select-window ctl-wind) - ))) - -(defun ediff-recenter-ancestor () - ;; do half-hearted job by recentering the ancestor buffer, if it is alive and - ;; visible. - (if (and (ediff-buffer-live-p ediff-ancestor-buffer) - (ediff-valid-difference-p)) - (let ((window (ediff-get-visible-buffer-window ediff-ancestor-buffer)) - (ctl-wind (selected-window)) - (job-name ediff-job-name) - (ctl-buf ediff-control-buffer)) - (ediff-with-current-buffer ediff-ancestor-buffer - (goto-char (ediff-get-diff-posn 'Ancestor 'beg nil ctl-buf)) - (if window - (progn - (select-window window) - (ediff-position-region - (ediff-get-diff-posn 'Ancestor 'beg nil ctl-buf) - (ediff-get-diff-posn 'Ancestor 'end nil ctl-buf) - (ediff-get-diff-posn 'Ancestor 'beg nil ctl-buf) - job-name)))) - (select-window ctl-wind) - ))) - - -;; This will have to be refined for 3way jobs -(defun ediff-toggle-split () - "Toggle vertical/horizontal window split. -Does nothing if file-A and file-B are in different frames." - (interactive) - (ediff-barf-if-not-control-buffer) - (let* ((wind-A (if (window-live-p ediff-window-A) ediff-window-A)) - (wind-B (if (window-live-p ediff-window-B) ediff-window-B)) - (wind-C (if (window-live-p ediff-window-C) ediff-window-C)) - (frame-A (if wind-A (window-frame wind-A))) - (frame-B (if wind-B (window-frame wind-B))) - (frame-C (if wind-C (window-frame wind-C)))) - (if (or (eq frame-A frame-B) - (not (frame-live-p frame-A)) - (not (frame-live-p frame-B)) - (if ediff-3way-comparison-job - (or (not (frame-live-p frame-C)) - (eq frame-A frame-C) (eq frame-B frame-C)))) - (setq ediff-split-window-function - (if (eq ediff-split-window-function 'split-window-vertically) - 'split-window-horizontally - 'split-window-vertically)) - (message "Buffers being compared are in different frames")) - (ediff-recenter 'no-rehighlight))) - -(defun ediff-toggle-hilit () - "Switch between highlighting using ASCII flags and highlighting using faces. -On a dumb terminal, switches between ASCII highlighting and no highlighting." - (interactive) - (ediff-barf-if-not-control-buffer) - - (ediff-unselect-and-select-difference - ediff-current-difference 'unselect-only) - ;; cycle through highlighting - (cond ((and ediff-use-faces - (ediff-has-face-support-p) - ediff-highlight-all-diffs) - (message "Unhighlighting unselected difference regions") - (setq ediff-highlight-all-diffs nil - ediff-highlighting-style 'face)) - ((or (and ediff-use-faces (ediff-has-face-support-p) - (eq ediff-highlighting-style 'face)) ; has face support - (and (not (ediff-has-face-support-p)) ; no face support - (eq ediff-highlighting-style 'off))) - (message "Highlighting with ASCII flags") - (setq ediff-highlighting-style 'ascii - ediff-highlight-all-diffs nil - ediff-use-faces nil)) - ((eq ediff-highlighting-style 'ascii) - (message "ASCII highlighting flags removed") - (setq ediff-highlighting-style 'off - ediff-highlight-all-diffs nil)) - ((ediff-has-face-support-p) ; catch-all for cases with face support - (message "Re-highlighting all difference regions") - (setq ediff-use-faces t - ediff-highlighting-style 'face - ediff-highlight-all-diffs t))) - - (if (and ediff-use-faces ediff-highlight-all-diffs) - (ediff-paint-background-regions) - (ediff-paint-background-regions 'unhighlight)) - - (ediff-unselect-and-select-difference - ediff-current-difference 'select-only)) - - -(defun ediff-toggle-autorefine () - "Toggle auto-refine mode." - (interactive) - (ediff-barf-if-not-control-buffer) - (if ediff-word-mode - (error "No fine differences in this mode")) - (cond ((eq ediff-auto-refine 'nix) - (setq ediff-auto-refine 'on) - (ediff-make-fine-diffs ediff-current-difference 'noforce) - (message "Auto-refining is ON")) - ((eq ediff-auto-refine 'on) - (message "Auto-refining is OFF") - (setq ediff-auto-refine 'off)) - (t ;; nix 'em - (ediff-set-fine-diff-properties ediff-current-difference 'default) - (message "Refinements are HIDDEN") - (setq ediff-auto-refine 'nix)) - )) - -(defun ediff-show-ancestor () - "Show the ancestor buffer in a suitable window." - (interactive) - (ediff-recenter) - (or (ediff-buffer-live-p ediff-ancestor-buffer) - (if ediff-merge-with-ancestor-job - (error "Lost connection to ancestor buffer...sorry") - (error "Not merging with ancestor"))) - (let (wind) - (cond ((setq wind (ediff-get-visible-buffer-window ediff-ancestor-buffer)) - (raise-frame (window-frame wind))) - (t (set-window-buffer ediff-window-C ediff-ancestor-buffer))))) - -(defun ediff-make-or-kill-fine-diffs (arg) - "Compute fine diffs. With negative prefix arg, kill fine diffs. -In both cases, operates on the current difference region." - (interactive "P") - (ediff-barf-if-not-control-buffer) - (cond ((eq arg '-) - (ediff-clear-fine-differences ediff-current-difference)) - ((and (numberp arg) (< arg 0)) - (ediff-clear-fine-differences ediff-current-difference)) - (t (ediff-make-fine-diffs)))) - - -(defun ediff-toggle-help () - "Toggle short/long help message." - (interactive) - (ediff-barf-if-not-control-buffer) - (let (buffer-read-only) - (erase-buffer) - (setq ediff-use-long-help-message (not ediff-use-long-help-message)) - (ediff-set-help-message)) - ;; remember the icon status of the control frame when the user requested - ;; full control message - (if (and ediff-use-long-help-message (ediff-multiframe-setup-p)) - (setq ediff-prefer-iconified-control-frame - (ediff-frame-iconified-p ediff-control-frame))) - - (setq ediff-window-config-saved "") ; force redisplay - (ediff-recenter 'no-rehighlight)) - - -;; If BUF, this is the buffer to toggle, not current buffer. -(defun ediff-toggle-read-only (&optional buf) - "Toggle read-only in current buffer. -If buffer is under version control and locked, check it out first. -If optional argument BUF is specified, toggle read-only in that buffer instead -of the current buffer." - (interactive) - (ediff-barf-if-not-control-buffer) - (let ((ctl-buf (if (null buf) (current-buffer))) - (buf-type (ediff-char-to-buftype (ediff-last-command-char)))) - (or buf (ediff-recenter)) - (or buf - (setq buf (ediff-get-buffer buf-type))) - - (ediff-with-current-buffer buf ; eval in buf A/B/C - (let* ((file (buffer-file-name buf)) - (file-writable (and file - (file-exists-p file) - (file-writable-p file))) - (toggle-ro-cmd (cond (ediff-toggle-read-only-function) - ((ediff-file-checked-out-p file) - 'toggle-read-only) - (file-writable 'toggle-read-only) - (t (key-binding "\C-x\C-q"))))) - ;; If the file is checked in, make sure we don't make buffer modifiable - ;; without warning the user. The user can fool our checks by making the - ;; buffer non-RO without checking the file out. We regard this as a - ;; user problem. - (if (and (ediff-file-checked-in-p file) - ;; If ctl-buf is null, this means we called this - ;; non-interactively, in which case don't ask questions - ctl-buf) - (cond ((not buffer-read-only) - (setq toggle-ro-cmd 'toggle-read-only)) - ((and (or (beep 1) t) ; always beep - (y-or-n-p - (format - "File %s is under version control. Check it out? " - (ediff-abbreviate-file-name file)))) - ;; if we checked the file out, we should also change the - ;; original state of buffer-read-only to nil. If we don't - ;; do this, the mode line will show %%, since the file was - ;; RO before ediff started, so the user will think the file - ;; is checked in. - (ediff-with-current-buffer ctl-buf - (ediff-change-saved-variable - 'buffer-read-only nil buf-type))) - (t - (setq toggle-ro-cmd 'toggle-read-only) - (beep 1) (beep 1) - (message - "Boy, this is risky! Don't modify this file...") - (sit-for 3)))) ; let the user see the warning - (if (and toggle-ro-cmd - (string-match "toggle-read-only" (symbol-name toggle-ro-cmd))) - (save-excursion - (save-window-excursion - (select-window (ediff-get-visible-buffer-window buf)) - (command-execute toggle-ro-cmd))) - (error "Don't know how to toggle read-only in buffer %S" buf)) - - ;; Check if we made the current buffer updatable, but its file is RO. - ;; Signal a warning in this case. - (if (and file (not buffer-read-only) - (eq this-command 'ediff-toggle-read-only) - (file-exists-p file) - (not (file-writable-p file))) - (progn - (beep 1) - (message "Warning: file %s is read-only" - (ediff-abbreviate-file-name file)))) - )))) - -;; checkout if visited file is checked in -(defun ediff-maybe-checkout (buf) - (let ((file (expand-file-name (buffer-file-name buf))) - (checkout-function (key-binding "\C-x\C-q"))) - (if (and (ediff-file-checked-in-p file) - (or (beep 1) t) - (y-or-n-p - (format - "File %s is under version control. Check it out? " - (ediff-abbreviate-file-name file)))) - (ediff-with-current-buffer buf - (command-execute checkout-function))))) - - -;; This is a simple-minded check for whether a file is under version control. -;; If file,v exists but file doesn't, this file is considered to be not checked -;; in and not checked out for the purpose of patching (since patch won't be -;; able to read such a file anyway). -;; FILE is a string representing file name -;;(defun ediff-file-under-version-control (file) -;; (let* ((filedir (file-name-directory file)) -;; (file-nondir (file-name-nondirectory file)) -;; (trial (concat file-nondir ",v")) -;; (full-trial (concat filedir trial)) -;; (full-rcs-trial (concat filedir "RCS/" trial))) -;; (and (stringp file) -;; (file-exists-p file) -;; (or -;; (and -;; (file-exists-p full-trial) -;; ;; in FAT FS, `file,v' and `file' may turn out to be the same! -;; ;; don't be fooled by this! -;; (not (equal (file-attributes file) -;; (file-attributes full-trial)))) -;; ;; check if a version is in RCS/ directory -;; (file-exists-p full-rcs-trial))) -;; )) - - -(defun ediff-file-checked-out-p (file) - (or (not (featurep 'vc-hooks)) - (and (vc-backend file) - (if (fboundp 'vc-state) - (or (memq (vc-state file) '(edited needs-merge)) - (stringp (vc-state file))) - ;; XEmacs has no vc-state - (when (featurep 'xemacs) (vc-locking-user file))) - ))) - -(defun ediff-file-checked-in-p (file) - (and (featurep 'vc-hooks) - ;; Only RCS and SCCS files are considered checked in - (memq (vc-backend file) '(RCS SCCS)) - (if (fboundp 'vc-state) - (and - (not (memq (vc-state file) '(edited needs-merge))) - (not (stringp (vc-state file)))) - ;; XEmacs has no vc-state - (when (featurep 'xemacs) (not (vc-locking-user file)))) - )) - -(defun ediff-file-compressed-p (file) - (condition-case nil - (require 'jka-compr) - (error)) - (if (featurep 'jka-compr) - (string-match (jka-compr-build-file-regexp) file))) - - -(defun ediff-swap-buffers () - "Rotate the display of buffers A, B, and C." - (interactive) - (ediff-barf-if-not-control-buffer) - (if (and (window-live-p ediff-window-A) (window-live-p ediff-window-B)) - (let ((buf ediff-buffer-A) - (values ediff-buffer-values-orig-A) - (diff-vec ediff-difference-vector-A) - (hide-regexp ediff-regexp-hide-A) - (focus-regexp ediff-regexp-focus-A) - (wide-visibility-p (eq ediff-visible-bounds ediff-wide-bounds)) - (overlay (if (ediff-has-face-support-p) - ediff-current-diff-overlay-A))) - (if ediff-3way-comparison-job - (progn - (set-window-buffer ediff-window-A ediff-buffer-C) - (set-window-buffer ediff-window-B ediff-buffer-A) - (set-window-buffer ediff-window-C ediff-buffer-B) - ) - (set-window-buffer ediff-window-A ediff-buffer-B) - (set-window-buffer ediff-window-B ediff-buffer-A)) - ;; swap diff buffers - (if ediff-3way-comparison-job - (setq ediff-buffer-A ediff-buffer-C - ediff-buffer-C ediff-buffer-B - ediff-buffer-B buf) - (setq ediff-buffer-A ediff-buffer-B - ediff-buffer-B buf)) - - ;; swap saved buffer characteristics - (if ediff-3way-comparison-job - (setq ediff-buffer-values-orig-A ediff-buffer-values-orig-C - ediff-buffer-values-orig-C ediff-buffer-values-orig-B - ediff-buffer-values-orig-B values) - (setq ediff-buffer-values-orig-A ediff-buffer-values-orig-B - ediff-buffer-values-orig-B values)) - - ;; swap diff vectors - (if ediff-3way-comparison-job - (setq ediff-difference-vector-A ediff-difference-vector-C - ediff-difference-vector-C ediff-difference-vector-B - ediff-difference-vector-B diff-vec) - (setq ediff-difference-vector-A ediff-difference-vector-B - ediff-difference-vector-B diff-vec)) - - ;; swap hide/focus regexp - (if ediff-3way-comparison-job - (setq ediff-regexp-hide-A ediff-regexp-hide-C - ediff-regexp-hide-C ediff-regexp-hide-B - ediff-regexp-hide-B hide-regexp - ediff-regexp-focus-A ediff-regexp-focus-C - ediff-regexp-focus-C ediff-regexp-focus-B - ediff-regexp-focus-B focus-regexp) - (setq ediff-regexp-hide-A ediff-regexp-hide-B - ediff-regexp-hide-B hide-regexp - ediff-regexp-focus-A ediff-regexp-focus-B - ediff-regexp-focus-B focus-regexp)) - - ;; The following is needed for XEmacs, since there one can't move - ;; overlay to another buffer. In Emacs, this swap is redundant. - (if (ediff-has-face-support-p) - (if ediff-3way-comparison-job - (setq ediff-current-diff-overlay-A ediff-current-diff-overlay-C - ediff-current-diff-overlay-C ediff-current-diff-overlay-B - ediff-current-diff-overlay-B overlay) - (setq ediff-current-diff-overlay-A ediff-current-diff-overlay-B - ediff-current-diff-overlay-B overlay))) - - ;; swap wide bounds - (setq ediff-wide-bounds - (cond (ediff-3way-comparison-job - (list (nth 2 ediff-wide-bounds) - (nth 0 ediff-wide-bounds) - (nth 1 ediff-wide-bounds))) - (ediff-3way-job - (list (nth 1 ediff-wide-bounds) - (nth 0 ediff-wide-bounds) - (nth 2 ediff-wide-bounds))) - (t - (list (nth 1 ediff-wide-bounds) - (nth 0 ediff-wide-bounds))))) - ;; swap narrow bounds - (setq ediff-narrow-bounds - (cond (ediff-3way-comparison-job - (list (nth 2 ediff-narrow-bounds) - (nth 0 ediff-narrow-bounds) - (nth 1 ediff-narrow-bounds))) - (ediff-3way-job - (list (nth 1 ediff-narrow-bounds) - (nth 0 ediff-narrow-bounds) - (nth 2 ediff-narrow-bounds))) - (t - (list (nth 1 ediff-narrow-bounds) - (nth 0 ediff-narrow-bounds))))) - (if wide-visibility-p - (setq ediff-visible-bounds ediff-wide-bounds) - (setq ediff-visible-bounds ediff-narrow-bounds)) - )) - (if ediff-3way-job - (ediff-set-state-of-all-diffs-in-all-buffers ediff-control-buffer)) - (ediff-recenter 'no-rehighlight) - ) - - -(defun ediff-toggle-wide-display () - "Toggle wide/regular display. -This is especially useful when comparing buffers side-by-side." - (interactive) - (ediff-barf-if-not-control-buffer) - (or (ediff-window-display-p) - (error "%sEmacs is not running as a window application" - (if (featurep 'emacs) "" "X"))) - (ediff-recenter 'no-rehighlight) ; make sure buffs are displayed in windows - (let ((ctl-buf ediff-control-buffer)) - (setq ediff-wide-display-p (not ediff-wide-display-p)) - (if (not ediff-wide-display-p) - (ediff-with-current-buffer ctl-buf - (modify-frame-parameters - ediff-wide-display-frame ediff-wide-display-orig-parameters) - ;;(sit-for (if (featurep 'xemacs) 0.4 0)) - ;; restore control buf, since ctl window may have been deleted - ;; during resizing - (set-buffer ctl-buf) - (setq ediff-wide-display-orig-parameters nil - ediff-window-B nil) ; force update of window config - (ediff-recenter 'no-rehighlight)) - (funcall ediff-make-wide-display-function) - ;;(sit-for (if (featurep 'xemacs) 0.4 0)) - (ediff-with-current-buffer ctl-buf - (setq ediff-window-B nil) ; force update of window config - (ediff-recenter 'no-rehighlight))))) - -;;;###autoload -(defun ediff-toggle-multiframe () - "Switch from multiframe display to single-frame display and back. -To change the default, set the variable `ediff-window-setup-function', -which see." - (interactive) - (let (window-setup-func) - (or (ediff-window-display-p) - (error "%sEmacs is not running as a window application" - (if (featurep 'emacs) "" "X"))) - - (cond ((eq ediff-window-setup-function 'ediff-setup-windows-multiframe) - (setq ediff-multiframe nil) - (setq window-setup-func 'ediff-setup-windows-plain)) - ((eq ediff-window-setup-function 'ediff-setup-windows-plain) - (if (ediff-in-control-buffer-p) - (ediff-kill-bottom-toolbar)) - (if (and (ediff-buffer-live-p ediff-control-buffer) - (window-live-p ediff-control-window)) - (set-window-dedicated-p ediff-control-window nil)) - (setq ediff-multiframe t) - (setq window-setup-func 'ediff-setup-windows-multiframe)) - (t - (if (and (ediff-buffer-live-p ediff-control-buffer) - (window-live-p ediff-control-window)) - (set-window-dedicated-p ediff-control-window nil)) - (setq ediff-multiframe t) - (setq window-setup-func 'ediff-setup-windows-multiframe)) - ) - - ;; change default - (setq-default ediff-window-setup-function window-setup-func) - ;; change in all active ediff sessions - (mapc (lambda(buf) (ediff-with-current-buffer buf - (setq ediff-window-setup-function window-setup-func - ediff-window-B nil))) - ediff-session-registry) - (if (ediff-in-control-buffer-p) - (progn - (set-window-dedicated-p (selected-window) nil) - (ediff-recenter 'no-rehighlight))))) - - -;;;###autoload -(defun ediff-toggle-use-toolbar () - "Enable or disable Ediff toolbar. -Works only in versions of Emacs that support toolbars. -To change the default, set the variable `ediff-use-toolbar-p', which see." - (interactive) - (if (featurep 'ediff-tbar) - (progn - (or (ediff-window-display-p) - (error "%sEmacs is not running as a window application" - (if (featurep 'emacs) "" "X"))) - (if (ediff-use-toolbar-p) - (ediff-kill-bottom-toolbar)) - ;; do this only after killing the toolbar - (setq ediff-use-toolbar-p (not ediff-use-toolbar-p)) - - (mapc (lambda(buf) - (ediff-with-current-buffer buf - ;; force redisplay - (setq ediff-window-config-saved "") - )) - ediff-session-registry) - (if (ediff-in-control-buffer-p) - (ediff-recenter 'no-rehighlight))))) - - -;; if was using toolbar, kill it -(defun ediff-kill-bottom-toolbar () - ;; Using ctl-buffer or ediff-control-window for LOCALE does not - ;; work properly in XEmacs 19.14: we have to use - ;;(selected-frame). - ;; The problem with this is that any previous bottom-toolbar - ;; will not re-appear after our cleanup here. Is there a way - ;; to do "push" and "pop" toolbars ? --marcpa - (if (featurep 'xemacs) - (when (ediff-use-toolbar-p) - (set-specifier bottom-toolbar (list (selected-frame) nil)) - (set-specifier bottom-toolbar-visible-p (list (selected-frame) nil))))) - -;; If wants to use toolbar, make it. -;; If not, zero the toolbar for XEmacs. -;; Do nothing for Emacs. -(defun ediff-make-bottom-toolbar (&optional frame) - (when (ediff-window-display-p) - (setq frame (or frame (selected-frame))) - (if (featurep 'xemacs) - (cond ((ediff-use-toolbar-p) ; this checks for XEmacs - (set-specifier - bottom-toolbar - (list frame (if (ediff-3way-comparison-job) - ediff-toolbar-3way ediff-toolbar))) - (set-specifier bottom-toolbar-visible-p (list frame t)) - (set-specifier bottom-toolbar-height - (list frame ediff-toolbar-height))) - ((ediff-has-toolbar-support-p) - (set-specifier bottom-toolbar-height (list frame 0))))))) - -;; Merging - -(defun ediff-toggle-show-clashes-only () - "Toggle the mode that shows only the merge regions where both variants differ from the ancestor." - (interactive) - (ediff-barf-if-not-control-buffer) - (if (not ediff-merge-with-ancestor-job) - (error "This command makes sense only when merging with an ancestor")) - (setq ediff-show-clashes-only (not ediff-show-clashes-only)) - (if ediff-show-clashes-only - (message "Focus on regions where both buffers differ from the ancestor") - (message "Canceling focus on regions where changes clash"))) - -(defun ediff-toggle-skip-changed-regions () - "Toggle the mode that skips the merge regions that differ from the default." - (interactive) - (ediff-barf-if-not-control-buffer) - (setq ediff-skip-merge-regions-that-differ-from-default - (not ediff-skip-merge-regions-that-differ-from-default)) - (if ediff-skip-merge-regions-that-differ-from-default - (message "Skipping regions that differ from default setting") - (message "Showing regions that differ from default setting"))) - - - -;; Widening/narrowing - -(defun ediff-toggle-narrow-region () - "Toggle narrowing in buffers A, B, and C. -Used in ediff-windows/regions only." - (interactive) - (if (eq ediff-buffer-A ediff-buffer-B) - (error ediff-NO-DIFFERENCES)) - (if (eq ediff-visible-bounds ediff-wide-bounds) - (setq ediff-visible-bounds ediff-narrow-bounds) - (setq ediff-visible-bounds ediff-wide-bounds)) - (ediff-recenter 'no-rehighlight)) - -;; Narrow bufs A/B/C to ediff-visible-bounds. If this is currently set to -;; ediff-wide-bounds, then this actually widens. -;; This function does nothing if job-name is not -;; ediff-regions-wordwise/linewise or ediff-windows-wordwise/linewise. -;; Does nothing if buffer-A = buffer-B since we can't narrow -;; to two different regions in one buffer. -(defun ediff-visible-region () - (if (or (eq ediff-buffer-A ediff-buffer-B) - (eq ediff-buffer-A ediff-buffer-C) - (eq ediff-buffer-C ediff-buffer-B)) - () - ;; If ediff-*-regions/windows, ediff-visible-bounds is already set - ;; Otherwise, always use full range. - (if (not ediff-narrow-job) - (setq ediff-visible-bounds ediff-wide-bounds)) - (let ((overl-A (ediff-get-value-according-to-buffer-type - 'A ediff-visible-bounds)) - (overl-B (ediff-get-value-according-to-buffer-type - 'B ediff-visible-bounds)) - (overl-C (ediff-get-value-according-to-buffer-type - 'C ediff-visible-bounds)) - ) - (ediff-with-current-buffer ediff-buffer-A - (if (ediff-overlay-buffer overl-A) - (narrow-to-region - (ediff-overlay-start overl-A) (ediff-overlay-end overl-A)))) - (ediff-with-current-buffer ediff-buffer-B - (if (ediff-overlay-buffer overl-B) - (narrow-to-region - (ediff-overlay-start overl-B) (ediff-overlay-end overl-B)))) - - (if (and ediff-3way-job (ediff-overlay-buffer overl-C)) - (ediff-with-current-buffer ediff-buffer-C - (narrow-to-region - (ediff-overlay-start overl-C) (ediff-overlay-end overl-C)))) - ))) - - -;; Window scrolling operations - -;; Performs some operation on the two file windows (if they are showing). -;; Traps all errors on the operation in windows A/B/C. -;; Usually, errors come from scrolling off the -;; beginning or end of the buffer, and this gives error messages. -(defun ediff-operate-on-windows (operation arg) - - ;; make sure windows aren't dead - (if (not (and (window-live-p ediff-window-A) (window-live-p ediff-window-B))) - (ediff-recenter 'no-rehighlight)) - (if (not (and (ediff-buffer-live-p ediff-buffer-A) - (ediff-buffer-live-p ediff-buffer-B) - (or (not ediff-3way-job) ediff-buffer-C) - )) - (error ediff-KILLED-VITAL-BUFFER)) - - (let* ((wind (selected-window)) - (wind-A ediff-window-A) - (wind-B ediff-window-B) - (wind-C ediff-window-C) - (coefA (ediff-get-region-size-coefficient 'A operation)) - (coefB (ediff-get-region-size-coefficient 'B operation)) - (three-way ediff-3way-job) - (coefC (if three-way - (ediff-get-region-size-coefficient 'C operation)))) - - (select-window wind-A) - (condition-case nil - (funcall operation (round (* coefA arg))) - (error)) - (select-window wind-B) - (condition-case nil - (funcall operation (round (* coefB arg))) - (error)) - (if three-way - (progn - (select-window wind-C) - (condition-case nil - (funcall operation (round (* coefC arg))) - (error)))) - (select-window wind))) - -(defun ediff-scroll-vertically (&optional arg) - "Vertically scroll buffers A, B \(and C if appropriate\). -With optional argument ARG, scroll ARG lines; otherwise scroll by nearly -the one half of the height of window-A." - (interactive "P") - (ediff-barf-if-not-control-buffer) - - ;; make sure windows aren't dead - (if (not (and (window-live-p ediff-window-A) (window-live-p ediff-window-B))) - (ediff-recenter 'no-rehighlight)) - (if (not (and (ediff-buffer-live-p ediff-buffer-A) - (ediff-buffer-live-p ediff-buffer-B) - (or (not ediff-3way-job) - (ediff-buffer-live-p ediff-buffer-C)) - )) - (error ediff-KILLED-VITAL-BUFFER)) - - (ediff-operate-on-windows - (if (memq (ediff-last-command-char) '(?v ?\C-v)) - 'scroll-up - 'scroll-down) - ;; calculate argument to scroll-up/down - ;; if there is an explicit argument - (if (and arg (not (equal arg '-))) - ;; use it - (prefix-numeric-value arg) - ;; if not, see if we can determine a default amount (the window height) - (let (default-amount) - (setq default-amount - (- (/ (min (window-height ediff-window-A) - (window-height ediff-window-B) - (if ediff-3way-job - (window-height ediff-window-C) - 500)) ; some large number - 2) - 1 next-screen-context-lines)) - ;; window found - (if arg - ;; C-u as argument means half of default amount - (/ default-amount 2) - ;; no argument means default amount - default-amount))))) - - -(defun ediff-scroll-horizontally (&optional arg) - "Horizontally scroll buffers A, B \(and C if appropriate\). -If an argument is given, that is how many columns are scrolled, else nearly -the width of the A/B/C windows." - (interactive "P") - (ediff-barf-if-not-control-buffer) - - ;; make sure windows aren't dead - (if (not (and (window-live-p ediff-window-A) (window-live-p ediff-window-B))) - (ediff-recenter 'no-rehighlight)) - (if (not (and (ediff-buffer-live-p ediff-buffer-A) - (ediff-buffer-live-p ediff-buffer-B) - (or (not ediff-3way-job) - (ediff-buffer-live-p ediff-buffer-C)) - )) - (error ediff-KILLED-VITAL-BUFFER)) - - (ediff-operate-on-windows - ;; Arrange for scroll-left and scroll-right being called - ;; interactively so that they set the window's min_hscroll. - ;; Otherwise, automatic hscrolling will undo the effect of - ;; hscrolling. - (if (= (ediff-last-command-char) ?<) - (lambda (arg) - (let ((prefix-arg arg)) - (call-interactively 'scroll-left))) - (lambda (arg) - (let ((prefix-arg arg)) - (call-interactively 'scroll-right)))) - ;; calculate argument to scroll-left/right - ;; if there is an explicit argument - (if (and arg (not (equal arg '-))) - ;; use it - (prefix-numeric-value arg) - ;; if not, see if we can determine a default amount - ;; (half the window width) - (if (null ediff-control-window) - ;; no control window, use nil - nil - (let ((default-amount - (- (/ (min (window-width ediff-window-A) - (window-width ediff-window-B) - (if ediff-3way-comparison-job - (window-width ediff-window-C) - 500) ; some large number - ) - 2) - 3))) - ;; window found - (if arg - ;; C-u as argument means half of default amount - (/ default-amount 2) - ;; no argument means default amount - default-amount)))))) - - -;;BEG, END show the region to be positioned. -;;JOB-NAME holds ediff-job-name. The ediff-windows job positions regions -;;differently. -(defun ediff-position-region (beg end pos job-name) - (if (> end (point-max)) - (setq end (point-max))) - (if ediff-windows-job - (if (pos-visible-in-window-p end) - () ; do nothing, wind is already positioned - ;; at this point, windows are positioned at the beginning of the - ;; file regions (not diff-regions) being compared. - (save-excursion - (move-to-window-line (- (window-height) 2)) - (let ((amount (+ 2 (count-lines (point) end)))) - (scroll-up amount)))) - (set-window-start (selected-window) beg) - (if (pos-visible-in-window-p end) - ;; Determine the number of lines that the region occupies - (let ((lines 0) - (prev-point 0)) - (while ( and (> end (progn - (move-to-window-line lines) - (point))) - ;; `end' may be beyond the window bottom, so check - ;; that we are making progress - (< prev-point (point))) - (setq prev-point (point)) - (setq lines (1+ lines))) - ;; And position the beginning on the right line - (goto-char beg) - (recenter (/ (1+ (max (- (1- (window-height (selected-window))) - lines) - 1) - ) - 2)))) - (goto-char pos) - )) - -;; get number of lines from window start to region end -(defun ediff-get-lines-to-region-end (buf-type &optional n ctl-buf) - (or n (setq n ediff-current-difference)) - (or ctl-buf (setq ctl-buf ediff-control-buffer)) - (ediff-with-current-buffer ctl-buf - (let* ((buf (ediff-get-buffer buf-type)) - (wind (eval (ediff-get-symbol-from-alist - buf-type ediff-window-alist))) - (beg (window-start wind)) - (end (ediff-get-diff-posn buf-type 'end)) - lines) - (ediff-with-current-buffer buf - (if (< beg end) - (setq lines (count-lines beg end)) - (setq lines 0)) - lines - )))) - -;; Calculate the number of lines from window end to the start of diff region -(defun ediff-get-lines-to-region-start (buf-type &optional diff-num ctl-buf) - (or diff-num (setq diff-num ediff-current-difference)) - (or ctl-buf (setq ctl-buf ediff-control-buffer)) - (ediff-with-current-buffer ctl-buf - (let* ((buf (ediff-get-buffer buf-type)) - (wind (eval (ediff-get-symbol-from-alist - buf-type ediff-window-alist))) - (end (or (window-end wind) (window-end wind t))) - (beg (ediff-get-diff-posn buf-type 'beg diff-num))) - (ediff-with-current-buffer buf - (if (< beg end) - (count-lines (max beg (point-min)) (min end (point-max))) 0)) - ))) - - -;; region size coefficient is a coefficient by which to adjust scrolling -;; up/down of the window displaying buffer of type BUFTYPE. -;; The purpose of this coefficient is to make the windows scroll in sync, so -;; that it won't happen that one diff region is scrolled off while the other is -;; still seen. -;; -;; If the difference region is invalid, the coefficient is 1 -(defun ediff-get-region-size-coefficient (buf-type op &optional n ctl-buf) - (ediff-with-current-buffer (or ctl-buf ediff-control-buffer) - (if (ediff-valid-difference-p n) - (let* ((func (cond ((eq op 'scroll-down) - 'ediff-get-lines-to-region-start) - ((eq op 'scroll-up) - 'ediff-get-lines-to-region-end) - (t '(lambda (a b c) 0)))) - (max-lines (max (funcall func 'A n ctl-buf) - (funcall func 'B n ctl-buf) - (if (ediff-buffer-live-p ediff-buffer-C) - (funcall func 'C n ctl-buf) - 0)))) - ;; this covers the horizontal coefficient as well: - ;; if max-lines = 0 then coef = 1 - (if (> max-lines 0) - (/ (+ (funcall func buf-type n ctl-buf) 0.0) - (+ max-lines 0.0)) - 1)) - 1))) - - -(defun ediff-next-difference (&optional arg) - "Advance to the next difference. -With a prefix argument, go forward that many differences." - (interactive "p") - (ediff-barf-if-not-control-buffer) - (if (< ediff-current-difference ediff-number-of-differences) - (let ((n (min ediff-number-of-differences - (+ ediff-current-difference (or arg 1)))) - non-clash-skip skip-changed regexp-skip) - - (ediff-visible-region) - (or (>= n ediff-number-of-differences) - (setq regexp-skip (funcall ediff-skip-diff-region-function n)) - ;; this won't exec if regexp-skip is t - (setq non-clash-skip (ediff-merge-region-is-non-clash-to-skip n) - skip-changed - (ediff-skip-merge-region-if-changed-from-default-p n)) - (ediff-install-fine-diff-if-necessary n)) - ;; Skip loop - (while (and (< n ediff-number-of-differences) - (or - ;; regexp skip - regexp-skip - ;; skip clashes, if necessary - non-clash-skip - ;; skip processed regions - skip-changed - ;; skip difference regions that differ in white space - (and ediff-ignore-similar-regions - (ediff-merge-region-is-non-clash n) - (or (eq (ediff-no-fine-diffs-p n) t) - (and (ediff-merge-job) - (eq (ediff-no-fine-diffs-p n) 'C))) - ))) - (setq n (1+ n)) - (if (= 0 (mod n 20)) - (message "Skipped over region %d and counting ..." n)) - (or (>= n ediff-number-of-differences) - (setq regexp-skip (funcall ediff-skip-diff-region-function n)) - ;; this won't exec if regexp-skip is t - (setq non-clash-skip (ediff-merge-region-is-non-clash-to-skip n) - skip-changed - (ediff-skip-merge-region-if-changed-from-default-p n)) - (ediff-install-fine-diff-if-necessary n)) - ) - (message "") - (ediff-unselect-and-select-difference n) - ) ; let - (ediff-visible-region) - (error "At end of the difference list"))) - -(defun ediff-previous-difference (&optional arg) - "Go to the previous difference. -With a prefix argument, go back that many differences." - (interactive "p") - (ediff-barf-if-not-control-buffer) - (if (> ediff-current-difference -1) - (let ((n (max -1 (- ediff-current-difference (or arg 1)))) - non-clash-skip skip-changed regexp-skip) - - (ediff-visible-region) - (or (< n 0) - (setq regexp-skip (funcall ediff-skip-diff-region-function n)) - ;; this won't exec if regexp-skip is t - (setq non-clash-skip (ediff-merge-region-is-non-clash-to-skip n) - skip-changed - (ediff-skip-merge-region-if-changed-from-default-p n)) - (ediff-install-fine-diff-if-necessary n)) - (while (and (> n -1) - (or - ;; regexp skip - regexp-skip - ;; skip clashes, if necessary - non-clash-skip - ;; skipp changed regions - skip-changed - ;; skip difference regions that differ in white space - (and ediff-ignore-similar-regions - (ediff-merge-region-is-non-clash n) - (or (eq (ediff-no-fine-diffs-p n) t) - (and (ediff-merge-job) - (eq (ediff-no-fine-diffs-p n) 'C))) - ))) - (if (= 0 (mod (1+ n) 20)) - (message "Skipped over region %d and counting ..." (1+ n))) - (setq n (1- n)) - (or (< n 0) - (setq regexp-skip (funcall ediff-skip-diff-region-function n)) - ;; this won't exec if regexp-skip is t - (setq non-clash-skip (ediff-merge-region-is-non-clash-to-skip n) - skip-changed - (ediff-skip-merge-region-if-changed-from-default-p n)) - (ediff-install-fine-diff-if-necessary n)) - ) - (message "") - (ediff-unselect-and-select-difference n) - ) ; let - (ediff-visible-region) - (error "At beginning of the difference list"))) - -;; The diff number is as perceived by the user (i.e., 1+ the internal -;; representation) -(defun ediff-jump-to-difference (difference-number) - "Go to the difference specified as a prefix argument. -If the prefix is negative, count differences from the end." - (interactive "p") - (ediff-barf-if-not-control-buffer) - (setq difference-number - (cond ((< difference-number 0) - (+ ediff-number-of-differences difference-number)) - ((> difference-number 0) (1- difference-number)) - (t -1))) - ;; -1 is allowed by ediff-unselect-and-select-difference --- it is the - ;; position before the first one. - (if (and (>= difference-number -1) - (<= difference-number ediff-number-of-differences)) - (ediff-unselect-and-select-difference difference-number) - (error ediff-BAD-DIFF-NUMBER - this-command (1+ difference-number) ediff-number-of-differences))) - -(defun ediff-jump-to-difference-at-point (arg) - "Go to difference closest to the point in buffer A, B, or C. -The buffer depends on last command character \(a, b, or c\) that invoked this -command. For instance, if the command was `ga' then the point value in buffer -A is used. -With a prefix argument, synchronize all files around the current point position -in the specified buffer." - (interactive "P") - (ediff-barf-if-not-control-buffer) - (let* ((buf-type (ediff-char-to-buftype (ediff-last-command-char))) - (buffer (ediff-get-buffer buf-type)) - (pt (ediff-with-current-buffer buffer (point))) - (diff-no (ediff-diff-at-point buf-type nil (if arg 'after))) - (past-last-diff (< ediff-number-of-differences diff-no)) - (beg (if past-last-diff - (ediff-with-current-buffer buffer (point-max)) - (ediff-get-diff-posn buf-type 'beg (1- diff-no)))) - ctl-wind wind-A wind-B wind-C - shift) - (if past-last-diff - (ediff-jump-to-difference -1) - (ediff-jump-to-difference diff-no)) - (setq ctl-wind (selected-window) - wind-A ediff-window-A - wind-B ediff-window-B - wind-C ediff-window-C) - (if arg - (progn - (ediff-with-current-buffer buffer - (setq shift (- beg pt))) - (select-window wind-A) - (if past-last-diff (goto-char (point-max))) - (condition-case nil - (backward-char shift) ; noerror, if beginning of buffer - (error)) - (recenter) - (select-window wind-B) - (if past-last-diff (goto-char (point-max))) - (condition-case nil - (backward-char shift) ; noerror, if beginning of buffer - (error)) - (recenter) - (if (window-live-p wind-C) - (progn - (select-window wind-C) - (if past-last-diff (goto-char (point-max))) - (condition-case nil - (backward-char shift) ; noerror, if beginning of buffer - (error)) - (recenter) - )) - (select-window ctl-wind) - )) - )) - - -;; find region most related to the current point position (or POS, if given) -;; returns diff number as seen by the user (i.e., 1+ the internal -;; representation) -;; The optional argument WHICH-DIFF can be `after' or `before'. If `after', -;; find the diff after the point. If `before', find the diff before the -;; point. If the point is inside a diff, return that diff. -(defun ediff-diff-at-point (buf-type &optional pos which-diff) - (let ((buffer (ediff-get-buffer buf-type)) - (ctl-buffer ediff-control-buffer) - (max-dif-num (1- ediff-number-of-differences)) - (diff-no -1) - (prev-beg 0) - (prev-end 0) - (beg 0) - (end 0)) - - (ediff-with-current-buffer buffer - (setq pos (or pos (point))) - (while (and (or (< pos prev-beg) (> pos beg)) - (< diff-no max-dif-num)) - (setq diff-no (1+ diff-no)) - (setq prev-beg beg - prev-end end) - (setq beg (ediff-get-diff-posn buf-type 'beg diff-no ctl-buffer) - end (ediff-get-diff-posn buf-type 'end diff-no ctl-buffer)) - ) - - ;; boost diff-no by 1, if past the last diff region - (if (and (memq which-diff '(after before)) - (> pos beg) (= diff-no max-dif-num)) - (setq diff-no (1+ diff-no))) - - (cond ((eq which-diff 'after) (1+ diff-no)) - ((eq which-diff 'before) diff-no) - ((< (abs (count-lines pos (max 1 prev-end))) - (abs (count-lines pos (max 1 beg)))) - diff-no) ; choose prev difference - (t - (1+ diff-no))) ; choose next difference - ))) - - -;;; Copying diffs. - -(defun ediff-diff-to-diff (arg &optional keys) - "Copy buffer-X'th difference region to buffer Y \(X,Y are A, B, or C\). -If numerical prefix argument, copy the difference specified in the arg. -Otherwise, copy the difference given by `ediff-current-difference'. -This command assumes it is bound to a 2-character key sequence, `ab', `ba', -`ac', etc., which is used to determine the types of buffers to be used for -copying difference regions. The first character in the sequence specifies -the source buffer and the second specifies the target. - -If the second optional argument, a 2-character string, is given, use it to -determine the source and the target buffers instead of the command keys." - (interactive "P") - (ediff-barf-if-not-control-buffer) - (or keys (setq keys (this-command-keys))) - (if (eq arg '-) (setq arg -1)) ; translate neg arg to -1 - (if (numberp arg) (ediff-jump-to-difference arg)) - - (let* ((key1 (aref keys 0)) - (key2 (aref keys 1)) - (char1 (ediff-event-key key1)) - (char2 (ediff-event-key key2)) - ediff-verbose-p) - (ediff-copy-diff ediff-current-difference - (ediff-char-to-buftype char1) - (ediff-char-to-buftype char2)) - ;; recenter with rehighlighting, but no messages - (ediff-recenter))) - -(defun ediff-copy-A-to-B (arg) - "Copy ARGth difference region from buffer A to B. -ARG is a prefix argument. If nil, copy the current difference region." - (interactive "P") - (ediff-diff-to-diff arg "ab")) - -(defun ediff-copy-B-to-A (arg) - "Copy ARGth difference region from buffer B to A. -ARG is a prefix argument. If nil, copy the current difference region." - (interactive "P") - (ediff-diff-to-diff arg "ba")) - -(defun ediff-copy-A-to-C (arg) - "Copy ARGth difference region from buffer A to buffer C. -ARG is a prefix argument. If nil, copy the current difference region." - (interactive "P") - (ediff-diff-to-diff arg "ac")) - -(defun ediff-copy-B-to-C (arg) - "Copy ARGth difference region from buffer B to buffer C. -ARG is a prefix argument. If nil, copy the current difference region." - (interactive "P") - (ediff-diff-to-diff arg "bc")) - -(defun ediff-copy-C-to-B (arg) - "Copy ARGth difference region from buffer C to B. -ARG is a prefix argument. If nil, copy the current difference region." - (interactive "P") - (ediff-diff-to-diff arg "cb")) - -(defun ediff-copy-C-to-A (arg) - "Copy ARGth difference region from buffer C to A. -ARG is a prefix argument. If nil, copy the current difference region." - (interactive "P") - (ediff-diff-to-diff arg "ca")) - - - -;; Copy diff N from FROM-BUF-TYPE \(given as A, B or C\) to TO-BUF-TYPE. -;; If optional DO-NOT-SAVE is non-nil, do not save the old value of the -;; target diff. This is used in merging, when constructing the merged -;; version. -(defun ediff-copy-diff (n from-buf-type to-buf-type - &optional batch-invocation reg-to-copy) - (let* ((to-buf (ediff-get-buffer to-buf-type)) - ;;(from-buf (if (not reg-to-copy) (ediff-get-buffer from-buf-type))) - (ctrl-buf ediff-control-buffer) - (saved-p t) - (three-way ediff-3way-job) - messg - ediff-verbose-p - reg-to-delete reg-to-delete-beg reg-to-delete-end) - - (setq reg-to-delete-beg - (ediff-get-diff-posn to-buf-type 'beg n ctrl-buf)) - (setq reg-to-delete-end - (ediff-get-diff-posn to-buf-type 'end n ctrl-buf)) - - (if reg-to-copy - (setq from-buf-type nil) - (setq reg-to-copy (ediff-get-region-contents n from-buf-type ctrl-buf))) - - (setq reg-to-delete (ediff-get-region-contents - n to-buf-type ctrl-buf - reg-to-delete-beg reg-to-delete-end)) - - (if (string= reg-to-delete reg-to-copy) - (setq saved-p nil) ; don't copy identical buffers - ;; seems ok to copy - (if (or batch-invocation (ediff-test-save-region n to-buf-type)) - (condition-case conds - (progn - (ediff-with-current-buffer to-buf - ;; to prevent flags from interfering if buffer is writable - (let ((inhibit-read-only (null buffer-read-only))) - - (goto-char reg-to-delete-end) - (insert reg-to-copy) - - (if (> reg-to-delete-end reg-to-delete-beg) - (kill-region reg-to-delete-beg reg-to-delete-end)) - )) - (or batch-invocation - (setq - messg - (ediff-save-diff-region n to-buf-type reg-to-delete)))) - (error (message "ediff-copy-diff: %s %s" - (car conds) - (mapconcat 'prin1-to-string (cdr conds) " ")) - (beep 1) - (sit-for 2) ; let the user see the error msg - (setq saved-p nil) - ))) - ) - - ;; adjust state of difference in case 3-way and diff was copied ok - (if (and saved-p three-way) - (ediff-set-state-of-diff-in-all-buffers n ctrl-buf)) - - (if batch-invocation - (ediff-clear-fine-differences n) - ;; If diff3 job, we should recompute fine diffs so we clear them - ;; before reinserting flags (and thus before ediff-recenter). - (if (and saved-p three-way) - (ediff-clear-fine-differences n)) - - (ediff-refresh-mode-lines) - - ;; For diff2 jobs, don't recompute fine diffs, since we know there - ;; aren't any. So we clear diffs after ediff-recenter. - (if (and saved-p (not three-way)) - (ediff-clear-fine-differences n)) - ;; Make sure that the message about saving and how to restore is seen - ;; by the user - (message "%s" messg)) - )) - -;; Save Nth diff of buffer BUF-TYPE \(A, B, or C\). -;; That is to say, the Nth diff on the `ediff-killed-diffs-alist'. REG -;; is the region to save. It is redundant here, but is passed anyway, for -;; convenience. -(defun ediff-save-diff-region (n buf-type reg) - (let* ((n-th-diff-saved (assoc n ediff-killed-diffs-alist)) - (buf (ediff-get-buffer buf-type)) - (this-buf-n-th-diff-saved (assoc buf (cdr n-th-diff-saved)))) - - (if this-buf-n-th-diff-saved - ;; either nothing saved for n-th diff and buffer or we OK'ed - ;; overriding - (setcdr this-buf-n-th-diff-saved reg) - (if n-th-diff-saved ;; n-th diff saved, but for another buffer - (nconc n-th-diff-saved (list (cons buf reg))) - (setq ediff-killed-diffs-alist ;; create record for n-th diff - (cons (list n (cons buf reg)) - ediff-killed-diffs-alist)))) - (message "Saving old diff region #%d of buffer %S. To recover, type `r%s'" - (1+ n) buf-type - (if ediff-merge-job - "" (downcase (symbol-name buf-type)))) - )) - -;; Test if saving Nth difference region of buffer BUF-TYPE is possible. -(defun ediff-test-save-region (n buf-type) - (let* ((n-th-diff-saved (assoc n ediff-killed-diffs-alist)) - (buf (ediff-get-buffer buf-type)) - (this-buf-n-th-diff-saved (assoc buf (cdr n-th-diff-saved)))) - - (if this-buf-n-th-diff-saved - (if (yes-or-no-p - (format - "You've previously copied diff region %d to buffer %S. Confirm? " - (1+ n) buf-type)) - t - (error "Quit")) - t))) - -(defun ediff-pop-diff (n buf-type) - "Pop last killed Nth diff region from buffer BUF-TYPE." - (let* ((n-th-record (assoc n ediff-killed-diffs-alist)) - (buf (ediff-get-buffer buf-type)) - (saved-rec (assoc buf (cdr n-th-record))) - (three-way ediff-3way-job) - (ctl-buf ediff-control-buffer) - ediff-verbose-p - saved-diff reg-beg reg-end recovered) - - (if (cdr saved-rec) - (setq saved-diff (cdr saved-rec)) - (if (> ediff-number-of-differences 0) - (error "Nothing saved for diff %d in buffer %S" (1+ n) buf-type) - (error ediff-NO-DIFFERENCES))) - - (setq reg-beg (ediff-get-diff-posn buf-type 'beg n ediff-control-buffer)) - (setq reg-end (ediff-get-diff-posn buf-type 'end n ediff-control-buffer)) - - (condition-case conds - (ediff-with-current-buffer buf - (let ((inhibit-read-only (null buffer-read-only))) - - (goto-char reg-end) - (insert saved-diff) - - (if (> reg-end reg-beg) - (kill-region reg-beg reg-end)) - - (setq recovered t) - )) - (error (message "ediff-pop-diff: %s %s" - (car conds) - (mapconcat 'prin1-to-string (cdr conds) " ")) - (beep 1))) - - ;; Clearing fine diffs is necessary for - ;; ediff-unselect-and-select-difference to properly recompute them. We - ;; can't rely on ediff-copy-diff to clear this vector, as the user might - ;; have modified diff regions after copying and, thus, may have recomputed - ;; fine diffs. - (if recovered - (ediff-clear-fine-differences n)) - - ;; adjust state of difference - (if (and three-way recovered) - (ediff-set-state-of-diff-in-all-buffers n ctl-buf)) - - (ediff-refresh-mode-lines) - - (if recovered - (progn - (setq n-th-record (delq saved-rec n-th-record)) - (message "Diff region %d in buffer %S restored" (1+ n) buf-type) - )) - )) - -(defun ediff-restore-diff (arg &optional key) - "Restore ARGth diff from `ediff-killed-diffs-alist'. -ARG is a prefix argument. If ARG is nil, restore the current-difference. -If the second optional argument, a character, is given, use it to -determine the target buffer instead of (ediff-last-command-char)" - (interactive "P") - (ediff-barf-if-not-control-buffer) - (if (numberp arg) - (ediff-jump-to-difference arg)) - (ediff-pop-diff ediff-current-difference - (ediff-char-to-buftype (or key (ediff-last-command-char)))) - ;; recenter with rehighlighting, but no messages - (let (ediff-verbose-p) - (ediff-recenter))) - -(defun ediff-restore-diff-in-merge-buffer (arg) - "Restore ARGth diff in the merge buffer. -ARG is a prefix argument. If nil, restore the current diff." - (interactive "P") - (ediff-restore-diff arg ?c)) - - -(defun ediff-toggle-regexp-match () - "Toggle between focusing and hiding of difference regions that match -a regular expression typed in by the user." - (interactive) - (ediff-barf-if-not-control-buffer) - (let ((regexp-A "") - (regexp-B "") - (regexp-C "") - msg-connective alt-msg-connective alt-connective) - (cond - ((or (and (eq ediff-skip-diff-region-function - ediff-focus-on-regexp-matches-function) - (eq (ediff-last-command-char) ?f)) - (and (eq ediff-skip-diff-region-function - ediff-hide-regexp-matches-function) - (eq (ediff-last-command-char) ?h))) - (message "Selective browsing by regexp turned off") - (setq ediff-skip-diff-region-function 'ediff-show-all-diffs)) - ((eq (ediff-last-command-char) ?h) - (setq ediff-skip-diff-region-function ediff-hide-regexp-matches-function - regexp-A - (read-string - (format - "Ignore A-regions matching this regexp (default %s): " - ediff-regexp-hide-A)) - regexp-B - (read-string - (format - "Ignore B-regions matching this regexp (default %s): " - ediff-regexp-hide-B))) - (if ediff-3way-comparison-job - (setq regexp-C - (read-string - (format - "Ignore C-regions matching this regexp (default %s): " - ediff-regexp-hide-C)))) - (if (eq ediff-hide-regexp-connective 'and) - (setq msg-connective "BOTH" - alt-msg-connective "ONE OF" - alt-connective 'or) - (setq msg-connective "ONE OF" - alt-msg-connective "BOTH" - alt-connective 'and)) - (if (y-or-n-p - (format - "Ignore regions that match %s regexps, OK? " - msg-connective)) - (message "Will ignore regions that match %s regexps" msg-connective) - (setq ediff-hide-regexp-connective alt-connective) - (message "Will ignore regions that match %s regexps" - alt-msg-connective)) - - (or (string= regexp-A "") (setq ediff-regexp-hide-A regexp-A)) - (or (string= regexp-B "") (setq ediff-regexp-hide-B regexp-B)) - (or (string= regexp-C "") (setq ediff-regexp-hide-C regexp-C))) - - ((eq (ediff-last-command-char) ?f) - (setq ediff-skip-diff-region-function - ediff-focus-on-regexp-matches-function - regexp-A - (read-string - (format - "Focus on A-regions matching this regexp (default %s): " - ediff-regexp-focus-A)) - regexp-B - (read-string - (format - "Focus on B-regions matching this regexp (default %s): " - ediff-regexp-focus-B))) - (if ediff-3way-comparison-job - (setq regexp-C - (read-string - (format - "Focus on C-regions matching this regexp (default %s): " - ediff-regexp-focus-C)))) - (if (eq ediff-focus-regexp-connective 'and) - (setq msg-connective "BOTH" - alt-msg-connective "ONE OF" - alt-connective 'or) - (setq msg-connective "ONE OF" - alt-msg-connective "BOTH" - alt-connective 'and)) - (if (y-or-n-p - (format - "Focus on regions that match %s regexps, OK? " - msg-connective)) - (message "Will focus on regions that match %s regexps" - msg-connective) - (setq ediff-focus-regexp-connective alt-connective) - (message "Will focus on regions that match %s regexps" - alt-msg-connective)) - - (or (string= regexp-A "") (setq ediff-regexp-focus-A regexp-A)) - (or (string= regexp-B "") (setq ediff-regexp-focus-B regexp-B)) - (or (string= regexp-C "") (setq ediff-regexp-focus-C regexp-C)))))) - -(defun ediff-toggle-skip-similar () - (interactive) - (ediff-barf-if-not-control-buffer) - (if (not (eq ediff-auto-refine 'on)) - (error - "Can't skip over whitespace regions: first turn auto-refining on")) - (setq ediff-ignore-similar-regions (not ediff-ignore-similar-regions)) - (if ediff-ignore-similar-regions - (message - "Skipping regions that differ only in white space & line breaks") - (message "Skipping over white-space differences turned off"))) - -(defun ediff-focus-on-regexp-matches (n) - "Focus on diffs that match regexp `ediff-regexp-focus-A/B'. -Regions to be ignored according to this function are those where -buf A region doesn't match `ediff-regexp-focus-A' and buf B region -doesn't match `ediff-regexp-focus-B'. -This function returns nil if the region number N (specified as -an argument) is not to be ignored and t if region N is to be ignored. - -N is a region number used by Ediff internally. It is 1 less -the number seen by the user." - (if (ediff-valid-difference-p n) - (let* ((ctl-buf ediff-control-buffer) - (regex-A ediff-regexp-focus-A) - (regex-B ediff-regexp-focus-B) - (regex-C ediff-regexp-focus-C) - (reg-A-match (ediff-with-current-buffer ediff-buffer-A - (save-restriction - (narrow-to-region - (ediff-get-diff-posn 'A 'beg n ctl-buf) - (ediff-get-diff-posn 'A 'end n ctl-buf)) - (goto-char (point-min)) - (re-search-forward regex-A nil t)))) - (reg-B-match (ediff-with-current-buffer ediff-buffer-B - (save-restriction - (narrow-to-region - (ediff-get-diff-posn 'B 'beg n ctl-buf) - (ediff-get-diff-posn 'B 'end n ctl-buf)) - (re-search-forward regex-B nil t)))) - (reg-C-match (if ediff-3way-comparison-job - (ediff-with-current-buffer ediff-buffer-C - (save-restriction - (narrow-to-region - (ediff-get-diff-posn 'C 'beg n ctl-buf) - (ediff-get-diff-posn 'C 'end n ctl-buf)) - (re-search-forward regex-C nil t)))))) - (not (eval (if ediff-3way-comparison-job - (list ediff-focus-regexp-connective - reg-A-match reg-B-match reg-C-match) - (list ediff-focus-regexp-connective - reg-A-match reg-B-match)))) - ))) - -(defun ediff-hide-regexp-matches (n) - "Hide diffs that match regexp `ediff-regexp-hide-A/B/C'. -Regions to be ignored are those where buf A region matches -`ediff-regexp-hide-A' and buf B region matches `ediff-regexp-hide-B'. -This function returns nil if the region number N (specified as -an argument) is not to be ignored and t if region N is to be ignored. - -N is a region number used by Ediff internally. It is 1 less -the number seen by the user." - (if (ediff-valid-difference-p n) - (let* ((ctl-buf ediff-control-buffer) - (regex-A ediff-regexp-hide-A) - (regex-B ediff-regexp-hide-B) - (regex-C ediff-regexp-hide-C) - (reg-A-match (ediff-with-current-buffer ediff-buffer-A - (save-restriction - (narrow-to-region - (ediff-get-diff-posn 'A 'beg n ctl-buf) - (ediff-get-diff-posn 'A 'end n ctl-buf)) - (goto-char (point-min)) - (re-search-forward regex-A nil t)))) - (reg-B-match (ediff-with-current-buffer ediff-buffer-B - (save-restriction - (narrow-to-region - (ediff-get-diff-posn 'B 'beg n ctl-buf) - (ediff-get-diff-posn 'B 'end n ctl-buf)) - (goto-char (point-min)) - (re-search-forward regex-B nil t)))) - (reg-C-match (if ediff-3way-comparison-job - (ediff-with-current-buffer ediff-buffer-C - (save-restriction - (narrow-to-region - (ediff-get-diff-posn 'C 'beg n ctl-buf) - (ediff-get-diff-posn 'C 'end n ctl-buf)) - (goto-char (point-min)) - (re-search-forward regex-C nil t)))))) - (eval (if ediff-3way-comparison-job - (list ediff-hide-regexp-connective - reg-A-match reg-B-match reg-C-match) - (list ediff-hide-regexp-connective reg-A-match reg-B-match))) - ))) - - - -;;; Quitting, suspending, etc. - -(defun ediff-quit (reverse-default-keep-variants) - "Finish an Ediff session and exit Ediff. -Unselects the selected difference, if any, restores the read-only and modified -flags of the compared file buffers, kills Ediff buffers for this session -\(but not buffers A, B, C\). - -If `ediff-keep-variants' is nil, the user will be asked whether the buffers -containing the variants should be removed \(if they haven't been modified\). -If it is t, they will be preserved unconditionally. A prefix argument, -temporarily reverses the meaning of this variable." - (interactive "P") - (ediff-barf-if-not-control-buffer) - (let ((ctl-buf (current-buffer)) - (ctl-frm (selected-frame)) - (minibuffer-auto-raise t)) - (if (y-or-n-p (format "Quit this Ediff session%s? " - (if (ediff-buffer-live-p ediff-meta-buffer) - " & show containing session group" ""))) - (progn - (message "") - (set-buffer ctl-buf) - (ediff-really-quit reverse-default-keep-variants)) - (select-frame ctl-frm) - (raise-frame ctl-frm) - (message "")))) - - -;; Perform the quit operations. -(defun ediff-really-quit (reverse-default-keep-variants) - (ediff-unhighlight-diffs-totally) - (ediff-clear-diff-vector 'ediff-difference-vector-A 'fine-diffs-also) - (ediff-clear-diff-vector 'ediff-difference-vector-B 'fine-diffs-also) - (ediff-clear-diff-vector 'ediff-difference-vector-C 'fine-diffs-also) - (ediff-clear-diff-vector 'ediff-difference-vector-Ancestor 'fine-diffs-also) - - (ediff-delete-temp-files) - - ;; Restore the visibility range. This affects only ediff-*-regions/windows. - ;; Since for other job names ediff-visible-region sets - ;; ediff-visible-bounds to ediff-wide-bounds, the settings below are - ;; ignored for such jobs. - (if ediff-quit-widened - (setq ediff-visible-bounds ediff-wide-bounds) - (setq ediff-visible-bounds ediff-narrow-bounds)) - - ;; Apply selective display to narrow or widen - (ediff-visible-region) - (mapc (lambda (overl) - (if (ediff-overlayp overl) - (ediff-delete-overlay overl))) - ediff-wide-bounds) - (mapc (lambda (overl) - (if (ediff-overlayp overl) - (ediff-delete-overlay overl))) - ediff-narrow-bounds) - - ;; restore buffer mode line id's in buffer-A/B/C - (let ((control-buffer ediff-control-buffer) - (meta-buffer ediff-meta-buffer) - (after-quit-hook-internal ediff-after-quit-hook-internal) - (session-number ediff-meta-session-number) - ;; suitable working frame - (warp-frame (if (and (ediff-window-display-p) (eq ediff-grab-mouse t)) - (cond ((window-live-p ediff-window-A) - (window-frame ediff-window-A)) - ((window-live-p ediff-window-B) - (window-frame ediff-window-B)) - (t (next-frame)))))) - (condition-case nil - (ediff-with-current-buffer ediff-buffer-A - (setq ediff-this-buffer-ediff-sessions - (delq control-buffer ediff-this-buffer-ediff-sessions)) - (kill-local-variable 'mode-line-buffer-identification) - (kill-local-variable 'mode-line-format) - ) - (error)) - - (condition-case nil - (ediff-with-current-buffer ediff-buffer-B - (setq ediff-this-buffer-ediff-sessions - (delq control-buffer ediff-this-buffer-ediff-sessions)) - (kill-local-variable 'mode-line-buffer-identification) - (kill-local-variable 'mode-line-format) - ) - (error)) - - (condition-case nil - (ediff-with-current-buffer ediff-buffer-C - (setq ediff-this-buffer-ediff-sessions - (delq control-buffer ediff-this-buffer-ediff-sessions)) - (kill-local-variable 'mode-line-buffer-identification) - (kill-local-variable 'mode-line-format) - ) - (error)) - - (condition-case nil - (ediff-with-current-buffer ediff-ancestor-buffer - (setq ediff-this-buffer-ediff-sessions - (delq control-buffer ediff-this-buffer-ediff-sessions)) - (kill-local-variable 'mode-line-buffer-identification) - (kill-local-variable 'mode-line-format) - ) - (error)) - - (setq ediff-session-registry - (delq ediff-control-buffer ediff-session-registry)) - (ediff-update-registry) - ;; restore state of buffers to what it was before ediff - (ediff-restore-protected-variables) - - ;; If the user interrupts (canceling saving the merge buffer), continue - ;; normally. - (condition-case nil - (if (ediff-merge-job) - (run-hooks 'ediff-quit-merge-hook)) - (quit)) - - (run-hooks 'ediff-cleanup-hook) - - (ediff-janitor - 'ask - ;; reverse-default-keep-variants is t if the user quits with a prefix arg - (if reverse-default-keep-variants - (not ediff-keep-variants) - ediff-keep-variants)) - - ;; one hook here is ediff-cleanup-mess, which kills the control buffer and - ;; other auxiliary buffers. we made it into a hook to let the users do their - ;; own cleanup, if needed. - (run-hooks 'ediff-quit-hook) - (ediff-update-meta-buffer meta-buffer nil session-number) - - ;; warp mouse into a working window - (setq warp-frame ; if mouse is over a reasonable frame, use it - (cond ((ediff-good-frame-under-mouse)) - (t warp-frame))) - (if (and (ediff-window-display-p) (frame-live-p warp-frame) ediff-grab-mouse) - (set-mouse-position (if (featurep 'emacs) - warp-frame - (frame-selected-window warp-frame)) - 2 1)) - - (run-hooks 'after-quit-hook-internal) - )) - -;; Returns frame under mouse, if this frame is not a minibuffer -;; frame. Otherwise: nil -(defun ediff-good-frame-under-mouse () - (let ((frame-or-win (car (mouse-position))) - (buf-name "") - frame obj-ok) - (setq obj-ok - (if (featurep 'emacs) - (frame-live-p frame-or-win) - (window-live-p frame-or-win))) - (if obj-ok - (setq frame (if (featurep 'emacs) frame-or-win (window-frame frame-or-win)) - buf-name - (buffer-name (window-buffer (frame-selected-window frame))))) - (if (string-match "Minibuf" buf-name) - nil - frame))) - - -(defun ediff-delete-temp-files () - (if (and (stringp ediff-temp-file-A) (file-exists-p ediff-temp-file-A)) - (delete-file ediff-temp-file-A)) - (if (and (stringp ediff-temp-file-B) (file-exists-p ediff-temp-file-B)) - (delete-file ediff-temp-file-B)) - (if (and (stringp ediff-temp-file-C) (file-exists-p ediff-temp-file-C)) - (delete-file ediff-temp-file-C))) - - -;; Kill control buffer, other auxiliary Ediff buffers. -;; Leave one of the frames split between buffers A/B/C -(defun ediff-cleanup-mess () - (let* ((buff-A ediff-buffer-A) - (buff-B ediff-buffer-B) - (buff-C ediff-buffer-C) - (ctl-buf ediff-control-buffer) - (ctl-wind (ediff-get-visible-buffer-window ctl-buf)) - (ctl-frame ediff-control-frame) - (three-way-job ediff-3way-job) - (main-frame (cond ((window-live-p ediff-window-A) - (window-frame ediff-window-A)) - ((window-live-p ediff-window-B) - (window-frame ediff-window-B))))) - - (ediff-kill-buffer-carefully ediff-diff-buffer) - (ediff-kill-buffer-carefully ediff-custom-diff-buffer) - (ediff-kill-buffer-carefully ediff-fine-diff-buffer) - (ediff-kill-buffer-carefully ediff-tmp-buffer) - (ediff-kill-buffer-carefully ediff-error-buffer) - (ediff-kill-buffer-carefully ediff-msg-buffer) - (ediff-kill-buffer-carefully ediff-debug-buffer) - (if (boundp 'ediff-patch-diagnostics) - (ediff-kill-buffer-carefully ediff-patch-diagnostics)) - - ;; delete control frame or window - (cond ((and (ediff-window-display-p) (frame-live-p ctl-frame)) - (delete-frame ctl-frame)) - ((window-live-p ctl-wind) - (delete-window ctl-wind))) - - ;; Hide bottom toolbar. --marcpa - (if (not (ediff-multiframe-setup-p)) - (ediff-kill-bottom-toolbar)) - - (ediff-kill-buffer-carefully ctl-buf) - - (if (frame-live-p main-frame) - (select-frame main-frame)) - - ;; display only if not visible - (condition-case nil - (or (ediff-get-visible-buffer-window buff-B) - (switch-to-buffer buff-B)) - (error)) - (condition-case nil - (or (ediff-get-visible-buffer-window buff-A) - (progn - (if (and (ediff-get-visible-buffer-window buff-B) - (ediff-buffer-live-p buff-A)) - (funcall ediff-split-window-function)) - (switch-to-buffer buff-A))) - (error)) - (if three-way-job - (condition-case nil - (or (ediff-get-visible-buffer-window buff-C) - (progn - (if (and (or (ediff-get-visible-buffer-window buff-A) - (ediff-get-visible-buffer-window buff-B)) - (ediff-buffer-live-p buff-C)) - (funcall ediff-split-window-function)) - (switch-to-buffer buff-C))) - (error))) - (balance-windows) - (message "") - )) - -(defun ediff-janitor (ask keep-variants) - "Kill buffers A, B, and, possibly, C, if these buffers aren't modified. -In merge jobs, buffer C is not deleted here, but rather according to -ediff-quit-merge-hook. -A side effect of cleaning up may be that you should be careful when comparing -the same buffer in two separate Ediff sessions: quitting one of them might -delete this buffer in another session as well." - (ediff-dispose-of-variant-according-to-user - ediff-buffer-A 'A ask keep-variants) - (ediff-dispose-of-variant-according-to-user - ediff-buffer-B 'B ask keep-variants) - (if ediff-merge-job ; don't del buf C if merging--del ancestor buf instead - (ediff-dispose-of-variant-according-to-user - ediff-ancestor-buffer 'Ancestor ask keep-variants) - (ediff-dispose-of-variant-according-to-user - ediff-buffer-C 'C ask keep-variants) - )) - -;; Kill the variant buffer, according to user directives (ask, kill -;; unconditionaly, keep) -;; BUFF is the buffer, BUFF-TYPE is either 'A, or 'B, 'C, 'Ancestor -(defun ediff-dispose-of-variant-according-to-user (buff bufftype ask keep-variants) - ;; if this is indirect buffer, kill it and substitute with direct buf - (if (and (ediff-buffer-live-p buff) - (ediff-with-current-buffer buff ediff-temp-indirect-buffer)) - (let ((wind (ediff-get-visible-buffer-window buff)) - (base (buffer-base-buffer buff)) - (modified-p (buffer-modified-p buff))) - (if (and (window-live-p wind) (ediff-buffer-live-p base)) - (set-window-buffer wind base)) - ;; Kill indirect buffer even if it is modified, because the base buffer - ;; is still there. Note that if the base buffer is dead then so will be - ;; the indirect buffer - (ediff-with-current-buffer buff - (set-buffer-modified-p nil)) - (ediff-kill-buffer-carefully buff) - (ediff-with-current-buffer base - (set-buffer-modified-p modified-p))) - ;; otherwise, ask or use the value of keep-variants - (or (not (ediff-buffer-live-p buff)) - keep-variants - (buffer-modified-p buff) - (and ask - (not (y-or-n-p (format "Kill buffer %S [%s]? " - bufftype (buffer-name buff))))) - (ediff-kill-buffer-carefully buff)) - )) - -(defun ediff-maybe-save-and-delete-merge (&optional save-and-continue) - "Default hook to run on quitting a merge job. -This can also be used to save merge buffer in the middle of an Ediff session. - -If the optional SAVE-AND-CONTINUE argument is non-nil, save merge buffer and -continue. Otherwise: -If `ediff-autostore-merges' is nil, this does nothing. -If it is t, it saves the merge buffer in the file `ediff-merge-store-file' -or asks the user, if the latter is nil. It then asks the user whether to -delete the merge buffer. -If `ediff-autostore-merges' is neither nil nor t, the merge buffer is saved -only if this merge job is part of a group, i.e., was invoked from within -`ediff-merge-directories', `ediff-merge-directory-revisions', and such." - (let ((merge-store-file ediff-merge-store-file) - (ediff-autostore-merges ; fake ediff-autostore-merges, if necessary - (if save-and-continue t ediff-autostore-merges))) - (if ediff-autostore-merges - (cond ((stringp merge-store-file) - ;; store, ask to delete - (ediff-write-merge-buffer-and-maybe-kill - ediff-buffer-C merge-store-file 'show-file save-and-continue)) - ((eq ediff-autostore-merges t) - ;; ask for file name - (setq merge-store-file - (read-file-name "Save the result of the merge in file: ")) - (ediff-write-merge-buffer-and-maybe-kill - ediff-buffer-C merge-store-file nil save-and-continue)) - ((and (ediff-buffer-live-p ediff-meta-buffer) - (ediff-with-current-buffer ediff-meta-buffer - (ediff-merge-metajob))) - ;; The parent metajob passed nil as the autostore file. - nil))) - )) - -;; write merge buffer. If the optional argument save-and-continue is non-nil, -;; then don't kill the merge buffer -(defun ediff-write-merge-buffer-and-maybe-kill (buf file - &optional - show-file save-and-continue) - (if (not (eq (find-buffer-visiting file) buf)) - (let ((warn-message - (format "Another buffer is visiting file %s. Too dangerous to save the merge buffer" - file))) - (beep) - (message "%s" warn-message) - (with-output-to-temp-buffer ediff-msg-buffer - (princ "\n\n") - (princ warn-message) - (princ "\n\n") - ) - (sit-for 2)) - (ediff-with-current-buffer buf - (if (or (not (file-exists-p file)) - (y-or-n-p (format "File %s exists, overwrite? " file))) - (progn - ;;(write-region nil nil file) - (ediff-with-current-buffer buf - (set-visited-file-name file) - (save-buffer)) - (if show-file - (progn - (message "Merge buffer saved in: %s" file) - (set-buffer-modified-p nil) - (sit-for 3))) - (if (and - (not save-and-continue) - (y-or-n-p "Merge buffer saved. Now kill the buffer? ")) - (ediff-kill-buffer-carefully buf))))) - )) - -;; The default way of suspending Ediff. -;; Buries Ediff buffers, kills all windows. -(defun ediff-default-suspend-function () - (let* ((buf-A ediff-buffer-A) - (buf-B ediff-buffer-B) - (buf-C ediff-buffer-C) - (buf-A-wind (ediff-get-visible-buffer-window buf-A)) - (buf-B-wind (ediff-get-visible-buffer-window buf-B)) - (buf-C-wind (ediff-get-visible-buffer-window buf-C)) - (buf-patch (if (boundp 'ediff-patchbufer) ediff-patchbufer nil)) - (buf-patch-diag (if (boundp 'ediff-patch-diagnostics) - ediff-patch-diagnostics nil)) - (buf-err ediff-error-buffer) - (buf-diff ediff-diff-buffer) - (buf-custom-diff ediff-custom-diff-buffer) - (buf-fine-diff ediff-fine-diff-buffer)) - - ;; hide the control panel - (if (and (ediff-window-display-p) (frame-live-p ediff-control-frame)) - (iconify-frame ediff-control-frame) - (bury-buffer)) - (if buf-err (bury-buffer buf-err)) - (if buf-diff (bury-buffer buf-diff)) - (if buf-custom-diff (bury-buffer buf-custom-diff)) - (if buf-fine-diff (bury-buffer buf-fine-diff)) - (if buf-patch (bury-buffer buf-patch)) - (if buf-patch-diag (bury-buffer buf-patch-diag)) - (if (window-live-p buf-A-wind) - (progn - (select-window buf-A-wind) - (delete-other-windows) - (bury-buffer)) - (if (ediff-buffer-live-p buf-A) - (progn - (set-buffer buf-A) - (bury-buffer)))) - (if (window-live-p buf-B-wind) - (progn - (select-window buf-B-wind) - (delete-other-windows) - (bury-buffer)) - (if (ediff-buffer-live-p buf-B) - (progn - (set-buffer buf-B) - (bury-buffer)))) - (if (window-live-p buf-C-wind) - (progn - (select-window buf-C-wind) - (delete-other-windows) - (bury-buffer)) - (if (ediff-buffer-live-p buf-C) - (progn - (set-buffer buf-C) - (bury-buffer)))) - )) - - -(defun ediff-suspend () - "Suspend Ediff. -To resume, switch to the appropriate `Ediff Control Panel' -buffer and then type \\[ediff-recenter]. Ediff will automatically set -up an appropriate window config." - (interactive) - (ediff-barf-if-not-control-buffer) - (run-hooks 'ediff-suspend-hook) - (message - "To resume, type M-x eregistry and select the desired Ediff session")) - -;; ediff-barf-if-not-control-buffer ensures only called from ediff. -(declare-function ediff-version "ediff" ()) - -(defun ediff-status-info () - "Show the names of the buffers or files being operated on by Ediff. -Hit \\[ediff-recenter] to reset the windows afterward." - (interactive) - (ediff-barf-if-not-control-buffer) - (save-excursion - (ediff-skip-unsuitable-frames)) - (with-output-to-temp-buffer ediff-msg-buffer - (ediff-with-current-buffer standard-output - (fundamental-mode)) - (raise-frame (selected-frame)) - (princ (ediff-version)) - (princ "\n\n") - (ediff-with-current-buffer ediff-buffer-A - (if buffer-file-name - (princ - (format "File A = %S\n" buffer-file-name)) - (princ - (format "Buffer A = %S\n" (buffer-name))))) - (ediff-with-current-buffer ediff-buffer-B - (if buffer-file-name - (princ - (format "File B = %S\n" buffer-file-name)) - (princ - (format "Buffer B = %S\n" (buffer-name))))) - (if ediff-3way-job - (ediff-with-current-buffer ediff-buffer-C - (if buffer-file-name - (princ - (format "File C = %S\n" buffer-file-name)) - (princ - (format "Buffer C = %S\n" (buffer-name)))))) - (princ (format "Customized diff output %s\n" - (if (ediff-buffer-live-p ediff-custom-diff-buffer) - (concat "\tin buffer " - (buffer-name ediff-custom-diff-buffer)) - " is not available"))) - (princ (format "Plain diff output %s\n" - (if (ediff-buffer-live-p ediff-diff-buffer) - (concat "\tin buffer " - (buffer-name ediff-diff-buffer)) - " is not available"))) - - (let* ((A-line (ediff-with-current-buffer ediff-buffer-A - (1+ (count-lines (point-min) (point))))) - (B-line (ediff-with-current-buffer ediff-buffer-B - (1+ (count-lines (point-min) (point))))) - C-line) - (princ (format "\Buffer A's point is on line %d\n" A-line)) - (princ (format "Buffer B's point is on line %d\n" B-line)) - (if ediff-3way-job - (progn - (setq C-line (ediff-with-current-buffer ediff-buffer-C - (1+ (count-lines (point-min) (point))))) - (princ (format "Buffer C's point is on line %d\n" C-line))))) - - (princ (format "\nCurrent difference number = %S\n" - (cond ((< ediff-current-difference 0) 'start) - ((>= ediff-current-difference - ediff-number-of-differences) 'end) - (t (1+ ediff-current-difference))))) - - (princ - (format "\n%s regions that differ in white space & line breaks only" - (if ediff-ignore-similar-regions - "Ignoring" "Showing"))) - (if (and ediff-merge-job ediff-show-clashes-only) - (princ - "\nFocusing on regions where both buffers differ from the ancestor")) - (if (and ediff-skip-merge-regions-that-differ-from-default ediff-merge-job) - (princ - "\nSkipping merge regions that differ from default setting")) - - (cond ((eq ediff-skip-diff-region-function 'ediff-show-all-diffs) - (princ "\nSelective browsing by regexp is off\n")) - ((eq ediff-skip-diff-region-function - ediff-hide-regexp-matches-function) - (princ - "\nIgnoring regions that match") - (princ - (format - "\n\t regexp `%s' in buffer A %S\n\t regexp `%s' in buffer B\n" - ediff-regexp-hide-A ediff-hide-regexp-connective - ediff-regexp-hide-B))) - ((eq ediff-skip-diff-region-function - ediff-focus-on-regexp-matches-function) - (princ - "\nFocusing on regions that match") - (princ - (format - "\n\t regexp `%s' in buffer A %S\n\t regexp `%s' in buffer B\n" - ediff-regexp-focus-A ediff-focus-regexp-connective - ediff-regexp-focus-B))) - (t (princ "\nSelective browsing via a user-defined method.\n"))) - - (princ - (format "\nBugs/suggestions: type `%s' while in Ediff Control Panel." - (substitute-command-keys "\\[ediff-submit-report]"))) - ) ; with output - (if (frame-live-p ediff-control-frame) - (ediff-reset-mouse ediff-control-frame)) - (if (window-live-p ediff-control-window) - (select-window ediff-control-window))) - - - - -;;; Support routines - -;; Select a difference by placing the ASCII flags around the appropriate -;; group of lines in the A, B buffers -;; This may have to be modified for buffer C, when it will be supported. -(defun ediff-select-difference (n) - (if (and (ediff-buffer-live-p ediff-buffer-A) - (ediff-buffer-live-p ediff-buffer-B) - (ediff-valid-difference-p n)) - (progn - (cond - ((and (ediff-has-face-support-p) ediff-use-faces) - (ediff-highlight-diff n)) - ((eq ediff-highlighting-style 'ascii) - (ediff-place-flags-in-buffer - 'A ediff-buffer-A ediff-control-buffer n) - (ediff-place-flags-in-buffer - 'B ediff-buffer-B ediff-control-buffer n) - (if ediff-3way-job - (ediff-place-flags-in-buffer - 'C ediff-buffer-C ediff-control-buffer n)) - (if (ediff-buffer-live-p ediff-ancestor-buffer) - (ediff-place-flags-in-buffer - 'Ancestor ediff-ancestor-buffer - ediff-control-buffer n)) - )) - - (ediff-install-fine-diff-if-necessary n) - ;; set current difference here so the hook will be able to refer to it - (setq ediff-current-difference n) - (run-hooks 'ediff-select-hook)))) - - -;; Unselect a difference by removing the ASCII flags in the buffers. -;; This may have to be modified for buffer C, when it will be supported. -(defun ediff-unselect-difference (n) - (if (ediff-valid-difference-p n) - (progn - (cond ((and (ediff-has-face-support-p) ediff-use-faces) - (ediff-unhighlight-diff)) - ((eq ediff-highlighting-style 'ascii) - (ediff-remove-flags-from-buffer - ediff-buffer-A - (ediff-get-diff-overlay n 'A)) - (ediff-remove-flags-from-buffer - ediff-buffer-B - (ediff-get-diff-overlay n 'B)) - (if ediff-3way-job - (ediff-remove-flags-from-buffer - ediff-buffer-C - (ediff-get-diff-overlay n 'C))) - (if (ediff-buffer-live-p ediff-ancestor-buffer) - (ediff-remove-flags-from-buffer - ediff-ancestor-buffer - (ediff-get-diff-overlay n 'Ancestor))) - )) - - ;; unhighlight fine diffs - (ediff-set-fine-diff-properties ediff-current-difference 'default) - (run-hooks 'ediff-unselect-hook)))) - - -;; Unselects prev diff and selects a new one, if FLAG has value other than -;; 'select-only or 'unselect-only. If FLAG is 'select-only, the -;; next difference is selected, but the current selection is not -;; unselected. If FLAG is 'unselect-only then the current selection is -;; unselected, but the next one is not selected. If NO-RECENTER is non-nil, -;; don't recenter buffers after selecting/unselecting. -(defun ediff-unselect-and-select-difference (n &optional flag no-recenter) - (let ((ediff-current-difference n)) - (or no-recenter - (ediff-recenter 'no-rehighlight))) - - (let ((control-buf ediff-control-buffer)) - (unwind-protect - (progn - (or (eq flag 'select-only) - (ediff-unselect-difference ediff-current-difference)) - - (or (eq flag 'unselect-only) - (ediff-select-difference n)) - ;; need to set current diff here even though it is also set in - ;; ediff-select-difference because ediff-select-difference might not - ;; be called if unselect-only is specified - (setq ediff-current-difference n) - ) ; end protected section - - (ediff-with-current-buffer control-buf (ediff-refresh-mode-lines))) - )) - - - -(defun ediff-highlight-diff-in-one-buffer (n buf-type) - (if (ediff-buffer-live-p (ediff-get-buffer buf-type)) - (let* ((buff (ediff-get-buffer buf-type)) - (last (ediff-with-current-buffer buff (point-max))) - (begin (ediff-get-diff-posn buf-type 'beg n)) - (end (ediff-get-diff-posn buf-type 'end n)) - (xtra (if (equal begin end) 1 0)) - (end-hilit (min last (+ end xtra))) - (current-diff-overlay - (symbol-value - (ediff-get-symbol-from-alist - buf-type ediff-current-diff-overlay-alist)))) - - (if (featurep 'xemacs) - (ediff-move-overlay current-diff-overlay begin end-hilit) - (ediff-move-overlay current-diff-overlay begin end-hilit buff)) - (ediff-overlay-put current-diff-overlay 'priority - (ediff-highest-priority begin end-hilit buff)) - (ediff-overlay-put current-diff-overlay 'ediff-diff-num n) - - ;; unhighlight the background overlay for diff n so it won't - ;; interfere with the current diff overlay - (ediff-set-overlay-face (ediff-get-diff-overlay n buf-type) nil) - ))) - - -(defun ediff-unhighlight-diff-in-one-buffer (buf-type) - (if (ediff-buffer-live-p (ediff-get-buffer buf-type)) - (let ((current-diff-overlay - (symbol-value - (ediff-get-symbol-from-alist - buf-type ediff-current-diff-overlay-alist))) - (overlay - (ediff-get-diff-overlay ediff-current-difference buf-type)) - ) - - (ediff-move-overlay current-diff-overlay 1 1) - - ;; rehighlight the overlay in the background of the - ;; current difference region - (ediff-set-overlay-face - overlay - (if (and (ediff-has-face-support-p) - ediff-use-faces ediff-highlight-all-diffs) - (ediff-background-face buf-type ediff-current-difference))) - ))) - -(defun ediff-unhighlight-diffs-totally-in-one-buffer (buf-type) - (ediff-unselect-and-select-difference -1) - (if (and (ediff-has-face-support-p) ediff-use-faces) - (let* ((inhibit-quit t) - (current-diff-overlay-var - (ediff-get-symbol-from-alist - buf-type ediff-current-diff-overlay-alist)) - (current-diff-overlay (symbol-value current-diff-overlay-var))) - (ediff-paint-background-regions 'unhighlight) - (if (ediff-overlayp current-diff-overlay) - (ediff-delete-overlay current-diff-overlay)) - (set current-diff-overlay-var nil) - ))) - - -(defun ediff-highlight-diff (n) - "Put face on diff N. Invoked for X displays only." - (ediff-highlight-diff-in-one-buffer n 'A) - (ediff-highlight-diff-in-one-buffer n 'B) - (ediff-highlight-diff-in-one-buffer n 'C) - (ediff-highlight-diff-in-one-buffer n 'Ancestor) - ) - - -(defun ediff-unhighlight-diff () - "Remove overlays from buffers A, B, and C." - (ediff-unhighlight-diff-in-one-buffer 'A) - (ediff-unhighlight-diff-in-one-buffer 'B) - (ediff-unhighlight-diff-in-one-buffer 'C) - (ediff-unhighlight-diff-in-one-buffer 'Ancestor) - ) - -;; delete highlighting overlays, restore faces to their original form -(defun ediff-unhighlight-diffs-totally () - (ediff-unhighlight-diffs-totally-in-one-buffer 'A) - (ediff-unhighlight-diffs-totally-in-one-buffer 'B) - (ediff-unhighlight-diffs-totally-in-one-buffer 'C) - (ediff-unhighlight-diffs-totally-in-one-buffer 'Ancestor) - ) - - -;; for compatibility -(defmacro ediff-minibuffer-with-setup-hook (fun &rest body) - `(if (fboundp 'minibuffer-with-setup-hook) - (minibuffer-with-setup-hook ,fun ,@body) - ,@body)) - -;; This is adapted from a similar function in `emerge.el'. -;; PROMPT should not have a trailing ': ', so that it can be modified -;; according to context. -;; If DEFAULT-FILE is set, it should be used as the default value. -;; If DEFAULT-DIR is non-nil, use it as the default directory. -;; Otherwise, use the value of Emacs' variable `default-directory.' -(defun ediff-read-file-name (prompt default-dir default-file &optional no-dirs) - ;; hack default-dir if it is not set - (setq default-dir - (file-name-as-directory - (ediff-abbreviate-file-name - (expand-file-name (or default-dir - (and default-file - (file-name-directory default-file)) - default-directory))))) - - ;; strip the directory from default-file - (if default-file - (setq default-file (file-name-nondirectory default-file))) - (if (string= default-file "") - (setq default-file nil)) - - (let ((defaults (and (fboundp 'dired-dwim-target-defaults) - (dired-dwim-target-defaults - (and default-file (list default-file)) - default-dir))) - f) - (setq f (ediff-minibuffer-with-setup-hook - (lambda () (when defaults - (setq minibuffer-default defaults))) - (read-file-name - (format "%s%s " - prompt - (cond (default-file - (concat " (default " default-file "):")) - (t (concat " (default " default-dir "):")))) - default-dir - (or default-file default-dir) - t ; must match, no-confirm - (if default-file (file-name-directory default-file))))) - (setq f (expand-file-name f default-dir)) - ;; If user entered a directory name, expand the default file in that - ;; directory. This allows the user to enter a directory name for the - ;; B-file and diff against the default-file in that directory instead - ;; of a DIRED listing! - (if (and (file-directory-p f) default-file) - (setq f (expand-file-name - (file-name-nondirectory default-file) f))) - (if (and no-dirs (file-directory-p f)) - (error "File %s is a directory" f)) - f)) - -;; If PREFIX is given, then it is used as a prefix for the temp file -;; name. Otherwise, `ediff' is used. If FILE is given, use this -;; file and don't create a new one. -;; In MS-DOS, make sure the prefix isn't too long, or else -;; `make-temp-name' isn't guaranteed to return a unique filename. -;; Also, save buffer from START to END in the file. -;; START defaults to (point-min), END to (point-max) -(defun ediff-make-temp-file (buff &optional prefix given-file start end) - (let* ((p (ediff-convert-standard-filename (or prefix "ediff"))) - (short-p p) - (coding-system-for-write ediff-coding-system-for-write) - f short-f) - (if (and (fboundp 'msdos-long-file-names) - (not (msdos-long-file-names)) - (> (length p) 2)) - (setq short-p (substring p 0 2))) - - (setq f (concat ediff-temp-file-prefix p) - short-f (concat ediff-temp-file-prefix short-p) - f (cond (given-file) - ((find-file-name-handler f 'insert-file-contents) - ;; to thwart file handlers in write-region, e.g., if file - ;; name ends with .Z or .gz - ;; This is needed so that patches produced by ediff will - ;; have more meaningful names - (ediff-make-empty-tmp-file short-f)) - (prefix - ;; Prefix is most often the same as the file name for the - ;; variant. Here we are trying to use the original file - ;; name but in the temp directory. - (ediff-make-empty-tmp-file f 'keep-name)) - (t - ;; If don't care about name, add some random stuff - ;; to proposed file name. - (ediff-make-empty-tmp-file short-f)))) - - ;; create the file - (ediff-with-current-buffer buff - (write-region (if start start (point-min)) - (if end end (point-max)) - f - nil ; don't append---erase - 'no-message) - (set-file-modes f ediff-temp-file-mode) - (expand-file-name f)))) - -;; Create a temporary file. -;; The returned file name (created by appending some random characters at the -;; end of PROPOSED-NAME is guaranteed to point to a newly created empty file. -;; This is a replacement for make-temp-name, which eliminates a security hole. -;; If KEEP-PROPOSED-NAME isn't nil, try to keep PROPOSED-NAME, unless such file -;; already exists. -;; It is a modified version of make-temp-file in emacs 20.5 -(defun ediff-make-empty-tmp-file (proposed-name &optional keep-proposed-name) - (let ((file proposed-name)) - (while (condition-case () - (progn - (if (or (file-exists-p file) (not keep-proposed-name)) - (setq file (make-temp-name proposed-name))) - ;; the with-temp-buffer thing is a workaround for an XEmacs - ;; bug: write-region complains that we are trying to visit a - ;; file in an indirect buffer, failing to notice that the - ;; VISIT flag is unset and that we are actually writing from a - ;; string and not from any buffer. - (with-temp-buffer - (write-region "" nil file nil 'silent nil 'excl)) - nil) - (file-already-exists t)) - ;; the file was somehow created by someone else between - ;; `make-temp-name' and `write-region', let's try again. - nil) - file)) - - -;; Quote metacharacters (using \) when executing diff in Unix, but not in -;; EMX OS/2 -;;(defun ediff-protect-metachars (str) -;; (or (memq system-type '(emx)) -;; (let ((limit 0)) -;; (while (string-match ediff-metachars str limit) -;; (setq str (concat (substring str 0 (match-beginning 0)) -;; "\\" -;; (substring str (match-beginning 0)))) -;; (setq limit (1+ (match-end 0)))))) -;; str) - -;; Make sure the current buffer (for a file) has the same contents as the -;; file on disk, and attempt to remedy the situation if not. -;; Signal an error if we can't make them the same, or the user doesn't want -;; to do what is necessary to make them the same. -;; Also, Ediff always offers to revert obsolete buffers, whether they -;; are modified or not. -(defun ediff-verify-file-buffer (&optional file-magic) - ;; First check if the file has been modified since the buffer visited it. - (if (verify-visited-file-modtime (current-buffer)) - (if (buffer-modified-p) - ;; If buffer is not obsolete and is modified, offer to save - (if (yes-or-no-p - (format "Buffer %s has been modified. Save it in file %s? " - (buffer-name) - buffer-file-name)) - (condition-case nil - (save-buffer) - (error - (beep) - (message "Couldn't save %s" buffer-file-name))) - (error "Buffer is out of sync for file %s" buffer-file-name)) - ;; If buffer is not obsolete and is not modified, do nothing - nil) - ;; If buffer is obsolete, offer to revert - (if (yes-or-no-p - (format "File %s was modified since visited by buffer %s. REVERT file %s? " - buffer-file-name - (buffer-name) - buffer-file-name)) - (progn - (if file-magic - (erase-buffer)) - (revert-buffer t t)) - (error "Buffer out of sync for file %s" buffer-file-name)))) - -;; if there is another buffer visiting the file of the merge buffer, offer to -;; save and delete the buffer; else bark -(defun ediff-verify-file-merge-buffer (file) - (let ((buff (if (stringp file) (find-buffer-visiting file))) - warn-message) - (or (null buff) - (progn - (setq warn-message - (format "Buffer %s is visiting %s. Save and kill the buffer? " - (buffer-name buff) file)) - (with-output-to-temp-buffer ediff-msg-buffer - (princ "\n\n") - (princ warn-message) - (princ "\n\n")) - (if (y-or-n-p - (message "%s" warn-message)) - (with-current-buffer buff - (save-buffer) - (kill-buffer (current-buffer))) - (error "Too dangerous to merge versions of a file visited by another buffer")))) - )) - - - -(defun ediff-filename-magic-p (file) - (or (ediff-file-compressed-p file) - (ediff-file-remote-p file))) - - -(defun ediff-save-buffer (arg) - "Safe way of saving buffers A, B, C, and the diff output. -`wa' saves buffer A, `wb' saves buffer B, `wc' saves buffer C, -and `wd' saves the diff output. - -With prefix argument, `wd' saves plain diff output. -Without an argument, it saves customized diff argument, if available -\(and plain output, if customized output was not generated\)." - (interactive "P") - (ediff-barf-if-not-control-buffer) - (ediff-compute-custom-diffs-maybe) - (ediff-with-current-buffer - (cond ((memq (ediff-last-command-char) '(?a ?b ?c)) - (ediff-get-buffer - (ediff-char-to-buftype (ediff-last-command-char)))) - ((eq (ediff-last-command-char) ?d) - (message "Saving diff output ...") - (sit-for 1) ; let the user see the message - (cond ((and arg (ediff-buffer-live-p ediff-diff-buffer)) - ediff-diff-buffer) - ((ediff-buffer-live-p ediff-custom-diff-buffer) - ediff-custom-diff-buffer) - ((ediff-buffer-live-p ediff-diff-buffer) - ediff-diff-buffer) - (t (error "Output from `diff' not found")))) - ) - (let ((window-min-height 2)) - (save-buffer)))) - - -;; idea suggested by Hannu Koivisto -(defun ediff-clone-buffer-for-region-comparison (buff region-name) - (let ((cloned-buff (ediff-make-cloned-buffer buff region-name)) - (pop-up-windows t) - wind - other-wind - msg-buf) - (ediff-with-current-buffer cloned-buff - (setq ediff-temp-indirect-buffer t)) - (pop-to-buffer cloned-buff) - (setq wind (ediff-get-visible-buffer-window cloned-buff)) - (select-window wind) - (delete-other-windows) - (ediff-activate-mark) - (split-window-vertically) - (ediff-select-lowest-window) - (setq other-wind (selected-window)) - (with-temp-buffer - (erase-buffer) - (insert - (format "\n ******* Mark a region in buffer %s (or confirm the existing one) *******\n" - (buffer-name cloned-buff))) - (insert - (ediff-with-current-buffer buff - (format "\n\t When done, type %s Use %s to abort\n " - (ediff-format-bindings-of 'exit-recursive-edit) - (ediff-format-bindings-of 'abort-recursive-edit)))) - (goto-char (point-min)) - (setq msg-buf (current-buffer)) - (set-window-buffer other-wind msg-buf) - (shrink-window-if-larger-than-buffer) - (if (window-live-p wind) - (select-window wind)) - (condition-case nil - (recursive-edit) - (quit - (ediff-kill-buffer-carefully cloned-buff))) - ) - cloned-buff)) - - -(defun ediff-clone-buffer-for-window-comparison (buff wind region-name) - (let ((cloned-buff (ediff-make-cloned-buffer buff region-name))) - (ediff-with-current-buffer cloned-buff - (setq ediff-temp-indirect-buffer t)) - (set-window-buffer wind cloned-buff) - cloned-buff)) - -(defun ediff-clone-buffer-for-current-diff-comparison (buff buf-type reg-name) - (let ((cloned-buff (ediff-make-cloned-buffer buff reg-name)) - (reg-start (ediff-get-diff-posn buf-type 'beg)) - (reg-end (ediff-get-diff-posn buf-type 'end))) - (ediff-with-current-buffer cloned-buff - ;; set region to be the current diff region - (goto-char reg-start) - (set-mark reg-end) - (setq ediff-temp-indirect-buffer t)) - cloned-buff)) - - - -(defun ediff-make-cloned-buffer (buff region-name) - (ediff-make-indirect-buffer - buff (generate-new-buffer-name - (concat (if (stringp buff) buff (buffer-name buff)) region-name)))) - - -(defun ediff-make-indirect-buffer (base-buf indirect-buf-name) - (if (featurep 'xemacs) - (make-indirect-buffer base-buf indirect-buf-name) - (make-indirect-buffer base-buf indirect-buf-name 'clone))) - - -;; This function operates only from an ediff control buffer -(defun ediff-compute-custom-diffs-maybe () - (let ((buf-A-file-name (buffer-file-name ediff-buffer-A)) - (buf-B-file-name (buffer-file-name ediff-buffer-B)) - file-A file-B) - (unless (and buf-A-file-name - (file-exists-p buf-A-file-name) - (not (ediff-file-remote-p buf-A-file-name))) - (setq file-A (ediff-make-temp-file ediff-buffer-A))) - (unless (and buf-B-file-name - (file-exists-p buf-B-file-name) - (not (ediff-file-remote-p buf-B-file-name))) - (setq file-B (ediff-make-temp-file ediff-buffer-B))) - (or (ediff-buffer-live-p ediff-custom-diff-buffer) - (setq ediff-custom-diff-buffer - (get-buffer-create - (ediff-unique-buffer-name "*ediff-custom-diff" "*")))) - (ediff-with-current-buffer ediff-custom-diff-buffer - (setq buffer-read-only nil) - (erase-buffer)) - (ediff-exec-process - ediff-custom-diff-program ediff-custom-diff-buffer 'synchronize - ediff-custom-diff-options - (or file-A buf-A-file-name) - (or file-B buf-B-file-name)) - ;; put the diff file in diff-mode, if it is available - (if (fboundp 'diff-mode) - (with-current-buffer ediff-custom-diff-buffer - (diff-mode))) - (and file-A (file-exists-p file-A) (delete-file file-A)) - (and file-B (file-exists-p file-B) (delete-file file-B)) - )) - -(defun ediff-show-diff-output (arg) - (interactive "P") - (ediff-barf-if-not-control-buffer) - (ediff-compute-custom-diffs-maybe) - (save-excursion - (ediff-skip-unsuitable-frames ' ok-unsplittable)) - (let ((buf (cond ((and arg (ediff-buffer-live-p ediff-diff-buffer)) - ediff-diff-buffer) - ((ediff-buffer-live-p ediff-custom-diff-buffer) - ediff-custom-diff-buffer) - ((ediff-buffer-live-p ediff-diff-buffer) - ediff-diff-buffer) - (t - (beep) - (message "Output from `diff' not found") - nil)))) - (if buf - (progn - (ediff-with-current-buffer buf - (goto-char (point-min))) - (switch-to-buffer buf) - (raise-frame (selected-frame))))) - (if (frame-live-p ediff-control-frame) - (ediff-reset-mouse ediff-control-frame)) - (if (window-live-p ediff-control-window) - (select-window ediff-control-window))) - - -(defun ediff-inferior-compare-regions () - "Compare regions in an active Ediff session. -Like ediff-regions-linewise but is called from under an active Ediff session on -the files that belong to that session. - -After quitting the session invoked via this function, type C-l to the parent -Ediff Control Panel to restore highlighting." - (interactive) - (let ((answer "") - (possibilities (list ?A ?B ?C)) - (zmacs-regions t) - use-current-diff-p - begA begB endA endB bufA bufB) - - (if (ediff-valid-difference-p ediff-current-difference) - (progn - (ediff-set-fine-diff-properties ediff-current-difference 'default) - (ediff-unhighlight-diff))) - (ediff-paint-background-regions 'unhighlight) - - (cond ((ediff-merge-job) - (setq bufB ediff-buffer-C) - ;; ask which buffer to compare to the merge buffer - (while (cond ((eq answer ?A) - (setq bufA ediff-buffer-A - possibilities '(?B)) - nil) - ((eq answer ?B) - (setq bufA ediff-buffer-B - possibilities '(?A)) - nil) - ((equal answer "")) - (t (beep 1) - (message "Valid values are A or B") - (sit-for 2) - t)) - (let ((cursor-in-echo-area t)) - (message - "Which buffer to compare to the merge buffer (A or B)? ") - (setq answer (capitalize (read-char-exclusive)))))) - - ((ediff-3way-comparison-job) - ;; ask which two buffers to compare - (while (cond ((memq answer possibilities) - (setq possibilities (delq answer possibilities)) - (setq bufA - (eval - (ediff-get-symbol-from-alist - answer ediff-buffer-alist))) - nil) - ((equal answer "")) - (t (beep 1) - (message - "Valid values are %s" - (mapconcat 'char-to-string possibilities " or ")) - (sit-for 2) - t)) - (let ((cursor-in-echo-area t)) - (message "Enter the 1st buffer you want to compare (%s): " - (mapconcat 'char-to-string possibilities " or ")) - (setq answer (capitalize (read-char-exclusive))))) - (setq answer "") ; silence error msg - (while (cond ((memq answer possibilities) - (setq possibilities (delq answer possibilities)) - (setq bufB - (eval - (ediff-get-symbol-from-alist - answer ediff-buffer-alist))) - nil) - ((equal answer "")) - (t (beep 1) - (message - "Valid values are %s" - (mapconcat 'char-to-string possibilities " or ")) - (sit-for 2) - t)) - (let ((cursor-in-echo-area t)) - (message "Enter the 2nd buffer you want to compare (%s): " - (mapconcat 'char-to-string possibilities "/")) - (setq answer (capitalize (read-char-exclusive)))))) - (t ; 2way comparison - (setq bufA ediff-buffer-A - bufB ediff-buffer-B - possibilities nil))) - - (if (and (ediff-valid-difference-p ediff-current-difference) - (y-or-n-p "Compare currently highlighted difference regions? ")) - (setq use-current-diff-p t)) - - (setq bufA (if use-current-diff-p - (ediff-clone-buffer-for-current-diff-comparison - bufA 'A "-Region.A-") - (ediff-clone-buffer-for-region-comparison bufA "-Region.A-"))) - (ediff-with-current-buffer bufA - (setq begA (region-beginning) - endA (region-end)) - (goto-char begA) - (beginning-of-line) - (setq begA (point)) - (goto-char endA) - (end-of-line) - (or (eobp) (forward-char)) ; include the newline char - (setq endA (point))) - - (setq bufB (if use-current-diff-p - (ediff-clone-buffer-for-current-diff-comparison - bufB 'B "-Region.B-") - (ediff-clone-buffer-for-region-comparison bufB "-Region.B-"))) - (ediff-with-current-buffer bufB - (setq begB (region-beginning) - endB (region-end)) - (goto-char begB) - (beginning-of-line) - (setq begB (point)) - (goto-char endB) - (end-of-line) - (or (eobp) (forward-char)) ; include the newline char - (setq endB (point))) - - - (ediff-regions-internal - bufA begA endA bufB begB endB - nil ; setup-hook - (if use-current-diff-p ; job name - 'ediff-regions-wordwise - 'ediff-regions-linewise) - (if use-current-diff-p ; word mode, if diffing current diff - t nil) - ;; setup param to pass to ediff-setup - (list (cons 'ediff-split-window-function ediff-split-window-function))) - )) - - - -(defun ediff-remove-flags-from-buffer (buffer overlay) - (ediff-with-current-buffer buffer - (let ((inhibit-read-only t)) - (if (featurep 'xemacs) - (ediff-overlay-put overlay 'begin-glyph nil) - (ediff-overlay-put overlay 'before-string nil)) - - (if (featurep 'xemacs) - (ediff-overlay-put overlay 'end-glyph nil) - (ediff-overlay-put overlay 'after-string nil)) - ))) - - - -(defun ediff-place-flags-in-buffer (buf-type buffer ctl-buffer diff) - (ediff-with-current-buffer buffer - (ediff-place-flags-in-buffer1 buf-type ctl-buffer diff))) - - -(defun ediff-place-flags-in-buffer1 (buf-type ctl-buffer diff-no) - (let* ((curr-overl (ediff-with-current-buffer ctl-buffer - (ediff-get-diff-overlay diff-no buf-type))) - (before (ediff-get-diff-posn buf-type 'beg diff-no ctl-buffer)) - after beg-of-line flag) - - ;; insert flag before the difference - (goto-char before) - (setq beg-of-line (bolp)) - - (setq flag (ediff-with-current-buffer ctl-buffer - (if (eq ediff-highlighting-style 'ascii) - (if beg-of-line - ediff-before-flag-bol ediff-before-flag-mol)))) - - ;; insert the flag itself - (if (featurep 'xemacs) - (ediff-overlay-put curr-overl 'begin-glyph flag) - (ediff-overlay-put curr-overl 'before-string flag)) - - ;; insert the flag after the difference - ;; `after' must be set here, after the before-flag was inserted - (setq after (ediff-get-diff-posn buf-type 'end diff-no ctl-buffer)) - (goto-char after) - (setq beg-of-line (bolp)) - - (setq flag (ediff-with-current-buffer ctl-buffer - (if (eq ediff-highlighting-style 'ascii) - (if beg-of-line - ediff-after-flag-eol ediff-after-flag-mol)))) - - ;; insert the flag itself - (if (featurep 'xemacs) - (ediff-overlay-put curr-overl 'end-glyph flag) - (ediff-overlay-put curr-overl 'after-string flag)) - )) - - -;;; Some diff region tests - -;; t if diff region is empty. -;; In case of buffer C, t also if it is not a 3way -;; comparison job (merging jobs return t as well). -(defun ediff-empty-diff-region-p (n buf-type) - (if (eq buf-type 'C) - (or (not ediff-3way-comparison-job) - (= (ediff-get-diff-posn 'C 'beg n) - (ediff-get-diff-posn 'C 'end n))) - (= (ediff-get-diff-posn buf-type 'beg n) - (ediff-get-diff-posn buf-type 'end n)))) - -;; Test if diff region is white space only. -;; If 2-way job and buf-type = C, then returns t. -(defun ediff-whitespace-diff-region-p (n buf-type) - (or (and (eq buf-type 'C) (not ediff-3way-job)) - (ediff-empty-diff-region-p n buf-type) - (let ((beg (ediff-get-diff-posn buf-type 'beg n)) - (end (ediff-get-diff-posn buf-type 'end n))) - (ediff-with-current-buffer (ediff-get-buffer buf-type) - (save-excursion - (goto-char beg) - (skip-chars-forward ediff-whitespace) - (>= (point) end)))))) - - -(defun ediff-get-region-contents (n buf-type ctrl-buf &optional start end) - (ediff-with-current-buffer - (ediff-with-current-buffer ctrl-buf (ediff-get-buffer buf-type)) - (buffer-substring - (or start (ediff-get-diff-posn buf-type 'beg n ctrl-buf)) - (or end (ediff-get-diff-posn buf-type 'end n ctrl-buf))))) - -;; Returns positions of difference sectors in the BUF-TYPE buffer. -;; BUF-TYPE should be a symbol -- `A', `B', or `C'. -;; POS is either `beg' or `end'--it specifies whether you want the position at -;; the beginning of a difference or at the end. -;; -;; The optional argument N says which difference (default: -;; `ediff-current-difference'). N is the internal difference number (1- what -;; the user sees). The optional argument CONTROL-BUF says -;; which control buffer is in effect in case it is not the current -;; buffer. -(defun ediff-get-diff-posn (buf-type pos &optional n control-buf) - (let (diff-overlay) - (or control-buf - (setq control-buf (current-buffer))) - - (ediff-with-current-buffer control-buf - (or n (setq n ediff-current-difference)) - (if (or (< n 0) (>= n ediff-number-of-differences)) - (if (> ediff-number-of-differences 0) - (error ediff-BAD-DIFF-NUMBER - this-command (1+ n) ediff-number-of-differences) - (error ediff-NO-DIFFERENCES))) - (setq diff-overlay (ediff-get-diff-overlay n buf-type))) - (if (not (ediff-buffer-live-p (ediff-overlay-buffer diff-overlay))) - (error ediff-KILLED-VITAL-BUFFER)) - (if (eq pos 'beg) - (ediff-overlay-start diff-overlay) - (ediff-overlay-end diff-overlay)) - )) - - -;; Restore highlighting to what it should be according to ediff-use-faces, -;; ediff-highlighting-style, and ediff-highlight-all-diffs variables. -(defun ediff-restore-highlighting (&optional ctl-buf) - (ediff-with-current-buffer (or ctl-buf (current-buffer)) - (if (and (ediff-has-face-support-p) - ediff-use-faces - ediff-highlight-all-diffs) - (ediff-paint-background-regions)) - (ediff-select-difference ediff-current-difference))) - - - -;; null out difference overlays so they won't slow down future -;; editing operations -;; VEC is either a difference vector or a fine-diff vector -(defun ediff-clear-diff-vector (vec-var &optional fine-diffs-also) - (if (vectorp (symbol-value vec-var)) - (mapc (lambda (elt) - (ediff-delete-overlay - (ediff-get-diff-overlay-from-diff-record elt)) - (if fine-diffs-also - (ediff-clear-fine-diff-vector elt)) - ) - (symbol-value vec-var))) - ;; allow them to be garbage collected - (set vec-var nil)) - - - -;;; Misc - -;; In Emacs, this just makes overlay. In the future, when Emacs will start -;; supporting sticky overlays, this function will make a sticky overlay. -;; BEG and END are expressions telling where overlay starts. -;; If they are numbers or buffers, then all is well. Otherwise, they must -;; be expressions to be evaluated in buffer BUF in order to get the overlay -;; bounds. -;; If BUFF is not a live buffer, then return nil; otherwise, return the -;; newly created overlay. -(defun ediff-make-bullet-proof-overlay (beg end buff) - (if (ediff-buffer-live-p buff) - (let (overl) - (ediff-with-current-buffer buff - (or (number-or-marker-p beg) - (setq beg (eval beg))) - (or (number-or-marker-p end) - (setq end (eval end))) - (setq overl - (if (featurep 'xemacs) - (make-extent beg end buff) - ;; advance front and rear of the overlay - (make-overlay beg end buff nil 'rear-advance))) - - ;; never detach - (ediff-overlay-put - overl (if (featurep 'emacs) 'evaporate 'detachable) nil) - ;; make overlay open-ended - ;; In emacs, it is made open ended at creation time - (when (featurep 'xemacs) - (ediff-overlay-put overl 'start-open nil) - (ediff-overlay-put overl 'end-open nil)) - (ediff-overlay-put overl 'ediff-diff-num 0) - overl)))) - - -(defun ediff-make-current-diff-overlay (type) - (if (ediff-has-face-support-p) - (let ((overlay (ediff-get-symbol-from-alist - type ediff-current-diff-overlay-alist)) - (buffer (ediff-get-buffer type)) - (face (ediff-get-symbol-from-alist - type ediff-current-diff-face-alist))) - (set overlay - (ediff-make-bullet-proof-overlay (point-max) (point-max) buffer)) - (ediff-set-overlay-face (symbol-value overlay) face) - (ediff-overlay-put (symbol-value overlay) 'ediff ediff-control-buffer)) - )) - - -;; Like other-buffer, but prefers visible buffers and ignores temporary or -;; other insignificant buffers (those beginning with "^[ *]"). -;; Gets one arg--buffer name or a list of buffer names (it won't return -;; these buffers). -;; EXCL-BUFF-LIST is an exclusion list. -(defun ediff-other-buffer (excl-buff-lst) - (or (listp excl-buff-lst) (setq excl-buff-lst (list excl-buff-lst))) - (let* ((all-buffers (nconc (ediff-get-selected-buffers) (buffer-list))) - ;; we compute this the second time because we need to do memq on it - ;; later, and nconc above will break it. Either this or use slow - ;; append instead of nconc - (selected-buffers (ediff-get-selected-buffers)) - (prefered-buffer (car all-buffers)) - visible-dired-buffers - (excl-buff-name-list - (mapcar - (lambda (b) (cond ((stringp b) b) - ((bufferp b) (buffer-name b)))) - excl-buff-lst)) - ;; if at least one buffer on the exclusion list is dired, then force - ;; all others to be dired. This is because this means that the user - ;; has already chosen a dired buffer before - (use-dired-major-mode - (cond ((null (ediff-buffer-live-p (car excl-buff-lst))) 'unknown) - ((eq (ediff-with-current-buffer (car excl-buff-lst) major-mode) - 'dired-mode) - 'yes) - (t 'no))) - ;; significant-buffers must be visible and not belong - ;; to the exclusion list `buff-list' - ;; We also exclude temporary buffers, but keep mail and gnus buffers - ;; Furthermore, we exclude dired buffers, unless they are the only - ;; ones visible (and there are at least two of them). - ;; Also, any visible window not on the exclusion list that is first in - ;; the buffer list is chosen regardless. (This is because the user - ;; clicked on it or did something to distinguish it). - (significant-buffers - (mapcar - (lambda (x) - (cond ((member (buffer-name x) excl-buff-name-list) nil) - ((memq x selected-buffers) x) - ((not (ediff-get-visible-buffer-window x)) nil) - ((eq x prefered-buffer) x) - ;; if prev selected buffer is dired, look only at - ;; dired. - ((eq use-dired-major-mode 'yes) - (if (eq (ediff-with-current-buffer x major-mode) - 'dired-mode) - x nil)) - ((eq (ediff-with-current-buffer x major-mode) - 'dired-mode) - (if (null use-dired-major-mode) - ;; don't know if we must enforce dired. - ;; Remember this buffer in case - ;; dired buffs are the only ones visible. - (setq visible-dired-buffers - (cons x visible-dired-buffers))) - ;; skip, if dired is not forced - nil) - ((memq (ediff-with-current-buffer x major-mode) - '(rmail-mode - vm-mode - gnus-article-mode - mh-show-mode)) - x) - ((string-match "^[ *]" (buffer-name x)) nil) - ((string= "*scratch*" (buffer-name x)) nil) - (t x))) - all-buffers)) - (clean-significant-buffers (delq nil significant-buffers)) - less-significant-buffers) - - (if (and (null clean-significant-buffers) - (> (length visible-dired-buffers) 0)) - (setq clean-significant-buffers visible-dired-buffers)) - - (cond (clean-significant-buffers (car clean-significant-buffers)) - ;; try also buffers that are not displayed in windows - ((setq less-significant-buffers - (delq nil - (mapcar - (lambda (x) - (cond ((member (buffer-name x) excl-buff-name-list) - nil) - ((eq use-dired-major-mode 'yes) - (if (eq (ediff-with-current-buffer - x major-mode) - 'dired-mode) - x nil)) - ((eq (ediff-with-current-buffer x major-mode) - 'dired-mode) - nil) - ((string-match "^[ *]" (buffer-name x)) nil) - ((string= "*scratch*" (buffer-name x)) nil) - (t x))) - all-buffers))) - (car less-significant-buffers)) - (t "*scratch*")) - )) - - -;; If current buffer is a Buffer-menu buffer, then take the selected buffers -;; and append the buffer at the cursor to the end. -;; This list would be the preferred list. -(defun ediff-get-selected-buffers () - (if (eq major-mode 'Buffer-menu-mode) - (let ((lis (condition-case nil - (list (Buffer-menu-buffer t)) - (error)) - )) - (save-excursion - (goto-char (point-max)) - (while (search-backward "\n>" nil t) - (forward-char 1) - (setq lis (cons (Buffer-menu-buffer t) lis))) - lis)) - )) - -;; Construct a unique buffer name. -;; The first one tried is prefixsuffix, then prefix<2>suffix, -;; prefix<3>suffix, etc. -(defun ediff-unique-buffer-name (prefix suffix) - (if (null (get-buffer (concat prefix suffix))) - (concat prefix suffix) - (let ((n 2)) - (while (get-buffer (format "%s<%d>%s" prefix n suffix)) - (setq n (1+ n))) - (format "%s<%d>%s" prefix n suffix)))) - - -(defun ediff-submit-report () - "Submit bug report on Ediff." - (interactive) - (ediff-barf-if-not-control-buffer) - (let ((reporter-prompt-for-summary-p t) - (ctl-buf ediff-control-buffer) - (ediff-device-type (ediff-device-type)) - varlist salutation buffer-name) - (setq varlist '(ediff-diff-program ediff-diff-options - ediff-diff3-program ediff-diff3-options - ediff-patch-program ediff-patch-options - ediff-shell - ediff-use-faces - ediff-auto-refine ediff-highlighting-style - ediff-buffer-A ediff-buffer-B ediff-control-buffer - ediff-forward-word-function - ediff-control-frame - ediff-control-frame-parameters - ediff-control-frame-position-function - ediff-prefer-iconified-control-frame - ediff-window-setup-function - ediff-split-window-function - ediff-job-name - ediff-word-mode - buffer-name - ediff-device-type - )) - (setq salutation " -Congratulations! You may have unearthed a bug in Ediff! - -Please make a concise and accurate summary of what happened -and mail it to the address above. ------------------------------------------------------------ -") - - (ediff-skip-unsuitable-frames) - (ediff-reset-mouse) - - (switch-to-buffer ediff-msg-buffer) - (erase-buffer) - (delete-other-windows) - (insert " -Please read this first: ----------------------- - -Some ``bugs'' may actually be no bugs at all. For instance, if you are -reporting that certain difference regions are not matched as you think they -should, this is most likely due to the way Unix diff program decides what -constitutes a difference region. Ediff is an Emacs interface to diff, and -it has nothing to do with those decisions---it only takes the output from -diff and presents it in a way that is better suited for human browsing and -manipulation. - -If Emacs happens to dump core, this is NOT an Ediff problem---it is -an Emacs bug. Report this to Emacs maintainers. - -Another popular topic for reports is compilation messages. Because Ediff -interfaces to several other packages and runs under Emacs and XEmacs, -byte-compilation may produce output like this: - - While compiling toplevel forms in file ediff.el: - ** reference to free variable pm-color-alist - ........................ - While compiling the end of the data: - ** The following functions are not known to be defined: - ediff-valid-color-p, ediff-set-face, - ........................ - -These are NOT errors, but inevitable warnings, which ought to be ignored. - -Please do not report those and similar things. However, comments and -suggestions are always welcome. - -Mail anyway? (y or n) ") - - (if (y-or-n-p "Mail anyway? ") - (progn - (if (ediff-buffer-live-p ctl-buf) - (set-buffer ctl-buf)) - (setq buffer-name (buffer-name)) - (require 'reporter) - (reporter-submit-bug-report "kifer@cs.stonybrook.edu" - (ediff-version) - varlist - nil - 'delete-other-windows - salutation)) - (bury-buffer) - (beep 1)(message "Bug report aborted") - (if (ediff-buffer-live-p ctl-buf) - (ediff-with-current-buffer ctl-buf - (ediff-recenter 'no-rehighlight)))) - )) - - -;; Find an appropriate syntax table for everyone to use -;; If buffer B is not fundamental or text mode, use its syntax table -;; Otherwise, use buffer B's. -;; The syntax mode is used in ediff-forward-word-function -;; The important thing is that every buffer should use the same syntax table -;; during the refinement operation -(defun ediff-choose-syntax-table () - (setq ediff-syntax-table - (ediff-with-current-buffer ediff-buffer-A - (if (not (memq major-mode - '(fundamental-mode text-mode indented-text-mode))) - (syntax-table)))) - (if (not ediff-syntax-table) - (setq ediff-syntax-table - (ediff-with-current-buffer ediff-buffer-B - (syntax-table)))) - ) - - -(defun ediff-deactivate-mark () - (if (featurep 'xemacs) - (zmacs-deactivate-region) - (deactivate-mark))) - -(defun ediff-activate-mark () - (if (featurep 'xemacs) - (zmacs-activate-region) - (make-local-variable 'transient-mark-mode) - (setq mark-active t transient-mark-mode t))) - -(defun ediff-nuke-selective-display () - (if (featurep 'xemacs) - (nuke-selective-display) - (save-excursion - (save-restriction - (widen) - (goto-char (point-min)) - (let ((mod-p (buffer-modified-p)) - buffer-read-only end) - (and (eq t selective-display) - (while (search-forward "\^M" nil t) - (end-of-line) - (setq end (point)) - (beginning-of-line) - (while (search-forward "\^M" end t) - (delete-char -1) - (insert "\^J")))) - (set-buffer-modified-p mod-p) - (setq selective-display nil)))))) - - -;; The next two are modified versions from emerge.el. -;; VARS must be a list of symbols -;; ediff-save-variables returns an association list: ((var . val) ...) -(defsubst ediff-save-variables (vars) - (mapcar (lambda (v) (cons v (symbol-value v))) - vars)) -;; VARS is a list of variable symbols. -(defun ediff-restore-variables (vars assoc-list) - (while vars - (set (car vars) (cdr (assoc (car vars) assoc-list))) - (setq vars (cdr vars)))) - -(defun ediff-change-saved-variable (var value buf-type) - (let* ((assoc-list - (symbol-value (ediff-get-symbol-from-alist - buf-type - ediff-buffer-values-orig-alist))) - (assoc-elt (assoc var assoc-list))) - (if assoc-elt - (setcdr assoc-elt value)))) - - -;; must execute in control buf -(defun ediff-save-protected-variables () - (setq ediff-buffer-values-orig-A - (ediff-with-current-buffer ediff-buffer-A - (ediff-save-variables ediff-protected-variables))) - (setq ediff-buffer-values-orig-B - (ediff-with-current-buffer ediff-buffer-B - (ediff-save-variables ediff-protected-variables))) - (if ediff-3way-comparison-job - (setq ediff-buffer-values-orig-C - (ediff-with-current-buffer ediff-buffer-C - (ediff-save-variables ediff-protected-variables)))) - (if (ediff-buffer-live-p ediff-ancestor-buffer) - (setq ediff-buffer-values-orig-Ancestor - (ediff-with-current-buffer ediff-ancestor-buffer - (ediff-save-variables ediff-protected-variables))))) - -;; must execute in control buf -(defun ediff-restore-protected-variables () - (let ((values-A ediff-buffer-values-orig-A) - (values-B ediff-buffer-values-orig-B) - (values-C ediff-buffer-values-orig-C) - (values-Ancestor ediff-buffer-values-orig-Ancestor)) - (ediff-with-current-buffer ediff-buffer-A - (ediff-restore-variables ediff-protected-variables values-A)) - (ediff-with-current-buffer ediff-buffer-B - (ediff-restore-variables ediff-protected-variables values-B)) - (if ediff-3way-comparison-job - (ediff-with-current-buffer ediff-buffer-C - (ediff-restore-variables ediff-protected-variables values-C))) - (if (ediff-buffer-live-p ediff-ancestor-buffer) - (ediff-with-current-buffer ediff-ancestor-buffer - (ediff-restore-variables ediff-protected-variables values-Ancestor))) - )) - -;; save BUFFER in FILE. used in hooks. -(defun ediff-save-buffer-in-file (buffer file) - (ediff-with-current-buffer buffer - (write-file file))) - - -;;; Debug - -(ediff-defvar-local ediff-command-begin-time '(0 0 0) "") - -;; calculate time used by command -(defun ediff-calc-command-time () - (let ((end (current-time)) - micro sec) - (setq micro - (if (>= (nth 2 end) (nth 2 ediff-command-begin-time)) - (- (nth 2 end) (nth 2 ediff-command-begin-time)) - (+ (nth 2 end) (- 1000000 (nth 2 ediff-command-begin-time))))) - (setq sec (- (nth 1 end) (nth 1 ediff-command-begin-time))) - (or (equal ediff-command-begin-time '(0 0 0)) - (message "Elapsed time: %d second(s) + %d microsecond(s)" sec micro)))) - -(defsubst ediff-save-time () - (setq ediff-command-begin-time (current-time))) - -(defun ediff-profile () - "Toggle profiling Ediff commands." - (interactive) - (ediff-barf-if-not-control-buffer) - - (if (featurep 'xemacs) - (make-local-hook 'post-command-hook)) - - (let ((pre-hook 'pre-command-hook) - (post-hook 'post-command-hook)) - (if (not (equal ediff-command-begin-time '(0 0 0))) - (progn (remove-hook pre-hook 'ediff-save-time) - (remove-hook post-hook 'ediff-calc-command-time) - (setq ediff-command-begin-time '(0 0 0)) - (message "Ediff profiling disabled")) - (add-hook pre-hook 'ediff-save-time t 'local) - (add-hook post-hook 'ediff-calc-command-time nil 'local) - (message "Ediff profiling enabled")))) - -(defun ediff-print-diff-vector (diff-vector-var) - (princ (format "\n*** %S ***\n" diff-vector-var)) - (mapcar (lambda (overl-vec) - (princ - (format - "Diff %d: \tOverlay: %S -\t\tFine diffs: %s -\t\tNo-fine-diff-flag: %S -\t\tState-of-diff:\t %S -\t\tState-of-merge:\t %S -" - (1+ (ediff-overlay-get (aref overl-vec 0) 'ediff-diff-num)) - (aref overl-vec 0) - ;; fine-diff-vector - (if (= (length (aref overl-vec 1)) 0) - "none\n" - (mapconcat 'prin1-to-string - (aref overl-vec 1) "\n\t\t\t ")) - (aref overl-vec 2) ; no fine diff flag - (aref overl-vec 3) ; state-of-diff - (aref overl-vec 4) ; state-of-merge - ))) - (eval diff-vector-var))) - - - -(defun ediff-debug-info () - (interactive) - (ediff-barf-if-not-control-buffer) - (with-output-to-temp-buffer ediff-debug-buffer - (ediff-with-current-buffer standard-output - (fundamental-mode)) - (princ (format "\nCtl buffer: %S\n" ediff-control-buffer)) - (ediff-print-diff-vector (intern "ediff-difference-vector-A")) - (ediff-print-diff-vector (intern "ediff-difference-vector-B")) - (ediff-print-diff-vector (intern "ediff-difference-vector-C")) - (ediff-print-diff-vector (intern "ediff-difference-vector-Ancestor")) - )) - - -;;; General utilities - -;; this uses comparison-func to decide who is a member -(defun ediff-member (elt lis comparison-func) - (while (and lis (not (funcall comparison-func (car lis) elt))) - (setq lis (cdr lis))) - lis) - -;; Make a readable representation of the invocation sequence for FUNC-DEF. -;; It would either be a key or M-x something. -(defun ediff-format-bindings-of (func-def) - (let ((desc (car (where-is-internal func-def - overriding-local-map - nil nil)))) - (if desc - (key-description desc) - (format "M-x %s" func-def)))) - -;; this uses comparison-func to decide who is a member, and this determines how -;; intersection looks like -(defun ediff-intersection (lis1 lis2 comparison-func) - (let ((result (list 'a))) - (while lis1 - (if (ediff-member (car lis1) lis2 comparison-func) - (nconc result (list (car lis1)))) - (setq lis1 (cdr lis1))) - (cdr result))) - - -;; eliminates duplicates using comparison-func -(defun ediff-union (lis1 lis2 comparison-func) - (let ((result (list 'a))) - (while lis1 - (or (ediff-member (car lis1) (cdr result) comparison-func) - (nconc result (list (car lis1)))) - (setq lis1 (cdr lis1))) - (while lis2 - (or (ediff-member (car lis2) (cdr result) comparison-func) - (nconc result (list (car lis2)))) - (setq lis2 (cdr lis2))) - (cdr result))) - -;; eliminates duplicates using comparison-func -(defun ediff-set-difference (lis1 lis2 comparison-func) - (let ((result (list 'a))) - (while lis1 - (or (ediff-member (car lis1) (cdr result) comparison-func) - (ediff-member (car lis1) lis2 comparison-func) - (nconc result (list (car lis1)))) - (setq lis1 (cdr lis1))) - (cdr result))) - -(defun ediff-add-to-history (history-var newelt) - (if (fboundp 'add-to-history) - (add-to-history history-var newelt) - (set history-var (cons newelt (symbol-value history-var))))) - -(defalias 'ediff-copy-list 'copy-sequence) - - -;; don't report error if version control package wasn't found -;;(ediff-load-version-control 'silent) - -(run-hooks 'ediff-load-hook) - - -;; 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: - -;; arch-tag: f51099b6-ef4b-470f-88a1-3a0e0b03a879 -;;; ediff-util.el ends here diff -r 681cd08dc0f7 -r e93288477c43 lisp/ediff-vers.el --- a/lisp/ediff-vers.el Thu Jun 10 22:43:47 2010 +0000 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,239 +0,0 @@ -;;; ediff-vers.el --- version control interface to Ediff - -;; Copyright (C) 1995, 1996, 1997, 2001, 2002, 2003, 2004, -;; 2005, 2006, 2007, 2008, 2009, 2010 Free Software Foundation, Inc. - -;; Author: Michael Kifer - -;; 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 3 of the License, 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. If not, see . - -;;; Commentary: - -;;; Code: - -;; Compiler pacifier -(defvar rcs-default-co-switches) - -(and noninteractive - (eval-when-compile - (condition-case nil - ;; for compatibility with current stable version of xemacs - (progn - ;;(require 'pcvs nil 'noerror) - ;;(require 'rcs nil 'noerror) - (require 'pcvs) - (require 'rcs)) - (error nil)) - (require 'vc) - (require 'ediff-init) - )) -;; end pacifier - -(defcustom ediff-keep-tmp-versions nil - "If t, do not delete temporary previous versions for the files on which -comparison or merge operations are being performed." - :type 'boolean - :group 'ediff-vers - ) - -(defalias 'ediff-vc-revision-other-window - (if (fboundp 'vc-revision-other-window) - 'vc-revision-other-window - 'vc-version-other-window)) - -(defalias 'ediff-vc-working-revision - (if (fboundp 'vc-working-revision) - 'vc-working-revision - 'vc-workfile-version)) - -;; VC.el support - -(eval-when-compile - (require 'vc-hooks)) ;; for vc-call macro - - -(defun ediff-vc-latest-version (file) - "Return the version level of the latest version of FILE in repository." - (if (fboundp 'vc-latest-version) - (vc-latest-version file) - (or (vc-file-getprop file 'vc-latest-revision) - (cond ((vc-backend file) - (vc-call state file) - (vc-file-getprop file 'vc-latest-revision)) - (t (error "File %s is not under version control" file)))) - )) - - -(defun ediff-vc-internal (rev1 rev2 &optional startup-hooks) - ;; Run Ediff on versions of the current buffer. - ;; If REV1 is "", use the latest version of the current buffer's file. - ;; If REV2 is "" then compare current buffer with REV1. - ;; If the current buffer is named `F', the version is named `F.~REV~'. - ;; If `F.~REV~' already exists, it is used instead of being re-created. - (let (file1 file2 rev1buf rev2buf) - (if (string= rev1 "") - (setq rev1 (ediff-vc-latest-version (buffer-file-name)))) - (save-window-excursion - (save-excursion - (ediff-vc-revision-other-window rev1) - (setq rev1buf (current-buffer) - file1 (buffer-file-name))) - (save-excursion - (or (string= rev2 "") ; use current buffer - (ediff-vc-revision-other-window rev2)) - (setq rev2buf (current-buffer) - file2 (buffer-file-name))) - (setq startup-hooks - (cons `(lambda () - (ediff-delete-version-file ,file1) - (or ,(string= rev2 "") (ediff-delete-version-file ,file2))) - startup-hooks))) - (ediff-buffers - rev1buf rev2buf - startup-hooks - 'ediff-revision))) - -;; RCS.el support -(defun rcs-ediff-view-revision (&optional rev) -;; View previous RCS revision of current file. -;; With prefix argument, prompts for a revision name. - (interactive (list (if current-prefix-arg - (read-string "Revision: ")))) - (let* ((filename (buffer-file-name (current-buffer))) - (switches (append '("-p") - (if rev (list (concat "-r" rev)) nil))) - (buff (concat (file-name-nondirectory filename) ".~" rev "~"))) - (message "Working ...") - (setq filename (expand-file-name filename)) - (with-output-to-temp-buffer buff - (ediff-with-current-buffer standard-output - (fundamental-mode)) - (let ((output-buffer (ediff-rcs-get-output-buffer filename buff))) - (delete-windows-on output-buffer) - (with-current-buffer output-buffer - (apply 'call-process "co" nil t nil - ;; -q: quiet (no diagnostics) - (append switches rcs-default-co-switches - (list "-q" filename))))) - (message "") - buff))) - -(defun ediff-rcs-get-output-buffer (file name) - ;; Get a buffer for RCS output for FILE, make it writable and clean it up. - ;; Optional NAME is name to use instead of `*RCS-output*'. - ;; This is a modified version from rcs.el v1.1. I use it here to make - ;; Ediff immune to changes in rcs.el - (let ((buf (get-buffer-create name))) - (with-current-buffer buf - (setq buffer-read-only nil - default-directory (file-name-directory (expand-file-name file))) - (erase-buffer)) - buf)) - -(defun ediff-rcs-internal (rev1 rev2 &optional startup-hooks) -;; Run Ediff on versions of the current buffer. -;; If REV2 is "" then use current buffer. - (let (rev2buf rev1buf) - (save-window-excursion - (setq rev2buf (if (string= rev2 "") - (current-buffer) - (rcs-ediff-view-revision rev2)) - rev1buf (rcs-ediff-view-revision rev1))) - - ;; rcs.el doesn't create temp version files, so we don't have to delete - ;; anything in startup hooks to ediff-buffers - (ediff-buffers rev1buf rev2buf startup-hooks 'ediff-revision) - )) - -;;; Merge with Version Control - -(defun ediff-vc-merge-internal (rev1 rev2 ancestor-rev - &optional startup-hooks merge-buffer-file) -;; If ANCESTOR-REV non-nil, merge with ancestor - (let (buf1 buf2 ancestor-buf) - (save-window-excursion - (save-excursion - (ediff-vc-revision-other-window rev1) - (setq buf1 (current-buffer))) - (save-excursion - (or (string= rev2 "") - (ediff-vc-revision-other-window rev2)) - (setq buf2 (current-buffer))) - (if ancestor-rev - (save-excursion - (if (string= ancestor-rev "") - (setq ancestor-rev (ediff-vc-working-revision buffer-file-name))) - (ediff-vc-revision-other-window ancestor-rev) - (setq ancestor-buf (current-buffer)))) - (setq startup-hooks - (cons - `(lambda () - (ediff-delete-version-file ,(buffer-file-name buf1)) - (or ,(string= rev2 "") - (ediff-delete-version-file ,(buffer-file-name buf2))) - (or ,(string= ancestor-rev "") - ,(not ancestor-rev) - (ediff-delete-version-file ,(buffer-file-name ancestor-buf))) - ) - startup-hooks))) - (if ancestor-rev - (ediff-merge-buffers-with-ancestor - buf1 buf2 ancestor-buf - startup-hooks 'ediff-merge-revisions-with-ancestor merge-buffer-file) - (ediff-merge-buffers - buf1 buf2 startup-hooks 'ediff-merge-revisions merge-buffer-file)) - )) - -(defun ediff-rcs-merge-internal (rev1 rev2 ancestor-rev - &optional - startup-hooks merge-buffer-file) - ;; If ANCESTOR-REV non-nil, merge with ancestor - (let (buf1 buf2 ancestor-buf) - (save-window-excursion - (setq buf1 (rcs-ediff-view-revision rev1) - buf2 (if (string= rev2 "") - (current-buffer) - (rcs-ediff-view-revision rev2)) - ancestor-buf (if ancestor-rev - (if (string= ancestor-rev "") - (current-buffer) - (rcs-ediff-view-revision ancestor-rev))))) - ;; rcs.el doesn't create temp version files, so we don't have to delete - ;; anything in startup hooks to ediff-buffers - (if ancestor-rev - (ediff-merge-buffers-with-ancestor - buf1 buf2 ancestor-buf - startup-hooks 'ediff-merge-revisions-with-ancestor merge-buffer-file) - (ediff-merge-buffers - buf1 buf2 startup-hooks 'ediff-merge-revisions merge-buffer-file)))) - - -;; delete version file on exit unless ediff-keep-tmp-versions is true -(defun ediff-delete-version-file (file) - (or ediff-keep-tmp-versions (delete-file file))) - - -(provide 'ediff-vers) - - -;; 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: - -;; arch-tag: bbb34f0c-2a90-426a-a77a-c75f479ebbbf -;;; ediff-vers.el ends here diff -r 681cd08dc0f7 -r e93288477c43 lisp/ediff-wind.el --- a/lisp/ediff-wind.el Thu Jun 10 22:43:47 2010 +0000 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,1313 +0,0 @@ -;;; ediff-wind.el --- window manipulation utilities - -;; Copyright (C) 1994, 1995, 1996, 1997, 2000, 2001, 2002, 2003, -;; 2004, 2005, 2006, 2007, 2008, 2009, 2010 Free Software Foundation, Inc. - -;; Author: Michael Kifer - -;; 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 3 of the License, 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. If not, see . - -;;; Commentary: - -;;; Code: - - -;; Compiler pacifier -(defvar icon-title-format) -(defvar top-toolbar-height) -(defvar bottom-toolbar-height) -(defvar left-toolbar-height) -(defvar right-toolbar-height) -(defvar left-toolbar-width) -(defvar right-toolbar-width) -(defvar default-menubar) -(defvar top-gutter) -(defvar frame-icon-title-format) -(defvar ediff-diff-status) - -;; declare-function does not exist in XEmacs -(eval-and-compile - (unless (fboundp 'declare-function) (defmacro declare-function (&rest r)))) - -(eval-when-compile - (require 'ediff-util) - (require 'ediff-help)) -;; end pacifier - -(require 'ediff-init) - -;; be careful with ediff-tbar -(if (featurep 'xemacs) - (require 'ediff-tbar) - (defun ediff-compute-toolbar-width () 0)) - -(defgroup ediff-window nil - "Ediff window manipulation." - :prefix "ediff-" - :group 'ediff - :group 'frames) - - -;; Determine which window setup function to use based on current window system. -(defun ediff-choose-window-setup-function-automatically () - (if (ediff-window-display-p) - 'ediff-setup-windows-multiframe - 'ediff-setup-windows-plain)) - -(defcustom ediff-window-setup-function (ediff-choose-window-setup-function-automatically) - "Function called to set up windows. -Ediff provides a choice of two functions: `ediff-setup-windows-plain', for -doing everything in one frame and `ediff-setup-windows-multiframe', which sets -the control panel in a separate frame. By default, the appropriate function is -chosen automatically depending on the current window system. -However, `ediff-toggle-multiframe' can be used to toggle between the multiframe -display and the single frame display. -If the multiframe function detects that one of the buffers A/B is seen in some -other frame, it will try to keep that buffer in that frame. - -If you don't like any of the two provided functions, write your own one. -The basic guidelines: - 1. It should leave the control buffer current and the control window - selected. - 2. It should set `ediff-window-A', `ediff-window-B', `ediff-window-C', - and `ediff-control-window' to contain window objects that display - the corresponding buffers. - 3. It should accept the following arguments: - buffer-A, buffer-B, buffer-C, control-buffer - Buffer C may not be used in jobs that compare only two buffers. -If you plan to do something fancy, take a close look at how the two -provided functions are written." - :type '(choice (const :tag "Multi Frame" ediff-setup-windows-multiframe) - (const :tag "Single Frame" ediff-setup-windows-plain) - (function :tag "Other function")) - :group 'ediff-window) - -;; indicates if we are in a multiframe setup -(ediff-defvar-local ediff-multiframe nil "") - -;; Share of the frame occupied by the merge window (buffer C) -(ediff-defvar-local ediff-merge-window-share 0.45 "") - -;; The control window. -(ediff-defvar-local ediff-control-window nil "") -;; Official window for buffer A -(ediff-defvar-local ediff-window-A nil "") -;; Official window for buffer B -(ediff-defvar-local ediff-window-B nil "") -;; Official window for buffer C -(ediff-defvar-local ediff-window-C nil "") -;; Ediff's window configuration. -;; Used to minimize the need to rearrange windows. -(ediff-defvar-local ediff-window-config-saved "" "") - -;; Association between buff-type and ediff-window-* -(defconst ediff-window-alist - '((A . ediff-window-A) - (?A . ediff-window-A) - (B . ediff-window-B) - (?B . ediff-window-B) - (C . ediff-window-C) - (?C . ediff-window-C))) - - -(defcustom ediff-split-window-function 'split-window-vertically - "The function used to split the main window between buffer-A and buffer-B. -You can set it to a horizontal split instead of the default vertical split -by setting this variable to `split-window-horizontally'. -You can also have your own function to do fancy splits. -This variable has no effect when buffer-A/B are shown in different frames. -In this case, Ediff will use those frames to display these buffers." - :type '(choice - (const :tag "Split vertically" split-window-vertically) - (const :tag "Split horizontally" split-window-horizontally) - function) - :group 'ediff-window) - -(defcustom ediff-merge-split-window-function 'split-window-horizontally - "The function used to split the main window between buffer-A and buffer-B. -You can set it to a vertical split instead of the default horizontal split -by setting this variable to `split-window-vertically'. -You can also have your own function to do fancy splits. -This variable has no effect when buffer-A/B/C are shown in different frames. -In this case, Ediff will use those frames to display these buffers." - :type '(choice - (const :tag "Split vertically" split-window-vertically) - (const :tag "Split horizontally" split-window-horizontally) - function) - :group 'ediff-window) - -;; Definitions hidden from the compiler by compat wrappers. -(declare-function ediff-display-pixel-width "ediff-init") -(declare-function ediff-display-pixel-height "ediff-init") - -(defconst ediff-control-frame-parameters - (list - '(name . "Ediff") - ;;'(unsplittable . t) - '(minibuffer . nil) - '(user-position . t) ; Emacs only - '(vertical-scroll-bars . nil) ; Emacs only - '(scrollbar-width . 0) ; XEmacs only - '(scrollbar-height . 0) ; XEmacs only - '(menu-bar-lines . 0) ; Emacs only - '(tool-bar-lines . 0) ; Emacs 21+ only - '(left-fringe . 0) - '(right-fringe . 0) - ;; don't lower but auto-raise - '(auto-lower . nil) - '(auto-raise . t) - '(visibility . nil) - ;; make initial frame small to avoid distraction - '(width . 1) '(height . 1) - ;; this blocks queries from window manager as to where to put - ;; ediff's control frame. we put the frame outside the display, - ;; so the initial frame won't jump all over the screen - (cons 'top (if (fboundp 'ediff-display-pixel-height) - (1+ (ediff-display-pixel-height)) - 3000)) - (cons 'left (if (fboundp 'ediff-display-pixel-width) - (1+ (ediff-display-pixel-width)) - 3000)) - ) - "Frame parameters for displaying Ediff Control Panel. -Used internally---not a user option.") - -;; position of the mouse; used to decide whether to warp the mouse into ctl -;; frame -(ediff-defvar-local ediff-mouse-pixel-position nil "") - -;; not used for now -(defvar ediff-mouse-pixel-threshold 30 - "If the user moves mouse more than this many pixels, Ediff won't warp mouse into control window.") - -(defcustom ediff-grab-mouse t - "If t, Ediff will always grab the mouse and put it in the control frame. -If 'maybe, Ediff will do it sometimes, but not after operations that require -relatively long time. If nil, the mouse will be entirely user's -responsibility." - :type 'boolean - :group 'ediff-window) - -(defcustom ediff-control-frame-position-function 'ediff-make-frame-position - "Function to call to determine the desired location for the control panel. -Expects three parameters: the control buffer, the desired width and height -of the control frame. It returns an association list -of the form \(\(top . \) \(left . \)\)" - :type 'function - :group 'ediff-window) - -(defcustom ediff-control-frame-upward-shift 42 - "The upward shift of control frame from the top of buffer A's frame. -Measured in pixels. -This is used by the default control frame positioning function, -`ediff-make-frame-position'. This variable is provided for easy -customization of the default control frame positioning." - :type 'integer - :group 'ediff-window) - -(defcustom ediff-narrow-control-frame-leftward-shift (if (featurep 'xemacs) 7 3) - "The leftward shift of control frame from the right edge of buf A's frame. -Measured in characters. -This is used by the default control frame positioning function, -`ediff-make-frame-position' to adjust the position of the control frame -when it shows the short menu. This variable is provided for easy -customization of the default." - :type 'integer - :group 'ediff-window) - -(defcustom ediff-wide-control-frame-rightward-shift 7 - "The rightward shift of control frame from the left edge of buf A's frame. -Measured in characters. -This is used by the default control frame positioning function, -`ediff-make-frame-position' to adjust the position of the control frame -when it shows the full menu. This variable is provided for easy -customization of the default." - :type 'integer - :group 'ediff-window) - - -;; Wide frame display - -;; t means Ediff is using wide display -(ediff-defvar-local ediff-wide-display-p nil "") -;; keeps frame config for toggling wide display -(ediff-defvar-local ediff-wide-display-orig-parameters nil - "Frame parameters to be restored when the user wants to toggle the wide -display off.") -(ediff-defvar-local ediff-wide-display-frame nil - "Frame to be used for wide display.") -(ediff-defvar-local ediff-make-wide-display-function 'ediff-make-wide-display - "The value is a function that is called to create a wide display. -The function is called without arguments. It should resize the frame in -which buffers A, B, and C are to be displayed, and it should save the old -frame parameters in `ediff-wide-display-orig-parameters'. -The variable `ediff-wide-display-frame' should be set to contain -the frame used for the wide display.") - -;; Frame used for the control panel in a windowing system. -(ediff-defvar-local ediff-control-frame nil "") - -(defcustom ediff-prefer-iconified-control-frame nil - "If t, keep control panel iconified when help message is off. -This has effect only on a windowing system. -If t, hitting `?' to toggle control panel off iconifies it. - -This is only useful in Emacs and only for certain kinds of window managers, -such as TWM and its derivatives, since the window manager must permit -keyboard input to go into icons. XEmacs completely ignores keyboard input -into icons, regardless of the window manager." - :type 'boolean - :group 'ediff-window) - -;;; Functions - -(defun ediff-get-window-by-clicking (wind prev-wind wind-number) - (let (event) - (message - "Select windows by clicking. Please click on Window %d " wind-number) - (while (not (ediff-mouse-event-p (setq event (ediff-read-event)))) - (if (sit-for 1) ; if sequence of events, wait till the final word - (beep 1)) - (message "Please click on Window %d " wind-number)) - (ediff-read-event) ; discard event - (setq wind (if (featurep 'xemacs) - (event-window event) - (posn-window (event-start event)))))) - - -;; Select the lowest window on the frame. -(defun ediff-select-lowest-window () - (if (featurep 'xemacs) - (select-window (frame-lowest-window)) - (let* ((lowest-window (selected-window)) - (bottom-edge (car (cdr (cdr (cdr (window-edges)))))) - (last-window (save-excursion - (other-window -1) (selected-window))) - (window-search t)) - (while window-search - (let* ((this-window (next-window)) - (next-bottom-edge - (car (cdr (cdr (cdr (window-edges this-window))))))) - (if (< bottom-edge next-bottom-edge) - (setq bottom-edge next-bottom-edge - lowest-window this-window)) - (select-window this-window) - (when (eq last-window this-window) - (select-window lowest-window) - (setq window-search nil))))))) - - -;;; Common window setup routines - -;; Set up the window configuration. If POS is given, set the points to -;; the beginnings of the buffers. -;; When 3way comparison is added, this will have to choose the appropriate -;; setup function based on ediff-job-name -(defun ediff-setup-windows (buffer-A buffer-B buffer-C control-buffer) - ;; Make sure we are not in the minibuffer window when we try to delete - ;; all other windows. - (run-hooks 'ediff-before-setup-windows-hook) - (if (eq (selected-window) (minibuffer-window)) - (other-window 1)) - - ;; in case user did a no-no on a tty - (or (ediff-window-display-p) - (setq ediff-window-setup-function 'ediff-setup-windows-plain)) - - (or (ediff-keep-window-config control-buffer) - (funcall - (ediff-with-current-buffer control-buffer ediff-window-setup-function) - buffer-A buffer-B buffer-C control-buffer)) - (run-hooks 'ediff-after-setup-windows-hook)) - -;; Just set up 3 windows. -;; Usually used without windowing systems -;; With windowing, we want to use dedicated frames. -(defun ediff-setup-windows-plain (buffer-A buffer-B buffer-C control-buffer) - (ediff-with-current-buffer control-buffer - (setq ediff-multiframe nil)) - (if ediff-merge-job - (ediff-setup-windows-plain-merge - buffer-A buffer-B buffer-C control-buffer) - (ediff-setup-windows-plain-compare - buffer-A buffer-B buffer-C control-buffer))) - -(defun ediff-setup-windows-plain-merge (buf-A buf-B buf-C control-buffer) - ;; skip dedicated and unsplittable frames - (ediff-destroy-control-frame control-buffer) - (let ((window-min-height 1) - split-window-function - merge-window-share merge-window-lines - wind-A wind-B wind-C) - (ediff-with-current-buffer control-buffer - (setq merge-window-share ediff-merge-window-share - ;; this lets us have local versions of ediff-split-window-function - split-window-function ediff-split-window-function)) - (delete-other-windows) - (set-window-dedicated-p (selected-window) nil) - (split-window-vertically) - (ediff-select-lowest-window) - (ediff-setup-control-buffer control-buffer) - - ;; go to the upper window and split it betw A, B, and possibly C - (other-window 1) - (setq merge-window-lines - (max 2 (round (* (window-height) merge-window-share)))) - (switch-to-buffer buf-A) - (setq wind-A (selected-window)) - - ;; XEmacs used to have a lot of trouble with display - ;; It did't set things right unless we tell it to sit still - ;; 19.12 seems ok. - ;;(if (featurep 'xemacs) (sit-for 0)) - - (split-window-vertically (max 2 (- (window-height) merge-window-lines))) - (if (eq (selected-window) wind-A) - (other-window 1)) - (setq wind-C (selected-window)) - (switch-to-buffer buf-C) - - (select-window wind-A) - (funcall split-window-function) - - (if (eq (selected-window) wind-A) - (other-window 1)) - (switch-to-buffer buf-B) - (setq wind-B (selected-window)) - - (ediff-with-current-buffer control-buffer - (setq ediff-window-A wind-A - ediff-window-B wind-B - ediff-window-C wind-C)) - - (ediff-select-lowest-window) - (ediff-setup-control-buffer control-buffer) - )) - - -;; This function handles all comparison jobs, including 3way jobs -(defun ediff-setup-windows-plain-compare (buf-A buf-B buf-C control-buffer) - ;; skip dedicated and unsplittable frames - (ediff-destroy-control-frame control-buffer) - (let ((window-min-height 1) - split-window-function wind-width-or-height - three-way-comparison - wind-A-start wind-B-start wind-A wind-B wind-C) - (ediff-with-current-buffer control-buffer - (setq wind-A-start (ediff-overlay-start - (ediff-get-value-according-to-buffer-type - 'A ediff-narrow-bounds)) - wind-B-start (ediff-overlay-start - (ediff-get-value-according-to-buffer-type - 'B ediff-narrow-bounds)) - ;; this lets us have local versions of ediff-split-window-function - split-window-function ediff-split-window-function - three-way-comparison ediff-3way-comparison-job)) - ;; if in minibuffer go somewhere else - (if (save-match-data - (string-match "\*Minibuf-" (buffer-name (window-buffer)))) - (select-window (next-window nil 'ignore-minibuf))) - (delete-other-windows) - (set-window-dedicated-p (selected-window) nil) - (split-window-vertically) - (ediff-select-lowest-window) - (ediff-setup-control-buffer control-buffer) - - ;; go to the upper window and split it betw A, B, and possibly C - (other-window 1) - (switch-to-buffer buf-A) - (setq wind-A (selected-window)) - (if three-way-comparison - (setq wind-width-or-height - (/ (if (eq split-window-function 'split-window-vertically) - (window-height wind-A) - (window-width wind-A)) - 3))) - - ;; XEmacs used to have a lot of trouble with display - ;; It did't set things right unless we told it to sit still - ;; 19.12 seems ok. - ;;(if (featurep 'xemacs) (sit-for 0)) - - (funcall split-window-function wind-width-or-height) - - (if (eq (selected-window) wind-A) - (other-window 1)) - (switch-to-buffer buf-B) - (setq wind-B (selected-window)) - - (if three-way-comparison - (progn - (funcall split-window-function) ; equally - (if (eq (selected-window) wind-B) - (other-window 1)) - (switch-to-buffer buf-C) - (setq wind-C (selected-window)))) - - (ediff-with-current-buffer control-buffer - (setq ediff-window-A wind-A - ediff-window-B wind-B - ediff-window-C wind-C)) - - ;; It is unlikely that we will want to implement 3way window comparison. - ;; So, only buffers A and B are used here. - (if ediff-windows-job - (progn - (set-window-start wind-A wind-A-start) - (set-window-start wind-B wind-B-start))) - - (ediff-select-lowest-window) - (ediff-setup-control-buffer control-buffer) - )) - - -;; dispatch an appropriate window setup function -(defun ediff-setup-windows-multiframe (buf-A buf-B buf-C control-buf) - (ediff-with-current-buffer control-buf - (setq ediff-multiframe t)) - (if ediff-merge-job - (ediff-setup-windows-multiframe-merge buf-A buf-B buf-C control-buf) - (ediff-setup-windows-multiframe-compare buf-A buf-B buf-C control-buf))) - -(defun ediff-setup-windows-multiframe-merge (buf-A buf-B buf-C control-buf) -;;; Algorithm: -;;; 1. Never use frames that have dedicated windows in them---it is bad to -;;; destroy dedicated windows. -;;; 2. If A and B are in the same frame but C's frame is different--- use one -;;; frame for A and B and use a separate frame for C. -;;; 3. If C's frame is non-existent, then: if the first suitable -;;; non-dedicated frame is different from A&B's, then use it for C. -;;; Otherwise, put A,B, and C in one frame. -;;; 4. If buffers A, B, C are is separate frames, use them to display these -;;; buffers. - - ;; Skip dedicated or iconified frames. - ;; Unsplittable frames are taken care of later. - (ediff-skip-unsuitable-frames 'ok-unsplittable) - - (let* ((window-min-height 1) - (wind-A (ediff-get-visible-buffer-window buf-A)) - (wind-B (ediff-get-visible-buffer-window buf-B)) - (wind-C (ediff-get-visible-buffer-window buf-C)) - (frame-A (if wind-A (window-frame wind-A))) - (frame-B (if wind-B (window-frame wind-B))) - (frame-C (if wind-C (window-frame wind-C))) - ;; on wide display, do things in one frame - (force-one-frame - (ediff-with-current-buffer control-buf ediff-wide-display-p)) - ;; this lets us have local versions of ediff-split-window-function - (split-window-function - (ediff-with-current-buffer control-buf ediff-split-window-function)) - (orig-wind (selected-window)) - (orig-frame (selected-frame)) - (use-same-frame (or force-one-frame - ;; A and C must be in one frame - (eq frame-A (or frame-C orig-frame)) - ;; B and C must be in one frame - (eq frame-B (or frame-C orig-frame)) - ;; A or B is not visible - (not (frame-live-p frame-A)) - (not (frame-live-p frame-B)) - ;; A or B is not suitable for display - (not (ediff-window-ok-for-display wind-A)) - (not (ediff-window-ok-for-display wind-B)) - ;; A and B in the same frame, and no good frame - ;; for C - (and (eq frame-A frame-B) - (not (frame-live-p frame-C))) - )) - ;; use-same-frame-for-AB implies wind A and B are ok for display - (use-same-frame-for-AB (and (not use-same-frame) - (eq frame-A frame-B))) - (merge-window-share (ediff-with-current-buffer control-buf - ediff-merge-window-share)) - merge-window-lines - designated-minibuffer-frame - done-A done-B done-C) - - ;; buf-A on its own - (if (and (window-live-p wind-A) - (null use-same-frame) ; implies wind-A is suitable - (null use-same-frame-for-AB)) - (progn ; bug A on its own - ;; buffer buf-A is seen in live wind-A - (select-window wind-A) - (delete-other-windows) - (setq wind-A (selected-window)) - (setq done-A t))) - - ;; buf-B on its own - (if (and (window-live-p wind-B) - (null use-same-frame) ; implies wind-B is suitable - (null use-same-frame-for-AB)) - (progn ; buf B on its own - ;; buffer buf-B is seen in live wind-B - (select-window wind-B) - (delete-other-windows) - (setq wind-B (selected-window)) - (setq done-B t))) - - ;; buf-C on its own - (if (and (window-live-p wind-C) - (ediff-window-ok-for-display wind-C) - (null use-same-frame)) ; buf C on its own - (progn - ;; buffer buf-C is seen in live wind-C - (select-window wind-C) - (delete-other-windows) - (setq wind-C (selected-window)) - (setq done-C t))) - - (if (and use-same-frame-for-AB ; implies wind A and B are suitable - (window-live-p wind-A)) - (progn - ;; wind-A must already be displaying buf-A - (select-window wind-A) - (delete-other-windows) - (setq wind-A (selected-window)) - - (funcall split-window-function) - (if (eq (selected-window) wind-A) - (other-window 1)) - (switch-to-buffer buf-B) - (setq wind-B (selected-window)) - - (setq done-A t - done-B t))) - - (if use-same-frame - (let ((window-min-height 1)) - (if (and (eq frame-A frame-B) - (eq frame-B frame-C) - (frame-live-p frame-A)) - (select-frame frame-A) - ;; avoid dedicated and non-splittable windows - (ediff-skip-unsuitable-frames)) - (delete-other-windows) - (setq merge-window-lines - (max 2 (round (* (window-height) merge-window-share)))) - (switch-to-buffer buf-A) - (setq wind-A (selected-window)) - - (split-window-vertically - (max 2 (- (window-height) merge-window-lines))) - (if (eq (selected-window) wind-A) - (other-window 1)) - (setq wind-C (selected-window)) - (switch-to-buffer buf-C) - - (select-window wind-A) - - (funcall split-window-function) - (if (eq (selected-window) wind-A) - (other-window 1)) - (switch-to-buffer buf-B) - (setq wind-B (selected-window)) - - (setq done-A t - done-B t - done-C t) - )) - - (or done-A ; Buf A to be set in its own frame, - ;;; or it was set before because use-same-frame = 1 - (progn - ;; Buf-A was not set up yet as it wasn't visible, - ;; and use-same-frame = nil, use-same-frame-for-AB = nil - (select-window orig-wind) - (delete-other-windows) - (switch-to-buffer buf-A) - (setq wind-A (selected-window)) - )) - (or done-B ; Buf B to be set in its own frame, - ;;; or it was set before because use-same-frame = 1 - (progn - ;; Buf-B was not set up yet as it wasn't visible - ;; and use-same-frame = nil, use-same-frame-for-AB = nil - (select-window orig-wind) - (delete-other-windows) - (switch-to-buffer buf-B) - (setq wind-B (selected-window)) - )) - - (or done-C ; Buf C to be set in its own frame, - ;;; or it was set before because use-same-frame = 1 - (progn - ;; Buf-C was not set up yet as it wasn't visible - ;; and use-same-frame = nil - (select-window orig-wind) - (delete-other-windows) - (switch-to-buffer buf-C) - (setq wind-C (selected-window)) - )) - - (ediff-with-current-buffer control-buf - (setq ediff-window-A wind-A - ediff-window-B wind-B - ediff-window-C wind-C) - (setq frame-A (window-frame ediff-window-A) - designated-minibuffer-frame - (window-frame (minibuffer-window frame-A)))) - - (ediff-setup-control-frame control-buf designated-minibuffer-frame) - )) - - -;; Window setup for all comparison jobs, including 3way comparisons -(defun ediff-setup-windows-multiframe-compare (buf-A buf-B buf-C control-buf) -;;; Algorithm: -;;; If a buffer is seen in a frame, use that frame for that buffer. -;;; If it is not seen, use the current frame. -;;; If both buffers are not seen, they share the current frame. If one -;;; of the buffers is not seen, it is placed in the current frame (where -;;; ediff started). If that frame is displaying the other buffer, it is -;;; shared between the two buffers. -;;; However, if we decide to put both buffers in one frame -;;; and the selected frame isn't splittable, we create a new frame and -;;; put both buffers there, event if one of this buffers is visible in -;;; another frame. - - ;; Skip dedicated or iconified frames. - ;; Unsplittable frames are taken care of later. - (ediff-skip-unsuitable-frames 'ok-unsplittable) - - (let* ((window-min-height 1) - (wind-A (ediff-get-visible-buffer-window buf-A)) - (wind-B (ediff-get-visible-buffer-window buf-B)) - (wind-C (ediff-get-visible-buffer-window buf-C)) - (frame-A (if wind-A (window-frame wind-A))) - (frame-B (if wind-B (window-frame wind-B))) - (frame-C (if wind-C (window-frame wind-C))) - (ctl-frame-exists-p (ediff-with-current-buffer control-buf - (frame-live-p ediff-control-frame))) - ;; on wide display, do things in one frame - (force-one-frame - (ediff-with-current-buffer control-buf ediff-wide-display-p)) - ;; this lets us have local versions of ediff-split-window-function - (split-window-function - (ediff-with-current-buffer control-buf ediff-split-window-function)) - (three-way-comparison - (ediff-with-current-buffer control-buf ediff-3way-comparison-job)) - (orig-wind (selected-window)) - (use-same-frame (or force-one-frame - (eq frame-A frame-B) - (not (ediff-window-ok-for-display wind-A)) - (not (ediff-window-ok-for-display wind-B)) - (if three-way-comparison - (or (eq frame-A frame-C) - (eq frame-B frame-C) - (not (ediff-window-ok-for-display wind-C)) - (not (frame-live-p frame-A)) - (not (frame-live-p frame-B)) - (not (frame-live-p frame-C)))) - (and (not (frame-live-p frame-B)) - (or ctl-frame-exists-p - (eq frame-A (selected-frame)))) - (and (not (frame-live-p frame-A)) - (or ctl-frame-exists-p - (eq frame-B (selected-frame)))))) - wind-A-start wind-B-start - designated-minibuffer-frame - done-A done-B done-C) - - (ediff-with-current-buffer control-buf - (setq wind-A-start (ediff-overlay-start - (ediff-get-value-according-to-buffer-type - 'A ediff-narrow-bounds)) - wind-B-start (ediff-overlay-start - (ediff-get-value-according-to-buffer-type - 'B ediff-narrow-bounds)))) - - (if (and (window-live-p wind-A) (null use-same-frame)) ; buf-A on its own - (progn - ;; buffer buf-A is seen in live wind-A - (select-window wind-A) ; must be displaying buf-A - (delete-other-windows) - (setq wind-A (selected-window)) - (setq done-A t))) - - (if (and (window-live-p wind-B) (null use-same-frame)) ; buf B on its own - (progn - ;; buffer buf-B is seen in live wind-B - (select-window wind-B) ; must be displaying buf-B - (delete-other-windows) - (setq wind-B (selected-window)) - (setq done-B t))) - - (if (and (window-live-p wind-C) (null use-same-frame)) ; buf C on its own - (progn - ;; buffer buf-C is seen in live wind-C - (select-window wind-C) ; must be displaying buf-C - (delete-other-windows) - (setq wind-C (selected-window)) - (setq done-C t))) - - (if use-same-frame - (let (wind-width-or-height) ; this affects 3way setups only - (if (and (eq frame-A frame-B) (frame-live-p frame-A)) - (select-frame frame-A) - ;; avoid dedicated and non-splittable windows - (ediff-skip-unsuitable-frames)) - (delete-other-windows) - (switch-to-buffer buf-A) - (setq wind-A (selected-window)) - - (if three-way-comparison - (setq wind-width-or-height - (/ - (if (eq split-window-function 'split-window-vertically) - (window-height wind-A) - (window-width wind-A)) - 3))) - - (funcall split-window-function wind-width-or-height) - (if (eq (selected-window) wind-A) - (other-window 1)) - (switch-to-buffer buf-B) - (setq wind-B (selected-window)) - - (if three-way-comparison - (progn - (funcall split-window-function) ; equally - (if (memq (selected-window) (list wind-A wind-B)) - (other-window 1)) - (switch-to-buffer buf-C) - (setq wind-C (selected-window)))) - (setq done-A t - done-B t - done-C t) - )) - - (or done-A ; Buf A to be set in its own frame - ;;; or it was set before because use-same-frame = 1 - (progn - ;; Buf-A was not set up yet as it wasn't visible, - ;; and use-same-frame = nil - (select-window orig-wind) - (delete-other-windows) - (switch-to-buffer buf-A) - (setq wind-A (selected-window)) - )) - (or done-B ; Buf B to be set in its own frame - ;;; or it was set before because use-same-frame = 1 - (progn - ;; Buf-B was not set up yet as it wasn't visible, - ;; and use-same-frame = nil - (select-window orig-wind) - (delete-other-windows) - (switch-to-buffer buf-B) - (setq wind-B (selected-window)) - )) - - (if three-way-comparison - (or done-C ; Buf C to be set in its own frame - ;;; or it was set before because use-same-frame = 1 - (progn - ;; Buf-C was not set up yet as it wasn't visible, - ;; and use-same-frame = nil - (select-window orig-wind) - (delete-other-windows) - (switch-to-buffer buf-C) - (setq wind-C (selected-window)) - ))) - - (ediff-with-current-buffer control-buf - (setq ediff-window-A wind-A - ediff-window-B wind-B - ediff-window-C wind-C) - - (setq frame-A (window-frame ediff-window-A) - designated-minibuffer-frame - (window-frame (minibuffer-window frame-A)))) - - ;; It is unlikely that we'll implement a version of ediff-windows that - ;; would compare 3 windows at once. So, we don't use buffer C here. - (if ediff-windows-job - (progn - (set-window-start wind-A wind-A-start) - (set-window-start wind-B wind-B-start))) - - (ediff-setup-control-frame control-buf designated-minibuffer-frame) - )) - -;; skip unsplittable frames and frames that have dedicated windows. -;; create a new splittable frame if none is found -(defun ediff-skip-unsuitable-frames (&optional ok-unsplittable) - (if (ediff-window-display-p) - (let ((wind-frame (window-frame (selected-window))) - seen-windows) - (while (and (not (memq (selected-window) seen-windows)) - (or - (ediff-frame-has-dedicated-windows wind-frame) - (ediff-frame-iconified-p wind-frame) - ;; skip small windows - (< (frame-height wind-frame) - (* 3 window-min-height)) - (if ok-unsplittable - nil - (ediff-frame-unsplittable-p wind-frame)))) - ;; remember history - (setq seen-windows (cons (selected-window) seen-windows)) - ;; try new window - (other-window 1 t) - (setq wind-frame (window-frame (selected-window))) - ) - (if (memq (selected-window) seen-windows) - ;; fed up, no appropriate frames - (setq wind-frame (make-frame '((unsplittable))))) - - (select-frame wind-frame) - ))) - -(defun ediff-frame-has-dedicated-windows (frame) - (let (ans) - (walk-windows - (lambda (wind) (if (window-dedicated-p wind) - (setq ans t))) - 'ignore-minibuffer - frame) - ans)) - -;; window is ok, if it is only one window on the frame, not counting the -;; minibuffer, or none of the frame's windows is dedicated. -;; The idea is that it is bad to destroy dedicated windows while creating an -;; ediff window setup -(defun ediff-window-ok-for-display (wind) - (and - (window-live-p wind) - (or - ;; only one window - (eq wind (next-window wind 'ignore-minibuffer (window-frame wind))) - ;; none is dedicated (in multiframe setup) - (not (ediff-frame-has-dedicated-windows (window-frame wind))) - ))) - -;; Prepare or refresh control frame -(defun ediff-setup-control-frame (ctl-buffer designated-minibuffer-frame) - (let ((window-min-height 1) - ctl-frame-iconified-p dont-iconify-ctl-frame deiconify-ctl-frame - ctl-frame old-ctl-frame lines - ;; user-grabbed-mouse - fheight fwidth adjusted-parameters) - - (ediff-with-current-buffer ctl-buffer - (if (and (featurep 'xemacs) (featurep 'menubar)) - (set-buffer-menubar nil)) - ;;(setq user-grabbed-mouse (ediff-user-grabbed-mouse)) - (run-hooks 'ediff-before-setup-control-frame-hook)) - - (setq old-ctl-frame (ediff-with-current-buffer ctl-buffer ediff-control-frame)) - (ediff-with-current-buffer ctl-buffer - (setq ctl-frame (if (frame-live-p old-ctl-frame) - old-ctl-frame - (make-frame ediff-control-frame-parameters)) - ediff-control-frame ctl-frame) - ;; protect against undefined face-attribute - (condition-case nil - (if (and (featurep 'emacs) (face-attribute 'mode-line :box)) - (set-face-attribute 'mode-line ctl-frame :box nil)) - (error))) - - (setq ctl-frame-iconified-p (ediff-frame-iconified-p ctl-frame)) - (select-frame ctl-frame) - (if (window-dedicated-p (selected-window)) - () - (delete-other-windows) - (switch-to-buffer ctl-buffer)) - - ;; must be before ediff-setup-control-buffer - ;; just a precaution--we should be in ctl-buffer already - (ediff-with-current-buffer ctl-buffer - (make-local-variable 'frame-title-format) - (make-local-variable 'frame-icon-title-format) ; XEmacs - (make-local-variable 'icon-title-format)) ; Emacs - - (ediff-setup-control-buffer ctl-buffer) - (setq dont-iconify-ctl-frame - (not (string= ediff-help-message ediff-brief-help-message))) - (setq deiconify-ctl-frame - (and (eq this-command 'ediff-toggle-help) - dont-iconify-ctl-frame)) - - ;; 1 more line for the modeline - (setq lines (1+ (count-lines (point-min) (point-max))) - fheight lines - fwidth (max (+ (ediff-help-message-line-length) 2) - (ediff-compute-toolbar-width)) - adjusted-parameters - (list - ;; possibly change surrogate minibuffer - (cons 'minibuffer - (minibuffer-window - designated-minibuffer-frame)) - (cons 'width fwidth) - (cons 'height fheight) - (cons 'user-position t) - )) - - ;; adjust autoraise - (setq adjusted-parameters - (cons (if ediff-use-long-help-message - '(auto-raise . nil) - '(auto-raise . t)) - adjusted-parameters)) - - ;; In XEmacs, buffer menubar needs to be killed before frame parameters - ;; are changed. - (if (ediff-has-toolbar-support-p) - (when (featurep 'xemacs) - (if (ediff-has-gutter-support-p) - (set-specifier top-gutter (list ctl-frame nil))) - (sit-for 0) - (set-specifier top-toolbar-height (list ctl-frame 0)) - ;;(set-specifier bottom-toolbar-height (list ctl-frame 0)) - (set-specifier left-toolbar-width (list ctl-frame 0)) - (set-specifier right-toolbar-width (list ctl-frame 0)))) - - ;; Under OS/2 (emx) we have to call modify frame parameters twice, in order - ;; to make sure that at least once we do it for non-iconified frame. If - ;; appears that in the OS/2 port of Emacs, one can't modify frame - ;; parameters of iconified frames. As a precaution, we do likewise for - ;; windows-nt. - (if (memq system-type '(emx windows-nt windows-95)) - (modify-frame-parameters ctl-frame adjusted-parameters)) - - ;; make or zap toolbar (if not requested) - (ediff-make-bottom-toolbar ctl-frame) - - (goto-char (point-min)) - - (modify-frame-parameters ctl-frame adjusted-parameters) - (make-frame-visible ctl-frame) - - ;; This works around a bug in 19.25 and earlier. There, if frame gets - ;; iconified, the current buffer changes to that of the frame that - ;; becomes exposed as a result of this iconification. - ;; So, we make sure the current buffer doesn't change. - (select-frame ctl-frame) - (ediff-refresh-control-frame) - - (cond ((and ediff-prefer-iconified-control-frame - (not ctl-frame-iconified-p) (not dont-iconify-ctl-frame)) - (iconify-frame ctl-frame)) - ((or deiconify-ctl-frame (not ctl-frame-iconified-p)) - (raise-frame ctl-frame))) - - (set-window-dedicated-p (selected-window) t) - - ;; Now move the frame. We must do it separately due to an obscure bug in - ;; XEmacs - (modify-frame-parameters - ctl-frame - (funcall ediff-control-frame-position-function ctl-buffer fwidth fheight)) - - ;; synchronize so the cursor will move to control frame - ;; per RMS suggestion - (if (ediff-window-display-p) - (let ((count 7)) - (sit-for .1) - (while (and (not (frame-visible-p ctl-frame)) (> count 0)) - (setq count (1- count)) - (sit-for .3)))) - - (or (ediff-frame-iconified-p ctl-frame) - ;; don't warp the mouse, unless ediff-grab-mouse = t - (ediff-reset-mouse ctl-frame - (or (eq this-command 'ediff-quit) - (not (eq ediff-grab-mouse t))))) - - (when (featurep 'xemacs) - (ediff-with-current-buffer ctl-buffer - (make-local-hook 'select-frame-hook) - (add-hook 'select-frame-hook - 'ediff-xemacs-select-frame-hook nil 'local))) - - (ediff-with-current-buffer ctl-buffer - (run-hooks 'ediff-after-setup-control-frame-hook)))) - - -(defun ediff-destroy-control-frame (ctl-buffer) - (ediff-with-current-buffer ctl-buffer - (if (and (ediff-window-display-p) (frame-live-p ediff-control-frame)) - (let ((ctl-frame ediff-control-frame)) - (if (and (featurep 'xemacs) (featurep 'menubar)) - (set-buffer-menubar default-menubar)) - (setq ediff-control-frame nil) - (delete-frame ctl-frame)))) - (if ediff-multiframe - (ediff-skip-unsuitable-frames)) - ;;(ediff-reset-mouse nil) - ) - - -;; finds a good place to clip control frame -(defun ediff-make-frame-position (ctl-buffer ctl-frame-width ctl-frame-height) - (ediff-with-current-buffer ctl-buffer - (let* ((frame-A (window-frame ediff-window-A)) - (frame-A-parameters (frame-parameters frame-A)) - (frame-A-top (eval (cdr (assoc 'top frame-A-parameters)))) - (frame-A-left (eval (cdr (assoc 'left frame-A-parameters)))) - (frame-A-width (frame-width frame-A)) - (ctl-frame ediff-control-frame) - horizontal-adjustment upward-adjustment - ctl-frame-top ctl-frame-left) - - ;; Multiple control frames are clipped based on the value of - ;; ediff-control-buffer-number. This is done in order not to obscure - ;; other active control panels. - (setq horizontal-adjustment (* 2 ediff-control-buffer-number) - upward-adjustment (* -14 ediff-control-buffer-number)) - - (setq ctl-frame-top - (- frame-A-top upward-adjustment ediff-control-frame-upward-shift) - ctl-frame-left - (+ frame-A-left - (if ediff-use-long-help-message - (* (ediff-frame-char-width ctl-frame) - (+ ediff-wide-control-frame-rightward-shift - horizontal-adjustment)) - (- (* frame-A-width (ediff-frame-char-width frame-A)) - (* (ediff-frame-char-width ctl-frame) - (+ ctl-frame-width - ediff-narrow-control-frame-leftward-shift - horizontal-adjustment)))))) - (setq ctl-frame-top - (min ctl-frame-top - (- (ediff-display-pixel-height) - (* 2 ctl-frame-height - (ediff-frame-char-height ctl-frame)))) - ctl-frame-left - (min ctl-frame-left - (- (ediff-display-pixel-width) - (* ctl-frame-width (ediff-frame-char-width ctl-frame))))) - ;; keep ctl frame within the visible bounds - (setq ctl-frame-top (max ctl-frame-top 1) - ctl-frame-left (max ctl-frame-left 1)) - - (list (cons 'top ctl-frame-top) - (cons 'left ctl-frame-left)) - ))) - -(defun ediff-xemacs-select-frame-hook () - (if (and (equal (selected-frame) ediff-control-frame) - (not ediff-use-long-help-message)) - (raise-frame ediff-control-frame))) - -(defun ediff-make-wide-display () - "Construct an alist of parameters for the wide display. -Saves the old frame parameters in `ediff-wide-display-orig-parameters'. -The frame to be resized is kept in `ediff-wide-display-frame'. -This function modifies only the left margin and the width of the display. -It assumes that it is called from within the control buffer." - (if (not (fboundp 'ediff-display-pixel-width)) - (error "Can't determine display width")) - (let* ((frame-A (window-frame ediff-window-A)) - (frame-A-params (frame-parameters frame-A)) - (cw (ediff-frame-char-width frame-A)) - (wd (- (/ (ediff-display-pixel-width) cw) 5))) - (setq ediff-wide-display-orig-parameters - (list (cons 'left (max 0 (eval (cdr (assoc 'left frame-A-params))))) - (cons 'width (cdr (assoc 'width frame-A-params)))) - ediff-wide-display-frame frame-A) - (modify-frame-parameters - frame-A `((left . ,cw) (width . ,wd) (user-position . t))))) - - -;; Revise the mode line to display which difference we have selected -;; Also resets modelines of buffers A/B, since they may be clobbered by -;; anothe invocations of Ediff. -(defun ediff-refresh-mode-lines () - (let (buf-A-state-diff buf-B-state-diff buf-C-state-diff buf-C-state-merge) - - (if (ediff-valid-difference-p) - (setq - buf-C-state-diff (ediff-get-state-of-diff ediff-current-difference 'C) - buf-C-state-merge (ediff-get-state-of-merge ediff-current-difference) - buf-A-state-diff (ediff-get-state-of-diff ediff-current-difference 'A) - buf-B-state-diff (ediff-get-state-of-diff ediff-current-difference 'B) - buf-A-state-diff (if buf-A-state-diff - (format "[%s] " buf-A-state-diff) - "") - buf-B-state-diff (if buf-B-state-diff - (format "[%s] " buf-B-state-diff) - "") - buf-C-state-diff (if (and (ediff-buffer-live-p ediff-buffer-C) - (or buf-C-state-diff buf-C-state-merge)) - (format "[%s%s%s] " - (or buf-C-state-diff "") - (if buf-C-state-merge - (concat " " buf-C-state-merge) - "") - (if (ediff-get-state-of-ancestor - ediff-current-difference) - " AncestorEmpty" - "") - ) - "")) - (setq buf-A-state-diff "" - buf-B-state-diff "" - buf-C-state-diff "")) - - ;; control buffer format - (setq mode-line-format - (if (ediff-narrow-control-frame-p) - (list " " mode-line-buffer-identification) - (list "-- " mode-line-buffer-identification " Quick Help"))) - ;; control buffer id - (setq mode-line-buffer-identification - (if (ediff-narrow-control-frame-p) - (ediff-make-narrow-control-buffer-id 'skip-name) - (ediff-make-wide-control-buffer-id))) - ;; Force mode-line redisplay - (force-mode-line-update) - - (if (and (ediff-window-display-p) (frame-live-p ediff-control-frame)) - (ediff-refresh-control-frame)) - - (ediff-with-current-buffer ediff-buffer-A - (setq ediff-diff-status buf-A-state-diff) - (ediff-strip-mode-line-format) - (setq mode-line-format - (list " A: " 'ediff-diff-status mode-line-format)) - (force-mode-line-update)) - (ediff-with-current-buffer ediff-buffer-B - (setq ediff-diff-status buf-B-state-diff) - (ediff-strip-mode-line-format) - (setq mode-line-format - (list " B: " 'ediff-diff-status mode-line-format)) - (force-mode-line-update)) - (if ediff-3way-job - (ediff-with-current-buffer ediff-buffer-C - (setq ediff-diff-status buf-C-state-diff) - (ediff-strip-mode-line-format) - (setq mode-line-format - (list " C: " 'ediff-diff-status mode-line-format)) - (force-mode-line-update))) - (if (ediff-buffer-live-p ediff-ancestor-buffer) - (ediff-with-current-buffer ediff-ancestor-buffer - (ediff-strip-mode-line-format) - ;; we keep the second dummy string in the mode line format of the - ;; ancestor, since for other buffers Ediff prepends 2 strings and - ;; ediff-strip-mode-line-format expects that. - (setq mode-line-format - (list " Ancestor: " - (cond ((not (stringp buf-C-state-merge)) - "") - ((string-match "prefer-A" buf-C-state-merge) - "[=diff(B)] ") - ((string-match "prefer-B" buf-C-state-merge) - "[=diff(A)] ") - (t "")) - mode-line-format)))) - )) - - -(defun ediff-refresh-control-frame () - (if (featurep 'emacs) - ;; set frame/icon titles for Emacs - (modify-frame-parameters - ediff-control-frame - (list (cons 'title (ediff-make-base-title)) - (cons 'icon-name (ediff-make-narrow-control-buffer-id)) - )) - ;; set frame/icon titles for XEmacs - (setq frame-title-format (ediff-make-base-title) - frame-icon-title-format (ediff-make-narrow-control-buffer-id)) - ;; force an update of the frame title - (modify-frame-parameters ediff-control-frame '(())))) - - -(defun ediff-make-narrow-control-buffer-id (&optional skip-name) - (concat - (if skip-name - " " - (ediff-make-base-title)) - (cond ((< ediff-current-difference 0) - (format " _/%d" ediff-number-of-differences)) - ((>= ediff-current-difference ediff-number-of-differences) - (format " $/%d" ediff-number-of-differences)) - (t - (format " %d/%d" - (1+ ediff-current-difference) - ediff-number-of-differences))))) - -(defun ediff-make-base-title () - (concat - (cdr (assoc 'name ediff-control-frame-parameters)) - ediff-control-buffer-suffix)) - -(defun ediff-make-wide-control-buffer-id () - (cond ((< ediff-current-difference 0) - (list (format "%%b At start of %d diffs" - ediff-number-of-differences))) - ((>= ediff-current-difference ediff-number-of-differences) - (list (format "%%b At end of %d diffs" - ediff-number-of-differences))) - (t - (list (format "%%b diff %d of %d" - (1+ ediff-current-difference) - ediff-number-of-differences))))) - - - -;; If buff is not live, return nil -(defun ediff-get-visible-buffer-window (buff) - (if (ediff-buffer-live-p buff) - (if (featurep 'xemacs) - (get-buffer-window buff t) - (get-buffer-window buff 'visible)))) - - -;;; Functions to decide when to redraw windows - -(defun ediff-keep-window-config (control-buf) - (and (eq control-buf (current-buffer)) - (/= (buffer-size) 0) - (ediff-with-current-buffer control-buf - (let ((ctl-wind ediff-control-window) - (A-wind ediff-window-A) - (B-wind ediff-window-B) - (C-wind ediff-window-C)) - - (and - (ediff-window-visible-p A-wind) - (ediff-window-visible-p B-wind) - ;; if buffer C is defined then take it into account - (or (not ediff-3way-job) - (ediff-window-visible-p C-wind)) - (eq (window-buffer A-wind) ediff-buffer-A) - (eq (window-buffer B-wind) ediff-buffer-B) - (or (not ediff-3way-job) - (eq (window-buffer C-wind) ediff-buffer-C)) - (string= ediff-window-config-saved - (format "%S%S%S%S%S%S%S" - ctl-wind A-wind B-wind C-wind - ediff-split-window-function - (ediff-multiframe-setup-p) - ediff-wide-display-p))))))) - - -(provide 'ediff-wind) - - -;; 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: - -;; arch-tag: 73d9a5d7-eed7-4d9c-8b4b-21d5d78eb597 -;;; ediff-wind.el ends here diff -r 681cd08dc0f7 -r e93288477c43 lisp/ediff.el --- a/lisp/ediff.el Thu Jun 10 22:43:47 2010 +0000 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,1565 +0,0 @@ -;;; ediff.el --- a comprehensive visual interface to diff & patch - -;; Copyright (C) 1994, 1995, 1996, 1997, 1998, 1999, 2000, 2001, 2002, -;; 2003, 2004, 2005, 2006, 2007, 2008, 2009, 2010 Free Software Foundation, Inc. - -;; Author: Michael Kifer -;; Created: February 2, 1994 -;; Keywords: comparing, merging, patching, tools, unix - -;; Yoni Rabkin contacted the maintainer of this -;; file on 20/3/2008, and the maintainer agreed that when a bug is -;; filed in the Emacs bug reporting system against this file, a copy -;; of the bug report be sent to the maintainer's email address. - -(defconst ediff-version "2.81.4" "The current version of Ediff") -(defconst ediff-date "December 7, 2009" "Date of last update") - - -;; 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 3 of the License, 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. If not, see . - -;;; Commentary: - -;; Never read that diff output again! -;; Apply patch interactively! -;; Merge with ease! - -;; This package provides a convenient way of simultaneous browsing through -;; the differences between a pair (or a triple) of files or buffers. The -;; files being compared, file-A, file-B, and file-C (if applicable) are -;; shown in separate windows (side by side, one above the another, or in -;; separate frames), and the differences are highlighted as you step -;; through them. You can also copy difference regions from one buffer to -;; another (and recover old differences if you change your mind). - -;; Ediff also supports merging operations on files and buffers, including -;; merging using ancestor versions. Both comparison and merging operations can -;; be performed on directories, i.e., by pairwise comparison of files in those -;; directories. - -;; In addition, Ediff can apply a patch to a file and then let you step -;; though both files, the patched and the original one, simultaneously, -;; difference-by-difference. You can even apply a patch right out of a -;; mail buffer, i.e., patches received by mail don't even have to be saved. -;; Since Ediff lets you copy differences between buffers, you can, in -;; effect, apply patches selectively (i.e., you can copy a difference -;; region from file_orig to file, thereby undoing any particular patch that -;; you don't like). - -;; Ediff is aware of version control, which lets the user compare -;; files with their older versions. Ediff can also work with remote and -;; compressed files. Details are given below. - -;; Finally, Ediff supports directory-level comparison, merging and patching. -;; See the on-line manual for details. - -;; This package builds upon the ideas borrowed from emerge.el and several -;; Ediff's functions are adaptations from emerge.el. Much of the functionality -;; Ediff provides is also influenced by emerge.el. - -;; The present version of Ediff supersedes Emerge. It provides a superior user -;; interface and has numerous major features not found in Emerge. In -;; particular, it can do patching, and 2-way and 3-way file comparison, -;; merging, and directory operations. - - - -;;; Bugs: - -;; 1. The undo command doesn't restore deleted regions well. That is, if -;; you delete all characters in a difference region and then invoke -;; `undo', the reinstated text will most likely be inserted outside of -;; what Ediff thinks is the current difference region. (This problem -;; doesn't seem to exist with XEmacs.) -;; -;; If at any point you feel that difference regions are no longer correct, -;; you can hit '!' to recompute the differences. - -;; 2. On a monochrome display, the repertoire of faces with which to -;; highlight fine differences is limited. By default, Ediff is using -;; underlining. However, if the region is already underlined by some other -;; overlays, there is no simple way to temporarily remove that residual -;; underlining. This problem occurs when a buffer is highlighted with -;; hilit19.el or font-lock.el packages. If this residual highlighting gets -;; in the way, you can do the following. Both font-lock.el and hilit19.el -;; provide commands for unhighlighting buffers. You can either place these -;; commands in `ediff-prepare-buffer-hook' (which will unhighlight every -;; buffer used by Ediff) or you can execute them interactively, at any time -;; and on any buffer. - - -;;; Acknowledgements: - -;; Ediff was inspired by Dale R. Worley's emerge.el. -;; Ediff would not have been possible without the help and encouragement of -;; its many users. See Ediff on-line Info for the full list of those who -;; helped. Improved defaults in Ediff file-name reading commands. - -;;; Code: - -(provide 'ediff) - -;; Compiler pacifier -(eval-and-compile - (unless (fboundp 'declare-function) (defmacro declare-function (&rest r)))) - - -(eval-when-compile - (require 'dired) - (require 'ediff-util) - (require 'ediff-ptch)) -;; end pacifier - -(require 'ediff-init) -(require 'ediff-mult) ; required because of the registry stuff - -(defgroup ediff nil - "A comprehensive visual interface to diff & patch." - :tag "Ediff" - :group 'tools) - - -(defcustom ediff-use-last-dir nil - "If t, Ediff will use previous directory as default when reading file name." - :type 'boolean - :group 'ediff) - -;; Last directory used by an Ediff command for file-A. -(defvar ediff-last-dir-A nil) -;; Last directory used by an Ediff command for file-B. -(defvar ediff-last-dir-B nil) -;; Last directory used by an Ediff command for file-C. -(defvar ediff-last-dir-C nil) -;; Last directory used by an Ediff command for the ancestor file. -(defvar ediff-last-dir-ancestor nil) -;; Last directory used by an Ediff command as the output directory for merge. -(defvar ediff-last-merge-autostore-dir nil) - - -;; Used as a startup hook to set `_orig' patch file read-only. -(defun ediff-set-read-only-in-buf-A () - (ediff-with-current-buffer ediff-buffer-A - (toggle-read-only 1))) - -;; Return a plausible default for ediff's first file: -;; In dired, return the file number FILENO (or 0) in the list -;; (all-selected-files, filename under the cursor), where directories are -;; ignored. Otherwise, return DEFAULT file name, if non-nil. Else, -;; if the buffer is visiting a file, return that file name. -(defun ediff-get-default-file-name (&optional default fileno) - (cond ((eq major-mode 'dired-mode) - (let ((current (dired-get-filename nil 'no-error)) - (marked (condition-case nil - (dired-get-marked-files 'no-dir) - (error nil))) - aux-list choices result) - (or (integerp fileno) (setq fileno 0)) - (if (stringp default) - (setq aux-list (cons default aux-list))) - (if (and (stringp current) (not (file-directory-p current))) - (setq aux-list (cons current aux-list))) - (setq choices (nconc marked aux-list)) - (setq result (elt choices fileno)) - (or result - default))) - ((stringp default) default) - ((buffer-file-name (current-buffer)) - (file-name-nondirectory (buffer-file-name (current-buffer)))) - )) - -;;; Compare files/buffers - -;;;###autoload -(defun ediff-files (file-A file-B &optional startup-hooks) - "Run Ediff on a pair of files, FILE-A and FILE-B." - (interactive - (let ((dir-A (if ediff-use-last-dir - ediff-last-dir-A - default-directory)) - dir-B f) - (list (setq f (ediff-read-file-name - "File A to compare" - dir-A - (ediff-get-default-file-name) - 'no-dirs)) - (ediff-read-file-name "File B to compare" - (setq dir-B - (if ediff-use-last-dir - ediff-last-dir-B - (file-name-directory f))) - (progn - (ediff-add-to-history - 'file-name-history - (ediff-abbreviate-file-name - (expand-file-name - (file-name-nondirectory f) - dir-B))) - (ediff-get-default-file-name f 1))) - ))) - (ediff-files-internal file-A - (if (file-directory-p file-B) - (expand-file-name - (file-name-nondirectory file-A) file-B) - file-B) - nil ; file-C - startup-hooks - 'ediff-files)) - -;;;###autoload -(defun ediff-files3 (file-A file-B file-C &optional startup-hooks) - "Run Ediff on three files, FILE-A, FILE-B, and FILE-C." - (interactive - (let ((dir-A (if ediff-use-last-dir - ediff-last-dir-A - default-directory)) - dir-B dir-C f ff) - (list (setq f (ediff-read-file-name - "File A to compare" - dir-A - (ediff-get-default-file-name) - 'no-dirs)) - (setq ff (ediff-read-file-name "File B to compare" - (setq dir-B - (if ediff-use-last-dir - ediff-last-dir-B - (file-name-directory f))) - (progn - (ediff-add-to-history - 'file-name-history - (ediff-abbreviate-file-name - (expand-file-name - (file-name-nondirectory f) - dir-B))) - (ediff-get-default-file-name f 1)))) - (ediff-read-file-name "File C to compare" - (setq dir-C (if ediff-use-last-dir - ediff-last-dir-C - (file-name-directory ff))) - (progn - (ediff-add-to-history - 'file-name-history - (ediff-abbreviate-file-name - (expand-file-name - (file-name-nondirectory ff) - dir-C))) - (ediff-get-default-file-name ff 2))) - ))) - (ediff-files-internal file-A - (if (file-directory-p file-B) - (expand-file-name - (file-name-nondirectory file-A) file-B) - file-B) - (if (file-directory-p file-C) - (expand-file-name - (file-name-nondirectory file-A) file-C) - file-C) - startup-hooks - 'ediff-files3)) - -;;;###autoload -(defalias 'ediff3 'ediff-files3) - - -(defun ediff-find-file (file-var buffer-name &optional last-dir hooks-var) - "Visit FILE and arrange its buffer to Ediff's liking. -FILE-VAR is actually a variable symbol whose value must contain a true -file name. -BUFFER-NAME is a variable symbol, which will get the buffer object into -which FILE is read. -LAST-DIR is the directory variable symbol where FILE's -directory name should be returned. HOOKS-VAR is a variable symbol that will -be assigned the hook to be executed after `ediff-startup' is finished. -`ediff-find-file' arranges that the temp files it might create will be -deleted." - (let* ((file (symbol-value file-var)) - (file-magic (ediff-filename-magic-p file)) - (temp-file-name-prefix (file-name-nondirectory file))) - (cond ((not (file-readable-p file)) - (error "File `%s' does not exist or is not readable" file)) - ((file-directory-p file) - (error "File `%s' is a directory" file))) - - ;; some of the commands, below, require full file name - (setq file (expand-file-name file)) - - ;; Record the directory of the file - (if last-dir - (set last-dir (expand-file-name (file-name-directory file)))) - - ;; Setup the buffer - (set buffer-name (find-file-noselect file)) - - (ediff-with-current-buffer (symbol-value buffer-name) - (widen) ; Make sure the entire file is seen - (cond (file-magic ; file has a handler, such as jka-compr-handler or - ;;; ange-ftp-hook-function--arrange for temp file - (ediff-verify-file-buffer 'magic) - (setq file - (ediff-make-temp-file - (current-buffer) temp-file-name-prefix)) - (set hooks-var (cons `(lambda () (delete-file ,file)) - (symbol-value hooks-var)))) - ;; file processed via auto-mode-alist, a la uncompress.el - ((not (equal (file-truename file) - (file-truename (buffer-file-name)))) - (setq file - (ediff-make-temp-file - (current-buffer) temp-file-name-prefix)) - (set hooks-var (cons `(lambda () (delete-file ,file)) - (symbol-value hooks-var)))) - (t ;; plain file---just check that the file matches the buffer - (ediff-verify-file-buffer)))) - (set file-var file))) - -;; MERGE-BUFFER-FILE is the file to be associated with the merge buffer -(defun ediff-files-internal (file-A file-B file-C startup-hooks job-name - &optional merge-buffer-file) - (let (buf-A buf-B buf-C) - (if (string= file-A file-B) - (error "Files A and B are the same")) - (if (stringp file-C) - (or (and (string= file-A file-C) (error "Files A and C are the same")) - (and (string= file-B file-C) (error "Files B and C are the same")))) - (message "Reading file %s ... " file-A) - ;;(sit-for 0) - (ediff-find-file 'file-A 'buf-A 'ediff-last-dir-A 'startup-hooks) - (message "Reading file %s ... " file-B) - ;;(sit-for 0) - (ediff-find-file 'file-B 'buf-B 'ediff-last-dir-B 'startup-hooks) - (if (stringp file-C) - (progn - (message "Reading file %s ... " file-C) - ;;(sit-for 0) - (ediff-find-file - 'file-C 'buf-C - (if (eq job-name 'ediff-merge-files-with-ancestor) - 'ediff-last-dir-ancestor 'ediff-last-dir-C) - 'startup-hooks))) - (ediff-setup buf-A file-A - buf-B file-B - buf-C file-C - startup-hooks - (list (cons 'ediff-job-name job-name)) - merge-buffer-file))) - -(declare-function diff-latest-backup-file "diff" (fn)) - -;;;###autoload -(defalias 'ediff 'ediff-files) - -;;;###autoload -(defun ediff-current-file () - "Start ediff between current buffer and its file on disk. -This command can be used instead of `revert-buffer'. If there is -nothing to revert then this command fails." - (interactive) - (unless (or revert-buffer-function - revert-buffer-insert-file-contents-function - (and buffer-file-number - (or (buffer-modified-p) - (not (verify-visited-file-modtime - (current-buffer)))))) - (error "Nothing to revert")) - (let* ((auto-save-p (and (recent-auto-save-p) - buffer-auto-save-file-name - (file-readable-p buffer-auto-save-file-name) - (y-or-n-p - "Buffer has been auto-saved recently. Compare with auto-save file? "))) - (file-name (if auto-save-p - buffer-auto-save-file-name - buffer-file-name)) - (revert-buf-name (concat "FILE=" file-name)) - (revert-buf (get-buffer revert-buf-name)) - (current-major major-mode)) - (unless file-name - (error "Buffer does not seem to be associated with any file")) - (when revert-buf - (kill-buffer revert-buf) - (setq revert-buf nil)) - (setq revert-buf (get-buffer-create revert-buf-name)) - (with-current-buffer revert-buf - (insert-file-contents file-name) - ;; Assume same modes: - (funcall current-major)) - (ediff-buffers revert-buf (current-buffer)))) - - -;;;###autoload -(defun ediff-backup (file) - "Run Ediff on FILE and its backup file. -Uses the latest backup, if there are several numerical backups. -If this file is a backup, `ediff' it with its original." - (interactive (list (read-file-name "Ediff (file with backup): "))) - ;; The code is taken from `diff-backup'. - (require 'diff) - (let (bak ori) - (if (backup-file-name-p file) - (setq bak file - ori (file-name-sans-versions file)) - (setq bak (or (diff-latest-backup-file file) - (error "No backup found for %s" file)) - ori file)) - (ediff-files bak ori))) - -;;;###autoload -(defun ediff-buffers (buffer-A buffer-B &optional startup-hooks job-name) - "Run Ediff on a pair of buffers, BUFFER-A and BUFFER-B." - (interactive - (let (bf) - (list (setq bf (read-buffer "Buffer A to compare: " - (ediff-other-buffer "") t)) - (read-buffer "Buffer B to compare: " - (progn - ;; realign buffers so that two visible bufs will be - ;; at the top - (save-window-excursion (other-window 1)) - (ediff-other-buffer bf)) - t)))) - (or job-name (setq job-name 'ediff-buffers)) - (ediff-buffers-internal buffer-A buffer-B nil startup-hooks job-name)) - -;;;###autoload -(defalias 'ebuffers 'ediff-buffers) - - -;;;###autoload -(defun ediff-buffers3 (buffer-A buffer-B buffer-C - &optional startup-hooks job-name) - "Run Ediff on three buffers, BUFFER-A, BUFFER-B, and BUFFER-C." - (interactive - (let (bf bff) - (list (setq bf (read-buffer "Buffer A to compare: " - (ediff-other-buffer "") t)) - (setq bff (read-buffer "Buffer B to compare: " - (progn - ;; realign buffers so that two visible - ;; bufs will be at the top - (save-window-excursion (other-window 1)) - (ediff-other-buffer bf)) - t)) - (read-buffer "Buffer C to compare: " - (progn - ;; realign buffers so that three visible - ;; bufs will be at the top - (save-window-excursion (other-window 1)) - (ediff-other-buffer (list bf bff))) - t) - ))) - (or job-name (setq job-name 'ediff-buffers3)) - (ediff-buffers-internal buffer-A buffer-B buffer-C startup-hooks job-name)) - -;;;###autoload -(defalias 'ebuffers3 'ediff-buffers3) - - - -;; MERGE-BUFFER-FILE is the file to be associated with the merge buffer -(defun ediff-buffers-internal (buf-A buf-B buf-C startup-hooks job-name - &optional merge-buffer-file) - (let* ((buf-A-file-name (buffer-file-name (get-buffer buf-A))) - (buf-B-file-name (buffer-file-name (get-buffer buf-B))) - (buf-C-is-alive (ediff-buffer-live-p buf-C)) - (buf-C-file-name (if buf-C-is-alive - (buffer-file-name (get-buffer buf-B)))) - file-A file-B file-C) - (unwind-protect - (progn - (if (not (ediff-buffer-live-p buf-A)) - (error "Buffer %S doesn't exist" buf-A)) - (if (not (ediff-buffer-live-p buf-B)) - (error "Buffer %S doesn't exist" buf-B)) - (let ((ediff-job-name job-name)) - (if (and ediff-3way-comparison-job - (not buf-C-is-alive)) - (error "Buffer %S doesn't exist" buf-C))) - (if (stringp buf-A-file-name) - (setq buf-A-file-name (file-name-nondirectory buf-A-file-name))) - (if (stringp buf-B-file-name) - (setq buf-B-file-name (file-name-nondirectory buf-B-file-name))) - (if (stringp buf-C-file-name) - (setq buf-C-file-name (file-name-nondirectory buf-C-file-name))) - - (setq file-A (ediff-make-temp-file buf-A buf-A-file-name) - file-B (ediff-make-temp-file buf-B buf-B-file-name)) - (if buf-C-is-alive - (setq file-C (ediff-make-temp-file buf-C buf-C-file-name))) - - (ediff-setup (get-buffer buf-A) file-A - (get-buffer buf-B) file-B - (if buf-C-is-alive (get-buffer buf-C)) - file-C - (cons `(lambda () - (delete-file ,file-A) - (delete-file ,file-B) - (if (stringp ,file-C) (delete-file ,file-C))) - startup-hooks) - (list (cons 'ediff-job-name job-name)) - merge-buffer-file)) - (if (and (stringp file-A) (file-exists-p file-A)) - (delete-file file-A)) - (if (and (stringp file-B) (file-exists-p file-B)) - (delete-file file-B)) - (if (and (stringp file-C) (file-exists-p file-C)) - (delete-file file-C))))) - - -;;; Directory and file group operations - -;; Get appropriate default name for directory: -;; If ediff-use-last-dir, use ediff-last-dir-A. -;; In dired mode, use the directory that is under the point (if any); -;; otherwise, use default-directory -(defun ediff-get-default-directory-name () - (cond (ediff-use-last-dir ediff-last-dir-A) - ((eq major-mode 'dired-mode) - (let ((f (dired-get-filename nil 'noerror))) - (if (and (stringp f) (file-directory-p f)) - f - default-directory))) - (t default-directory))) - - -;;;###autoload -(defun ediff-directories (dir1 dir2 regexp) - "Run Ediff on a pair of directories, DIR1 and DIR2, comparing files that have -the same name in both. The third argument, REGEXP, is nil or a regular -expression; only file names that match the regexp are considered." - (interactive - (let ((dir-A (ediff-get-default-directory-name)) - (default-regexp (eval ediff-default-filtering-regexp)) - f) - (list (setq f (read-directory-name - "Directory A to compare:" dir-A nil 'must-match)) - (read-directory-name "Directory B to compare:" - (if ediff-use-last-dir - ediff-last-dir-B - (ediff-strip-last-dir f)) - nil 'must-match) - (read-string - (if (stringp default-regexp) - (format "Filter through regular expression (default %s): " - default-regexp) - "Filter through regular expression: ") - nil - 'ediff-filtering-regexp-history - (eval ediff-default-filtering-regexp)) - ))) - (ediff-directories-internal - dir1 dir2 nil regexp 'ediff-files 'ediff-directories - )) - -;;;###autoload -(defalias 'edirs 'ediff-directories) - - -;;;###autoload -(defun ediff-directory-revisions (dir1 regexp) - "Run Ediff on a directory, DIR1, comparing its files with their revisions. -The second argument, REGEXP, is a regular expression that filters the file -names. Only the files that are under revision control are taken into account." - (interactive - (let ((dir-A (ediff-get-default-directory-name)) - (default-regexp (eval ediff-default-filtering-regexp)) - ) - (list (read-directory-name - "Directory to compare with revision:" dir-A nil 'must-match) - (read-string - (if (stringp default-regexp) - (format "Filter through regular expression (default %s): " - default-regexp) - "Filter through regular expression: ") - nil - 'ediff-filtering-regexp-history - (eval ediff-default-filtering-regexp)) - ))) - (ediff-directory-revisions-internal - dir1 regexp 'ediff-revision 'ediff-directory-revisions - )) - -;;;###autoload -(defalias 'edir-revisions 'ediff-directory-revisions) - - -;;;###autoload -(defun ediff-directories3 (dir1 dir2 dir3 regexp) - "Run Ediff on three directories, DIR1, DIR2, and DIR3, comparing files that -have the same name in all three. The last argument, REGEXP, is nil or a -regular expression; only file names that match the regexp are considered." - - (interactive - (let ((dir-A (ediff-get-default-directory-name)) - (default-regexp (eval ediff-default-filtering-regexp)) - f) - (list (setq f (read-directory-name "Directory A to compare:" dir-A nil)) - (setq f (read-directory-name "Directory B to compare:" - (if ediff-use-last-dir - ediff-last-dir-B - (ediff-strip-last-dir f)) - nil 'must-match)) - (read-directory-name "Directory C to compare:" - (if ediff-use-last-dir - ediff-last-dir-C - (ediff-strip-last-dir f)) - nil 'must-match) - (read-string - (if (stringp default-regexp) - (format "Filter through regular expression (default %s): " - default-regexp) - "Filter through regular expression: ") - nil - 'ediff-filtering-regexp-history - (eval ediff-default-filtering-regexp)) - ))) - (ediff-directories-internal - dir1 dir2 dir3 regexp 'ediff-files3 'ediff-directories3 - )) - -;;;###autoload -(defalias 'edirs3 'ediff-directories3) - -;;;###autoload -(defun ediff-merge-directories (dir1 dir2 regexp &optional merge-autostore-dir) - "Run Ediff on a pair of directories, DIR1 and DIR2, merging files that have -the same name in both. The third argument, REGEXP, is nil or a regular -expression; only file names that match the regexp are considered." - (interactive - (let ((dir-A (ediff-get-default-directory-name)) - (default-regexp (eval ediff-default-filtering-regexp)) - f) - (list (setq f (read-directory-name "Directory A to merge:" - dir-A nil 'must-match)) - (read-directory-name "Directory B to merge:" - (if ediff-use-last-dir - ediff-last-dir-B - (ediff-strip-last-dir f)) - nil 'must-match) - (read-string - (if (stringp default-regexp) - (format "Filter through regular expression (default %s): " - default-regexp) - "Filter through regular expression: ") - nil - 'ediff-filtering-regexp-history - (eval ediff-default-filtering-regexp)) - ))) - (ediff-directories-internal - dir1 dir2 nil regexp 'ediff-merge-files 'ediff-merge-directories - nil merge-autostore-dir - )) - -;;;###autoload -(defalias 'edirs-merge 'ediff-merge-directories) - -;;;###autoload -(defun ediff-merge-directories-with-ancestor (dir1 dir2 ancestor-dir regexp - &optional - merge-autostore-dir) - "Merge files in directories DIR1 and DIR2 using files in ANCESTOR-DIR as ancestors. -Ediff merges files that have identical names in DIR1, DIR2. If a pair of files -in DIR1 and DIR2 doesn't have an ancestor in ANCESTOR-DIR, Ediff will merge -without ancestor. The fourth argument, REGEXP, is nil or a regular expression; -only file names that match the regexp are considered." - (interactive - (let ((dir-A (ediff-get-default-directory-name)) - (default-regexp (eval ediff-default-filtering-regexp)) - f) - (list (setq f (read-directory-name "Directory A to merge:" dir-A nil)) - (setq f (read-directory-name "Directory B to merge:" - (if ediff-use-last-dir - ediff-last-dir-B - (ediff-strip-last-dir f)) - nil 'must-match)) - (read-directory-name "Ancestor directory:" - (if ediff-use-last-dir - ediff-last-dir-C - (ediff-strip-last-dir f)) - nil 'must-match) - (read-string - (if (stringp default-regexp) - (format "Filter through regular expression (default %s): " - default-regexp) - "Filter through regular expression: ") - nil - 'ediff-filtering-regexp-history - (eval ediff-default-filtering-regexp)) - ))) - (ediff-directories-internal - dir1 dir2 ancestor-dir regexp - 'ediff-merge-files-with-ancestor 'ediff-merge-directories-with-ancestor - nil merge-autostore-dir - )) - -;;;###autoload -(defun ediff-merge-directory-revisions (dir1 regexp - &optional merge-autostore-dir) - "Run Ediff on a directory, DIR1, merging its files with their revisions. -The second argument, REGEXP, is a regular expression that filters the file -names. Only the files that are under revision control are taken into account." - (interactive - (let ((dir-A (ediff-get-default-directory-name)) - (default-regexp (eval ediff-default-filtering-regexp)) - ) - (list (read-directory-name - "Directory to merge with revisions:" dir-A nil 'must-match) - (read-string - (if (stringp default-regexp) - (format "Filter through regular expression (default %s): " - default-regexp) - "Filter through regular expression: ") - nil - 'ediff-filtering-regexp-history - (eval ediff-default-filtering-regexp)) - ))) - (ediff-directory-revisions-internal - dir1 regexp 'ediff-merge-revisions 'ediff-merge-directory-revisions - nil merge-autostore-dir - )) - -;;;###autoload -(defalias 'edir-merge-revisions 'ediff-merge-directory-revisions) - -;;;###autoload -(defun ediff-merge-directory-revisions-with-ancestor (dir1 regexp - &optional - merge-autostore-dir) - "Run Ediff on a directory, DIR1, merging its files with their revisions and ancestors. -The second argument, REGEXP, is a regular expression that filters the file -names. Only the files that are under revision control are taken into account." - (interactive - (let ((dir-A (ediff-get-default-directory-name)) - (default-regexp (eval ediff-default-filtering-regexp)) - ) - (list (read-directory-name - "Directory to merge with revisions and ancestors:" - dir-A nil 'must-match) - (read-string - (if (stringp default-regexp) - (format "Filter through regular expression (default %s): " - default-regexp) - "Filter through regular expression: ") - nil - 'ediff-filtering-regexp-history - (eval ediff-default-filtering-regexp)) - ))) - (ediff-directory-revisions-internal - dir1 regexp 'ediff-merge-revisions-with-ancestor - 'ediff-merge-directory-revisions-with-ancestor - nil merge-autostore-dir - )) - -;;;###autoload -(defalias - 'edir-merge-revisions-with-ancestor - 'ediff-merge-directory-revisions-with-ancestor) - -;;;###autoload -(defalias 'edirs-merge-with-ancestor 'ediff-merge-directories-with-ancestor) - -;; Run ediff-action (ediff-files, ediff-merge, ediff-merge-with-ancestors) -;; on a pair of directories (three directories, in case of ancestor). -;; The third argument, REGEXP, is nil or a regular expression; -;; only file names that match the regexp are considered. -;; JOBNAME is the symbol indicating the meta-job to be performed. -;; MERGE-AUTOSTORE-DIR is the directory in which to store merged files. -(defun ediff-directories-internal (dir1 dir2 dir3 regexp action jobname - &optional startup-hooks - merge-autostore-dir) - (if (stringp dir3) - (setq dir3 (if (file-directory-p dir3) dir3 (file-name-directory dir3)))) - - (cond ((string= dir1 dir2) - (error "Directories A and B are the same: %s" dir1)) - ((and (eq jobname 'ediff-directories3) - (string= dir1 dir3)) - (error "Directories A and C are the same: %s" dir1)) - ((and (eq jobname 'ediff-directories3) - (string= dir2 dir3)) - (error "Directories B and C are the same: %s" dir1))) - - (if merge-autostore-dir - (or (stringp merge-autostore-dir) - (error "%s: Directory for storing merged files must be a string" - jobname))) - (let (;; dir-diff-struct is of the form (common-list diff-list) - ;; It is a structure where ediff-intersect-directories returns - ;; commonalities and differences among directories - dir-diff-struct - meta-buf) - (if (and ediff-autostore-merges - (ediff-merge-metajob jobname) - (not merge-autostore-dir)) - (setq merge-autostore-dir - (read-directory-name "Save merged files in directory: " - (if ediff-use-last-dir - ediff-last-merge-autostore-dir - (ediff-strip-last-dir dir1)) - nil - 'must-match))) - ;; verify we are not merging into an orig directory - (if merge-autostore-dir - (cond ((and (stringp dir1) (string= merge-autostore-dir dir1)) - (or (y-or-n-p - "Directory for saving merged files = Directory A. Sure? ") - (error "Directory merge aborted"))) - ((and (stringp dir2) (string= merge-autostore-dir dir2)) - (or (y-or-n-p - "Directory for saving merged files = Directory B. Sure? ") - (error "Directory merge aborted"))) - ((and (stringp dir3) (string= merge-autostore-dir dir3)) - (or (y-or-n-p - "Directory for saving merged files = Ancestor Directory. Sure? ") - (error "Directory merge aborted"))))) - - (setq dir-diff-struct (ediff-intersect-directories - jobname - regexp dir1 dir2 dir3 merge-autostore-dir)) - (setq startup-hooks - ;; this sets various vars in the meta buffer inside - ;; ediff-prepare-meta-buffer - (cons `(lambda () - ;; tell what to do if the user clicks on a session record - (setq ediff-session-action-function (quote ,action)) - ;; set ediff-dir-difference-list - (setq ediff-dir-difference-list - (cdr (quote ,dir-diff-struct)))) - startup-hooks)) - (setq meta-buf (ediff-prepare-meta-buffer - 'ediff-filegroup-action - (car dir-diff-struct) - "*Ediff Session Group Panel" - 'ediff-redraw-directory-group-buffer - jobname - startup-hooks)) - (ediff-show-meta-buffer meta-buf) - )) - -;; MERGE-AUTOSTORE-DIR can be given to tell ediff where to store the merged -;; files -(defun ediff-directory-revisions-internal (dir1 regexp action jobname - &optional startup-hooks - merge-autostore-dir) - (setq dir1 (if (file-directory-p dir1) dir1 (file-name-directory dir1))) - - (if merge-autostore-dir - (or (stringp merge-autostore-dir) - (error "%S: Directory for storing merged files must be a string" - jobname))) - (let (file-list meta-buf) - (if (and ediff-autostore-merges - (ediff-merge-metajob jobname) - (not merge-autostore-dir)) - (setq merge-autostore-dir - (read-directory-name "Save merged files in directory: " - (if ediff-use-last-dir - ediff-last-merge-autostore-dir - (ediff-strip-last-dir dir1)) - nil - 'must-match))) - ;; verify merge-autostore-dir != dir1 - (if (and merge-autostore-dir - (stringp dir1) - (string= merge-autostore-dir dir1)) - (or (y-or-n-p - "Directory for saving merged file = directory A. Sure? ") - (error "Merge of directory revisions aborted"))) - - (setq file-list - (ediff-get-directory-files-under-revision - jobname regexp dir1 merge-autostore-dir)) - (setq startup-hooks - ;; this sets various vars in the meta buffer inside - ;; ediff-prepare-meta-buffer - (cons `(lambda () - ;; tell what to do if the user clicks on a session record - (setq ediff-session-action-function (quote ,action))) - startup-hooks)) - (setq meta-buf (ediff-prepare-meta-buffer - 'ediff-filegroup-action - file-list - "*Ediff Session Group Panel" - 'ediff-redraw-directory-group-buffer - jobname - startup-hooks)) - (ediff-show-meta-buffer meta-buf) - )) - - -;;; Compare regions and windows - -;;;###autoload -(defun ediff-windows-wordwise (dumb-mode &optional wind-A wind-B startup-hooks) - "Compare WIND-A and WIND-B, which are selected by clicking, wordwise. -With prefix argument, DUMB-MODE, or on a non-windowing display, works as -follows: -If WIND-A is nil, use selected window. -If WIND-B is nil, use window next to WIND-A." - (interactive "P") - (ediff-windows dumb-mode wind-A wind-B - startup-hooks 'ediff-windows-wordwise 'word-mode)) - -;;;###autoload -(defun ediff-windows-linewise (dumb-mode &optional wind-A wind-B startup-hooks) - "Compare WIND-A and WIND-B, which are selected by clicking, linewise. -With prefix argument, DUMB-MODE, or on a non-windowing display, works as -follows: -If WIND-A is nil, use selected window. -If WIND-B is nil, use window next to WIND-A." - (interactive "P") - (ediff-windows dumb-mode wind-A wind-B - startup-hooks 'ediff-windows-linewise nil)) - -;; Compare WIND-A and WIND-B, which are selected by clicking. -;; With prefix argument, DUMB-MODE, or on a non-windowing display, -;; works as follows: -;; If WIND-A is nil, use selected window. -;; If WIND-B is nil, use window next to WIND-A. -(defun ediff-windows (dumb-mode wind-A wind-B startup-hooks job-name word-mode) - (if (or dumb-mode (not (ediff-window-display-p))) - (setq wind-A (ediff-get-next-window wind-A nil) - wind-B (ediff-get-next-window wind-B wind-A)) - (setq wind-A (ediff-get-window-by-clicking wind-A nil 1) - wind-B (ediff-get-window-by-clicking wind-B wind-A 2))) - - (let ((buffer-A (window-buffer wind-A)) - (buffer-B (window-buffer wind-B)) - beg-A end-A beg-B end-B) - - (save-excursion - (save-window-excursion - (sit-for 0) ; sync before using window-start/end -- a precaution - (select-window wind-A) - (setq beg-A (window-start) - end-A (window-end)) - (select-window wind-B) - (setq beg-B (window-start) - end-B (window-end)))) - (setq buffer-A - (ediff-clone-buffer-for-window-comparison - buffer-A wind-A "-Window.A-") - buffer-B - (ediff-clone-buffer-for-window-comparison - buffer-B wind-B "-Window.B-")) - (ediff-regions-internal - buffer-A beg-A end-A buffer-B beg-B end-B - startup-hooks job-name word-mode nil))) - - -;;;###autoload -(defun ediff-regions-wordwise (buffer-A buffer-B &optional startup-hooks) - "Run Ediff on a pair of regions in specified buffers. -Regions \(i.e., point and mark\) can be set in advance or marked interactively. -This function is effective only for relatively small regions, up to 200 -lines. For large regions, use `ediff-regions-linewise'." - (interactive - (let (bf) - (list (setq bf (read-buffer "Region's A buffer: " - (ediff-other-buffer "") t)) - (read-buffer "Region's B buffer: " - (progn - ;; realign buffers so that two visible bufs will be - ;; at the top - (save-window-excursion (other-window 1)) - (ediff-other-buffer bf)) - t)))) - (if (not (ediff-buffer-live-p buffer-A)) - (error "Buffer %S doesn't exist" buffer-A)) - (if (not (ediff-buffer-live-p buffer-B)) - (error "Buffer %S doesn't exist" buffer-B)) - - - (let ((buffer-A - (ediff-clone-buffer-for-region-comparison buffer-A "-Region.A-")) - (buffer-B - (ediff-clone-buffer-for-region-comparison buffer-B "-Region.B-")) - reg-A-beg reg-A-end reg-B-beg reg-B-end) - (with-current-buffer buffer-A - (setq reg-A-beg (region-beginning) - reg-A-end (region-end)) - (set-buffer buffer-B) - (setq reg-B-beg (region-beginning) - reg-B-end (region-end))) - - (ediff-regions-internal - (get-buffer buffer-A) reg-A-beg reg-A-end - (get-buffer buffer-B) reg-B-beg reg-B-end - startup-hooks 'ediff-regions-wordwise 'word-mode nil))) - -;;;###autoload -(defun ediff-regions-linewise (buffer-A buffer-B &optional startup-hooks) - "Run Ediff on a pair of regions in specified buffers. -Regions \(i.e., point and mark\) can be set in advance or marked interactively. -Each region is enlarged to contain full lines. -This function is effective for large regions, over 100-200 -lines. For small regions, use `ediff-regions-wordwise'." - (interactive - (let (bf) - (list (setq bf (read-buffer "Region A's buffer: " - (ediff-other-buffer "") t)) - (read-buffer "Region B's buffer: " - (progn - ;; realign buffers so that two visible bufs will be - ;; at the top - (save-window-excursion (other-window 1)) - (ediff-other-buffer bf)) - t)))) - (if (not (ediff-buffer-live-p buffer-A)) - (error "Buffer %S doesn't exist" buffer-A)) - (if (not (ediff-buffer-live-p buffer-B)) - (error "Buffer %S doesn't exist" buffer-B)) - - (let ((buffer-A - (ediff-clone-buffer-for-region-comparison buffer-A "-Region.A-")) - (buffer-B - (ediff-clone-buffer-for-region-comparison buffer-B "-Region.B-")) - reg-A-beg reg-A-end reg-B-beg reg-B-end) - (with-current-buffer buffer-A - (setq reg-A-beg (region-beginning) - reg-A-end (region-end)) - ;; enlarge the region to hold full lines - (goto-char reg-A-beg) - (beginning-of-line) - (setq reg-A-beg (point)) - (goto-char reg-A-end) - (end-of-line) - (or (eobp) (forward-char)) ; include the newline char - (setq reg-A-end (point)) - - (set-buffer buffer-B) - (setq reg-B-beg (region-beginning) - reg-B-end (region-end)) - ;; enlarge the region to hold full lines - (goto-char reg-B-beg) - (beginning-of-line) - (setq reg-B-beg (point)) - (goto-char reg-B-end) - (end-of-line) - (or (eobp) (forward-char)) ; include the newline char - (setq reg-B-end (point)) - ) ; save excursion - - (ediff-regions-internal - (get-buffer buffer-A) reg-A-beg reg-A-end - (get-buffer buffer-B) reg-B-beg reg-B-end - startup-hooks 'ediff-regions-linewise nil nil))) ; no word mode - -;; compare region beg-A to end-A of buffer-A -;; to regions beg-B -- end-B in buffer-B. -(defun ediff-regions-internal (buffer-A beg-A end-A buffer-B beg-B end-B - startup-hooks job-name word-mode - setup-parameters) - (let ((tmp-buffer (get-buffer-create ediff-tmp-buffer)) - overl-A overl-B - file-A file-B) - (unwind-protect - (progn - ;; in case beg/end-A/B aren't markers--make them into markers - (ediff-with-current-buffer buffer-A - (setq beg-A (move-marker (make-marker) beg-A) - end-A (move-marker (make-marker) end-A))) - (ediff-with-current-buffer buffer-B - (setq beg-B (move-marker (make-marker) beg-B) - end-B (move-marker (make-marker) end-B))) - - ;; make file-A - (if word-mode - (ediff-wordify beg-A end-A buffer-A tmp-buffer) - (ediff-copy-to-buffer beg-A end-A buffer-A tmp-buffer)) - (setq file-A (ediff-make-temp-file tmp-buffer "regA")) - - ;; make file-B - (if word-mode - (ediff-wordify beg-B end-B buffer-B tmp-buffer) - (ediff-copy-to-buffer beg-B end-B buffer-B tmp-buffer)) - (setq file-B (ediff-make-temp-file tmp-buffer "regB")) - - (setq overl-A (ediff-make-bullet-proof-overlay beg-A end-A buffer-A)) - (setq overl-B (ediff-make-bullet-proof-overlay beg-B end-B buffer-B)) - (ediff-setup buffer-A file-A - buffer-B file-B - nil nil ; buffer & file C - (cons `(lambda () - (delete-file ,file-A) - (delete-file ,file-B)) - startup-hooks) - (append - (list (cons 'ediff-word-mode word-mode) - (cons 'ediff-narrow-bounds (list overl-A overl-B)) - (cons 'ediff-job-name job-name)) - setup-parameters))) - (if (and (stringp file-A) (file-exists-p file-A)) - (delete-file file-A)) - (if (and (stringp file-B) (file-exists-p file-B)) - (delete-file file-B))) - )) - - -;;; Merge files and buffers - -;;;###autoload -(defalias 'ediff-merge 'ediff-merge-files) - -(defsubst ediff-merge-on-startup () - (ediff-do-merge 0) - ;; Can't remember why this is here, but it may cause the automatically merged - ;; buffer to be lost. So, keep the buffer modified. - ;;(ediff-with-current-buffer ediff-buffer-C - ;; (set-buffer-modified-p nil)) - ) - -;;;###autoload -(defun ediff-merge-files (file-A file-B - ;; MERGE-BUFFER-FILE is the file to be - ;; associated with the merge buffer - &optional startup-hooks merge-buffer-file) - "Merge two files without ancestor." - (interactive - (let ((dir-A (if ediff-use-last-dir - ediff-last-dir-A - default-directory)) - dir-B f) - (list (setq f (ediff-read-file-name - "File A to merge" - dir-A - (ediff-get-default-file-name) - 'no-dirs)) - (ediff-read-file-name "File B to merge" - (setq dir-B - (if ediff-use-last-dir - ediff-last-dir-B - (file-name-directory f))) - (progn - (ediff-add-to-history - 'file-name-history - (ediff-abbreviate-file-name - (expand-file-name - (file-name-nondirectory f) - dir-B))) - (ediff-get-default-file-name f 1))) - ))) - (setq startup-hooks (cons 'ediff-merge-on-startup startup-hooks)) - (ediff-files-internal file-A - (if (file-directory-p file-B) - (expand-file-name - (file-name-nondirectory file-A) file-B) - file-B) - nil ; file-C - startup-hooks - 'ediff-merge-files - merge-buffer-file)) - -;;;###autoload -(defun ediff-merge-files-with-ancestor (file-A file-B file-ancestor - &optional - startup-hooks - ;; MERGE-BUFFER-FILE is the file - ;; to be associated with the - ;; merge buffer - merge-buffer-file) - "Merge two files with ancestor." - (interactive - (let ((dir-A (if ediff-use-last-dir - ediff-last-dir-A - default-directory)) - dir-B dir-ancestor f ff) - (list (setq f (ediff-read-file-name - "File A to merge" - dir-A - (ediff-get-default-file-name) - 'no-dirs)) - (setq ff (ediff-read-file-name "File B to merge" - (setq dir-B - (if ediff-use-last-dir - ediff-last-dir-B - (file-name-directory f))) - (progn - (ediff-add-to-history - 'file-name-history - (ediff-abbreviate-file-name - (expand-file-name - (file-name-nondirectory f) - dir-B))) - (ediff-get-default-file-name f 1)))) - (ediff-read-file-name "Ancestor file" - (setq dir-ancestor - (if ediff-use-last-dir - ediff-last-dir-ancestor - (file-name-directory ff))) - (progn - (ediff-add-to-history - 'file-name-history - (ediff-abbreviate-file-name - (expand-file-name - (file-name-nondirectory ff) - dir-ancestor))) - (ediff-get-default-file-name ff 2))) - ))) - (setq startup-hooks (cons 'ediff-merge-on-startup startup-hooks)) - (ediff-files-internal file-A - (if (file-directory-p file-B) - (expand-file-name - (file-name-nondirectory file-A) file-B) - file-B) - file-ancestor - startup-hooks - 'ediff-merge-files-with-ancestor - merge-buffer-file)) - -;;;###autoload -(defalias 'ediff-merge-with-ancestor 'ediff-merge-files-with-ancestor) - -;;;###autoload -(defun ediff-merge-buffers (buffer-A buffer-B - &optional - ;; MERGE-BUFFER-FILE is the file to be - ;; associated with the merge buffer - startup-hooks job-name merge-buffer-file) - "Merge buffers without ancestor." - (interactive - (let (bf) - (list (setq bf (read-buffer "Buffer A to merge: " - (ediff-other-buffer "") t)) - (read-buffer "Buffer B to merge: " - (progn - ;; realign buffers so that two visible bufs will be - ;; at the top - (save-window-excursion (other-window 1)) - (ediff-other-buffer bf)) - t)))) - - (setq startup-hooks (cons 'ediff-merge-on-startup startup-hooks)) - (or job-name (setq job-name 'ediff-merge-buffers)) - (ediff-buffers-internal - buffer-A buffer-B nil startup-hooks job-name merge-buffer-file)) - -;;;###autoload -(defun ediff-merge-buffers-with-ancestor (buffer-A buffer-B buffer-ancestor - &optional - startup-hooks - job-name - ;; MERGE-BUFFER-FILE is the - ;; file to be associated - ;; with the merge buffer - merge-buffer-file) - "Merge buffers with ancestor." - (interactive - (let (bf bff) - (list (setq bf (read-buffer "Buffer A to merge: " - (ediff-other-buffer "") t)) - (setq bff (read-buffer "Buffer B to merge: " - (progn - ;; realign buffers so that two visible - ;; bufs will be at the top - (save-window-excursion (other-window 1)) - (ediff-other-buffer bf)) - t)) - (read-buffer "Ancestor buffer: " - (progn - ;; realign buffers so that three visible - ;; bufs will be at the top - (save-window-excursion (other-window 1)) - (ediff-other-buffer (list bf bff))) - t) - ))) - - (setq startup-hooks (cons 'ediff-merge-on-startup startup-hooks)) - (or job-name (setq job-name 'ediff-merge-buffers-with-ancestor)) - (ediff-buffers-internal - buffer-A buffer-B buffer-ancestor startup-hooks job-name merge-buffer-file)) - - -;;;###autoload -(defun ediff-merge-revisions (&optional file startup-hooks merge-buffer-file) - ;; MERGE-BUFFER-FILE is the file to be associated with the merge buffer - "Run Ediff by merging two revisions of a file. -The file is the optional FILE argument or the file visited by the current -buffer." - (interactive) - (if (stringp file) (find-file file)) - (let (rev1 rev2) - (setq rev1 - (read-string - (format - "Version 1 to merge (default %s's working version): " - (if (stringp file) - (file-name-nondirectory file) "current buffer"))) - rev2 - (read-string - (format - "Version 2 to merge (default %s): " - (if (stringp file) - (file-name-nondirectory file) "current buffer")))) - (ediff-load-version-control) - ;; ancestor-revision=nil - (funcall - (intern (format "ediff-%S-merge-internal" ediff-version-control-package)) - rev1 rev2 nil startup-hooks merge-buffer-file))) - - -;;;###autoload -(defun ediff-merge-revisions-with-ancestor (&optional - file startup-hooks - ;; MERGE-BUFFER-FILE is the file to - ;; be associated with the merge - ;; buffer - merge-buffer-file) - "Run Ediff by merging two revisions of a file with a common ancestor. -The file is the optional FILE argument or the file visited by the current -buffer." - (interactive) - (if (stringp file) (find-file file)) - (let (rev1 rev2 ancestor-rev) - (setq rev1 - (read-string - (format - "Version 1 to merge (default %s's working version): " - (if (stringp file) - (file-name-nondirectory file) "current buffer"))) - rev2 - (read-string - (format - "Version 2 to merge (default %s): " - (if (stringp file) - (file-name-nondirectory file) "current buffer"))) - ancestor-rev - (read-string - (format - "Ancestor version (default %s's base revision): " - (if (stringp file) - (file-name-nondirectory file) "current buffer")))) - (ediff-load-version-control) - (funcall - (intern (format "ediff-%S-merge-internal" ediff-version-control-package)) - rev1 rev2 ancestor-rev startup-hooks merge-buffer-file))) - -;;; Apply patch - -;;;###autoload -(defun ediff-patch-file (&optional arg patch-buf) - "Run Ediff by patching SOURCE-FILENAME. -If optional PATCH-BUF is given, use the patch in that buffer -and don't ask the user. -If prefix argument, then: if even argument, assume that the patch is in a -buffer. If odd -- assume it is in a file." - (interactive "P") - (let (source-dir source-file) - (require 'ediff-ptch) - (setq patch-buf - (ediff-get-patch-buffer - (if arg (prefix-numeric-value arg)) patch-buf)) - (setq source-dir (cond (ediff-use-last-dir ediff-last-dir-patch) - ((and (not ediff-patch-default-directory) - (buffer-file-name patch-buf)) - (file-name-directory - (expand-file-name - (buffer-file-name patch-buf)))) - (t default-directory))) - (setq source-file - (read-file-name - "File to patch (directory, if multifile patch): " - ;; use an explicit initial file - source-dir nil nil (ediff-get-default-file-name))) - (ediff-dispatch-file-patching-job patch-buf source-file))) - -;;;###autoload -(defun ediff-patch-buffer (&optional arg patch-buf) - "Run Ediff by patching the buffer specified at prompt. -Without the optional prefix ARG, asks if the patch is in some buffer and -prompts for the buffer or a file, depending on the answer. -With ARG=1, assumes the patch is in a file and prompts for the file. -With ARG=2, assumes the patch is in a buffer and prompts for the buffer. -PATCH-BUF is an optional argument, which specifies the buffer that contains the -patch. If not given, the user is prompted according to the prefix argument." - (interactive "P") - (require 'ediff-ptch) - (setq patch-buf - (ediff-get-patch-buffer - (if arg (prefix-numeric-value arg)) patch-buf)) - (ediff-patch-buffer-internal - patch-buf - (read-buffer - "Which buffer to patch? " - (ediff-other-buffer patch-buf)))) - - -;;;###autoload -(defalias 'epatch 'ediff-patch-file) -;;;###autoload -(defalias 'epatch-buffer 'ediff-patch-buffer) - - - - -;;; Versions Control functions - -;;;###autoload -(defun ediff-revision (&optional file startup-hooks) - "Run Ediff by comparing versions of a file. -The file is an optional FILE argument or the file entered at the prompt. -Default: the file visited by the current buffer. -Uses `vc.el' or `rcs.el' depending on `ediff-version-control-package'." - ;; if buffer is non-nil, use that buffer instead of the current buffer - (interactive "P") - (if (not (stringp file)) - (setq file - (ediff-read-file-name "Compare revisions for file" - (if ediff-use-last-dir - ediff-last-dir-A - default-directory) - (ediff-get-default-file-name) - 'no-dirs))) - (find-file file) - (if (and (buffer-modified-p) - (y-or-n-p (format "Buffer %s is modified. Save buffer? " - (buffer-name)))) - (save-buffer (current-buffer))) - (let (rev1 rev2) - (setq rev1 - (read-string - (format "Revision 1 to compare (default %s's latest revision): " - (file-name-nondirectory file))) - rev2 - (read-string - (format "Revision 2 to compare (default %s's current state): " - (file-name-nondirectory file)))) - (ediff-load-version-control) - (funcall - (intern (format "ediff-%S-internal" ediff-version-control-package)) - rev1 rev2 startup-hooks) - )) - - -;;;###autoload -(defalias 'erevision 'ediff-revision) - - -;; Test if version control package is loaded and load if not -;; Is SILENT is non-nil, don't report error if package is not found. -(defun ediff-load-version-control (&optional silent) - (require 'ediff-vers) - (or (featurep ediff-version-control-package) - (if (locate-library (symbol-name ediff-version-control-package)) - (progn - (message "") ; kill the message from `locate-library' - (require ediff-version-control-package)) - (or silent - (error "Version control package %S.el not found. Use vc.el instead" - ediff-version-control-package))))) - - -;;;###autoload -(defun ediff-version () - "Return string describing the version of Ediff. -When called interactively, displays the version." - (interactive) - ;; called-interactively-p - not in XEmacs - ;; (if (called-interactively-p 'interactive) - (if (interactive-p) - (message "%s" (ediff-version)) - (format "Ediff %s of %s" ediff-version ediff-date))) - -;; info is run first, and will autoload info.el. -(declare-function Info-goto-node "info" (nodename &optional fork)) - -;;;###autoload -(defun ediff-documentation (&optional node) - "Display Ediff's manual. -With optional NODE, goes to that node." - (interactive) - (let ((ctl-window ediff-control-window) - (ctl-buf ediff-control-buffer)) - - (ediff-skip-unsuitable-frames) - (condition-case nil - (progn - (pop-to-buffer (get-buffer-create "*info*")) - (info (if (featurep 'xemacs) "ediff.info" "ediff")) - (if node - (Info-goto-node node) - (message "Type `i' to search for a specific topic")) - (raise-frame (selected-frame))) - (error (beep 1) - (with-output-to-temp-buffer ediff-msg-buffer - (ediff-with-current-buffer standard-output - (fundamental-mode)) - (princ ediff-BAD-INFO)) - (if (window-live-p ctl-window) - (progn - (select-window ctl-window) - (set-window-buffer ctl-window ctl-buf))))))) - - -(dolist (mess '("^Errors in diff output. Diff output is in " - "^Hmm... I don't see an Ediff command around here...$" - "^Undocumented command! Type `G' in Ediff Control Panel to drop a note to the Ediff maintainer$" - ": This command runs in Ediff Control Buffer only!$" - ": Invalid op in ediff-check-version$" - "^ediff-shrink-window-C can be used only for merging jobs$" - "^Lost difference info on these directories$" - "^This command is inapplicable in the present context$" - "^This session group has no parent$" - "^Can't hide active session, $" - "^Ediff: something wrong--no multiple diffs buffer$" - "^Can't make context diff for Session $" - "^The patch buffer wasn't found$" - "^Aborted$" - "^This Ediff session is not part of a session group$" - "^No active Ediff sessions or corrupted session registry$" - "^No session info in this line$" - "^`.*' is not an ordinary file$" - "^Patch appears to have failed$" - "^Recomputation of differences cancelled$" - "^No fine differences in this mode$" - "^Lost connection to ancestor buffer...sorry$" - "^Not merging with ancestor$" - "^Don't know how to toggle read-only in buffer " - "Emacs is not running as a window application$" - "^This command makes sense only when merging with an ancestor$" - "^At end of the difference list$" - "^At beginning of the difference list$" - "^Nothing saved for diff .* in buffer " - "^Buffer is out of sync for file " - "^Buffer out of sync for file " - "^Output from `diff' not found$" - "^You forgot to specify a region in buffer " - "^All right. Make up your mind and come back...$" - "^Current buffer is not visiting any file$" - "^Failed to retrieve revision: $" - "^Can't determine display width.$" - "^File `.*' does not exist or is not readable$" - "^File `.*' is a directory$" - "^Buffer .* doesn't exist$" - "^Directories . and . are the same: " - "^Directory merge aborted$" - "^Merge of directory revisions aborted$" - "^Buffer .* doesn't exist$" - "^There is no file to merge$" - "^Version control package .*.el not found. Use vc.el instead$")) - (add-to-list 'debug-ignored-errors mess)) - - -(require 'ediff-util) - -(run-hooks 'ediff-load-hook) - - -;; 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: - -;; arch-tag: 97c71396-db02-4f41-8b48-6a51c3348fcc -;;; ediff.el ends here diff -r 681cd08dc0f7 -r e93288477c43 lisp/emerge.el --- a/lisp/emerge.el Thu Jun 10 22:43:47 2010 +0000 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,3209 +0,0 @@ -;;; emerge.el --- merge diffs under Emacs control - -;;; The author has placed this file in the public domain. - -;; This file is part of GNU Emacs. - -;; Author: Dale R. Worley -;; Keywords: unix, tools - -;; This software was created by Dale R. Worley and is -;; distributed free of charge. It is placed in the public domain and -;; permission is granted to anyone to use, duplicate, modify and redistribute -;; it provided that this notice is attached. - -;; Dale R. Worley provides absolutely NO WARRANTY OF ANY KIND -;; with respect to this software. The entire risk as to the quality and -;; performance of this software is with the user. IN NO EVENT WILL DALE -;; R. WORLEY BE LIABLE TO ANYONE FOR ANY DAMAGES ARISING OUT THE -;; USE OF THIS SOFTWARE, INCLUDING, WITHOUT LIMITATION, DAMAGES RESULTING FROM -;; LOST DATA OR LOST PROFITS, OR FOR ANY SPECIAL, INCIDENTAL OR CONSEQUENTIAL -;; DAMAGES. - -;;; Commentary: - -;;; Code: - -;; There aren't really global variables, just dynamic bindings -(defvar A-begin) -(defvar A-end) -(defvar B-begin) -(defvar B-end) -(defvar diff) -(defvar diff-vector) -(defvar merge-begin) -(defvar merge-end) -(defvar template) -(defvar valid-diff) - -;;; Macros - -(defmacro emerge-eval-in-buffer (buffer &rest forms) - "Macro to switch to BUFFER, evaluate FORMS, returns to original buffer. -Differs from `save-excursion' in that it doesn't save the point and mark." - `(let ((StartBuffer (current-buffer))) - (unwind-protect - (progn - (set-buffer ,buffer) - ,@forms) - (set-buffer StartBuffer)))) - -(defmacro emerge-defvar-local (var value doc) - "Defines SYMBOL as an advertised variable. -Performs a defvar, then executes `make-variable-buffer-local' on -the variable. Also sets the `preserved' property, so that -`kill-all-local-variables' (called by major-mode setting commands) -won't destroy Emerge control variables." - `(progn - (defvar ,var ,value ,doc) - (make-variable-buffer-local ',var) - (put ',var 'preserved t))) - -;; Add entries to minor-mode-alist so that emerge modes show correctly -(defvar emerge-minor-modes-list - '((emerge-mode " Emerge") - (emerge-fast-mode " F") - (emerge-edit-mode " E") - (emerge-auto-advance " A") - (emerge-skip-prefers " S"))) -(if (not (assq 'emerge-mode minor-mode-alist)) - (setq minor-mode-alist (append emerge-minor-modes-list - minor-mode-alist))) - -;; We need to define this function so describe-mode can describe Emerge mode. -(defun emerge-mode () - "Emerge mode is used by the Emerge file-merging package. -It is entered only through one of the functions: - `emerge-files' - `emerge-files-with-ancestor' - `emerge-buffers' - `emerge-buffers-with-ancestor' - `emerge-files-command' - `emerge-files-with-ancestor-command' - `emerge-files-remote' - `emerge-files-with-ancestor-remote' - -Commands: -\\{emerge-basic-keymap} -Commands must be prefixed by \\\\[emerge-basic-keymap] in `edit' mode, -but can be invoked directly in `fast' mode.") - -(define-obsolete-variable-alias 'emerge-version 'emacs-version "23.2") - -(defun emerge-version () - "Return string describing the version of Emerge. -When called interactively, displays the version." - (interactive) - (if (called-interactively-p 'interactive) - (message "Emerge version %s" emacs-version) - emacs-version)) - -(make-obsolete 'emerge-version 'emacs-version "23.2") - -;;; Emerge configuration variables - -(defgroup emerge nil - "Merge diffs under Emacs control." - :group 'tools) - -;; Commands that produce difference files -;; All that can be configured is the name of the programs to execute -;; (emerge-diff-program and emerge-diff3-program) and the options -;; to be provided (emerge-diff-options). The order in which the file names -;; are given is fixed. -;; The file names are always expanded (see expand-file-name) before being -;; passed to diff, thus they need not be invoked under a shell that -;; understands `~'. -;; The code which processes the diff/diff3 output depends on all the -;; finicky details of their output, including the somewhat strange -;; way they number lines of a file. -(defcustom emerge-diff-program "diff" - "Name of the program which compares two files." - :type 'string - :group 'emerge) -(defcustom emerge-diff3-program "diff3" - "Name of the program which compares three files. -Its arguments are the ancestor file and the two variant files." - :type 'string - :group 'emerge) -(defcustom emerge-diff-options "" - "Options to pass to `emerge-diff-program' and `emerge-diff3-program'." - :type 'string - :group 'emerge) -(defcustom emerge-match-diff-line - (let ((x "\\([0-9]+\\)\\(\\|,\\([0-9]+\\)\\)")) - (concat "^" x "\\([acd]\\)" x "$")) - "Pattern to match lines produced by diff that describe differences. -This is as opposed to lines from the source files." - :type 'regexp - :group 'emerge) -(defcustom emerge-diff-ok-lines-regexp - "^\\([0-9,]+[acd][0-9,]+$\\|[<>] \\|---\\)" - "Regexp that matches normal output lines from `emerge-diff-program'. -Lines that do not match are assumed to be error messages." - :type 'regexp - :group 'emerge) -(defcustom emerge-diff3-ok-lines-regexp - "^\\([1-3]:\\|====\\| \\)" - "Regexp that matches normal output lines from `emerge-diff3-program'. -Lines that do not match are assumed to be error messages." - :type 'regexp - :group 'emerge) - -(defcustom emerge-rcs-ci-program "ci" - "Name of the program that checks in RCS revisions." - :type 'string - :group 'emerge) -(defcustom emerge-rcs-co-program "co" - "Name of the program that checks out RCS revisions." - :type 'string - :group 'emerge) - -(defcustom emerge-process-local-variables nil - "Non-nil if Emerge should process local-variables lists in merge buffers. -\(You can explicitly request processing the local-variables -by executing `(hack-local-variables)'.)" - :type 'boolean - :group 'emerge) -(defcustom emerge-execute-line-deletions nil - "If non-nil: `emerge-execute-line' makes no output if an input was deleted. -It concludes that an input version has been deleted when an ancestor entry -is present, only one A or B entry is present, and an output entry is present. -If nil: In such circumstances, the A or B file that is present will be -copied to the designated output file." - :type 'boolean - :group 'emerge) - -(defcustom emerge-before-flag "vvvvvvvvvvvvvvvvvvvv\n" - "Flag placed above the highlighted block of code. Must end with newline. -Must be set before Emerge is loaded, or emerge-new-flags must be run -after setting." - :type 'string - :group 'emerge) -(defcustom emerge-after-flag "^^^^^^^^^^^^^^^^^^^^\n" - "Flag placed below the highlighted block of code. Must end with newline. -Must be set before Emerge is loaded, or emerge-new-flags must be run -after setting." - :type 'string - :group 'emerge) - -;; Hook variables - -(defcustom emerge-startup-hook nil - "Hook to run in the merge buffer after the merge has been set up." - :type 'hook - :group 'emerge) -(defcustom emerge-select-hook nil - "Hook to run after a difference has been selected. -The variable `n' holds the (internal) number of the difference." - :type 'hook - :group 'emerge) -(defcustom emerge-unselect-hook nil - "Hook to run after a difference has been unselected. -The variable `n' holds the (internal) number of the difference." - :type 'hook - :group 'emerge) - -;; Variables to control the default directories of the arguments to -;; Emerge commands. - -(defcustom emerge-default-last-directories nil - "If nil, default dir for filenames in emerge is `default-directory'. -If non-nil, filenames complete in the directory of the last argument of the -same type to an `emerge-files...' command." - :type 'boolean - :group 'emerge) - -(defvar emerge-last-dir-A nil - "Last directory for the first file of an `emerge-files...' command.") -(defvar emerge-last-dir-B nil - "Last directory for the second file of an `emerge-files...' command.") -(defvar emerge-last-dir-ancestor nil - "Last directory for the ancestor file of an `emerge-files...' command.") -(defvar emerge-last-dir-output nil - "Last directory for the output file of an `emerge-files...' command.") -(defvar emerge-last-revision-A nil - "Last RCS revision used for first file of an `emerge-revisions...' command.") -(defvar emerge-last-revision-B nil - "Last RCS revision used for second file of an `emerge-revisions...' command.") -(defvar emerge-last-revision-ancestor nil - "Last RCS revision used for ancestor file of an `emerge-revisions...' command.") - -(defvar emerge-before-flag-length) -(defvar emerge-before-flag-lines) -(defvar emerge-before-flag-match) -(defvar emerge-after-flag-length) -(defvar emerge-after-flag-lines) -(defvar emerge-after-flag-match) -(defvar emerge-diff-buffer) -(defvar emerge-diff-error-buffer) -(defvar emerge-prefix-argument) -(defvar emerge-file-out) -(defvar emerge-exit-func) -(defvar emerge-globalized-difference-list) -(defvar emerge-globalized-number-of-differences) - -;; The flags used to mark differences in the buffers. - -;; These function definitions need to be up here, because they are used -;; during loading. -(defun emerge-new-flags () - "Function to be called after `emerge-{before,after}-flag'. -This is called after these functions are changed to compute values that -depend on the flags." - (setq emerge-before-flag-length (length emerge-before-flag)) - (setq emerge-before-flag-lines - (emerge-count-matches-string emerge-before-flag "\n")) - (setq emerge-before-flag-match (regexp-quote emerge-before-flag)) - (setq emerge-after-flag-length (length emerge-after-flag)) - (setq emerge-after-flag-lines - (emerge-count-matches-string emerge-after-flag "\n")) - (setq emerge-after-flag-match (regexp-quote emerge-after-flag))) - -(defun emerge-count-matches-string (string regexp) - "Return the number of matches in STRING for REGEXP." - (let ((i 0) - (count 0)) - (while (string-match regexp string i) - (setq count (1+ count)) - (setq i (match-end 0))) - count)) - -;; Calculate dependent variables -(emerge-new-flags) - -(defcustom emerge-min-visible-lines 3 - "Number of lines that we want to show above and below the flags when we are -displaying a difference." - :type 'integer - :group 'emerge) - -(defcustom emerge-temp-file-prefix - (expand-file-name "emerge" temporary-file-directory) - "Prefix to put on Emerge temporary file names. -Do not start with `~/' or `~USERNAME/'." - :type 'string - :group 'emerge) - -(defcustom emerge-temp-file-mode 384 ; u=rw only - "Mode for Emerge temporary files." - :type 'integer - :group 'emerge) - -(defcustom emerge-combine-versions-template - "#ifdef NEW\n%b#else /* not NEW */\n%a#endif /* not NEW */\n" - "Template for `emerge-combine-versions' to combine the two versions. -The template is inserted as a string, with the following interpolations: - %a the A version of the difference - %b the B version of the difference - %% the character `%' -Don't forget to end the template with a newline. -Note that this variable can be made local to a particular merge buffer by -giving a prefix argument to `emerge-set-combine-versions-template'." - :type 'string - :group 'emerge) - -;; Build keymaps - -(defvar emerge-basic-keymap nil - "Keymap of Emerge commands. -Directly available in `fast' mode; -must be prefixed by \\\\[emerge-basic-keymap] in `edit' mode.") - -(defvar emerge-fast-keymap nil - "Local keymap used in Emerge `fast' mode. -Makes Emerge commands directly available.") - -(defvar emerge-options-menu - (make-sparse-keymap "Options")) - -(defvar emerge-merge-menu - (make-sparse-keymap "Merge")) - -(defvar emerge-move-menu - (make-sparse-keymap "Move")) - -(defcustom emerge-command-prefix "\C-c\C-c" - "Command prefix for Emerge commands in `edit' mode. -Must be set before Emerge is loaded." - :type 'string - :group 'emerge) - -;; This function sets up the fixed keymaps. It is executed when the first -;; Emerge is done to allow the user maximum time to set up the global keymap. -(defun emerge-setup-fixed-keymaps () - ;; Set up the basic keymap - (setq emerge-basic-keymap (make-keymap)) - (suppress-keymap emerge-basic-keymap) ; this sets 0..9 to digit-argument and - ; - to negative-argument - (define-key emerge-basic-keymap "p" 'emerge-previous-difference) - (define-key emerge-basic-keymap "n" 'emerge-next-difference) - (define-key emerge-basic-keymap "a" 'emerge-select-A) - (define-key emerge-basic-keymap "b" 'emerge-select-B) - (define-key emerge-basic-keymap "j" 'emerge-jump-to-difference) - (define-key emerge-basic-keymap "." 'emerge-find-difference) - (define-key emerge-basic-keymap "q" 'emerge-quit) - (define-key emerge-basic-keymap "\C-]" 'emerge-abort) - (define-key emerge-basic-keymap "f" 'emerge-fast-mode) - (define-key emerge-basic-keymap "e" 'emerge-edit-mode) - (define-key emerge-basic-keymap "s" nil) - (define-key emerge-basic-keymap "sa" 'emerge-auto-advance) - (define-key emerge-basic-keymap "ss" 'emerge-skip-prefers) - (define-key emerge-basic-keymap "l" 'emerge-recenter) - (define-key emerge-basic-keymap "d" nil) - (define-key emerge-basic-keymap "da" 'emerge-default-A) - (define-key emerge-basic-keymap "db" 'emerge-default-B) - (define-key emerge-basic-keymap "c" nil) - (define-key emerge-basic-keymap "ca" 'emerge-copy-as-kill-A) - (define-key emerge-basic-keymap "cb" 'emerge-copy-as-kill-B) - (define-key emerge-basic-keymap "i" nil) - (define-key emerge-basic-keymap "ia" 'emerge-insert-A) - (define-key emerge-basic-keymap "ib" 'emerge-insert-B) - (define-key emerge-basic-keymap "m" 'emerge-mark-difference) - (define-key emerge-basic-keymap "v" 'emerge-scroll-up) - (define-key emerge-basic-keymap "^" 'emerge-scroll-down) - (define-key emerge-basic-keymap "<" 'emerge-scroll-left) - (define-key emerge-basic-keymap ">" 'emerge-scroll-right) - (define-key emerge-basic-keymap "|" 'emerge-scroll-reset) - (define-key emerge-basic-keymap "x" nil) - (define-key emerge-basic-keymap "x1" 'emerge-one-line-window) - (define-key emerge-basic-keymap "xc" 'emerge-combine-versions) - (define-key emerge-basic-keymap "xC" 'emerge-combine-versions-register) - (define-key emerge-basic-keymap "xf" 'emerge-file-names) - (define-key emerge-basic-keymap "xj" 'emerge-join-differences) - (define-key emerge-basic-keymap "xl" 'emerge-line-numbers) - (define-key emerge-basic-keymap "xm" 'emerge-set-merge-mode) - (define-key emerge-basic-keymap "xs" 'emerge-split-difference) - (define-key emerge-basic-keymap "xt" 'emerge-trim-difference) - (define-key emerge-basic-keymap "xx" 'emerge-set-combine-versions-template) - ;; Allow emerge-basic-keymap to be referenced indirectly - (fset 'emerge-basic-keymap emerge-basic-keymap) - ;; Set up the fast mode keymap - (setq emerge-fast-keymap (copy-keymap emerge-basic-keymap)) - ;; Allow prefixed commands to work in fast mode - (define-key emerge-fast-keymap emerge-command-prefix 'emerge-basic-keymap) - ;; Allow emerge-fast-keymap to be referenced indirectly - (fset 'emerge-fast-keymap emerge-fast-keymap) - ;; Suppress write-file and save-buffer - (define-key emerge-fast-keymap [remap write-file] 'emerge-query-write-file) - (define-key emerge-fast-keymap [remap save-buffer] 'emerge-query-save-buffer) - - (define-key emerge-basic-keymap [menu-bar] (make-sparse-keymap)) - - (define-key emerge-fast-keymap [menu-bar emerge-options] - (cons "Merge-Options" emerge-options-menu)) - (define-key emerge-fast-keymap [menu-bar merge] - (cons "Merge" emerge-merge-menu)) - (define-key emerge-fast-keymap [menu-bar move] - (cons "Move" emerge-move-menu)) - - (define-key emerge-move-menu [emerge-scroll-reset] - '("Scroll Reset" . emerge-scroll-reset)) - (define-key emerge-move-menu [emerge-scroll-right] - '("Scroll Right" . emerge-scroll-right)) - (define-key emerge-move-menu [emerge-scroll-left] - '("Scroll Left" . emerge-scroll-left)) - (define-key emerge-move-menu [emerge-scroll-down] - '("Scroll Down" . emerge-scroll-down)) - (define-key emerge-move-menu [emerge-scroll-up] - '("Scroll Up" . emerge-scroll-up)) - (define-key emerge-move-menu [emerge-recenter] - '("Recenter" . emerge-recenter)) - (define-key emerge-move-menu [emerge-mark-difference] - '("Mark Difference" . emerge-mark-difference)) - (define-key emerge-move-menu [emerge-jump-to-difference] - '("Jump To Difference" . emerge-jump-to-difference)) - (define-key emerge-move-menu [emerge-find-difference] - '("Find Difference" . emerge-find-difference)) - (define-key emerge-move-menu [emerge-previous-difference] - '("Previous Difference" . emerge-previous-difference)) - (define-key emerge-move-menu [emerge-next-difference] - '("Next Difference" . emerge-next-difference)) - - - (define-key emerge-options-menu [emerge-one-line-window] - '("One Line Window" . emerge-one-line-window)) - (define-key emerge-options-menu [emerge-set-merge-mode] - '("Set Merge Mode..." . emerge-set-merge-mode)) - (define-key emerge-options-menu [emerge-set-combine-template] - '("Set Combine Template..." . emerge-set-combine-template)) - (define-key emerge-options-menu [emerge-default-B] - '("Default B" . emerge-default-B)) - (define-key emerge-options-menu [emerge-default-A] - '("Default A" . emerge-default-A)) - (define-key emerge-options-menu [emerge-skip-prefers] - '(menu-item "Skip Prefers" emerge-skip-prefers - :button (:toggle . emerge-skip-prefers))) - (define-key emerge-options-menu [emerge-auto-advance] - '(menu-item "Auto Advance" emerge-auto-advance - :button (:toggle . emerge-auto-advance))) - (define-key emerge-options-menu [emerge-edit-mode] - '(menu-item "Edit Mode" emerge-edit-mode :enable (not emerge-edit-mode))) - (define-key emerge-options-menu [emerge-fast-mode] - '(menu-item "Fast Mode" emerge-fast-mode :enable (not emerge-fast-mode))) - - (define-key emerge-merge-menu [emerge-abort] '("Abort" . emerge-abort)) - (define-key emerge-merge-menu [emerge-quit] '("Quit" . emerge-quit)) - (define-key emerge-merge-menu [emerge-split-difference] - '("Split Difference" . emerge-split-difference)) - (define-key emerge-merge-menu [emerge-join-differences] - '("Join Differences" . emerge-join-differences)) - (define-key emerge-merge-menu [emerge-trim-difference] - '("Trim Difference" . emerge-trim-difference)) - (define-key emerge-merge-menu [emerge-combine-versions] - '("Combine Versions" . emerge-combine-versions)) - (define-key emerge-merge-menu [emerge-copy-as-kill-B] - '("Copy B as Kill" . emerge-copy-as-kill-B)) - (define-key emerge-merge-menu [emerge-copy-as-kill-A] - '("Copy A as Kill" . emerge-copy-as-kill-A)) - (define-key emerge-merge-menu [emerge-insert-B] - '("Insert B" . emerge-insert-B)) - (define-key emerge-merge-menu [emerge-insert-A] - '("Insert A" . emerge-insert-A)) - (define-key emerge-merge-menu [emerge-select-B] - '("Select B" . emerge-select-B)) - (define-key emerge-merge-menu [emerge-select-A] - '("Select A" . emerge-select-A))) - - -;; Variables which control each merge. They are local to the merge buffer. - -;; Mode variables -(emerge-defvar-local emerge-mode nil - "Indicator for emerge-mode.") -(emerge-defvar-local emerge-fast-mode nil - "Indicator for emerge-mode fast submode.") -(emerge-defvar-local emerge-edit-mode nil - "Indicator for emerge-mode edit submode.") -(emerge-defvar-local emerge-A-buffer nil - "The buffer in which the A variant is stored.") -(emerge-defvar-local emerge-B-buffer nil - "The buffer in which the B variant is stored.") -(emerge-defvar-local emerge-merge-buffer nil - "The buffer in which the merged file is manipulated.") -(emerge-defvar-local emerge-ancestor-buffer nil - "The buffer in which the ancestor variant is stored, -or nil if there is none.") - -(defconst emerge-saved-variables - '((buffer-modified-p set-buffer-modified-p) - buffer-read-only - buffer-auto-save-file-name) - "Variables and properties of a buffer which are saved, modified and restored -during a merge.") -(defconst emerge-merging-values '(nil t nil) - "Values to be assigned to emerge-saved-variables during a merge.") - -(emerge-defvar-local emerge-A-buffer-values nil - "Remembers emerge-saved-variables for emerge-A-buffer.") -(emerge-defvar-local emerge-B-buffer-values nil - "Remembers emerge-saved-variables for emerge-B-buffer.") - -(emerge-defvar-local emerge-difference-list nil - "Vector of differences between the variants, and markers in the buffers to -show where they are. Each difference is represented by a vector of seven -elements. The first two are markers to the beginning and end of the difference -section in the A buffer, the second two are markers for the B buffer, the third -two are markers for the merge buffer, and the last element is the \"state\" of -that difference in the merge buffer. - A section of a buffer is described by two markers, one to the beginning of -the first line of the section, and one to the beginning of the first line -after the section. (If the section is empty, both markers point to the same -point.) If the section is part of the selected difference, then the markers -are moved into the flags, so the user can edit the section without disturbing -the markers. - The \"states\" are: - A the merge buffer currently contains the A variant - B the merge buffer currently contains the B variant - default-A the merge buffer contains the A variant by default, - but this difference hasn't been selected yet, so - change-default commands can alter it - default-B the merge buffer contains the B variant by default, - but this difference hasn't been selected yet, so - change-default commands can alter it - prefer-A in a three-file merge, the A variant is the preferred - choice - prefer-B in a three-file merge, the B variant is the preferred - choice") -(emerge-defvar-local emerge-current-difference -1 - "The difference that is currently selected.") -(emerge-defvar-local emerge-number-of-differences nil - "Number of differences found.") -(emerge-defvar-local emerge-edit-keymap nil - "The local keymap for the merge buffer, with the emerge commands defined in -it. Used to save the local keymap during fast mode, when the local keymap is -replaced by emerge-fast-keymap.") -(emerge-defvar-local emerge-old-keymap nil - "The original local keymap for the merge buffer.") -(emerge-defvar-local emerge-auto-advance nil - "*If non-nil, emerge-select-A and emerge-select-B automatically advance to -the next difference.") -(emerge-defvar-local emerge-skip-prefers nil - "*If non-nil, differences for which there is a preference are automatically -skipped.") -(emerge-defvar-local emerge-quit-hook nil - "Hooks to run in the merge buffer after the merge has been finished. -`emerge-prefix-argument' will hold the prefix argument of the `emerge-quit' -command. -This is *not* a user option, since Emerge uses it for its own processing.") -(emerge-defvar-local emerge-output-description nil - "Describes output destination of emerge, for `emerge-file-names'.") - -;;; Setup functions for two-file mode. - -(defun emerge-files-internal (file-A file-B &optional startup-hooks quit-hooks - output-file) - (if (not (file-readable-p file-A)) - (error "File `%s' does not exist or is not readable" file-A)) - (if (not (file-readable-p file-B)) - (error "File `%s' does not exist or is not readable" file-B)) - (let ((buffer-A (find-file-noselect file-A)) - (buffer-B (find-file-noselect file-B))) - ;; Record the directories of the files - (setq emerge-last-dir-A (file-name-directory file-A)) - (setq emerge-last-dir-B (file-name-directory file-B)) - (if output-file - (setq emerge-last-dir-output (file-name-directory output-file))) - ;; Make sure the entire files are seen, and they reflect what is on disk - (emerge-eval-in-buffer - buffer-A - (widen) - (let ((temp (file-local-copy file-A))) - (if temp - (setq file-A temp - startup-hooks - (cons `(lambda () (delete-file ,file-A)) - startup-hooks)) - ;; Verify that the file matches the buffer - (emerge-verify-file-buffer)))) - (emerge-eval-in-buffer - buffer-B - (widen) - (let ((temp (file-local-copy file-B))) - (if temp - (setq file-B temp - startup-hooks - (cons `(lambda () (delete-file ,file-B)) - startup-hooks)) - ;; Verify that the file matches the buffer - (emerge-verify-file-buffer)))) - (emerge-setup buffer-A file-A buffer-B file-B startup-hooks quit-hooks - output-file))) - -;; Start up Emerge on two files -(defun emerge-setup (buffer-A file-A buffer-B file-B startup-hooks quit-hooks - output-file) - (setq file-A (expand-file-name file-A)) - (setq file-B (expand-file-name file-B)) - (setq output-file (and output-file (expand-file-name output-file))) - (let* ((merge-buffer-name (emerge-unique-buffer-name "*merge" "*")) - ;; create the merge buffer from buffer A, so it inherits buffer A's - ;; default directory, etc. - (merge-buffer (emerge-eval-in-buffer - buffer-A - (get-buffer-create merge-buffer-name)))) - (emerge-eval-in-buffer - merge-buffer - (emerge-copy-modes buffer-A) - (setq buffer-read-only nil) - (auto-save-mode 1) - (setq emerge-mode t) - (setq emerge-A-buffer buffer-A) - (setq emerge-B-buffer buffer-B) - (setq emerge-ancestor-buffer nil) - (setq emerge-merge-buffer merge-buffer) - (setq emerge-output-description - (if output-file - (concat "Output to file: " output-file) - (concat "Output to buffer: " (buffer-name merge-buffer)))) - (save-excursion (insert-buffer-substring emerge-A-buffer)) - (emerge-set-keys) - (setq emerge-difference-list (emerge-make-diff-list file-A file-B)) - (setq emerge-number-of-differences (length emerge-difference-list)) - (setq emerge-current-difference -1) - (setq emerge-quit-hook quit-hooks) - (emerge-remember-buffer-characteristics) - (emerge-handle-local-variables)) - (emerge-setup-windows buffer-A buffer-B merge-buffer t) - (emerge-eval-in-buffer merge-buffer - (run-hooks 'startup-hooks 'emerge-startup-hook) - (setq buffer-read-only t)))) - -;; Generate the Emerge difference list between two files -(defun emerge-make-diff-list (file-A file-B) - (setq emerge-diff-buffer (get-buffer-create "*emerge-diff*")) - (emerge-eval-in-buffer - emerge-diff-buffer - (erase-buffer) - (shell-command - (format "%s %s %s %s" - emerge-diff-program emerge-diff-options - (emerge-protect-metachars file-A) - (emerge-protect-metachars file-B)) - t)) - (emerge-prepare-error-list emerge-diff-ok-lines-regexp) - (emerge-convert-diffs-to-markers - emerge-A-buffer emerge-B-buffer emerge-merge-buffer - (emerge-extract-diffs emerge-diff-buffer))) - -(defun emerge-extract-diffs (diff-buffer) - (let (list) - (emerge-eval-in-buffer - diff-buffer - (goto-char (point-min)) - (while (re-search-forward emerge-match-diff-line nil t) - (let* ((a-begin (string-to-number (buffer-substring (match-beginning 1) - (match-end 1)))) - (a-end (let ((b (match-beginning 3)) - (e (match-end 3))) - (if b - (string-to-number (buffer-substring b e)) - a-begin))) - (diff-type (buffer-substring (match-beginning 4) (match-end 4))) - (b-begin (string-to-number (buffer-substring (match-beginning 5) - (match-end 5)))) - (b-end (let ((b (match-beginning 7)) - (e (match-end 7))) - (if b - (string-to-number (buffer-substring b e)) - b-begin)))) - ;; fix the beginning and end numbers, because diff is somewhat - ;; strange about how it numbers lines - (if (string-equal diff-type "a") - (progn - (setq b-end (1+ b-end)) - (setq a-begin (1+ a-begin)) - (setq a-end a-begin)) - (if (string-equal diff-type "d") - (progn - (setq a-end (1+ a-end)) - (setq b-begin (1+ b-begin)) - (setq b-end b-begin)) - ;; (string-equal diff-type "c") - (progn - (setq a-end (1+ a-end)) - (setq b-end (1+ b-end))))) - (setq list (cons (vector a-begin a-end - b-begin b-end - 'default-A) - list))))) - (nreverse list))) - -;; Set up buffer of diff/diff3 error messages. -(defun emerge-prepare-error-list (ok-regexp) - (setq emerge-diff-error-buffer (get-buffer-create "*emerge-diff-errors*")) - (emerge-eval-in-buffer - emerge-diff-error-buffer - (erase-buffer) - (save-excursion (insert-buffer-substring emerge-diff-buffer)) - (delete-matching-lines ok-regexp))) - -;;; Top-level and setup functions for three-file mode. - -(defun emerge-files-with-ancestor-internal (file-A file-B file-ancestor - &optional startup-hooks quit-hooks - output-file) - (if (not (file-readable-p file-A)) - (error "File `%s' does not exist or is not readable" file-A)) - (if (not (file-readable-p file-B)) - (error "File `%s' does not exist or is not readable" file-B)) - (if (not (file-readable-p file-ancestor)) - (error "File `%s' does not exist or is not readable" file-ancestor)) - (let ((buffer-A (find-file-noselect file-A)) - (buffer-B (find-file-noselect file-B)) - (buffer-ancestor (find-file-noselect file-ancestor))) - ;; Record the directories of the files - (setq emerge-last-dir-A (file-name-directory file-A)) - (setq emerge-last-dir-B (file-name-directory file-B)) - (setq emerge-last-dir-ancestor (file-name-directory file-ancestor)) - (if output-file - (setq emerge-last-dir-output (file-name-directory output-file))) - ;; Make sure the entire files are seen, and they reflect what is on disk - (emerge-eval-in-buffer - buffer-A - (widen) - (let ((temp (file-local-copy file-A))) - (if temp - (setq file-A temp - startup-hooks - (cons `(lambda () (delete-file ,file-A)) - startup-hooks)) - ;; Verify that the file matches the buffer - (emerge-verify-file-buffer)))) - (emerge-eval-in-buffer - buffer-B - (widen) - (let ((temp (file-local-copy file-B))) - (if temp - (setq file-B temp - startup-hooks - (cons `(lambda () (delete-file ,file-B)) - startup-hooks)) - ;; Verify that the file matches the buffer - (emerge-verify-file-buffer)))) - (emerge-eval-in-buffer - buffer-ancestor - (widen) - (let ((temp (file-local-copy file-ancestor))) - (if temp - (setq file-ancestor temp - startup-hooks - (cons `(lambda () (delete-file ,file-ancestor)) - startup-hooks)) - ;; Verify that the file matches the buffer - (emerge-verify-file-buffer)))) - (emerge-setup-with-ancestor buffer-A file-A buffer-B file-B - buffer-ancestor file-ancestor - startup-hooks quit-hooks output-file))) - -;; Start up Emerge on two files with an ancestor -(defun emerge-setup-with-ancestor (buffer-A file-A buffer-B file-B - buffer-ancestor file-ancestor - &optional startup-hooks quit-hooks - output-file) - (setq file-A (expand-file-name file-A)) - (setq file-B (expand-file-name file-B)) - (setq file-ancestor (expand-file-name file-ancestor)) - (setq output-file (and output-file (expand-file-name output-file))) - (let* ((merge-buffer-name (emerge-unique-buffer-name "*merge" "*")) - ;; create the merge buffer from buffer A, so it inherits buffer A's - ;; default directory, etc. - (merge-buffer (emerge-eval-in-buffer - buffer-A - (get-buffer-create merge-buffer-name)))) - (emerge-eval-in-buffer - merge-buffer - (emerge-copy-modes buffer-A) - (setq buffer-read-only nil) - (auto-save-mode 1) - (setq emerge-mode t) - (setq emerge-A-buffer buffer-A) - (setq emerge-B-buffer buffer-B) - (setq emerge-ancestor-buffer buffer-ancestor) - (setq emerge-merge-buffer merge-buffer) - (setq emerge-output-description - (if output-file - (concat "Output to file: " output-file) - (concat "Output to buffer: " (buffer-name merge-buffer)))) - (save-excursion (insert-buffer-substring emerge-A-buffer)) - (emerge-set-keys) - (setq emerge-difference-list - (emerge-make-diff3-list file-A file-B file-ancestor)) - (setq emerge-number-of-differences (length emerge-difference-list)) - (setq emerge-current-difference -1) - (setq emerge-quit-hook quit-hooks) - (emerge-remember-buffer-characteristics) - (emerge-select-prefer-Bs) - (emerge-handle-local-variables)) - (emerge-setup-windows buffer-A buffer-B merge-buffer t) - (emerge-eval-in-buffer merge-buffer - (run-hooks 'startup-hooks 'emerge-startup-hook) - (setq buffer-read-only t)))) - -;; Generate the Emerge difference list between two files with an ancestor -(defun emerge-make-diff3-list (file-A file-B file-ancestor) - (setq emerge-diff-buffer (get-buffer-create "*emerge-diff*")) - (emerge-eval-in-buffer - emerge-diff-buffer - (erase-buffer) - (shell-command - (format "%s %s %s %s %s" - emerge-diff3-program emerge-diff-options - (emerge-protect-metachars file-A) - (emerge-protect-metachars file-ancestor) - (emerge-protect-metachars file-B)) - t)) - (emerge-prepare-error-list emerge-diff3-ok-lines-regexp) - (emerge-convert-diffs-to-markers - emerge-A-buffer emerge-B-buffer emerge-merge-buffer - (emerge-extract-diffs3 emerge-diff-buffer))) - -(defun emerge-extract-diffs3 (diff-buffer) - (let (list) - (emerge-eval-in-buffer - diff-buffer - (while (re-search-forward "^====\\(.?\\)$" nil t) - ;; leave point after matched line - (beginning-of-line 2) - (let ((agreement (buffer-substring (match-beginning 1) (match-end 1)))) - ;; if the A and B files are the same, ignore the difference - (if (not (string-equal agreement "2")) - (setq list - (cons - (let (group-1 group-3 pos) - (setq pos (point)) - (setq group-1 (emerge-get-diff3-group "1")) - (goto-char pos) - (setq group-3 (emerge-get-diff3-group "3")) - (vector (car group-1) (car (cdr group-1)) - (car group-3) (car (cdr group-3)) - (cond ((string-equal agreement "1") 'prefer-A) - ((string-equal agreement "3") 'prefer-B) - (t 'default-A)))) - list)))))) - (nreverse list))) - -(defun emerge-get-diff3-group (file) - ;; This save-excursion allows emerge-get-diff3-group to be called for the - ;; various groups of lines (1, 2, 3) in any order, and for the lines to - ;; appear in any order. The reason this is necessary is that Gnu diff3 - ;; can produce the groups in the order 1, 2, 3 or 1, 3, 2. - (save-excursion - (re-search-forward - (concat "^" file ":\\([0-9]+\\)\\(,\\([0-9]+\\)\\)?\\([ac]\\)$")) - (beginning-of-line 2) - ;; treatment depends on whether it is an "a" group or a "c" group - (if (string-equal (buffer-substring (match-beginning 4) (match-end 4)) "c") - ;; it is a "c" group - (if (match-beginning 2) - ;; it has two numbers - (list (string-to-number - (buffer-substring (match-beginning 1) (match-end 1))) - (1+ (string-to-number - (buffer-substring (match-beginning 3) (match-end 3))))) - ;; it has one number - (let ((x (string-to-number - (buffer-substring (match-beginning 1) (match-end 1))))) - (list x (1+ x)))) - ;; it is an "a" group - (let ((x (1+ (string-to-number - (buffer-substring (match-beginning 1) (match-end 1)))))) - (list x x))))) - -;;; Functions to start Emerge on files - -;;;###autoload -(defun emerge-files (arg file-A file-B file-out &optional startup-hooks - quit-hooks) - "Run Emerge on two files." - (interactive - (let (f) - (list current-prefix-arg - (setq f (emerge-read-file-name "File A to merge" emerge-last-dir-A - nil nil t)) - (emerge-read-file-name "File B to merge" emerge-last-dir-B nil f t) - (and current-prefix-arg - (emerge-read-file-name "Output file" emerge-last-dir-output - f f nil))))) - (if file-out - (add-hook 'quit-hooks `(lambda () (emerge-files-exit ,file-out)))) - (emerge-files-internal - file-A file-B startup-hooks - quit-hooks - file-out)) - -;;;###autoload -(defun emerge-files-with-ancestor (arg file-A file-B file-ancestor file-out - &optional startup-hooks quit-hooks) - "Run Emerge on two files, giving another file as the ancestor." - (interactive - (let (f) - (list current-prefix-arg - (setq f (emerge-read-file-name "File A to merge" emerge-last-dir-A - nil nil t)) - (emerge-read-file-name "File B to merge" emerge-last-dir-B nil f t) - (emerge-read-file-name "Ancestor file" emerge-last-dir-ancestor - nil f t) - (and current-prefix-arg - (emerge-read-file-name "Output file" emerge-last-dir-output - f f nil))))) - (if file-out - (add-hook 'quit-hooks `(lambda () (emerge-files-exit ,file-out)))) - (emerge-files-with-ancestor-internal - file-A file-B file-ancestor startup-hooks - quit-hooks - file-out)) - -;; Write the merge buffer out in place of the file the A buffer is visiting. -(defun emerge-files-exit (file-out) - ;; if merge was successful was given, save to disk - (if (not emerge-prefix-argument) - (emerge-write-and-delete file-out))) - -;;; Functions to start Emerge on buffers - -;;;###autoload -(defun emerge-buffers (buffer-A buffer-B &optional startup-hooks quit-hooks) - "Run Emerge on two buffers." - (interactive "bBuffer A to merge: \nbBuffer B to merge: ") - (let ((emerge-file-A (emerge-make-temp-file "A")) - (emerge-file-B (emerge-make-temp-file "B"))) - (emerge-eval-in-buffer - buffer-A - (write-region (point-min) (point-max) emerge-file-A nil 'no-message)) - (emerge-eval-in-buffer - buffer-B - (write-region (point-min) (point-max) emerge-file-B nil 'no-message)) - (emerge-setup (get-buffer buffer-A) emerge-file-A - (get-buffer buffer-B) emerge-file-B - (cons `(lambda () - (delete-file ,emerge-file-A) - (delete-file ,emerge-file-B)) - startup-hooks) - quit-hooks - nil))) - -;;;###autoload -(defun emerge-buffers-with-ancestor (buffer-A buffer-B buffer-ancestor - &optional startup-hooks - quit-hooks) - "Run Emerge on two buffers, giving another buffer as the ancestor." - (interactive - "bBuffer A to merge: \nbBuffer B to merge: \nbAncestor buffer: ") - (let ((emerge-file-A (emerge-make-temp-file "A")) - (emerge-file-B (emerge-make-temp-file "B")) - (emerge-file-ancestor (emerge-make-temp-file "anc"))) - (emerge-eval-in-buffer - buffer-A - (write-region (point-min) (point-max) emerge-file-A nil 'no-message)) - (emerge-eval-in-buffer - buffer-B - (write-region (point-min) (point-max) emerge-file-B nil 'no-message)) - (emerge-eval-in-buffer - buffer-ancestor - (write-region (point-min) (point-max) emerge-file-ancestor nil - 'no-message)) - (emerge-setup-with-ancestor (get-buffer buffer-A) emerge-file-A - (get-buffer buffer-B) emerge-file-B - (get-buffer buffer-ancestor) - emerge-file-ancestor - (cons `(lambda () - (delete-file ,emerge-file-A) - (delete-file ,emerge-file-B) - (delete-file - ,emerge-file-ancestor)) - startup-hooks) - quit-hooks - nil))) - -;;; Functions to start Emerge from the command line - -;;;###autoload -(defun emerge-files-command () - (let ((file-a (nth 0 command-line-args-left)) - (file-b (nth 1 command-line-args-left)) - (file-out (nth 2 command-line-args-left))) - (setq command-line-args-left (nthcdr 3 command-line-args-left)) - (emerge-files-internal - file-a file-b nil - (list `(lambda () (emerge-command-exit ,file-out)))))) - -;;;###autoload -(defun emerge-files-with-ancestor-command () - (let (file-a file-b file-anc file-out) - ;; check for a -a flag, for filemerge compatibility - (if (string= (car command-line-args-left) "-a") - ;; arguments are "-a ancestor file-a file-b file-out" - (progn - (setq file-a (nth 2 command-line-args-left)) - (setq file-b (nth 3 command-line-args-left)) - (setq file-anc (nth 1 command-line-args-left)) - (setq file-out (nth 4 command-line-args-left)) - (setq command-line-args-left (nthcdr 5 command-line-args-left))) - ;; arguments are "file-a file-b ancestor file-out" - (setq file-a (nth 0 command-line-args-left)) - (setq file-b (nth 1 command-line-args-left)) - (setq file-anc (nth 2 command-line-args-left)) - (setq file-out (nth 3 command-line-args-left)) - (setq command-line-args-left (nthcdr 4 command-line-args-left))) - (emerge-files-with-ancestor-internal - file-a file-b file-anc nil - (list `(lambda () (emerge-command-exit ,file-out)))))) - -(defun emerge-command-exit (file-out) - (emerge-write-and-delete file-out) - (kill-emacs (if emerge-prefix-argument 1 0))) - -;;; Functions to start Emerge via remote request - -;;;###autoload -(defun emerge-files-remote (file-a file-b file-out) - (setq emerge-file-out file-out) - (emerge-files-internal - file-a file-b nil - (list `(lambda () (emerge-remote-exit ,file-out ',emerge-exit-func))) - file-out) - (throw 'client-wait nil)) - -;;;###autoload -(defun emerge-files-with-ancestor-remote (file-a file-b file-anc file-out) - (setq emerge-file-out file-out) - (emerge-files-with-ancestor-internal - file-a file-b file-anc nil - (list `(lambda () (emerge-remote-exit ,file-out ',emerge-exit-func))) - file-out) - (throw 'client-wait nil)) - -(defun emerge-remote-exit (file-out emerge-exit-func) - (emerge-write-and-delete file-out) - (kill-buffer emerge-merge-buffer) - (funcall emerge-exit-func (if emerge-prefix-argument 1 0))) - -;;; Functions to start Emerge on RCS versions - -;;;###autoload -(defun emerge-revisions (arg file revision-A revision-B - &optional startup-hooks quit-hooks) - "Emerge two RCS revisions of a file." - (interactive - (list current-prefix-arg - (read-file-name "File to merge: " nil nil 'confirm) - (read-string "Revision A to merge: " emerge-last-revision-A) - (read-string "Revision B to merge: " emerge-last-revision-B))) - (setq emerge-last-revision-A revision-A - emerge-last-revision-B revision-B) - (emerge-revisions-internal - file revision-A revision-B startup-hooks - (if arg - (cons `(lambda () - (shell-command - ,(format "%s %s" emerge-rcs-ci-program file))) - quit-hooks) - quit-hooks))) - -;;;###autoload -(defun emerge-revisions-with-ancestor (arg file revision-A - revision-B ancestor - &optional - startup-hooks quit-hooks) - "Emerge two RCS revisions of a file, with another revision as ancestor." - (interactive - (list current-prefix-arg - (read-file-name "File to merge: " nil nil 'confirm) - (read-string "Revision A to merge: " emerge-last-revision-A) - (read-string "Revision B to merge: " emerge-last-revision-B) - (read-string "Ancestor: " emerge-last-revision-ancestor))) - (setq emerge-last-revision-A revision-A - emerge-last-revision-B revision-B - emerge-last-revision-ancestor ancestor) - (emerge-revision-with-ancestor-internal - file revision-A revision-B ancestor startup-hooks - (if arg - (let ((cmd )) - (cons `(lambda () - (shell-command - ,(format "%s %s" emerge-rcs-ci-program file))) - quit-hooks)) - quit-hooks))) - -(defun emerge-revisions-internal (file revision-A revision-B &optional - startup-hooks quit-hooks output-file) - (let ((buffer-A (get-buffer-create (format "%s,%s" file revision-A))) - (buffer-B (get-buffer-create (format "%s,%s" file revision-B))) - (emerge-file-A (emerge-make-temp-file "A")) - (emerge-file-B (emerge-make-temp-file "B"))) - ;; Get the revisions into buffers - (emerge-eval-in-buffer - buffer-A - (erase-buffer) - (shell-command - (format "%s -q -p%s %s" emerge-rcs-co-program revision-A file) - t) - (write-region (point-min) (point-max) emerge-file-A nil 'no-message) - (set-buffer-modified-p nil)) - (emerge-eval-in-buffer - buffer-B - (erase-buffer) - (shell-command - (format "%s -q -p%s %s" emerge-rcs-co-program revision-B file) - t) - (write-region (point-min) (point-max) emerge-file-B nil 'no-message) - (set-buffer-modified-p nil)) - ;; Do the merge - (emerge-setup buffer-A emerge-file-A - buffer-B emerge-file-B - (cons `(lambda () - (delete-file ,emerge-file-A) - (delete-file ,emerge-file-B)) - startup-hooks) - (cons `(lambda () (emerge-files-exit ,file)) - quit-hooks) - nil))) - -(defun emerge-revision-with-ancestor-internal (file revision-A revision-B - ancestor - &optional startup-hooks - quit-hooks output-file) - (let ((buffer-A (get-buffer-create (format "%s,%s" file revision-A))) - (buffer-B (get-buffer-create (format "%s,%s" file revision-B))) - (buffer-ancestor (get-buffer-create (format "%s,%s" file ancestor))) - (emerge-file-A (emerge-make-temp-file "A")) - (emerge-file-B (emerge-make-temp-file "B")) - (emerge-ancestor (emerge-make-temp-file "ancestor"))) - ;; Get the revisions into buffers - (emerge-eval-in-buffer - buffer-A - (erase-buffer) - (shell-command - (format "%s -q -p%s %s" emerge-rcs-co-program - revision-A file) - t) - (write-region (point-min) (point-max) emerge-file-A nil 'no-message) - (set-buffer-modified-p nil)) - (emerge-eval-in-buffer - buffer-B - (erase-buffer) - (shell-command - (format "%s -q -p%s %s" emerge-rcs-co-program revision-B file) - t) - (write-region (point-min) (point-max) emerge-file-B nil 'no-message) - (set-buffer-modified-p nil)) - (emerge-eval-in-buffer - buffer-ancestor - (erase-buffer) - (shell-command - (format "%s -q -p%s %s" emerge-rcs-co-program ancestor file) - t) - (write-region (point-min) (point-max) emerge-ancestor nil 'no-message) - (set-buffer-modified-p nil)) - ;; Do the merge - (emerge-setup-with-ancestor - buffer-A emerge-file-A buffer-B emerge-file-B - buffer-ancestor emerge-ancestor - (cons `(lambda () - (delete-file ,emerge-file-A) - (delete-file ,emerge-file-B) - (delete-file ,emerge-ancestor)) - startup-hooks) - (cons `(lambda () (emerge-files-exit ,file)) - quit-hooks) - output-file))) - -;;; Function to start Emerge based on a line in a file - -(defun emerge-execute-line () - "Run Emerge using files named in current text line. -Looks in that line for whitespace-separated entries of these forms: - a=file1 - b=file2 - ancestor=file3 - output=file4 -to specify the files to use in Emerge. - -In addition, if only one of `a=file' or `b=file' is present, and `output=file' -is present: -If `emerge-execute-line-deletions' is non-nil and `ancestor=file' is present, -it is assumed that the file in question has been deleted, and it is -not copied to the output file. -Otherwise, the A or B file present is copied to the output file." - (interactive) - (let (file-A file-B file-ancestor file-out - (case-fold-search t)) - ;; Stop if at end of buffer (even though we might be in a line, if - ;; the line does not end with newline) - (if (eobp) - (error "At end of buffer")) - ;; Go to the beginning of the line - (beginning-of-line) - ;; Skip any initial whitespace - (if (looking-at "[ \t]*") - (goto-char (match-end 0))) - ;; Process the entire line - (while (not (eolp)) - ;; Get the next entry - (if (looking-at "\\([a-z]+\\)=\\([^ \t\n]+\\)[ \t]*") - ;; Break apart the tab (before =) and the filename (after =) - (let ((tag (downcase - (buffer-substring (match-beginning 1) (match-end 1)))) - (file (buffer-substring (match-beginning 2) (match-end 2)))) - ;; Move point after the entry - (goto-char (match-end 0)) - ;; Store the filename in the right variable - (cond - ((string-equal tag "a") - (if file-A - (error "This line has two `A' entries")) - (setq file-A file)) - ((string-equal tag "b") - (if file-B - (error "This line has two `B' entries")) - (setq file-B file)) - ((or (string-equal tag "anc") (string-equal tag "ancestor")) - (if file-ancestor - (error "This line has two `ancestor' entries")) - (setq file-ancestor file)) - ((or (string-equal tag "out") (string-equal tag "output")) - (if file-out - (error "This line has two `output' entries")) - (setq file-out file)) - (t - (error "Unrecognized entry")))) - ;; If the match on the entry pattern failed - (error "Unparsable entry"))) - ;; Make sure that file-A and file-B are present - (if (not (or (and file-A file-B) file-out)) - (error "Must have both `A' and `B' entries")) - (if (not (or file-A file-B)) - (error "Must have `A' or `B' entry")) - ;; Go to the beginning of the next line, so next execution will use - ;; next line in buffer. - (beginning-of-line 2) - ;; Execute the correct command - (cond - ;; Merge of two files with ancestor - ((and file-A file-B file-ancestor) - (message "Merging %s and %s..." file-A file-B) - (emerge-files-with-ancestor (not (not file-out)) file-A file-B - file-ancestor file-out - nil - ;; When done, return to this buffer. - (list - `(lambda () - (switch-to-buffer ,(current-buffer)) - (message "Merge done."))))) - ;; Merge of two files without ancestor - ((and file-A file-B) - (message "Merging %s and %s..." file-A file-B) - (emerge-files (not (not file-out)) file-A file-B file-out - nil - ;; When done, return to this buffer. - (list - `(lambda () - (switch-to-buffer ,(current-buffer)) - (message "Merge done."))))) - ;; There is an output file (or there would have been an error above), - ;; but only one input file. - ;; The file appears to have been deleted in one version; do nothing. - ((and file-ancestor emerge-execute-line-deletions) - (message "No action.")) - ;; The file should be copied from the version that contains it - (t (let ((input-file (or file-A file-B))) - (message "Copying...") - (copy-file input-file file-out) - (message "%s copied to %s." input-file file-out)))))) - -;;; Sample function for creating information for emerge-execute-line - -(defcustom emerge-merge-directories-filename-regexp "[^.]" - "Regexp describing files to be processed by `emerge-merge-directories'." - :type 'regexp - :group 'emerge) - -;;;###autoload -(defun emerge-merge-directories (a-dir b-dir ancestor-dir output-dir) - (interactive - (list - (read-file-name "A directory: " nil nil 'confirm) - (read-file-name "B directory: " nil nil 'confirm) - (read-file-name "Ancestor directory (null for none): " nil nil 'confirm) - (read-file-name "Output directory (null for none): " nil nil 'confirm))) - ;; Check that we're not on a line - (if (not (and (bolp) (eolp))) - (error "There is text on this line")) - ;; Turn null strings into nil to indicate directories not used. - (if (and ancestor-dir (string-equal ancestor-dir "")) - (setq ancestor-dir nil)) - (if (and output-dir (string-equal output-dir "")) - (setq output-dir nil)) - ;; Canonicalize the directory names - (setq a-dir (expand-file-name a-dir)) - (if (not (string-equal (substring a-dir -1) "/")) - (setq a-dir (concat a-dir "/"))) - (setq b-dir (expand-file-name b-dir)) - (if (not (string-equal (substring b-dir -1) "/")) - (setq b-dir (concat b-dir "/"))) - (if ancestor-dir - (progn - (setq ancestor-dir (expand-file-name ancestor-dir)) - (if (not (string-equal (substring ancestor-dir -1) "/")) - (setq ancestor-dir (concat ancestor-dir "/"))))) - (if output-dir - (progn - (setq output-dir (expand-file-name output-dir)) - (if (not (string-equal (substring output-dir -1) "/")) - (setq output-dir (concat output-dir "/"))))) - ;; Set the mark to where we start - (push-mark) - ;; Find out what files are in the directories. - (let* ((a-dir-files - (directory-files a-dir nil emerge-merge-directories-filename-regexp)) - (b-dir-files - (directory-files b-dir nil emerge-merge-directories-filename-regexp)) - (ancestor-dir-files - (and ancestor-dir - (directory-files ancestor-dir nil - emerge-merge-directories-filename-regexp))) - (all-files (sort (nconc (copy-sequence a-dir-files) - (copy-sequence b-dir-files) - (copy-sequence ancestor-dir-files)) - (function string-lessp)))) - ;; Remove duplicates from all-files. - (let ((p all-files)) - (while p - (if (and (cdr p) (string-equal (car p) (car (cdr p)))) - (setcdr p (cdr (cdr p))) - (setq p (cdr p))))) - ;; Generate the control lines for the various files. - (while all-files - (let ((f (car all-files))) - (setq all-files (cdr all-files)) - (if (and a-dir-files (string-equal (car a-dir-files) f)) - (progn - (insert "A=" a-dir f "\t") - (setq a-dir-files (cdr a-dir-files)))) - (if (and b-dir-files (string-equal (car b-dir-files) f)) - (progn - (insert "B=" b-dir f "\t") - (setq b-dir-files (cdr b-dir-files)))) - (if (and ancestor-dir-files (string-equal (car ancestor-dir-files) f)) - (progn - (insert "ancestor=" ancestor-dir f "\t") - (setq ancestor-dir-files (cdr ancestor-dir-files)))) - (if output-dir - (insert "output=" output-dir f "\t")) - (backward-delete-char 1) - (insert "\n"))))) - -;;; Common setup routines - -;; Set up the window configuration. If POS is given, set the points to -;; the beginnings of the buffers. -(defun emerge-setup-windows (buffer-A buffer-B merge-buffer &optional pos) - ;; Make sure we are not in the minibuffer window when we try to delete - ;; all other windows. - (if (eq (selected-window) (minibuffer-window)) - (other-window 1)) - (delete-other-windows) - (switch-to-buffer merge-buffer) - (emerge-refresh-mode-line) - (split-window-vertically) - (split-window-horizontally) - (switch-to-buffer buffer-A) - (if pos - (goto-char (point-min))) - (other-window 1) - (switch-to-buffer buffer-B) - (if pos - (goto-char (point-min))) - (other-window 1) - (if pos - (goto-char (point-min))) - ;; If diff/diff3 reports errors, display them rather than the merge buffer. - (if (/= 0 (emerge-eval-in-buffer emerge-diff-error-buffer (buffer-size))) - (progn - (ding) - (message "Errors found in diff/diff3 output. Merge buffer is %s." - (buffer-name emerge-merge-buffer)) - (switch-to-buffer emerge-diff-error-buffer)))) - -;; Set up the keymap in the merge buffer -(defun emerge-set-keys () - ;; Set up fixed keymaps if necessary - (if (not emerge-basic-keymap) - (emerge-setup-fixed-keymaps)) - ;; Save the old local map - (setq emerge-old-keymap (current-local-map)) - ;; Construct the edit keymap - (setq emerge-edit-keymap (if emerge-old-keymap - (copy-keymap emerge-old-keymap) - (make-sparse-keymap))) - ;; Install the Emerge commands - (emerge-force-define-key emerge-edit-keymap emerge-command-prefix - 'emerge-basic-keymap) - (define-key emerge-edit-keymap [menu-bar] (make-sparse-keymap)) - - ;; Create the additional menu bar items. - (define-key emerge-edit-keymap [menu-bar emerge-options] - (cons "Merge-Options" emerge-options-menu)) - (define-key emerge-edit-keymap [menu-bar merge] - (cons "Merge" emerge-merge-menu)) - (define-key emerge-edit-keymap [menu-bar move] - (cons "Move" emerge-move-menu)) - - ;; Suppress write-file and save-buffer - (substitute-key-definition 'write-file - 'emerge-query-write-file - emerge-edit-keymap) - (substitute-key-definition 'save-buffer - 'emerge-query-save-buffer - emerge-edit-keymap) - (define-key emerge-edit-keymap [remap write-file] 'emerge-query-write-file) - (define-key emerge-edit-keymap [remap save-buffer] 'emerge-query-save-buffer) - (use-local-map emerge-fast-keymap) - (setq emerge-edit-mode nil) - (setq emerge-fast-mode t)) - -(defun emerge-remember-buffer-characteristics () - "Record certain properties of the buffers being merged. -Must be called in the merge buffer. Remembers read-only, modified, -auto-save, and saves them in buffer local variables. Sets the buffers -read-only and turns off `auto-save-mode'. -These characteristics are restored by `emerge-restore-buffer-characteristics'." - ;; force auto-save, because we will turn off auto-saving in buffers for the - ;; duration - (do-auto-save) - ;; remember and alter buffer characteristics - (setq emerge-A-buffer-values - (emerge-eval-in-buffer - emerge-A-buffer - (prog1 - (emerge-save-variables emerge-saved-variables) - (emerge-restore-variables emerge-saved-variables - emerge-merging-values)))) - (setq emerge-B-buffer-values - (emerge-eval-in-buffer - emerge-B-buffer - (prog1 - (emerge-save-variables emerge-saved-variables) - (emerge-restore-variables emerge-saved-variables - emerge-merging-values))))) - -(defun emerge-restore-buffer-characteristics () - "Restore characteristics saved by `emerge-remember-buffer-characteristics'." - (let ((A-values emerge-A-buffer-values) - (B-values emerge-B-buffer-values)) - (emerge-eval-in-buffer emerge-A-buffer - (emerge-restore-variables emerge-saved-variables - A-values)) - (emerge-eval-in-buffer emerge-B-buffer - (emerge-restore-variables emerge-saved-variables - B-values)))) - -;; Move to line DESIRED-LINE assuming we are at line CURRENT-LINE. -;; Return DESIRED-LINE. -(defun emerge-goto-line (desired-line current-line) - (forward-line (- desired-line current-line)) - desired-line) - -(defun emerge-convert-diffs-to-markers (A-buffer - B-buffer - merge-buffer - lineno-list) - (let* (marker-list - (A-point-min (emerge-eval-in-buffer A-buffer (point-min))) - (offset (1- A-point-min)) - (B-point-min (emerge-eval-in-buffer B-buffer (point-min))) - ;; Record current line number in each buffer - ;; so we don't have to count from the beginning. - (a-line 1) - (b-line 1)) - (emerge-eval-in-buffer A-buffer (goto-char (point-min))) - (emerge-eval-in-buffer B-buffer (goto-char (point-min))) - (while lineno-list - (let* ((list-element (car lineno-list)) - a-begin-marker - a-end-marker - b-begin-marker - b-end-marker - merge-begin-marker - merge-end-marker - (a-begin (aref list-element 0)) - (a-end (aref list-element 1)) - (b-begin (aref list-element 2)) - (b-end (aref list-element 3)) - (state (aref list-element 4))) - ;; place markers at the appropriate places in the buffers - (emerge-eval-in-buffer - A-buffer - (setq a-line (emerge-goto-line a-begin a-line)) - (setq a-begin-marker (point-marker)) - (setq a-line (emerge-goto-line a-end a-line)) - (setq a-end-marker (point-marker))) - (emerge-eval-in-buffer - B-buffer - (setq b-line (emerge-goto-line b-begin b-line)) - (setq b-begin-marker (point-marker)) - (setq b-line (emerge-goto-line b-end b-line)) - (setq b-end-marker (point-marker))) - (setq merge-begin-marker (set-marker - (make-marker) - (- (marker-position a-begin-marker) - offset) - merge-buffer)) - (setq merge-end-marker (set-marker - (make-marker) - (- (marker-position a-end-marker) - offset) - merge-buffer)) - ;; record all the markers for this difference - (setq marker-list (cons (vector a-begin-marker a-end-marker - b-begin-marker b-end-marker - merge-begin-marker merge-end-marker - state) - marker-list))) - (setq lineno-list (cdr lineno-list))) - ;; convert the list of difference information into a vector for - ;; fast access - (setq emerge-difference-list (apply 'vector (nreverse marker-list))))) - -;; If we have an ancestor, select all B variants that we prefer -(defun emerge-select-prefer-Bs () - (let ((n 0)) - (while (< n emerge-number-of-differences) - (if (eq (aref (aref emerge-difference-list n) 6) 'prefer-B) - (progn - (emerge-unselect-and-select-difference n t) - (emerge-select-B) - (aset (aref emerge-difference-list n) 6 'prefer-B))) - (setq n (1+ n)))) - (emerge-unselect-and-select-difference -1)) - -;; Process the local-variables list at the end of the merged file, if -;; requested. -(defun emerge-handle-local-variables () - (if emerge-process-local-variables - (condition-case err - (hack-local-variables) - (error (message "Local-variables error in merge buffer: %s" - (prin1-to-string err)))))) - -;;; Common exit routines - -(defun emerge-write-and-delete (file-out) - ;; clear screen format - (delete-other-windows) - ;; delete A, B, and ancestor buffers, if they haven't been changed - (if (not (buffer-modified-p emerge-A-buffer)) - (kill-buffer emerge-A-buffer)) - (if (not (buffer-modified-p emerge-B-buffer)) - (kill-buffer emerge-B-buffer)) - (if (and emerge-ancestor-buffer - (not (buffer-modified-p emerge-ancestor-buffer))) - (kill-buffer emerge-ancestor-buffer)) - ;; Write merge buffer to file - (and file-out - (write-file file-out))) - -;;; Commands - -(defun emerge-recenter (&optional arg) - "Bring the highlighted region of all three merge buffers into view. -This brings the buffers into view if they are in windows. -With an argument, reestablish the default three-window display." - (interactive "P") - ;; If there is an argument, rebuild the window structure - (if arg - (emerge-setup-windows emerge-A-buffer emerge-B-buffer - emerge-merge-buffer)) - ;; Redisplay whatever buffers are showing, if there is a selected difference - (if (and (>= emerge-current-difference 0) - (< emerge-current-difference emerge-number-of-differences)) - (let* ((merge-buffer emerge-merge-buffer) - (buffer-A emerge-A-buffer) - (buffer-B emerge-B-buffer) - (window-A (get-buffer-window buffer-A 'visible)) - (window-B (get-buffer-window buffer-B 'visible)) - (merge-window (get-buffer-window merge-buffer)) - (diff-vector - (aref emerge-difference-list emerge-current-difference))) - (if window-A (progn - (select-window window-A) - (emerge-position-region - (- (aref diff-vector 0) - (1- emerge-before-flag-length)) - (+ (aref diff-vector 1) - (1- emerge-after-flag-length)) - (1+ (aref diff-vector 0))))) - (if window-B (progn - (select-window window-B) - (emerge-position-region - (- (aref diff-vector 2) - (1- emerge-before-flag-length)) - (+ (aref diff-vector 3) - (1- emerge-after-flag-length)) - (1+ (aref diff-vector 2))))) - (if merge-window (progn - (select-window merge-window) - (emerge-position-region - (- (aref diff-vector 4) - (1- emerge-before-flag-length)) - (+ (aref diff-vector 5) - (1- emerge-after-flag-length)) - (1+ (aref diff-vector 4)))))))) - -;;; Window scrolling operations -;; These operations are designed to scroll all three windows the same amount, -;; so as to keep the text in them aligned. - -;; Perform some operation on all three windows (if they are showing). -;; Catches all errors on the operation in the A and B windows, but not -;; in the merge window. Usually, errors come from scrolling off the -;; beginning or end of the buffer, and this gives a nice error message: -;; End of buffer is reported in the merge buffer, but if the scroll was -;; possible in the A or B windows, it is performed there before the error -;; is reported. -(defun emerge-operate-on-windows (operation arg) - (let* ((merge-buffer emerge-merge-buffer) - (buffer-A emerge-A-buffer) - (buffer-B emerge-B-buffer) - (window-A (get-buffer-window buffer-A 'visible)) - (window-B (get-buffer-window buffer-B 'visible)) - (merge-window (get-buffer-window merge-buffer))) - (if window-A (progn - (select-window window-A) - (condition-case nil - (funcall operation arg) - (error)))) - (if window-B (progn - (select-window window-B) - (condition-case nil - (funcall operation arg) - (error)))) - (if merge-window (progn - (select-window merge-window) - (funcall operation arg))))) - -(defun emerge-scroll-up (&optional arg) - "Scroll up all three merge buffers, if they are in windows. -With argument N, scroll N lines; otherwise scroll by nearly -the height of the merge window. -`C-u -' alone as argument scrolls half the height of the merge window." - (interactive "P") - (emerge-operate-on-windows - 'scroll-up - ;; calculate argument to scroll-up - ;; if there is an explicit argument - (if (and arg (not (equal arg '-))) - ;; use it - (prefix-numeric-value arg) - ;; if not, see if we can determine a default amount (the window height) - (let ((merge-window (get-buffer-window emerge-merge-buffer))) - (if (null merge-window) - ;; no window, use nil - nil - (let ((default-amount - (- (window-height merge-window) 1 next-screen-context-lines))) - ;; the window was found - (if arg - ;; C-u as argument means half of default amount - (/ default-amount 2) - ;; no argument means default amount - default-amount))))))) - -(defun emerge-scroll-down (&optional arg) - "Scroll down all three merge buffers, if they are in windows. -With argument N, scroll N lines; otherwise scroll by nearly -the height of the merge window. -`C-u -' alone as argument scrolls half the height of the merge window." - (interactive "P") - (emerge-operate-on-windows - 'scroll-down - ;; calculate argument to scroll-down - ;; if there is an explicit argument - (if (and arg (not (equal arg '-))) - ;; use it - (prefix-numeric-value arg) - ;; if not, see if we can determine a default amount (the window height) - (let ((merge-window (get-buffer-window emerge-merge-buffer))) - (if (null merge-window) - ;; no window, use nil - nil - (let ((default-amount - (- (window-height merge-window) 1 next-screen-context-lines))) - ;; the window was found - (if arg - ;; C-u as argument means half of default amount - (/ default-amount 2) - ;; no argument means default amount - default-amount))))))) - -(defun emerge-scroll-left (&optional arg) - "Scroll left all three merge buffers, if they are in windows. -If an argument is given, that is how many columns are scrolled, else nearly -the width of the A and B windows. `C-u -' alone as argument scrolls half the -width of the A and B windows." - (interactive "P") - (emerge-operate-on-windows - 'scroll-left - ;; calculate argument to scroll-left - ;; if there is an explicit argument - (if (and arg (not (equal arg '-))) - ;; use it - (prefix-numeric-value arg) - ;; if not, see if we can determine a default amount - ;; (half the window width) - (let ((merge-window (get-buffer-window emerge-merge-buffer))) - (if (null merge-window) - ;; no window, use nil - nil - (let ((default-amount - (- (/ (window-width merge-window) 2) 3))) - ;; the window was found - (if arg - ;; C-u as argument means half of default amount - (/ default-amount 2) - ;; no argument means default amount - default-amount))))))) - -(defun emerge-scroll-right (&optional arg) - "Scroll right all three merge buffers, if they are in windows. -If an argument is given, that is how many columns are scrolled, else nearly -the width of the A and B windows. `C-u -' alone as argument scrolls half the -width of the A and B windows." - (interactive "P") - (emerge-operate-on-windows - 'scroll-right - ;; calculate argument to scroll-right - ;; if there is an explicit argument - (if (and arg (not (equal arg '-))) - ;; use it - (prefix-numeric-value arg) - ;; if not, see if we can determine a default amount - ;; (half the window width) - (let ((merge-window (get-buffer-window emerge-merge-buffer))) - (if (null merge-window) - ;; no window, use nil - nil - (let ((default-amount - (- (/ (window-width merge-window) 2) 3))) - ;; the window was found - (if arg - ;; C-u as argument means half of default amount - (/ default-amount 2) - ;; no argument means default amount - default-amount))))))) - -(defun emerge-scroll-reset () - "Reset horizontal scrolling in Emerge. -This resets the horizontal scrolling of all three merge buffers -to the left margin, if they are in windows." - (interactive) - (emerge-operate-on-windows - (function (lambda (x) (set-window-hscroll (selected-window) 0))) - nil)) - -;; Attempt to show the region nicely. -;; If there are min-lines lines above and below the region, then don't do -;; anything. -;; If not, recenter the region to make it so. -;; If that isn't possible, remove context lines balancedly from top and bottom -;; so the entire region shows. -;; If that isn't possible, show the top of the region. -;; BEG must be at the beginning of a line. -(defun emerge-position-region (beg end pos) - ;; First test whether the entire region is visible with - ;; emerge-min-visible-lines above and below it - (if (not (and (<= (progn - (move-to-window-line emerge-min-visible-lines) - (point)) - beg) - (<= end (progn - (move-to-window-line - (- (1+ emerge-min-visible-lines))) - (point))))) - ;; We failed that test, see if it fits at all - ;; Meanwhile positioning it correctly in case it doesn't fit - (progn - (set-window-start (selected-window) beg) - (if (pos-visible-in-window-p end) - ;; Determine the number of lines that the region occupies - (let ((lines 0)) - (while (> end (progn - (move-to-window-line lines) - (point))) - (setq lines (1+ lines))) - ;; And position the beginning on the right line - (goto-char beg) - (recenter (/ (1+ (- (1- (window-height (selected-window))) - lines)) - 2)))))) - (goto-char pos)) - -(defun emerge-next-difference () - "Advance to the next difference." - (interactive) - (if (< emerge-current-difference emerge-number-of-differences) - (let ((n (1+ emerge-current-difference))) - (while (and emerge-skip-prefers - (< n emerge-number-of-differences) - (memq (aref (aref emerge-difference-list n) 6) - '(prefer-A prefer-B))) - (setq n (1+ n))) - (let ((buffer-read-only nil)) - (emerge-unselect-and-select-difference n))) - (error "At end"))) - -(defun emerge-previous-difference () - "Go to the previous difference." - (interactive) - (if (> emerge-current-difference -1) - (let ((n (1- emerge-current-difference))) - (while (and emerge-skip-prefers - (> n -1) - (memq (aref (aref emerge-difference-list n) 6) - '(prefer-A prefer-B))) - (setq n (1- n))) - (let ((buffer-read-only nil)) - (emerge-unselect-and-select-difference n))) - (error "At beginning"))) - -(defun emerge-jump-to-difference (difference-number) - "Go to the N-th difference." - (interactive "p") - (let ((buffer-read-only nil)) - (setq difference-number (1- difference-number)) - (if (and (>= difference-number -1) - (< difference-number (1+ emerge-number-of-differences))) - (emerge-unselect-and-select-difference difference-number) - (error "Bad difference number")))) - -(defun emerge-abort () - "Abort the Emerge session." - (interactive) - (emerge-quit t)) - -(defun emerge-quit (arg) - "Finish the Emerge session and exit Emerge. -Prefix argument means to abort rather than successfully finish. -The difference depends on how the merge was started, -but usually means to not write over one of the original files, or to signal -to some process which invoked Emerge a failure code. - -Unselects the selected difference, if any, restores the read-only and modified -flags of the merged file buffers, restores the local keymap of the merge -buffer, and sets off various emerge flags. Using Emerge commands in this -buffer after this will cause serious problems." - (interactive "P") - (if (prog1 - (y-or-n-p - (if (not arg) - "Do you really want to successfully finish this merge? " - "Do you really want to abort this merge? ")) - (message "")) - (emerge-really-quit arg))) - -;; Perform the quit operations. -(defun emerge-really-quit (arg) - (setq buffer-read-only nil) - (emerge-unselect-and-select-difference -1) - (emerge-restore-buffer-characteristics) - ;; null out the difference markers so they don't slow down future editing - ;; operations - (mapc (function (lambda (d) - (set-marker (aref d 0) nil) - (set-marker (aref d 1) nil) - (set-marker (aref d 2) nil) - (set-marker (aref d 3) nil) - (set-marker (aref d 4) nil) - (set-marker (aref d 5) nil))) - emerge-difference-list) - ;; allow them to be garbage collected - (setq emerge-difference-list nil) - ;; restore the local map - (use-local-map emerge-old-keymap) - ;; turn off all the emerge modes - (setq emerge-mode nil) - (setq emerge-fast-mode nil) - (setq emerge-edit-mode nil) - (setq emerge-auto-advance nil) - (setq emerge-skip-prefers nil) - ;; restore mode line - (kill-local-variable 'mode-line-buffer-identification) - (let ((emerge-prefix-argument arg)) - (run-hooks 'emerge-quit-hook))) - -(defun emerge-select-A (&optional force) - "Select the A variant of this difference. -Refuses to function if this difference has been edited, i.e., if it -is neither the A nor the B variant. -A prefix argument forces the variant to be selected -even if the difference has been edited." - (interactive "P") - (let ((operate - (function (lambda () - (emerge-select-A-edit merge-begin merge-end A-begin A-end) - (if emerge-auto-advance - (emerge-next-difference))))) - (operate-no-change - (function (lambda () - (if emerge-auto-advance - (emerge-next-difference)))))) - (emerge-select-version force operate-no-change operate operate))) - -;; Actually select the A variant -(defun emerge-select-A-edit (merge-begin merge-end A-begin A-end) - (emerge-eval-in-buffer - emerge-merge-buffer - (delete-region merge-begin merge-end) - (goto-char merge-begin) - (insert-buffer-substring emerge-A-buffer A-begin A-end) - (goto-char merge-begin) - (aset diff-vector 6 'A) - (emerge-refresh-mode-line))) - -(defun emerge-select-B (&optional force) - "Select the B variant of this difference. -Refuses to function if this difference has been edited, i.e., if it -is neither the A nor the B variant. -A prefix argument forces the variant to be selected -even if the difference has been edited." - (interactive "P") - (let ((operate - (function (lambda () - (emerge-select-B-edit merge-begin merge-end B-begin B-end) - (if emerge-auto-advance - (emerge-next-difference))))) - (operate-no-change - (function (lambda () - (if emerge-auto-advance - (emerge-next-difference)))))) - (emerge-select-version force operate operate-no-change operate))) - -;; Actually select the B variant -(defun emerge-select-B-edit (merge-begin merge-end B-begin B-end) - (emerge-eval-in-buffer - emerge-merge-buffer - (delete-region merge-begin merge-end) - (goto-char merge-begin) - (insert-buffer-substring emerge-B-buffer B-begin B-end) - (goto-char merge-begin) - (aset diff-vector 6 'B) - (emerge-refresh-mode-line))) - -(defun emerge-default-A () - "Make the A variant the default from here down. -This selects the A variant for all differences from here down in the buffer -which are still defaulted, i.e., which the user has not selected and for -which there is no preference." - (interactive) - (let ((buffer-read-only nil)) - (let ((selected-difference emerge-current-difference) - (n (max emerge-current-difference 0))) - (while (< n emerge-number-of-differences) - (let ((diff-vector (aref emerge-difference-list n))) - (if (eq (aref diff-vector 6) 'default-B) - (progn - (emerge-unselect-and-select-difference n t) - (emerge-select-A) - (aset diff-vector 6 'default-A)))) - (setq n (1+ n)) - (if (zerop (% n 10)) - (message "Setting default to A...%d" n))) - (emerge-unselect-and-select-difference selected-difference))) - (message "Default choice is now A")) - -(defun emerge-default-B () - "Make the B variant the default from here down. -This selects the B variant for all differences from here down in the buffer -which are still defaulted, i.e., which the user has not selected and for -which there is no preference." - (interactive) - (let ((buffer-read-only nil)) - (let ((selected-difference emerge-current-difference) - (n (max emerge-current-difference 0))) - (while (< n emerge-number-of-differences) - (let ((diff-vector (aref emerge-difference-list n))) - (if (eq (aref diff-vector 6) 'default-A) - (progn - (emerge-unselect-and-select-difference n t) - (emerge-select-B) - (aset diff-vector 6 'default-B)))) - (setq n (1+ n)) - (if (zerop (% n 10)) - (message "Setting default to B...%d" n))) - (emerge-unselect-and-select-difference selected-difference))) - (message "Default choice is now B")) - -(defun emerge-fast-mode () - "Set fast mode, for Emerge. -In this mode ordinary Emacs commands are disabled, and Emerge commands -need not be prefixed with \\\\[emerge-basic-keymap]." - (interactive) - (setq buffer-read-only t) - (use-local-map emerge-fast-keymap) - (setq emerge-mode t) - (setq emerge-fast-mode t) - (setq emerge-edit-mode nil) - (message "Fast mode set") - (force-mode-line-update)) - -(defun emerge-edit-mode () - "Set edit mode, for Emerge. -In this mode ordinary Emacs commands are available, and Emerge commands -must be prefixed with \\\\[emerge-basic-keymap]." - (interactive) - (setq buffer-read-only nil) - (use-local-map emerge-edit-keymap) - (setq emerge-mode t) - (setq emerge-fast-mode nil) - (setq emerge-edit-mode t) - (message "Edit mode set") - (force-mode-line-update)) - -(defun emerge-auto-advance (arg) - "Toggle Auto-Advance mode, for Emerge. -This mode causes `emerge-select-A' and `emerge-select-B' to automatically -advance to the next difference. -With a positive argument, turn on Auto-Advance mode. -With a negative argument, turn off Auto-Advance mode." - (interactive "P") - (setq emerge-auto-advance (if (null arg) - (not emerge-auto-advance) - (> (prefix-numeric-value arg) 0))) - (message (if emerge-auto-advance - "Auto-advance set" - "Auto-advance cleared")) - (force-mode-line-update)) - -(defun emerge-skip-prefers (arg) - "Toggle Skip-Prefers mode, for Emerge. -This mode causes `emerge-next-difference' and `emerge-previous-difference' -to automatically skip over differences for which there is a preference. -With a positive argument, turn on Skip-Prefers mode. -With a negative argument, turn off Skip-Prefers mode." - (interactive "P") - (setq emerge-skip-prefers (if (null arg) - (not emerge-skip-prefers) - (> (prefix-numeric-value arg) 0))) - (message (if emerge-skip-prefers - "Skip-prefers set" - "Skip-prefers cleared")) - (force-mode-line-update)) - -(defun emerge-copy-as-kill-A () - "Put the A variant of this difference in the kill ring." - (interactive) - (emerge-validate-difference) - (let* ((diff-vector - (aref emerge-difference-list emerge-current-difference)) - (A-begin (1+ (aref diff-vector 0))) - (A-end (1- (aref diff-vector 1))) - ;; so further kills don't append - this-command) - (with-current-buffer emerge-A-buffer - (copy-region-as-kill A-begin A-end)))) - -(defun emerge-copy-as-kill-B () - "Put the B variant of this difference in the kill ring." - (interactive) - (emerge-validate-difference) - (let* ((diff-vector - (aref emerge-difference-list emerge-current-difference)) - (B-begin (1+ (aref diff-vector 2))) - (B-end (1- (aref diff-vector 3))) - ;; so further kills don't append - this-command) - (with-current-buffer emerge-B-buffer - (copy-region-as-kill B-begin B-end)))) - -(defun emerge-insert-A (arg) - "Insert the A variant of this difference at the point. -Leaves point after text, mark before. -With prefix argument, puts point before, mark after." - (interactive "P") - (emerge-validate-difference) - (let* ((diff-vector - (aref emerge-difference-list emerge-current-difference)) - (A-begin (1+ (aref diff-vector 0))) - (A-end (1- (aref diff-vector 1))) - (opoint (point)) - (buffer-read-only nil)) - (insert-buffer-substring emerge-A-buffer A-begin A-end) - (if (not arg) - (set-mark opoint) - (set-mark (point)) - (goto-char opoint)))) - -(defun emerge-insert-B (arg) - "Insert the B variant of this difference at the point. -Leaves point after text, mark before. -With prefix argument, puts point before, mark after." - (interactive "P") - (emerge-validate-difference) - (let* ((diff-vector - (aref emerge-difference-list emerge-current-difference)) - (B-begin (1+ (aref diff-vector 2))) - (B-end (1- (aref diff-vector 3))) - (opoint (point)) - (buffer-read-only nil)) - (insert-buffer-substring emerge-B-buffer B-begin B-end) - (if (not arg) - (set-mark opoint) - (set-mark (point)) - (goto-char opoint)))) - -(defun emerge-mark-difference (arg) - "Leaves the point before this difference and the mark after it. -With prefix argument, puts mark before, point after." - (interactive "P") - (emerge-validate-difference) - (let* ((diff-vector - (aref emerge-difference-list emerge-current-difference)) - (merge-begin (1+ (aref diff-vector 4))) - (merge-end (1- (aref diff-vector 5)))) - (if (not arg) - (progn - (goto-char merge-begin) - (set-mark merge-end)) - (goto-char merge-end) - (set-mark merge-begin)))) - -(defun emerge-file-names () - "Show the names of the buffers or files being operated on by Emerge. -Use C-u l to reset the windows afterward." - (interactive) - (delete-other-windows) - (let ((temp-buffer-show-function - (function (lambda (buf) - (split-window-vertically) - (switch-to-buffer buf) - (other-window 1))))) - (with-output-to-temp-buffer "*Help*" - (emerge-eval-in-buffer emerge-A-buffer - (if buffer-file-name - (progn - (princ "File A is: ") - (princ buffer-file-name)) - (progn - (princ "Buffer A is: ") - (princ (buffer-name)))) - (princ "\n")) - (emerge-eval-in-buffer emerge-B-buffer - (if buffer-file-name - (progn - (princ "File B is: ") - (princ buffer-file-name)) - (progn - (princ "Buffer B is: ") - (princ (buffer-name)))) - (princ "\n")) - (if emerge-ancestor-buffer - (emerge-eval-in-buffer emerge-ancestor-buffer - (if buffer-file-name - (progn - (princ "Ancestor file is: ") - (princ buffer-file-name)) - (progn - (princ "Ancestor buffer is: ") - (princ (buffer-name)))) - (princ "\n"))) - (princ emerge-output-description) - (with-current-buffer standard-output - (help-mode))))) - -(defun emerge-join-differences (arg) - "Join the selected difference with the following one. -With a prefix argument, join with the preceding one." - (interactive "P") - (let ((n emerge-current-difference)) - ;; adjust n to be first difference to join - (if arg - (setq n (1- n))) - ;; n and n+1 are the differences to join - ;; check that they are both differences - (if (or (< n 0) (>= n (1- emerge-number-of-differences))) - (error "Incorrect differences to join")) - ;; remove the flags - (emerge-unselect-difference emerge-current-difference) - ;; decrement total number of differences - (setq emerge-number-of-differences (1- emerge-number-of-differences)) - ;; build new differences vector - (let ((i 0) - (new-differences (make-vector emerge-number-of-differences nil))) - (while (< i emerge-number-of-differences) - (aset new-differences i - (cond - ((< i n) (aref emerge-difference-list i)) - ((> i n) (aref emerge-difference-list (1+ i))) - (t (let ((prev (aref emerge-difference-list i)) - (next (aref emerge-difference-list (1+ i)))) - (vector (aref prev 0) - (aref next 1) - (aref prev 2) - (aref next 3) - (aref prev 4) - (aref next 5) - (let ((ps (aref prev 6)) - (ns (aref next 6))) - (cond - ((eq ps ns) - ps) - ((and (or (eq ps 'B) (eq ps 'prefer-B)) - (or (eq ns 'B) (eq ns 'prefer-B))) - 'B) - (t 'A)))))))) - (setq i (1+ i))) - (setq emerge-difference-list new-differences)) - ;; set the current difference correctly - (setq emerge-current-difference n) - ;; fix the mode line - (emerge-refresh-mode-line) - ;; reinsert the flags - (emerge-select-difference emerge-current-difference) - (emerge-recenter))) - -(defun emerge-split-difference () - "Split the current difference where the points are in the three windows." - (interactive) - (let ((n emerge-current-difference)) - ;; check that this is a valid difference - (emerge-validate-difference) - ;; get the point values and old difference - (let ((A-point (emerge-eval-in-buffer emerge-A-buffer - (point-marker))) - (B-point (emerge-eval-in-buffer emerge-B-buffer - (point-marker))) - (merge-point (point-marker)) - (old-diff (aref emerge-difference-list n))) - ;; check location of the points, give error if they aren't in the - ;; differences - (if (or (< A-point (aref old-diff 0)) - (> A-point (aref old-diff 1))) - (error "Point outside of difference in A buffer")) - (if (or (< B-point (aref old-diff 2)) - (> B-point (aref old-diff 3))) - (error "Point outside of difference in B buffer")) - (if (or (< merge-point (aref old-diff 4)) - (> merge-point (aref old-diff 5))) - (error "Point outside of difference in merge buffer")) - ;; remove the flags - (emerge-unselect-difference emerge-current-difference) - ;; increment total number of differences - (setq emerge-number-of-differences (1+ emerge-number-of-differences)) - ;; build new differences vector - (let ((i 0) - (new-differences (make-vector emerge-number-of-differences nil))) - (while (< i emerge-number-of-differences) - (aset new-differences i - (cond - ((< i n) - (aref emerge-difference-list i)) - ((> i (1+ n)) - (aref emerge-difference-list (1- i))) - ((= i n) - (vector (aref old-diff 0) - A-point - (aref old-diff 2) - B-point - (aref old-diff 4) - merge-point - (aref old-diff 6))) - (t - (vector (copy-marker A-point) - (aref old-diff 1) - (copy-marker B-point) - (aref old-diff 3) - (copy-marker merge-point) - (aref old-diff 5) - (aref old-diff 6))))) - (setq i (1+ i))) - (setq emerge-difference-list new-differences)) - ;; set the current difference correctly - (setq emerge-current-difference n) - ;; fix the mode line - (emerge-refresh-mode-line) - ;; reinsert the flags - (emerge-select-difference emerge-current-difference) - (emerge-recenter)))) - -(defun emerge-trim-difference () - "Trim lines off top and bottom of difference that are the same. -If lines are the same in both the A and the B versions, strip them off. -\(This can happen when the A and B versions have common lines that the -ancestor version does not share.)" - (interactive) - ;; make sure we are in a real difference - (emerge-validate-difference) - ;; remove the flags - (emerge-unselect-difference emerge-current-difference) - (let* ((diff (aref emerge-difference-list emerge-current-difference)) - (top-a (marker-position (aref diff 0))) - (bottom-a (marker-position (aref diff 1))) - (top-b (marker-position (aref diff 2))) - (bottom-b (marker-position (aref diff 3))) - (top-m (marker-position (aref diff 4))) - (bottom-m (marker-position (aref diff 5))) - size success sa sb sm) - ;; move down the tops of the difference regions as much as possible - ;; Try advancing comparing 1000 chars at a time. - ;; When that fails, go 500 chars at a time, and so on. - (setq size 1000) - (while (> size 0) - (setq success t) - (while success - (setq size (min size (- bottom-a top-a) (- bottom-b top-b) - (- bottom-m top-m))) - (setq sa (emerge-eval-in-buffer emerge-A-buffer - (buffer-substring top-a - (+ size top-a)))) - (setq sb (emerge-eval-in-buffer emerge-B-buffer - (buffer-substring top-b - (+ size top-b)))) - (setq sm (buffer-substring top-m (+ size top-m))) - (setq success (and (> size 0) (equal sa sb) (equal sb sm))) - (if success - (setq top-a (+ top-a size) - top-b (+ top-b size) - top-m (+ top-m size)))) - (setq size (/ size 2))) - ;; move up the bottoms of the difference regions as much as possible - ;; Try advancing comparing 1000 chars at a time. - ;; When that fails, go 500 chars at a time, and so on. - (setq size 1000) - (while (> size 0) - (setq success t) - (while success - (setq size (min size (- bottom-a top-a) (- bottom-b top-b) - (- bottom-m top-m))) - (setq sa (emerge-eval-in-buffer emerge-A-buffer - (buffer-substring (- bottom-a size) - bottom-a))) - (setq sb (emerge-eval-in-buffer emerge-B-buffer - (buffer-substring (- bottom-b size) - bottom-b))) - (setq sm (buffer-substring (- bottom-m size) bottom-m)) - (setq success (and (> size 0) (equal sa sb) (equal sb sm))) - (if success - (setq bottom-a (- bottom-a size) - bottom-b (- bottom-b size) - bottom-m (- bottom-m size)))) - (setq size (/ size 2))) - ;; {top,bottom}-{a,b,m} are now set at the new beginnings and ends - ;; of the difference regions. Move them to the beginning of lines, as - ;; appropriate. - (emerge-eval-in-buffer emerge-A-buffer - (goto-char top-a) - (beginning-of-line) - (aset diff 0 (point-marker)) - (goto-char bottom-a) - (beginning-of-line 2) - (aset diff 1 (point-marker))) - (emerge-eval-in-buffer emerge-B-buffer - (goto-char top-b) - (beginning-of-line) - (aset diff 2 (point-marker)) - (goto-char bottom-b) - (beginning-of-line 2) - (aset diff 3 (point-marker))) - (goto-char top-m) - (beginning-of-line) - (aset diff 4 (point-marker)) - (goto-char bottom-m) - (beginning-of-line 2) - (aset diff 5 (point-marker)) - ;; put the flags back in, recenter the display - (emerge-select-difference emerge-current-difference) - (emerge-recenter))) - -;; FIXME the manual advertised this as working in the A or B buffers, -;; but it does not, because all the buffer locals are nil there. -;; It would work to call it from the merge buffer and specify that one -;; wants to use the value of point in the A or B buffer. -;; But with the prefix argument already in use, there is no easy way -;; to have it ask for a buffer. -(defun emerge-find-difference (arg) - "Find the difference containing the current position of the point. -If there is no containing difference and the prefix argument is positive, -it finds the nearest following difference. A negative prefix argument finds -the nearest previous difference." - (interactive "P") - (cond ((eq (current-buffer) emerge-A-buffer) - (emerge-find-difference-A arg)) - ((eq (current-buffer) emerge-B-buffer) - (emerge-find-difference-B arg)) - (t (emerge-find-difference-merge arg)))) - -(defun emerge-find-difference-merge (arg) - "Find the difference containing point, in the merge buffer. -If there is no containing difference and the prefix argument is positive, -it finds the nearest following difference. A negative prefix argument finds -the nearest previous difference." - (interactive "P") - ;; search for the point in the merge buffer, using the markers - ;; for the beginning and end of the differences in the merge buffer - (emerge-find-difference1 arg (point) 4 5)) - -(defun emerge-find-difference-A (arg) - "Find the difference containing point, in the A buffer. -This command must be executed in the merge buffer. -If there is no containing difference and the prefix argument is positive, -it finds the nearest following difference. A negative prefix argument finds -the nearest previous difference." - (interactive "P") - ;; search for the point in the A buffer, using the markers - ;; for the beginning and end of the differences in the A buffer - (emerge-find-difference1 arg - (emerge-eval-in-buffer emerge-A-buffer (point)) - 0 1)) - -(defun emerge-find-difference-B (arg) - "Find the difference containing point, in the B buffer. -This command must be executed in the merge buffer. -If there is no containing difference and the prefix argument is positive, -it finds the nearest following difference. A negative prefix argument finds -the nearest previous difference." - (interactive "P") - ;; search for the point in the B buffer, using the markers - ;; for the beginning and end of the differences in the B buffer - (emerge-find-difference1 arg - (emerge-eval-in-buffer emerge-B-buffer (point)) - 2 3)) - -(defun emerge-find-difference1 (arg location begin end) - (let* ((index - ;; find first difference containing or after the current position - (catch 'search - (let ((n 0)) - (while (< n emerge-number-of-differences) - (let ((diff-vector (aref emerge-difference-list n))) - (if (<= location (marker-position (aref diff-vector end))) - (throw 'search n))) - (setq n (1+ n)))) - emerge-number-of-differences)) - (contains - ;; whether the found difference contains the current position - (and (< index emerge-number-of-differences) - (<= (marker-position (aref (aref emerge-difference-list index) - begin)) - location))) - (arg-value - ;; numeric value of prefix argument - (prefix-numeric-value arg))) - (emerge-unselect-and-select-difference - (cond - ;; if the point is in a difference, select it - (contains index) - ;; if the arg is nil and the point is not in a difference, error - ((null arg) (error "No difference contains point")) - ;; if the arg is positive, select the following difference - ((> arg-value 0) - (if (< index emerge-number-of-differences) - index - (error "No difference contains or follows point"))) - ;; if the arg is negative, select the preceding difference - (t - (if (> index 0) - (1- index) - (error "No difference contains or precedes point"))))))) - -(defun emerge-line-numbers () - "Display the current line numbers. -This function displays the line numbers of the points in the A, B, and -merge buffers." - (interactive) - (let* ((valid-diff - (and (>= emerge-current-difference 0) - (< emerge-current-difference emerge-number-of-differences))) - (diff (and valid-diff - (aref emerge-difference-list emerge-current-difference))) - (merge-line (emerge-line-number-in-buf 4 5)) - (A-line (emerge-eval-in-buffer emerge-A-buffer - (emerge-line-number-in-buf 0 1))) - (B-line (emerge-eval-in-buffer emerge-B-buffer - (emerge-line-number-in-buf 2 3)))) - (message "At lines: merge = %d, A = %d, B = %d" - merge-line A-line B-line))) - -(defun emerge-line-number-in-buf (begin-marker end-marker) - (let (temp) - (setq temp (save-excursion - (beginning-of-line) - (1+ (count-lines 1 (point))))) - (if valid-diff - (progn - (if (> (point) (aref diff begin-marker)) - (setq temp (- temp emerge-before-flag-lines))) - (if (> (point) (aref diff end-marker)) - (setq temp (- temp emerge-after-flag-lines))))) - temp)) - -(defun emerge-set-combine-template (string &optional localize) - "Set `emerge-combine-versions-template' to STRING. -This value controls how `emerge-combine-versions' combines the two versions. -With prefix argument, `emerge-combine-versions-template' is made local to this -merge buffer. Localization is permanent for any particular merge buffer." - (interactive "s\nP") - (if localize - (make-local-variable 'emerge-combine-versions-template)) - (setq emerge-combine-versions-template string) - (message - (if (assq 'emerge-combine-versions-template (buffer-local-variables)) - "emerge-set-combine-versions-template set locally" - "emerge-set-combine-versions-template set"))) - -(defun emerge-set-combine-versions-template (start end &optional localize) - "Copy region into `emerge-combine-versions-template'. -This controls how `emerge-combine-versions' will combine the two versions. -With prefix argument, `emerge-combine-versions-template' is made local to this -merge buffer. Localization is permanent for any particular merge buffer." - (interactive "r\nP") - (if localize - (make-local-variable 'emerge-combine-versions-template)) - (setq emerge-combine-versions-template (buffer-substring start end)) - (message - (if (assq 'emerge-combine-versions-template (buffer-local-variables)) - "emerge-set-combine-versions-template set locally." - "emerge-set-combine-versions-template set."))) - -(defun emerge-combine-versions (&optional force) - "Combine versions using the template in `emerge-combine-versions-template'. -Refuses to function if this difference has been edited, i.e., if it is -neither the A nor the B variant. -An argument forces the variant to be selected even if the difference has -been edited." - (interactive "P") - (emerge-combine-versions-internal emerge-combine-versions-template force)) - -(defun emerge-combine-versions-register (char &optional force) - "Combine the two versions using the template in register REG. -See documentation of the variable `emerge-combine-versions-template' -for how the template is interpreted. -Refuses to function if this difference has been edited, i.e., if it is -neither the A nor the B variant. -An argument forces the variant to be selected even if the difference has -been edited." - (interactive "cRegister containing template: \nP") - (let ((template (get-register char))) - (if (not (stringp template)) - (error "Register does not contain text")) - (emerge-combine-versions-internal template force))) - -(defun emerge-combine-versions-internal (template force) - (let ((operate - (function (lambda () - (emerge-combine-versions-edit merge-begin merge-end - A-begin A-end B-begin B-end) - (if emerge-auto-advance - (emerge-next-difference)))))) - (emerge-select-version force operate operate operate))) - -(defun emerge-combine-versions-edit (merge-begin merge-end - A-begin A-end B-begin B-end) - (emerge-eval-in-buffer - emerge-merge-buffer - (delete-region merge-begin merge-end) - (goto-char merge-begin) - (let ((i 0)) - (while (< i (length template)) - (let ((c (aref template i))) - (if (= c ?%) - (progn - (setq i (1+ i)) - (setq c - (condition-case nil - (aref template i) - (error ?%))) - (cond ((= c ?a) - (insert-buffer-substring emerge-A-buffer A-begin A-end)) - ((= c ?b) - (insert-buffer-substring emerge-B-buffer B-begin B-end)) - ((= c ?%) - (insert ?%)) - (t - (insert c)))) - (insert c))) - (setq i (1+ i)))) - (goto-char merge-begin) - (aset diff-vector 6 'combined) - (emerge-refresh-mode-line))) - -(defun emerge-set-merge-mode (mode) - "Set the major mode in a merge buffer. -Overrides any change that the mode might make to the mode line or local -keymap. Leaves merge in fast mode." - (interactive - (list (intern (completing-read "New major mode for merge buffer: " - obarray 'commandp t nil)))) - (funcall mode) - (emerge-refresh-mode-line) - (if emerge-fast-mode - (emerge-fast-mode) - (emerge-edit-mode))) - -(defun emerge-one-line-window () - (interactive) - (let ((window-min-height 1)) - (shrink-window (- (window-height) 2)))) - -;;; Support routines - -;; Select a difference by placing the visual flags around the appropriate -;; group of lines in the A, B, and merge buffers -(defun emerge-select-difference (n) - (let ((emerge-globalized-difference-list emerge-difference-list) - (emerge-globalized-number-of-differences emerge-number-of-differences)) - (emerge-place-flags-in-buffer emerge-A-buffer n 0 1) - (emerge-place-flags-in-buffer emerge-B-buffer n 2 3) - (emerge-place-flags-in-buffer nil n 4 5)) - (run-hooks 'emerge-select-hook)) - -(defun emerge-place-flags-in-buffer (buffer difference before-index - after-index) - (if buffer - (emerge-eval-in-buffer - buffer - (emerge-place-flags-in-buffer1 difference before-index after-index)) - (emerge-place-flags-in-buffer1 difference before-index after-index))) - -(defun emerge-place-flags-in-buffer1 (difference before-index after-index) - (let ((buffer-read-only nil)) - ;; insert the flag before the difference - (let ((before (aref (aref emerge-globalized-difference-list difference) - before-index)) - here) - (goto-char before) - ;; insert the flag itself - (insert-before-markers emerge-before-flag) - (setq here (point)) - ;; Put the marker(s) referring to this position 1 character before the - ;; end of the flag, so it won't be damaged by the user. - ;; This gets a bit tricky, as there could be a number of markers - ;; that have to be moved. - (set-marker before (1- before)) - (let ((n (1- difference)) after-marker before-marker diff-list) - (while (and - (>= n 0) - (progn - (setq diff-list (aref emerge-globalized-difference-list n) - after-marker (aref diff-list after-index)) - (= after-marker here))) - (set-marker after-marker (1- after-marker)) - (setq before-marker (aref diff-list before-index)) - (if (= before-marker here) - (setq before-marker (1- before-marker))) - (setq n (1- n))))) - ;; insert the flag after the difference - (let* ((after (aref (aref emerge-globalized-difference-list difference) - after-index)) - (here (marker-position after))) - (goto-char here) - ;; insert the flag itself - (insert emerge-after-flag) - ;; Put the marker(s) referring to this position 1 character after the - ;; beginning of the flag, so it won't be damaged by the user. - ;; This gets a bit tricky, as there could be a number of markers - ;; that have to be moved. - (set-marker after (1+ after)) - (let ((n (1+ difference)) before-marker after-marker diff-list) - (while (and - (< n emerge-globalized-number-of-differences) - (progn - (setq diff-list (aref emerge-globalized-difference-list n) - before-marker (aref diff-list before-index)) - (= before-marker here))) - (set-marker before-marker (1+ before-marker)) - (setq after-marker (aref diff-list after-index)) - (if (= after-marker here) - (setq after-marker (1+ after-marker))) - (setq n (1+ n))))))) - -;; Unselect a difference by removing the visual flags in the buffers. -(defun emerge-unselect-difference (n) - (let ((diff-vector (aref emerge-difference-list n))) - (emerge-remove-flags-in-buffer emerge-A-buffer - (aref diff-vector 0) (aref diff-vector 1)) - (emerge-remove-flags-in-buffer emerge-B-buffer - (aref diff-vector 2) (aref diff-vector 3)) - (emerge-remove-flags-in-buffer emerge-merge-buffer - (aref diff-vector 4) (aref diff-vector 5))) - (run-hooks 'emerge-unselect-hook)) - -(defun emerge-remove-flags-in-buffer (buffer before after) - (emerge-eval-in-buffer - buffer - (let ((buffer-read-only nil)) - ;; remove the flags, if they're there - (goto-char (- before (1- emerge-before-flag-length))) - (if (looking-at emerge-before-flag-match) - (delete-char emerge-before-flag-length) - ;; the flag isn't there - (ding) - (message "Trouble removing flag")) - (goto-char (1- after)) - (if (looking-at emerge-after-flag-match) - (delete-char emerge-after-flag-length) - ;; the flag isn't there - (ding) - (message "Trouble removing flag"))))) - -;; Select a difference, removing any flags that exist now. -(defun emerge-unselect-and-select-difference (n &optional suppress-display) - (if (and (>= emerge-current-difference 0) - (< emerge-current-difference emerge-number-of-differences)) - (emerge-unselect-difference emerge-current-difference)) - (if (and (>= n 0) (< n emerge-number-of-differences)) - (progn - (emerge-select-difference n) - (let* ((diff-vector (aref emerge-difference-list n)) - (selection-type (aref diff-vector 6))) - (if (eq selection-type 'default-A) - (aset diff-vector 6 'A) - (if (eq selection-type 'default-B) - (aset diff-vector 6 'B)))))) - (setq emerge-current-difference n) - (if (not suppress-display) - (progn - (emerge-recenter) - (emerge-refresh-mode-line)))) - -;; Perform tests to see whether user should be allowed to select a version -;; of this difference: -;; a valid difference has been selected; and -;; the difference text in the merge buffer is: -;; the A version (execute a-version), or -;; the B version (execute b-version), or -;; empty (execute neither-version), or -;; argument FORCE is true (execute neither-version) -;; Otherwise, signal an error. -(defun emerge-select-version (force a-version b-version neither-version) - (emerge-validate-difference) - (let ((buffer-read-only nil)) - (let* ((diff-vector - (aref emerge-difference-list emerge-current-difference)) - (A-begin (1+ (aref diff-vector 0))) - (A-end (1- (aref diff-vector 1))) - (B-begin (1+ (aref diff-vector 2))) - (B-end (1- (aref diff-vector 3))) - (merge-begin (1+ (aref diff-vector 4))) - (merge-end (1- (aref diff-vector 5)))) - (if (emerge-compare-buffers emerge-A-buffer A-begin A-end - emerge-merge-buffer merge-begin - merge-end) - (funcall a-version) - (if (emerge-compare-buffers emerge-B-buffer B-begin B-end - emerge-merge-buffer merge-begin - merge-end) - (funcall b-version) - (if (or force (= merge-begin merge-end)) - (funcall neither-version) - (error "This difference region has been edited"))))))) - -;; Read a file name, handling all of the various defaulting rules. - -(defun emerge-read-file-name (prompt alternative-default-dir default-file - A-file must-match) - ;; `prompt' should not have trailing ": ", so that it can be modified - ;; according to context. - ;; If alternative-default-dir is non-nil, it should be used as the default - ;; directory instead if default-directory, if emerge-default-last-directories - ;; is set. - ;; If default-file is set, it should be used as the default value. - ;; If A-file is set, and its directory is different from - ;; alternative-default-dir, and if emerge-default-last-directories is set, - ;; the default file should be the last part of A-file in the default - ;; directory. (Overriding default-file.) - (cond - ;; If this is not the A-file argument (shown by non-nil A-file), and - ;; if emerge-default-last-directories is set, and - ;; the default directory exists but is not the same as the directory of the - ;; A-file, - ;; then make the default file have the same name as the A-file, but in - ;; the default directory. - ((and emerge-default-last-directories - A-file - alternative-default-dir - (not (string-equal alternative-default-dir - (file-name-directory A-file)))) - (read-file-name (format "%s (default %s): " - prompt (file-name-nondirectory A-file)) - alternative-default-dir - (concat alternative-default-dir - (file-name-nondirectory A-file)) - (and must-match 'confirm))) - ;; If there is a default file, use it. - (default-file - (read-file-name (format "%s (default %s): " prompt default-file) - ;; If emerge-default-last-directories is set, use the - ;; directory from the same argument of the last call of - ;; Emerge as the default for this argument. - (and emerge-default-last-directories - alternative-default-dir) - default-file (and must-match 'confirm))) - (t - (read-file-name (concat prompt ": ") - ;; If emerge-default-last-directories is set, use the - ;; directory from the same argument of the last call of - ;; Emerge as the default for this argument. - (and emerge-default-last-directories - alternative-default-dir) - nil (and must-match 'confirm))))) - -;; Revise the mode line to display which difference we have selected - -(defun emerge-refresh-mode-line () - (setq mode-line-buffer-identification - (list (format "Emerge: %%b diff %d of %d%s" - (1+ emerge-current-difference) - emerge-number-of-differences - (if (and (>= emerge-current-difference 0) - (< emerge-current-difference - emerge-number-of-differences)) - (cdr (assq (aref (aref emerge-difference-list - emerge-current-difference) - 6) - '((A . " - A") - (B . " - B") - (prefer-A . " - A*") - (prefer-B . " - B*") - (combined . " - comb")))) - "")))) - (force-mode-line-update)) - -;; compare two regions in two buffers for containing the same text -(defun emerge-compare-buffers (buffer-x x-begin x-end buffer-y y-begin y-end) - ;; first check that the two regions are the same length - (if (not (and (= (- x-end x-begin) (- y-end y-begin)))) - nil - (catch 'exit - (while (< x-begin x-end) - ;; bite off and compare no more than 1000 characters at a time - (let* ((compare-length (min (- x-end x-begin) 1000)) - (x-string (emerge-eval-in-buffer - buffer-x - (buffer-substring x-begin - (+ x-begin compare-length)))) - (y-string (emerge-eval-in-buffer - buffer-y - (buffer-substring y-begin - (+ y-begin compare-length))))) - (if (not (string-equal x-string y-string)) - (throw 'exit nil) - (setq x-begin (+ x-begin compare-length)) - (setq y-begin (+ y-begin compare-length))))) - t))) - -;; Construct a unique buffer name. -;; The first one tried is prefixsuffix, then prefix<2>suffix, -;; prefix<3>suffix, etc. -(defun emerge-unique-buffer-name (prefix suffix) - (if (null (get-buffer (concat prefix suffix))) - (concat prefix suffix) - (let ((n 2)) - (while (get-buffer (format "%s<%d>%s" prefix n suffix)) - (setq n (1+ n))) - (format "%s<%d>%s" prefix n suffix)))) - -;; Verify that we have a difference selected. -(defun emerge-validate-difference () - (if (not (and (>= emerge-current-difference 0) - (< emerge-current-difference emerge-number-of-differences))) - (error "No difference selected"))) - -;;; Functions for saving and restoring a batch of variables - -;; These functions save (get the values of) and restore (set the values of) -;; a list of variables. The argument is a list of symbols (the names of -;; the variables). A list element can also be a list of two functions, -;; the first of which (when called with no arguments) gets the value, and -;; the second (when called with a value as an argument) sets the value. -;; A "function" is anything that funcall can handle as an argument. - -(defun emerge-save-variables (vars) - (mapcar (function (lambda (v) (if (symbolp v) - (symbol-value v) - (funcall (car v))))) - vars)) - -(defun emerge-restore-variables (vars values) - (while vars - (let ((var (car vars)) - (value (car values))) - (if (symbolp var) - (set var value) - (funcall (car (cdr var)) value))) - (setq vars (cdr vars)) - (setq values (cdr values)))) - -;; Make a temporary file that only we have access to. -;; PREFIX is appended to emerge-temp-file-prefix to make the filename prefix. -(defun emerge-make-temp-file (prefix) - (let (f (old-modes (default-file-modes))) - (unwind-protect - (progn - (set-default-file-modes emerge-temp-file-mode) - (setq f (make-temp-file (concat emerge-temp-file-prefix prefix)))) - (set-default-file-modes old-modes)) - f)) - -;;; Functions that query the user before he can write out the current buffer. - -(defun emerge-query-write-file () - "Ask the user whether to write out an incomplete merge. -If answer is yes, call `write-file' to do so. See `emerge-query-and-call' -for details of the querying process." - (interactive) - (emerge-query-and-call 'write-file)) - -(defun emerge-query-save-buffer () - "Ask the user whether to save an incomplete merge. -If answer is yes, call `save-buffer' to do so. See `emerge-query-and-call' -for details of the querying process." - (interactive) - (emerge-query-and-call 'save-buffer)) - -(defun emerge-query-and-call (command) - "Ask the user whether to save or write out the incomplete merge. -If answer is yes, call COMMAND interactively. During the call, the flags -around the current difference are removed." - (if (yes-or-no-p "Do you really write to write out this unfinished merge? ") - ;; He really wants to do it -- unselect the difference for the duration - (progn - (if (and (>= emerge-current-difference 0) - (< emerge-current-difference emerge-number-of-differences)) - (emerge-unselect-difference emerge-current-difference)) - ;; call-interactively takes the value of current-prefix-arg as the - ;; prefix argument value to be passed to the command. Thus, we have - ;; to do nothing special to make sure the prefix argument is - ;; transmitted to the command. - (call-interactively command) - (if (and (>= emerge-current-difference 0) - (< emerge-current-difference emerge-number-of-differences)) - (progn - (emerge-select-difference emerge-current-difference) - (emerge-recenter)))) - ;; He's being smart and not doing it - (message "Not written"))) - -;; Make sure the current buffer (for a file) has the same contents as the -;; file on disk, and attempt to remedy the situation if not. -;; Signal an error if we can't make them the same, or the user doesn't want -;; to do what is necessary to make them the same. -(defun emerge-verify-file-buffer () - ;; First check if the file has been modified since the buffer visited it. - (if (verify-visited-file-modtime (current-buffer)) - (if (buffer-modified-p) - ;; If buffer is not obsolete and is modified, offer to save - (if (yes-or-no-p (format "Save file %s? " buffer-file-name)) - (save-buffer) - (error "Buffer out of sync for file %s" buffer-file-name)) - ;; If buffer is not obsolete and is not modified, do nothing - nil) - (if (buffer-modified-p) - ;; If buffer is obsolete and is modified, give error - (error "Buffer out of sync for file %s" buffer-file-name) - ;; If buffer is obsolete and is not modified, offer to revert - (if (yes-or-no-p (format "Revert file %s? " buffer-file-name)) - (revert-buffer t t) - (error "Buffer out of sync for file %s" buffer-file-name))))) - -;; Utilities that might have value outside of Emerge. - -;; Set up the mode in the current buffer to duplicate the mode in another -;; buffer. -(defun emerge-copy-modes (buffer) - ;; Set the major mode - (funcall (emerge-eval-in-buffer buffer major-mode))) - -;; Define a key, even if a prefix of it is defined -(defun emerge-force-define-key (keymap key definition) - "Like `define-key', but forcibly creates prefix characters as needed. -If some prefix of KEY has a non-prefix definition, it is redefined." - ;; Find out if a prefix of key is defined - (let ((v (lookup-key keymap key))) - ;; If so, undefine it - (if (integerp v) - (define-key keymap (substring key 0 v) nil))) - ;; Now define the key - (define-key keymap key definition)) - -;;;;; Improvements to describe-mode, so that it describes minor modes as well -;;;;; as the major mode -;;(defun describe-mode (&optional minor) -;; "Display documentation of current major mode. -;;If optional arg MINOR is non-nil (or prefix argument is given if interactive), -;;display documentation of active minor modes as well. -;;For this to work correctly for a minor mode, the mode's indicator variable -;;\(listed in `minor-mode-alist') must also be a function whose documentation -;;describes the minor mode." -;; (interactive) -;; (with-output-to-temp-buffer "*Help*" -;; (princ mode-name) -;; (princ " Mode:\n") -;; (princ (documentation major-mode)) -;; (let ((minor-modes minor-mode-alist) -;; (locals (buffer-local-variables))) -;; (while minor-modes -;; (let* ((minor-mode (car (car minor-modes))) -;; (indicator (car (cdr (car minor-modes)))) -;; (local-binding (assq minor-mode locals))) -;; ;; Document a minor mode if it is listed in minor-mode-alist, -;; ;; bound locally in this buffer, non-nil, and has a function -;; ;; definition. -;; (if (and local-binding -;; (cdr local-binding) -;; (fboundp minor-mode)) -;; (progn -;; (princ (format "\n\n\n%s minor mode (indicator%s):\n" -;; minor-mode indicator)) -;; (princ (documentation minor-mode))))) -;; (setq minor-modes (cdr minor-modes)))) -;; (with-current-buffer standard-output -;; (help-mode)) -;; (help-print-return-message))) - -;; This goes with the redefinition of describe-mode. -;;;; Adjust things so that keyboard macro definitions are documented correctly. -;;(fset 'defining-kbd-macro (symbol-function 'start-kbd-macro)) - -;; substitute-key-definition should work now. -;;;; Function to shadow a definition in a keymap with definitions in another. -;;(defun emerge-shadow-key-definition (olddef newdef keymap shadowmap) -;; "Shadow OLDDEF with NEWDEF for any keys in KEYMAP with entries in SHADOWMAP. -;;In other words, SHADOWMAP will now shadow all definitions of OLDDEF in KEYMAP -;;with NEWDEF. Does not affect keys that are already defined in SHADOWMAP, -;;including those whose definition is OLDDEF." -;; ;; loop through all keymaps accessible from keymap -;; (let ((maps (accessible-keymaps keymap))) -;; (while maps -;; (let ((prefix (car (car maps))) -;; (map (cdr (car maps)))) -;; ;; examine a keymap -;; (if (arrayp map) -;; ;; array keymap -;; (let ((len (length map)) -;; (i 0)) -;; (while (< i len) -;; (if (eq (aref map i) olddef) -;; ;; set the shadowing definition -;; (let ((key (concat prefix (char-to-string i)))) -;; (emerge-define-key-if-possible shadowmap key newdef))) -;; (setq i (1+ i)))) -;; ;; sparse keymap -;; (while map -;; (if (eq (cdr-safe (car-safe map)) olddef) -;; ;; set the shadowing definition -;; (let ((key -;; (concat prefix (char-to-string (car (car map)))))) -;; (emerge-define-key-if-possible shadowmap key newdef))) -;; (setq map (cdr map))))) -;; (setq maps (cdr maps))))) - -;; Define a key if it (or a prefix) is not already defined in the map. -(defun emerge-define-key-if-possible (keymap key definition) - ;; look up the present definition of the key - (let ((present (lookup-key keymap key))) - (if (integerp present) - ;; if it is "too long", look up the valid prefix - (if (not (lookup-key keymap (substring key 0 present))) - ;; if the prefix isn't defined, define it - (define-key keymap key definition)) - ;; if there is no present definition, define it - (if (not present) - (define-key keymap key definition))))) - -;; Ordinary substitute-key-definition should do this now. -;;(defun emerge-recursively-substitute-key-definition (olddef newdef keymap) -;; "Like `substitute-key-definition', but act recursively on subkeymaps. -;;Make sure that subordinate keymaps aren't shared with other keymaps! -;;\(`copy-keymap' will suffice.)" -;; ;; Loop through all keymaps accessible from keymap -;; (let ((maps (accessible-keymaps keymap))) -;; (while maps -;; ;; Substitute in this keymap -;; (substitute-key-definition olddef newdef (cdr (car maps))) -;; (setq maps (cdr maps))))) - -;; Show the name of the file in the buffer. -(defun emerge-show-file-name () - "Displays the name of the file loaded into the current buffer. -If the name won't fit on one line, the minibuffer is expanded to hold it, -and the command waits for a keystroke from the user. If the keystroke is -SPC, it is ignored; if it is anything else, it is processed as a command." - (interactive) - (let ((name (buffer-file-name))) - (or name - (setq name "Buffer has no file name.")) - (save-window-excursion - (select-window (minibuffer-window)) - (unwind-protect - (progn - (erase-buffer) - (insert name) - (while (and (not (pos-visible-in-window-p)) - (not (window-full-height-p))) - (enlarge-window 1)) - (let* ((echo-keystrokes 0) - (c (read-event))) - (if (not (eq c 32)) - (setq unread-command-events (list c))))) - (erase-buffer))))) - -;; Improved auto-save file names. -;; This function fixes many problems with the standard auto-save file names: -;; Auto-save files for non-file buffers get put in the default directory -;; for the buffer, whether that makes sense or not. -;; Auto-save files for file buffers get put in the directory of the file, -;; regardless of whether we can write into it or not. -;; Auto-save files for non-file buffers don't use the process id, so if a -;; user runs more than on Emacs, they can make auto-save files that overwrite -;; each other. -;; To use this function, do: -;; (fset 'make-auto-save-file-name -;; (symbol-function 'emerge-make-auto-save-file-name)) -(defun emerge-make-auto-save-file-name () - "Return file name to use for auto-saves of current buffer. -Does not consider `auto-save-visited-file-name'; -that is checked before calling this function. -You can redefine this for customization. -See also `auto-save-file-name-p'." - (if buffer-file-name - ;; if buffer has a file, try the format /## - (let ((f (concat (file-name-directory buffer-file-name) - "#" - (file-name-nondirectory buffer-file-name) - "#"))) - (if (file-writable-p f) - ;; the file is writable, so use it - f - ;; the file isn't writable, so use the format - ;; ~/#&&# - (concat (getenv "HOME") - "/#&" - (file-name-nondirectory buffer-file-name) - "&" - (emerge-hash-string-into-string - (file-name-directory buffer-file-name)) - "#"))) - ;; if buffer has no file, use the format ~/#%%# - (expand-file-name (concat (getenv "HOME") - "/#%" - ;; quote / into \! and \ into \\ - (emerge-unslashify-name (buffer-name)) - "%" - (make-temp-name "") - "#")))) - -;; Hash a string into five characters more-or-less suitable for use in a file -;; name. (Allowed characters are ! through ~, except /.) -(defun emerge-hash-string-into-string (s) - (let ((bins (vector 0 0 0 0 0)) - (i 0)) - (while (< i (length s)) - (aset bins (% i 5) (% (+ (* (aref bins (% i 5)) 35) - (aref s i)) - 65536)) - (setq i (1+ i))) - (mapconcat (function (lambda (b) - (setq b (+ (% b 93) ?!)) - (if (>= b ?/) - (setq b (1+ b))) - (char-to-string b))) - bins ""))) - -;; Quote any /s in a string by replacing them with \!. -;; Also, replace any \s by \\, to make it one-to-one. -(defun emerge-unslashify-name (s) - (let ((limit 0)) - (while (string-match "[/\\]" s limit) - (setq s (concat (substring s 0 (match-beginning 0)) - (if (string= (substring s (match-beginning 0) - (match-end 0)) - "/") - "\\!" - "\\\\") - (substring s (match-end 0)))) - (setq limit (1+ (match-end 0))))) - s) - -;; Metacharacters that have to be protected from the shell when executing -;; a diff/diff3 command. -(defcustom emerge-metachars "[ \t\n!\"#$&'()*;<=>?[\\^`{|~]" - "Characters that must be quoted with \\ when used in a shell command line. -More precisely, a [...] regexp to match any one such character." - :type 'regexp - :group 'emerge) - -;; Quote metacharacters (using \) when executing a diff/diff3 command. -(defun emerge-protect-metachars (s) - (let ((limit 0)) - (while (string-match emerge-metachars s limit) - (setq s (concat (substring s 0 (match-beginning 0)) - "\\" - (substring s (match-beginning 0)))) - (setq limit (1+ (match-end 0))))) - s) - -(provide 'emerge) - -;; arch-tag: a575f092-6e44-400e-b8a2-4124e9377585 -;;; emerge.el ends here diff -r 681cd08dc0f7 -r e93288477c43 lisp/facemenu.el --- a/lisp/facemenu.el Thu Jun 10 22:43:47 2010 +0000 +++ b/lisp/facemenu.el Sun Jun 13 22:57:55 2010 +0000 @@ -526,15 +526,27 @@ (let* ((opoint (point)) (color-values (color-values (car color))) (light-p (>= (apply 'max color-values) - (* (car (color-values "white")) .5)))) + (* (car (color-values "white")) .5))) + (max-len (max (- (window-width) 33) 20))) (insert (car color)) (indent-to 22) (put-text-property opoint (point) 'face `(:background ,(car color))) (put-text-property (prog1 (point) - (insert " " (if (cdr color) - (mapconcat 'identity (cdr color) ", ") - (car color)))) + (insert " ") + (if (cdr color) + ;; Insert as many color names as possible, fitting max-len. + (let ((names (list (car color))) + (others (cdr color)) + (len (length (car color))) + newlen) + (while (and others + (< (setq newlen (+ len 2 (length (car others)))) + max-len)) + (setq len newlen) + (push (pop others) names)) + (insert (mapconcat 'identity (nreverse names) ", "))) + (insert (car color)))) (point) 'face (list :foreground (car color))) (indent-to (max (- (window-width) 8) 44)) diff -r 681cd08dc0f7 -r e93288477c43 lisp/files.el --- a/lisp/files.el Thu Jun 10 22:43:47 2010 +0000 +++ b/lisp/files.el Sun Jun 13 22:57:55 2010 +0000 @@ -4635,16 +4635,17 @@ (force-mode-line-update)))) (defun make-directory (dir &optional parents) - "Create the directory DIR and any nonexistent parent dirs. -If DIR already exists as a directory, signal an error, unless PARENTS is set. - -Interactively, the default choice of directory to create -is the current default directory for file names. -That is useful when you have visited a file in a nonexistent directory. - -Noninteractively, the second (optional) argument PARENTS says whether -to create parent directories if they don't exist. Interactively, -this happens by default." + "Create the directory DIR and optionally any nonexistent parent dirs. +If DIR already exists as a directory, signal an error, unless +PARENTS is non-nil. + +Interactively, the default choice of directory to create is the +current buffer's default directory. That is useful when you have +visited a file in a nonexistent directory. + +Noninteractively, the second (optional) argument PARENTS, if +non-nil, says whether to create parent directories that don't +exist. Interactively, this happens by default." (interactive (list (read-file-name "Make directory: " default-directory default-directory nil nil) diff -r 681cd08dc0f7 -r e93288477c43 lisp/finder.el --- a/lisp/finder.el Thu Jun 10 22:43:47 2010 +0000 +++ b/lisp/finder.el Sun Jun 13 22:57:55 2010 +0000 @@ -76,6 +76,7 @@ (tex . "supporting code for the TeX formatter") (tools . "programming tools") (unix . "front-ends/assistants for, or emulators of, UNIX-like features") + (vc . "version control") (wp . "word processing") )) diff -r 681cd08dc0f7 -r e93288477c43 lisp/gnus/ChangeLog --- a/lisp/gnus/ChangeLog Thu Jun 10 22:43:47 2010 +0000 +++ b/lisp/gnus/ChangeLog Sun Jun 13 22:57:55 2010 +0000 @@ -1,3 +1,7 @@ +2010-06-12 Romain Francoise + + * gnus-util.el (gnus-date-get-time): Move up before first use. + 2010-06-10 Katsumi Yamaoka * gnus-art.el (gnus-mime-buttonized-part-id): New internal variable. diff -r 681cd08dc0f7 -r e93288477c43 lisp/gnus/gnus-util.el --- a/lisp/gnus/gnus-util.el Thu Jun 10 22:43:47 2010 +0000 +++ b/lisp/gnus/gnus-util.el Sun Jun 13 22:57:55 2010 +0000 @@ -429,6 +429,20 @@ (+ (car now) (* (car (cdr now)) 60) (* (car (nthcdr 2 now)) 3600) (* (- (string-to-number days) 1) 3600 24)))) +(defmacro gnus-date-get-time (date) + "Convert DATE string to Emacs time. +Cache the result as a text property stored in DATE." + ;; Either return the cached value... + `(let ((d ,date)) + (if (equal "" d) + '(0 0) + (or (get-text-property 0 'gnus-time d) + ;; or compute the value... + (let ((time (safe-date-to-time d))) + ;; and store it back in the string. + (put-text-property 0 1 'gnus-time time d) + time))))) + (defvar gnus-user-date-format-alist '(((gnus-seconds-today) . "%k:%M") (604800 . "%a %k:%M") ;;that's one week @@ -480,20 +494,6 @@ (format-time-string "%d-%b" (gnus-date-get-time messy-date)) (error " - "))) -(defmacro gnus-date-get-time (date) - "Convert DATE string to Emacs time. -Cache the result as a text property stored in DATE." - ;; Either return the cached value... - `(let ((d ,date)) - (if (equal "" d) - '(0 0) - (or (get-text-property 0 'gnus-time d) - ;; or compute the value... - (let ((time (safe-date-to-time d))) - ;; and store it back in the string. - (put-text-property 0 1 'gnus-time time d) - time))))) - (defsubst gnus-time-iso8601 (time) "Return a string of TIME in YYYYMMDDTHHMMSS format." (format-time-string "%Y%m%dT%H%M%S" time)) diff -r 681cd08dc0f7 -r e93288477c43 lisp/loadup.el --- a/lisp/loadup.el Thu Jun 10 22:43:47 2010 +0000 +++ b/lisp/loadup.el Sun Jun 13 22:57:55 2010 +0000 @@ -243,8 +243,8 @@ (progn ; floating pt. functions if we have float support. (load "emacs-lisp/float-sup"))) -(load "vc-hooks") -(load "ediff-hook") +(load "vc/vc-hooks") +(load "vc/ediff-hook") (if (fboundp 'x-show-tip) (load "tooltip")) ;If you want additional libraries to be preloaded and their diff -r 681cd08dc0f7 -r e93288477c43 lisp/log-edit.el --- a/lisp/log-edit.el Thu Jun 10 22:43:47 2010 +0000 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,835 +0,0 @@ -;;; log-edit.el --- Major mode for editing CVS commit messages - -;; Copyright (C) 1999, 2000, 2001, 2002, 2003, 2004, 2005, 2006, 2007, -;; 2008, 2009, 2010 Free Software Foundation, Inc. - -;; Author: Stefan Monnier -;; Keywords: pcl-cvs cvs commit log - -;; 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 3 of the License, 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. If not, see . - -;;; Commentary: - -;; Todo: - -;; - Move in VC's code -;; - Add compatibility for VC's hook variables - -;;; Code: - -(eval-when-compile (require 'cl)) -(require 'add-log) ; for all the ChangeLog goodies -(require 'pcvs-util) -(require 'ring) - -;;;; -;;;; Global Variables -;;;; - -(defgroup log-edit nil - "Major mode for editing RCS and CVS commit messages." - :group 'pcl-cvs - :group 'vc ; It's used by VC. - :version "21.1" - :prefix "log-edit-") - -;; compiler pacifiers -(defvar cvs-buffer) - - -;; The main keymap - -(easy-mmode-defmap log-edit-mode-map - `(("\C-c\C-c" . log-edit-done) - ("\C-c\C-a" . log-edit-insert-changelog) - ("\C-c\C-d" . log-edit-show-diff) - ("\C-c\C-f" . log-edit-show-files) - ("\M-n" . log-edit-next-comment) - ("\M-p" . log-edit-previous-comment) - ("\M-r" . log-edit-comment-search-backward) - ("\M-s" . log-edit-comment-search-forward) - ("\C-c?" . log-edit-mode-help)) - "Keymap for the `log-edit-mode' (to edit version control log messages)." - :group 'log-edit) - -;; Compatibility with old names. Should we bother ? -(defvar vc-log-mode-map log-edit-mode-map) -(defvar vc-log-entry-mode vc-log-mode-map) - -(easy-menu-define log-edit-menu log-edit-mode-map - "Menu used for `log-edit-mode'." - '("Log-Edit" - ["Done" log-edit-done - :help "Exit log-edit and proceed with the actual action."] - "--" - ["Insert ChangeLog" log-edit-insert-changelog - :help "Insert a log message by looking at the ChangeLog"] - ["Add to ChangeLog" log-edit-add-to-changelog - :help "Insert this log message into the appropriate ChangeLog file"] - "--" - ["Show diff" log-edit-show-diff - :help "Show the diff for the files to be committed."] - ["List files" log-edit-show-files - :help "Show the list of relevant files."] - "--" - ["Previous comment" log-edit-previous-comment - :help "Cycle backwards through comment history"] - ["Next comment" log-edit-next-comment - :help "Cycle forwards through comment history."] - ["Search comment forward" log-edit-comment-search-forward - :help "Search forwards through comment history for a substring match of str"] - ["Search comment backward" log-edit-comment-search-backward - :help "Search backwards through comment history for substring match of str"])) - -(defcustom log-edit-confirm 'changed - "If non-nil, `log-edit-done' will request confirmation. -If 'changed, only request confirmation if the list of files has - changed since the beginning of the log-edit session." - :group 'log-edit - :type '(choice (const changed) (const t) (const nil))) - -(defcustom log-edit-keep-buffer nil - "If non-nil, don't hide the buffer after `log-edit-done'." - :group 'log-edit - :type 'boolean) - -(defvar cvs-commit-buffer-require-final-newline t) -(make-obsolete-variable 'cvs-commit-buffer-require-final-newline - 'log-edit-require-final-newline - "21.1") - -(defcustom log-edit-require-final-newline - cvs-commit-buffer-require-final-newline - "Enforce a newline at the end of commit log messages. -Enforce it silently if t, query if non-nil and don't do anything if nil." - :group 'log-edit - :type '(choice (const ask) (const t) (const nil))) - -(defcustom log-edit-setup-invert nil - "Non-nil means `log-edit' should invert the meaning of its SETUP arg. -If SETUP is 'force, this variable has no effect." - :group 'log-edit - :type 'boolean) - -(defcustom log-edit-hook '(log-edit-insert-cvs-template - log-edit-show-files - log-edit-insert-changelog) - "Hook run at the end of `log-edit'." - :group 'log-edit - :type '(hook :options (log-edit-insert-changelog - log-edit-insert-cvs-rcstemplate - log-edit-insert-cvs-template - log-edit-insert-filenames))) - -(defcustom log-edit-mode-hook (if (boundp 'vc-log-mode-hook) vc-log-mode-hook) - "Hook run when entering `log-edit-mode'." - :group 'log-edit - :type 'hook) - -(defcustom log-edit-done-hook nil - "Hook run before doing the actual commit. -This hook can be used to cleanup the message, enforce various -conventions, or to allow recording the message in some other database, -such as a bug-tracking system. The list of files about to be committed -can be obtained from `log-edit-files'." - :group 'log-edit - :type '(hook :options (log-edit-set-common-indentation - log-edit-add-to-changelog))) - -(defcustom log-edit-strip-single-file-name nil - "If non-nil, remove file name from single-file log entries." - :type 'boolean - :safe 'booleanp - :group 'log-edit - :version "24.1") - -(defvar cvs-changelog-full-paragraphs t) -(make-obsolete-variable 'cvs-changelog-full-paragraphs - 'log-edit-changelog-full-paragraphs - "21.1") - -(defvar log-edit-changelog-full-paragraphs cvs-changelog-full-paragraphs - "*If non-nil, include full ChangeLog paragraphs in the log. -This may be set in the ``local variables'' section of a ChangeLog, to -indicate the policy for that ChangeLog. - -A ChangeLog paragraph is a bunch of log text containing no blank lines; -a paragraph usually describes a set of changes with a single purpose, -but perhaps spanning several functions in several files. Changes in -different paragraphs are unrelated. - -You could argue that the log entry for a file should contain the -full ChangeLog paragraph mentioning the change to the file, even though -it may mention other files, because that gives you the full context you -need to understand the change. This is the behavior you get when this -variable is set to t. - -On the other hand, you could argue that the log entry for a change -should contain only the text for the changes which occurred in that -file, because the log is per-file. This is the behavior you get -when this variable is set to nil.") - -;;;; Internal global or buffer-local vars - -(defconst log-edit-files-buf "*log-edit-files*") -(defvar log-edit-initial-files nil) -(defvar log-edit-callback nil) -(defvar log-edit-diff-function nil) -(defvar log-edit-listfun nil) - -(defvar log-edit-parent-buffer nil) - -;;; Originally taken from VC-Log mode - -(defconst log-edit-maximum-comment-ring-size 32 - "Maximum number of saved comments in the comment ring.") -(defvar log-edit-comment-ring (make-ring log-edit-maximum-comment-ring-size)) -(defvar log-edit-comment-ring-index nil) -(defvar log-edit-last-comment-match "") - -(defun log-edit-new-comment-index (stride len) - "Return the comment index STRIDE elements from the current one. -LEN is the length of `log-edit-comment-ring'." - (mod (cond - (log-edit-comment-ring-index (+ log-edit-comment-ring-index stride)) - ;; Initialize the index on the first use of this command - ;; so that the first M-p gets index 0, and the first M-n gets - ;; index -1. - ((> stride 0) (1- stride)) - (t stride)) - len)) - -(defun log-edit-previous-comment (arg) - "Cycle backwards through comment history. -With a numeric prefix ARG, go back ARG comments." - (interactive "*p") - (let ((len (ring-length log-edit-comment-ring))) - (if (<= len 0) - (progn (message "Empty comment ring") (ding)) - ;; Don't use `erase-buffer' because we don't want to `widen'. - (delete-region (point-min) (point-max)) - (setq log-edit-comment-ring-index (log-edit-new-comment-index arg len)) - (message "Comment %d" (1+ log-edit-comment-ring-index)) - (insert (ring-ref log-edit-comment-ring log-edit-comment-ring-index))))) - -(defun log-edit-next-comment (arg) - "Cycle forwards through comment history. -With a numeric prefix ARG, go forward ARG comments." - (interactive "*p") - (log-edit-previous-comment (- arg))) - -(defun log-edit-comment-search-backward (str &optional stride) - "Search backwards through comment history for substring match of STR. -If the optional argument STRIDE is present, that is a step-width to use -when going through the comment ring." - ;; Why substring rather than regexp ? -sm - (interactive - (list (read-string "Comment substring: " nil nil log-edit-last-comment-match))) - (unless stride (setq stride 1)) - (if (string= str "") - (setq str log-edit-last-comment-match) - (setq log-edit-last-comment-match str)) - (let* ((str (regexp-quote str)) - (len (ring-length log-edit-comment-ring)) - (n (log-edit-new-comment-index stride len))) - (while (progn (when (or (>= n len) (< n 0)) (error "Not found")) - (not (string-match str (ring-ref log-edit-comment-ring n)))) - (setq n (+ n stride))) - (setq log-edit-comment-ring-index n) - (log-edit-previous-comment 0))) - -(defun log-edit-comment-search-forward (str) - "Search forwards through comment history for a substring match of STR." - (interactive - (list (read-string "Comment substring: " nil nil log-edit-last-comment-match))) - (log-edit-comment-search-backward str -1)) - -(defun log-edit-comment-to-change-log (&optional whoami file-name) - "Enter last VC comment into the change log for the current file. -WHOAMI (interactive prefix) non-nil means prompt for user name -and site. FILE-NAME is the name of the change log; if nil, use -`change-log-default-name'. - -This may be useful as a `log-edit-checkin-hook' to update change logs -automatically." - (interactive (if current-prefix-arg - (list current-prefix-arg - (prompt-for-change-log-name)))) - (let (;; Extract the comment first so we get any error before doing anything. - (comment (ring-ref log-edit-comment-ring 0)) - ;; Don't let add-change-log-entry insert a defun name. - (add-log-current-defun-function 'ignore) - end) - ;; Call add-log to do half the work. - (add-change-log-entry whoami file-name t t) - ;; Insert the VC comment, leaving point before it. - (setq end (save-excursion (insert comment) (point-marker))) - (if (looking-at "\\s *\\s(") - ;; It starts with an open-paren, as in "(foo): Frobbed." - ;; So remove the ": " add-log inserted. - (delete-char -2)) - ;; Canonicalize the white space between the file name and comment. - (just-one-space) - ;; Indent rest of the text the same way add-log indented the first line. - (let ((indentation (current-indentation))) - (save-excursion - (while (< (point) end) - (forward-line 1) - (indent-to indentation)) - (setq end (point)))) - ;; Fill the inserted text, preserving open-parens at bol. - (let ((paragraph-start (concat paragraph-start "\\|\\s *\\s("))) - (beginning-of-line) - (fill-region (point) end)) - ;; Canonicalize the white space at the end of the entry so it is - ;; separated from the next entry by a single blank line. - (skip-syntax-forward " " end) - (delete-char (- (skip-syntax-backward " "))) - (or (eobp) (looking-at "\n\n") - (insert "\n")))) - -;; Compatibility with old names. -(define-obsolete-variable-alias 'vc-comment-ring 'log-edit-comment-ring "22.1") -(define-obsolete-variable-alias 'vc-comment-ring-index 'log-edit-comment-ring-index "22.1") -(define-obsolete-function-alias 'vc-previous-comment 'log-edit-previous-comment "22.1") -(define-obsolete-function-alias 'vc-next-comment 'log-edit-next-comment "22.1") -(define-obsolete-function-alias 'vc-comment-search-reverse 'log-edit-comment-search-backward "22.1") -(define-obsolete-function-alias 'vc-comment-search-forward 'log-edit-comment-search-forward "22.1") -(define-obsolete-function-alias 'vc-comment-to-change-log 'log-edit-comment-to-change-log "22.1") - -;;; -;;; Actual code -;;; - -(defface log-edit-summary '((t :inherit font-lock-function-name-face)) - "Face for the summary in `log-edit-mode' buffers.") - -(defface log-edit-header '((t :inherit font-lock-keyword-face)) - "Face for the headers in `log-edit-mode' buffers.") - -(defface log-edit-unknown-header '((t :inherit font-lock-comment-face)) - "Face for unknown headers in `log-edit-mode' buffers.") - -(defvar log-edit-headers-alist '(("Summary" . log-edit-summary) - ("Fixes") ("Author")) - "AList of known headers and the face to use to highlight them.") - -(defconst log-edit-header-contents-regexp - "[ \t]*\\(.*\\(\n[ \t].*\\)*\\)\n?") - -(defun log-edit-match-to-eoh (limit) - ;; FIXME: copied from message-match-to-eoh. - (let ((start (point))) - (rfc822-goto-eoh) - ;; Typical situation: some temporary change causes the header to be - ;; incorrect, so EOH comes earlier than intended: the last lines of the - ;; intended headers are now not considered part of the header any more, - ;; so they don't have the multiline property set. When the change is - ;; completed and the header has its correct shape again, the lack of the - ;; multiline property means we won't rehighlight the last lines of - ;; the header. - (if (< (point) start) - nil ;No header within start..limit. - ;; Here we disregard LIMIT so that we may extend the area again. - (set-match-data (list start (point))) - (point)))) - -(defvar log-edit-font-lock-keywords - ;; Copied/inspired by message-font-lock-keywords. - `((log-edit-match-to-eoh - (,(concat "^\\(\\([a-z]+\\):\\)" log-edit-header-contents-regexp - "\\|\\(.*\\)") - (progn (goto-char (match-beginning 0)) (match-end 0)) nil - (1 (if (assoc (match-string 2) log-edit-headers-alist) - 'log-edit-header - 'log-edit-unknown-header) - nil lax) - (3 (or (cdr (assoc (match-string 2) log-edit-headers-alist)) - 'log-edit-header) - nil lax) - (4 font-lock-warning-face nil lax))))) - -;;;###autoload -(defun log-edit (callback &optional setup params buffer mode &rest ignore) - "Setup a buffer to enter a log message. -\\The buffer will be put in mode MODE or `log-edit-mode' -if MODE is nil. -If SETUP is non-nil, the buffer is then erased and `log-edit-hook' is run. -Mark and point will be set around the entire contents of the buffer so -that it is easy to kill the contents of the buffer with \\[kill-region]. -Once you're done editing the message, pressing \\[log-edit-done] will call -`log-edit-done' which will end up calling CALLBACK to do the actual commit. - -PARAMS if non-nil is an alist. Possible keys and associated values: - `log-edit-listfun' -- function taking no arguments that returns the list of - files that are concerned by the current operation (using relative names); - `log-edit-diff-function' -- function taking no arguments that - displays a diff of the files concerned by the current operation. - -If BUFFER is non-nil `log-edit' will jump to that buffer, use it to edit the -log message and go back to the current buffer when done. Otherwise, it -uses the current buffer." - (let ((parent (current-buffer))) - (if buffer (pop-to-buffer buffer)) - (when (and log-edit-setup-invert (not (eq setup 'force))) - (setq setup (not setup))) - (when setup - (erase-buffer) - (insert "Summary: ") - (save-excursion (insert "\n\n"))) - (if mode - (funcall mode) - (log-edit-mode)) - (set (make-local-variable 'log-edit-callback) callback) - (if (listp params) - (dolist (crt params) - (set (make-local-variable (car crt)) (cdr crt))) - ;; For backward compatibility with log-edit up to version 22.2 - ;; accept non-list PARAMS to mean `log-edit-list'. - (set (make-local-variable 'log-edit-listfun) params)) - - (if buffer (set (make-local-variable 'log-edit-parent-buffer) parent)) - (set (make-local-variable 'log-edit-initial-files) (log-edit-files)) - (when setup (run-hooks 'log-edit-hook)) - (goto-char (point-min)) (push-mark (point-max)) - (message "%s" (substitute-command-keys - "Press \\[log-edit-done] when you are done editing.")))) - -(define-derived-mode log-edit-mode text-mode "Log-Edit" - "Major mode for editing version-control log messages. -When done editing the log entry, just type \\[log-edit-done] which -will trigger the actual commit of the file(s). -Several other handy support commands are provided of course and -the package from which this is used might also provide additional -commands (under C-x v for VC, for example). - -\\{log-edit-mode-map}" - (set (make-local-variable 'font-lock-defaults) - '(log-edit-font-lock-keywords t t)) - (make-local-variable 'log-edit-comment-ring-index) - (hack-dir-local-variables-non-file-buffer)) - -(defun log-edit-hide-buf (&optional buf where) - (when (setq buf (get-buffer (or buf log-edit-files-buf))) - (let ((win (get-buffer-window buf where))) - (if win (ignore-errors (delete-window win)))) - (bury-buffer buf))) - -(defun log-edit-done () - "Finish editing the log message and commit the files. -If you want to abort the commit, simply delete the buffer." - (interactive) - ;; Clean up empty headers. - (goto-char (point-min)) - (while (looking-at (concat "^[a-z]*:" log-edit-header-contents-regexp)) - (let ((beg (match-beginning 0))) - (goto-char (match-end 0)) - (if (string-match "\\`[ \n\t]*\\'" (match-string 1)) - (delete-region beg (point))))) - ;; Get rid of leading empty lines. - (goto-char (point-min)) - (when (looking-at "\\([ \t]*\n\\)+") - (delete-region (match-beginning 0) (match-end 0))) - ;; Get rid of trailing empty lines - (goto-char (point-max)) - (skip-syntax-backward " ") - (when (equal (char-after) ?\n) (forward-char 1)) - (delete-region (point) (point-max)) - ;; Check for final newline - (if (and (> (point-max) (point-min)) - (/= (char-before (point-max)) ?\n) - (or (eq log-edit-require-final-newline t) - (and log-edit-require-final-newline - (y-or-n-p - (format "Buffer %s does not end in newline. Add one? " - (buffer-name)))))) - (save-excursion - (goto-char (point-max)) - (insert ?\n))) - (let ((comment (buffer-string))) - (when (or (ring-empty-p log-edit-comment-ring) - (not (equal comment (ring-ref log-edit-comment-ring 0)))) - (ring-insert log-edit-comment-ring comment))) - (let ((win (get-buffer-window log-edit-files-buf))) - (if (and log-edit-confirm - (not (and (eq log-edit-confirm 'changed) - (equal (log-edit-files) log-edit-initial-files))) - (progn - (log-edit-show-files) - (not (y-or-n-p "Really commit? ")))) - (progn (when (not win) (log-edit-hide-buf)) - (message "Oh, well! Later maybe?")) - (run-hooks 'log-edit-done-hook) - (log-edit-hide-buf) - (unless (or log-edit-keep-buffer (not log-edit-parent-buffer)) - (cvs-bury-buffer (current-buffer) log-edit-parent-buffer)) - (call-interactively log-edit-callback)))) - -(defun log-edit-files () - "Return the list of files that are about to be committed." - (ignore-errors (funcall log-edit-listfun))) - -(defun log-edit-mode-help () - "Provide help for the `log-edit-mode-map'." - (interactive) - (if (eq last-command 'log-edit-mode-help) - (describe-function major-mode) - (message "%s" - (substitute-command-keys - "Type `\\[log-edit-done]' to finish commit. Try `\\[describe-function] log-edit-done' for more help.")))) - -(defcustom log-edit-common-indent 0 - "Minimum indentation to use in `log-edit-set-common-indentation'." - :group 'log-edit - :type 'integer) - -(defun log-edit-set-common-indentation () - "(Un)Indent the current buffer rigidly to `log-edit-common-indent'." - (save-excursion - (let ((common (point-max))) - (rfc822-goto-eoh) - (while (< (point) (point-max)) - (if (not (looking-at "^[ \t]*$")) - (setq common (min common (current-indentation)))) - (forward-line 1)) - (rfc822-goto-eoh) - (indent-rigidly (point) (point-max) - (- log-edit-common-indent common))))) - -(defun log-edit-show-diff () - "Show the diff for the files to be committed." - (interactive) - (if (functionp log-edit-diff-function) - (funcall log-edit-diff-function) - (error "Diff functionality has not been setup"))) - -(defun log-edit-show-files () - "Show the list of files to be committed." - (interactive) - (let* ((files (log-edit-files)) - (buf (get-buffer-create log-edit-files-buf))) - (with-current-buffer buf - (log-edit-hide-buf buf 'all) - (setq buffer-read-only nil) - (erase-buffer) - (cvs-insert-strings files) - (setq buffer-read-only t) - (goto-char (point-min)) - (save-selected-window - (cvs-pop-to-buffer-same-frame buf) - (shrink-window-if-larger-than-buffer) - (selected-window))))) - -(defun log-edit-insert-cvs-template () - "Insert the template specified by the CVS administrator, if any. -This simply uses the local CVS/Template file." - (interactive) - (when (or (called-interactively-p 'interactive) - (= (point-min) (point-max))) - (when (file-readable-p "CVS/Template") - (insert-file-contents "CVS/Template")))) - -(defun log-edit-insert-cvs-rcstemplate () - "Insert the rcstemplate from the CVS repository. -This contacts the repository to get the rcstemplate file and -can thus take some time." - (interactive) - (when (or (called-interactively-p 'interactive) - (= (point-min) (point-max))) - (when (file-readable-p "CVS/Root") - ;; Ignore the stderr stuff, even if it's an error. - (call-process "cvs" nil '(t nil) nil - "checkout" "-p" "CVSROOT/rcstemplate")))) - -(defun log-edit-insert-filenames () - "Insert the list of files that are to be committed." - (interactive) - (insert "Affected files: \n" - (mapconcat 'identity (log-edit-files) " \n"))) - -(defun log-edit-add-to-changelog () - "Insert this log message into the appropriate ChangeLog file." - (interactive) - ;; Yuck! - (unless (string= (buffer-string) (ring-ref log-edit-comment-ring 0)) - (ring-insert log-edit-comment-ring (buffer-string))) - (dolist (f (log-edit-files)) - (let ((buffer-file-name (expand-file-name f))) - (save-excursion - (log-edit-comment-to-change-log))))) - -(defvar log-edit-changelog-use-first nil) -(defun log-edit-insert-changelog (&optional use-first) - "Insert a log message by looking at the ChangeLog. -The idea is to write your ChangeLog entries first, and then use this -command to commit your changes. - -To select default log text, we: -- find the ChangeLog entries for the files to be checked in, -- verify that the top entry in the ChangeLog is on the current date - and by the current user; if not, we don't provide any default text, -- search the ChangeLog entry for paragraphs containing the names of - the files we're checking in, and finally -- use those paragraphs as the log text. - -If the optional prefix arg USE-FIRST is given (via \\[universal-argument]), -or if the command is repeated a second time in a row, use the first log entry -regardless of user name or time." - (interactive "P") - (let ((eoh (save-excursion (rfc822-goto-eoh) (point)))) - (when (<= (point) eoh) - (goto-char eoh) - (if (looking-at "\n") (forward-char 1)))) - (let ((log-edit-changelog-use-first - (or use-first (eq last-command 'log-edit-insert-changelog)))) - (log-edit-insert-changelog-entries (log-edit-files))) - (log-edit-set-common-indentation) - (goto-char (point-min)) - (when (and log-edit-strip-single-file-name (looking-at "\\*\\s-+")) - (forward-line 1) - (when (not (re-search-forward "^\\*\\s-+" nil t)) - (goto-char (point-min)) - (skip-chars-forward "^():") - (skip-chars-forward ": ") - (delete-region (point-min) (point))))) - -;;;; -;;;; functions for getting commit message from ChangeLog a file... -;;;; Courtesy Jim Blandy -;;;; - -(defun log-edit-narrow-changelog () - "Narrow to the top page of the current buffer, a ChangeLog file. -Actually, the narrowed region doesn't include the date line. -A \"page\" in a ChangeLog file is the area between two dates." - (or (eq major-mode 'change-log-mode) - (error "log-edit-narrow-changelog: current buffer isn't a ChangeLog")) - - (goto-char (point-min)) - - ;; Skip date line and subsequent blank lines. - (forward-line 1) - (if (looking-at "[ \t\n]*\n") - (goto-char (match-end 0))) - - (let ((start (point))) - (forward-page 1) - (narrow-to-region start (point)) - (goto-char (point-min)))) - -(defun log-edit-changelog-paragraph () - "Return the bounds of the ChangeLog paragraph containing point. -If we are between paragraphs, return the previous paragraph." - (beginning-of-line) - (if (looking-at "^[ \t]*$") - (skip-chars-backward " \t\n" (point-min))) - (list (progn - (if (re-search-backward "^[ \t]*\n" nil 'or-to-limit) - (goto-char (match-end 0))) - (point)) - (if (re-search-forward "^[ \t\n]*$" nil t) - (match-beginning 0) - (point-max)))) - -(defun log-edit-changelog-subparagraph () - "Return the bounds of the ChangeLog subparagraph containing point. -A subparagraph is a block of non-blank lines beginning with an asterisk. -If we are between sub-paragraphs, return the previous subparagraph." - (end-of-line) - (if (search-backward "*" nil t) - (list (progn (beginning-of-line) (point)) - (progn - (forward-line 1) - (if (re-search-forward "^[ \t]*[\n*]" nil t) - (match-beginning 0) - (point-max)))) - (list (point) (point)))) - -(defun log-edit-changelog-entry () - "Return the bounds of the ChangeLog entry containing point. -The variable `log-edit-changelog-full-paragraphs' decides whether an -\"entry\" is a paragraph or a subparagraph; see its documentation string -for more details." - (save-excursion - (if log-edit-changelog-full-paragraphs - (log-edit-changelog-paragraph) - (log-edit-changelog-subparagraph)))) - -(defvar user-full-name) -(defvar user-mail-address) -(defun log-edit-changelog-ours-p () - "See if ChangeLog entry at point is for the current user, today. -Return non-nil if it is." - ;; Code adapted from add-change-log-entry. - (let ((name (or (and (boundp 'add-log-full-name) add-log-full-name) - (and (fboundp 'user-full-name) (user-full-name)) - (and (boundp 'user-full-name) user-full-name))) - (mail (or (and (boundp 'add-log-mailing-address) add-log-mailing-address) - ;;(and (fboundp 'user-mail-address) (user-mail-address)) - (and (boundp 'user-mail-address) user-mail-address))) - (time (or (and (boundp 'add-log-time-format) - (functionp add-log-time-format) - (funcall add-log-time-format)) - (format-time-string "%Y-%m-%d")))) - (looking-at (if log-edit-changelog-use-first - "[^ \t]" - (regexp-quote (format "%s %s <%s>" time name mail)))))) - -(defun log-edit-changelog-entries (file) - "Return the ChangeLog entries for FILE, and the ChangeLog they came from. -The return value looks like this: - (LOGBUFFER (ENTRYSTART ENTRYEND) ...) -where LOGBUFFER is the name of the ChangeLog buffer, and each -\(ENTRYSTART . ENTRYEND\) pair is a buffer region." - (let ((changelog-file-name - (let ((default-directory - (file-name-directory (expand-file-name file))) - (visiting-buffer (find-buffer-visiting file))) - ;; If there is a buffer visiting FILE, and it has a local - ;; value for `change-log-default-name', use that. - (if (and visiting-buffer - (local-variable-p 'change-log-default-name - visiting-buffer)) - (with-current-buffer visiting-buffer - change-log-default-name) - ;; `find-change-log' uses `change-log-default-name' if set - ;; and sets it before exiting, so we need to work around - ;; that memoizing which is undesired here - (setq change-log-default-name nil) - (find-change-log))))) - (with-current-buffer (find-file-noselect changelog-file-name) - (unless (eq major-mode 'change-log-mode) (change-log-mode)) - (goto-char (point-min)) - (if (looking-at "\\s-*\n") (goto-char (match-end 0))) - (if (not (log-edit-changelog-ours-p)) - (list (current-buffer)) - (save-restriction - (log-edit-narrow-changelog) - (goto-char (point-min)) - - ;; Search for the name of FILE relative to the ChangeLog. If that - ;; doesn't occur anywhere, they're not using full relative - ;; filenames in the ChangeLog, so just look for FILE; we'll accept - ;; some false positives. - (let ((pattern (file-relative-name - file (file-name-directory changelog-file-name)))) - (if (or (string= pattern "") - (not (save-excursion - (search-forward pattern nil t)))) - (setq pattern (file-name-nondirectory file))) - - (setq pattern (concat "\\(^\\|[^[:alnum:]]\\)" - pattern - "\\($\\|[^[:alnum:]]\\)")) - - (let (texts - (pos (point))) - (while (and (not (eobp)) (re-search-forward pattern nil t)) - (let ((entry (log-edit-changelog-entry))) - (if (< (elt entry 1) (max (1+ pos) (point))) - ;; This is not relevant, actually. - nil - (push entry texts)) - ;; Make sure we make progress. - (setq pos (max (1+ pos) (elt entry 1))) - (goto-char pos))) - - (cons (current-buffer) texts)))))))) - -(defun log-edit-changelog-insert-entries (buffer beg end &rest files) - "Insert the text from BUFFER between BEG and END. -Rename relative filenames in the ChangeLog entry as FILES." - (let ((opoint (point)) - (log-name (buffer-file-name buffer)) - (case-fold-search nil) - bound) - (insert-buffer-substring buffer beg end) - (setq bound (point-marker)) - (when log-name - (dolist (f files) - (save-excursion - (goto-char opoint) - (when (re-search-forward - (concat "\\(^\\|[ \t]\\)\\(" - (file-relative-name f (file-name-directory log-name)) - "\\)[, :\n]") - bound t) - (replace-match f t t nil 2))))) - ;; Eliminate tabs at the beginning of the line. - (save-excursion - (goto-char opoint) - (while (re-search-forward "^\\(\t+\\)" bound t) - (replace-match ""))))) - -(defun log-edit-insert-changelog-entries (files) - "Given a list of files FILES, insert the ChangeLog entries for them." - (let ((log-entries nil)) - ;; Note that any ChangeLog entry can apply to more than one file. - ;; Here we construct a log-entries list with elements of the form - ;; ((LOGBUFFER ENTRYSTART ENTRYEND) FILE1 FILE2...) - (dolist (file files) - (let* ((entries (log-edit-changelog-entries file)) - (buf (car entries)) - key entry) - (dolist (region (cdr entries)) - (setq key (cons buf region)) - (if (setq entry (assoc key log-entries)) - (setcdr entry (append (cdr entry) (list file))) - (push (list key file) log-entries))))) - ;; Now map over log-entries, and extract the strings. - (dolist (log-entry (nreverse log-entries)) - (apply 'log-edit-changelog-insert-entries - (append (car log-entry) (cdr log-entry))) - (insert "\n")))) - -(defun log-edit-extract-headers (headers comment) - "Extract headers from COMMENT to form command line arguments. -HEADERS should be an alist with elements of the form (HEADER . CMDARG) -associating header names to the corresponding cmdline option name and the -result is then a list of the form (MSG CMDARG1 HDRTEXT1 CMDARG2 HDRTEXT2...). -where MSG is the remaining text from STRING. -If \"Summary\" is not in HEADERS, then the \"Summary\" header is extracted -anyway and put back as the first line of MSG." - (with-temp-buffer - (insert comment) - (rfc822-goto-eoh) - (narrow-to-region (point-min) (point)) - (let ((case-fold-search t) - (summary ()) - (res ())) - (dolist (header (if (assoc "Summary" headers) headers - (cons '("Summary" . t) headers))) - (goto-char (point-min)) - (while (re-search-forward (concat "^" (car header) - ":" log-edit-header-contents-regexp) - nil t) - (if (eq t (cdr header)) - (setq summary (match-string 1)) - (push (match-string 1) res) - (push (or (cdr header) (car header)) res)) - (replace-match "" t t))) - ;; Remove header separator if the header is empty. - (widen) - (goto-char (point-min)) - (when (looking-at "\\([ \t]*\n\\)+") - (delete-region (match-beginning 0) (match-end 0))) - (if summary (insert summary "\n")) - (cons (buffer-string) res)))) - -(provide 'log-edit) - -;; arch-tag: 8089b39c-983b-4e83-93cd-ed0a64c7fdcc -;;; log-edit.el ends here diff -r 681cd08dc0f7 -r e93288477c43 lisp/log-view.el --- a/lisp/log-view.el Thu Jun 10 22:43:47 2010 +0000 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,545 +0,0 @@ -;;; log-view.el --- Major mode for browsing RCS/CVS/SCCS log output - -;; Copyright (C) 1999, 2000, 2001, 2002, 2003, 2004, 2005, 2006, 2007, -;; 2008, 2009, 2010 Free Software Foundation, Inc. - -;; Author: Stefan Monnier -;; Keywords: rcs, sccs, cvs, log, version control, tools - -;; 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 3 of the License, 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. If not, see . - -;;; Commentary: - -;; Major mode to browse revision log histories. -;; Currently supports the format output by: -;; RCS, SCCS, CVS, Subversion, and DaRCS. - -;; Examples of log output: - -;;;; RCS/CVS: - -;; ---------------------------- -;; revision 1.35 locked by: turlutut -;; date: 2005-03-22 18:48:38 +0000; author: monnier; state: Exp; lines: +6 -8 -;; (gnus-display-time-event-handler): -;; Check display-time-timer at runtime rather than only at load time -;; in case display-time-mode is turned off in the mean time. -;; ---------------------------- -;; revision 1.34 -;; date: 2005-02-09 15:50:38 +0000; author: kfstorm; state: Exp; lines: +7 -7 -;; branches: 1.34.2; -;; Change release version from 21.4 to 22.1 throughout. -;; Change development version from 21.3.50 to 22.0.50. - -;;;; SCCS: - -;;;; Subversion: - -;; ------------------------------------------------------------------------ -;; r4622 | ckuethe | 2007-12-23 18:18:01 -0500 (Sun, 23 Dec 2007) | 2 lines -;; -;; uBlox AEK-4T in binary mode. Added to unstable because it breaks gpsfake -;; -;; ------------------------------------------------------------------------ -;; r4621 | ckuethe | 2007-12-23 16:48:11 -0500 (Sun, 23 Dec 2007) | 3 lines -;; -;; Add a note about requiring usbfs to use the garmin gps18 (usb) -;; Mention firmware testing the AC12 with firmware BQ00 and BQ04 -;; -;; ------------------------------------------------------------------------ -;; r4620 | ckuethe | 2007-12-23 15:52:34 -0500 (Sun, 23 Dec 2007) | 1 line -;; -;; add link to latest hardware reference -;; ------------------------------------------------------------------------ -;; r4619 | ckuethe | 2007-12-23 14:37:31 -0500 (Sun, 23 Dec 2007) | 1 line -;; -;; there is now a regression test for AC12 without raw data output - -;;;; Darcs: - -;; Changes to darcsum.el: -;; -;; Mon Nov 28 15:19:38 GMT 2005 Dave Love -;; * Abstract process startup into darcsum-start-process. Use TERM=dumb. -;; TERM=dumb avoids escape characters, at least, for any old darcs that -;; doesn't understand DARCS_DONT_COLOR & al. -;; -;; Thu Nov 24 15:20:45 GMT 2005 Dave Love -;; * darcsum-mode-related changes. -;; Don't call font-lock-mode (unnecessary) or use-local-map (redundant). -;; Use mode-class 'special. Add :group. -;; Add trailing-whitespace option to mode hook and fix -;; darcsum-display-changeset not to use trailing whitespace. - -;;;; Mercurial - -;; changeset: 11:8ff1a4166444 -;; tag: tip -;; user: Eric S. Raymond -;; date: Wed Dec 26 12:18:58 2007 -0500 -;; summary: Explain keywords. Add markup fixes. -;; -;; changeset: 10:20abc7ab09c3 -;; user: Eric S. Raymond -;; date: Wed Dec 26 11:37:28 2007 -0500 -;; summary: Typo fixes. -;; -;; changeset: 9:ada9f4da88aa -;; user: Eric S. Raymond -;; date: Wed Dec 26 11:23:00 2007 -0500 -;; summary: Add RCS example session. - -;;; Todo: - -;; - add ability to modify a log-entry (via cvs-mode-admin ;-) -;; - remove references to cvs-* -;; - make it easier to add support for new backends without changing the code. - -;;; Code: - -(eval-when-compile (require 'cl)) -(require 'pcvs-util) -(autoload 'vc-find-revision "vc") -(autoload 'vc-diff-internal "vc") - -(defvar cvs-minor-wrap-function) - -(defgroup log-view nil - "Major mode for browsing log output of RCS/CVS/SCCS." - :group 'pcl-cvs - :prefix "log-view-") - -;; Needed because log-view-mode-map inherits from widget-keymap. (Bug#5311) -(require 'wid-edit) - -(easy-mmode-defmap log-view-mode-map - '(("z" . kill-this-buffer) - ("q" . quit-window) - ("m" . log-view-toggle-mark-entry) - ("e" . log-view-modify-change-comment) - ("d" . log-view-diff) - ("=" . log-view-diff) - ("D" . log-view-diff-changeset) - ("a" . log-view-annotate-version) - ("f" . log-view-find-revision) - ("n" . log-view-msg-next) - ("p" . log-view-msg-prev) - ("\t" . log-view-msg-next) - ([backtab] . log-view-msg-prev) - ("N" . log-view-file-next) - ("P" . log-view-file-prev) - ("\M-n" . log-view-file-next) - ("\M-p" . log-view-file-prev)) - "Log-View's keymap." - :inherit widget-keymap - :group 'log-view) - -(easy-menu-define log-view-mode-menu log-view-mode-map - "Log-View Display Menu" - `("Log-View" - ;; XXX Do we need menu entries for these? - ;; ["Quit" quit-window] - ;; ["Kill This Buffer" kill-this-buffer] - ["Mark Log Entry for Diff" set-mark-command - :help ""] - ["Diff Revisions" log-view-diff - :help "Get the diff between two revisions"] - ["Changeset Diff" log-view-diff-changeset - :help "Get the changeset diff between two revisions"] - ["Visit Version" log-view-find-revision - :help "Visit the version at point"] - ["Annotate Version" log-view-annotate-version - :help "Annotate the version at point"] - ["Modify Log Comment" log-view-modify-change-comment - :help "Edit the change comment displayed at point"] - "-----" - ["Next Log Entry" log-view-msg-next - :help "Go to the next count'th log message"] - ["Previous Log Entry" log-view-msg-prev - :help "Go to the previous count'th log message"] - ["Next File" log-view-file-next - :help "Go to the next count'th file"] - ["Previous File" log-view-file-prev - :help "Go to the previous count'th file"])) - -(defvar log-view-mode-hook nil - "Hook run at the end of `log-view-mode'.") - -(defface log-view-file - '((((class color) (background light)) - (:background "grey70" :weight bold)) - (t (:weight bold))) - "Face for the file header line in `log-view-mode'." - :group 'log-view) -(define-obsolete-face-alias 'log-view-file-face 'log-view-file "22.1") -(defvar log-view-file-face 'log-view-file) - -(defface log-view-message - '((((class color) (background light)) - (:background "grey85")) - (t (:weight bold))) - "Face for the message header line in `log-view-mode'." - :group 'log-view) -;; backward-compatibility alias -(define-obsolete-face-alias 'log-view-message-face 'log-view-message "22.1") -(defvar log-view-message-face 'log-view-message) - -(defvar log-view-file-re - (concat "^\\(?:Working file: \\(?1:.+\\)" ;RCS and CVS. - ;; Subversion has no such thing?? - "\\|\\(?:SCCS/s\\.\\|Changes to \\)\\(?1:.+\\):" ;SCCS and Darcs. - "\\)\n") ;Include the \n for font-lock reasons. - "Regexp matching the text identifying the file. -The match group number 1 should match the file name itself.") - -(defvar log-view-per-file-logs t - "Set if to t if the logs are shown one file at a time.") - -(defvar log-view-message-re - (concat "^\\(?:revision \\(?1:[.0-9]+\\)\\(?:\t.*\\)?" ; RCS and CVS. - "\\|r\\(?1:[0-9]+\\) | .* | .*" ; Subversion. - "\\|D \\(?1:[.0-9]+\\) .*" ; SCCS. - ;; Darcs doesn't have revision names. VC-darcs uses patch names - ;; instead. Darcs patch names are hashcodes, which do not appear - ;; in the log output :-(, but darcs accepts any prefix of the log - ;; message as a patch name, so we match the first line of the log - ;; message. - ;; First loosely match the date format. - (concat "\\|[^ \n].*[^0-9\n][0-9][0-9]:[0-9][0-9][^0-9\n].*[^ \n]" - ;;Email of user and finally Msg, used as revision name. - " .*@.*\n\\(?: \\* \\(?1:.*\\)\\)?") - "\\)$") - "Regexp matching the text identifying a revision. -The match group number 1 should match the revision number itself.") - -(defvar log-view-font-lock-keywords - ;; We use `eval' so as to use the buffer-local value of log-view-file-re - ;; and log-view-message-re, if applicable. - '((eval . `(,log-view-file-re - (1 (if (boundp 'cvs-filename-face) cvs-filename-face)) - (0 log-view-file-face append))) - (eval . `(,log-view-message-re . log-view-message-face)))) - -(defconst log-view-font-lock-defaults - '(log-view-font-lock-keywords t nil nil nil)) - -(defvar log-view-vc-fileset nil - "Set this to the fileset corresponding to the current log.") - -(defvar log-view-vc-backend nil - "Set this to the VC backend that created the current log.") - -;;;; -;;;; Actual code -;;;; - -;;;###autoload -(define-derived-mode log-view-mode special-mode "Log-View" - "Major mode for browsing CVS log output." - (setq buffer-read-only t) - (set (make-local-variable 'font-lock-defaults) log-view-font-lock-defaults) - (set (make-local-variable 'beginning-of-defun-function) - 'log-view-beginning-of-defun) - (set (make-local-variable 'end-of-defun-function) - 'log-view-end-of-defun) - (set (make-local-variable 'cvs-minor-wrap-function) 'log-view-minor-wrap) - (hack-dir-local-variables-non-file-buffer)) - -;;;; -;;;; Navigation -;;;; - -;; define log-view-{msg,file}-{next,prev} -(easy-mmode-define-navigation log-view-msg log-view-message-re "log message") -(easy-mmode-define-navigation log-view-file log-view-file-re "file") - -(defun log-view-goto-rev (rev) - (goto-char (point-min)) - (ignore-errors - (while (not (equal rev (log-view-current-tag))) - (log-view-msg-next)) - t)) - -;;;; -;;;; Linkage to PCL-CVS (mostly copied from cvs-status.el) -;;;; - -(defconst log-view-dir-re "^cvs[.ex]* [a-z]+: Logging \\(.+\\)$") - -(defun log-view-current-file () - (save-excursion - (forward-line 1) - (or (re-search-backward log-view-file-re nil t) - (re-search-forward log-view-file-re nil t) - (error "Unable to determine the current file")) - (let* ((file (match-string 1)) - (cvsdir (and (re-search-backward log-view-dir-re nil t) - (match-string 1))) - (pcldir (and (boundp 'cvs-pcl-cvs-dirchange-re) - (re-search-backward cvs-pcl-cvs-dirchange-re nil t) - (match-string 1))) - (dir "")) - (let ((default-directory "")) - (when pcldir (setq dir (expand-file-name pcldir dir))) - (when cvsdir (setq dir (expand-file-name cvsdir dir)))) - (expand-file-name file dir)))) - -(defun log-view-current-tag (&optional where) - (save-excursion - (when where (goto-char where)) - (forward-line 1) - (let ((pt (point))) - (when (re-search-backward log-view-message-re nil t) - (let ((rev (match-string-no-properties 1))) - (unless (re-search-forward log-view-file-re pt t) - rev)))))) - -(defun log-view-toggle-mark-entry () - "Toggle the marked state for the log entry at point. -Individual log entries can be marked and unmarked. The marked -entries are denoted by changing their background color. -`log-view-get-marked' returns the list of tags for the marked -log entries." - (interactive) - (save-excursion - (forward-line 1) - (let ((pt (point))) - (when (re-search-backward log-view-message-re nil t) - (let ((beg (match-beginning 0)) - end ov ovlist found tag) - (unless (re-search-forward log-view-file-re pt t) - ;; Look to see if the current entry is marked. - (setq found (get-char-property (point) 'log-view-self)) - (if found - (delete-overlay found) - ;; Create an overlay that covers this entry and change - ;; its color. - (setq tag (log-view-current-tag (point))) - (forward-line 1) - (setq end - (if (re-search-forward log-view-message-re nil t) - (match-beginning 0) - (point-max))) - (setq ov (make-overlay beg end)) - (overlay-put ov 'face 'log-view-file) - ;; This is used to check if the overlay is present. - (overlay-put ov 'log-view-self ov) - (overlay-put ov 'log-view-marked tag)))))))) - -(defun log-view-get-marked () - "Return the list of tags for the marked log entries." - (save-excursion - (let ((pos (point-min)) - marked-list ov) - (while (setq pos (next-single-property-change pos 'face)) - (when (setq ov (get-char-property pos 'log-view-self)) - (push (overlay-get ov 'log-view-marked) marked-list) - (setq pos (overlay-end ov)))) - marked-list))) - -(defun log-view-beginning-of-defun () - ;; This assumes that a log entry starts with a line matching - ;; `log-view-message-re'. Modes that derive from `log-view-mode' - ;; for which this assumption is not valid will have to provide - ;; another implementation of this function. `log-view-msg-prev' - ;; does a similar job to this function, we can't use it here - ;; directly because it prints messages that are not appropriate in - ;; this context and it does not move to the beginning of the buffer - ;; when the point is before the first log entry. - - ;; `log-view-beginning-of-defun' and `log-view-end-of-defun' have - ;; been checked to work with logs produced by RCS, CVS, git, - ;; mercurial and subversion. - - (re-search-backward log-view-message-re nil 'move)) - -(defun log-view-end-of-defun () - ;; The idea in this function is to search for the beginning of the - ;; next log entry using `log-view-message-re' and then go back one - ;; line when finding it. Modes that derive from `log-view-mode' for - ;; which this assumption is not valid will have to provide another - ;; implementation of this function. - - ;; Look back and if there is no entry there it means we are before - ;; the first log entry, so go forward until finding one. - (unless (save-excursion (re-search-backward log-view-message-re nil t)) - (re-search-forward log-view-message-re nil t)) - - ;; In case we are at the end of log entry going forward a line will - ;; make us find the next entry when searching. If we are inside of - ;; an entry going forward a line will still keep the point inside - ;; the same entry. - (forward-line 1) - - ;; In case we are at the beginning of an entry, move past it. - (when (looking-at log-view-message-re) - (goto-char (match-end 0)) - (forward-line 1)) - - ;; Search for the start of the next log entry. Go to the end of the - ;; buffer if we could not find a next entry. - (when (re-search-forward log-view-message-re nil 'move) - (goto-char (match-beginning 0)) - (forward-line -1))) - -(defvar cvs-minor-current-files) -(defvar cvs-branch-prefix) -(defvar cvs-secondary-branch-prefix) - -(defun log-view-minor-wrap (buf f) - (let ((data (with-current-buffer buf - (let* ((beg (point)) - (end (if mark-active (mark) (point))) - (fr (log-view-current-tag beg)) - (to (log-view-current-tag end))) - (when (string-equal fr to) - (save-excursion - (goto-char end) - (log-view-msg-next) - (setq to (log-view-current-tag)))) - (cons - ;; The first revision has to be the one at point, for - ;; operations that only take one revision - ;; (e.g. cvs-mode-edit). - (cons (log-view-current-file) fr) - (cons (log-view-current-file) to)))))) - (let ((cvs-branch-prefix (cdar data)) - (cvs-secondary-branch-prefix (and (cdar data) (cddr data))) - (cvs-minor-current-files - (cons (caar data) - (when (and (cadr data) (not (equal (caar data) (cadr data)))) - (list (cadr data))))) - ;; FIXME: I need to force because the fileinfos are UNKNOWN - (cvs-force-command "/F")) - (funcall f)))) - -(defun log-view-find-revision (pos) - "Visit the version at point." - (interactive "d") - (unless log-view-per-file-logs - (when (> (length log-view-vc-fileset) 1) - (error "Multiple files shown in this buffer, cannot use this command here"))) - (save-excursion - (goto-char pos) - (switch-to-buffer (vc-find-revision (if log-view-per-file-logs - (log-view-current-file) - (car log-view-vc-fileset)) - (log-view-current-tag))))) - - -(defun log-view-extract-comment () - "Parse comment from around the current point in the log." - (save-excursion - (let (st en (backend (vc-backend (log-view-current-file)))) - (log-view-end-of-defun) - (cond ((eq backend 'SVN) - (forward-line -1))) - (setq en (point)) - (log-view-beginning-of-defun) - (cond ((memq backend '(SCCS RCS CVS MCVS SVN)) - (forward-line 2)) - ((eq backend 'Hg) - (forward-line 4) - (re-search-forward "summary: *" nil t))) - (setq st (point)) - (buffer-substring st en)))) - -(declare-function vc-modify-change-comment "vc" (files rev oldcomment)) - -(defun log-view-modify-change-comment () - "Edit the change comment displayed at point." - (interactive) - (vc-modify-change-comment (list (if log-view-per-file-logs - (log-view-current-file) - (car log-view-vc-fileset))) - (log-view-current-tag) - (log-view-extract-comment))) - -(defun log-view-annotate-version (pos) - "Annotate the version at point." - (interactive "d") - (unless log-view-per-file-logs - (when (> (length log-view-vc-fileset) 1) - (error "Multiple files shown in this buffer, cannot use this command here"))) - (save-excursion - (goto-char pos) - (vc-annotate (if log-view-per-file-logs - (log-view-current-file) - (car log-view-vc-fileset)) - (log-view-current-tag)))) - -;; -;; diff -;; - -(defun log-view-diff (beg end) - "Get the diff between two revisions. -If the mark is not active or the mark is on the revision at point, -get the diff between the revision at point and its previous revision. -Otherwise, get the diff between the revisions where the region starts -and ends. -Contrary to `log-view-diff-changeset', it will only show the part of the -changeset that affected the currently considered file(s)." - (interactive - (list (if mark-active (region-beginning) (point)) - (if mark-active (region-end) (point)))) - (let ((fr (log-view-current-tag beg)) - (to (log-view-current-tag end))) - (when (string-equal fr to) - (save-excursion - (goto-char end) - (log-view-msg-next) - (setq to (log-view-current-tag)))) - (vc-diff-internal - t (list log-view-vc-backend - (if log-view-per-file-logs - (list (log-view-current-file)) - log-view-vc-fileset)) - to fr))) - -(declare-function vc-diff-internal "vc" - (async vc-fileset rev1 rev2 &optional verbose)) - -(defun log-view-diff-changeset (beg end) - "Get the diff between two revisions. -If the mark is not active or the mark is on the revision at point, -get the diff between the revision at point and its previous revision. -Otherwise, get the diff between the revisions where the region starts -and ends. -Contrary to `log-view-diff', it will show the whole changeset including -the changes that affected other files than the currently considered file(s)." - (interactive - (list (if mark-active (region-beginning) (point)) - (if mark-active (region-end) (point)))) - (when (eq (vc-call-backend log-view-vc-backend 'revision-granularity) 'file) - (error "The %s backend does not support changeset diffs" log-view-vc-backend)) - (let ((fr (log-view-current-tag beg)) - (to (log-view-current-tag end))) - (when (string-equal fr to) - ;; TO and FR are the same, look at the previous revision. - (setq to (vc-call-backend log-view-vc-backend 'previous-revision nil fr))) - (vc-diff-internal - t - ;; We want to see the diff for all the files in the changeset, so - ;; pass NIL for the file list. The value passed here should - ;; follow what `vc-deduce-fileset' returns. - (list log-view-vc-backend nil) - to fr))) - -(provide 'log-view) - -;; arch-tag: 0d64220b-ce7e-4f62-9c2a-6b04c2f81f4f -;;; log-view.el ends here diff -r 681cd08dc0f7 -r e93288477c43 lisp/makefile.w32-in --- a/lisp/makefile.w32-in Thu Jun 10 22:43:47 2010 +0000 +++ b/lisp/makefile.w32-in Sun Jun 13 22:57:55 2010 +0000 @@ -114,7 +114,8 @@ play \ progmodes \ textmodes \ - url + url \ + vc # Directories with lisp files to compile, and to extract data from # (customs, autoloads, etc.) diff -r 681cd08dc0f7 -r e93288477c43 lisp/net/tramp-compat.el --- a/lisp/net/tramp-compat.el Thu Jun 10 22:43:47 2010 +0000 +++ b/lisp/net/tramp-compat.el Sun Jun 13 22:57:55 2010 +0000 @@ -44,33 +44,31 @@ (autoload 'tramp-tramp-file-p "tramp") (autoload 'tramp-file-name-handler "tramp") - (autoload 'tramp-handle-file-remote-p "tramp") + + ;; We check whether `start-file-process' is bound. + (unless (fboundp 'start-file-process) - ;; tramp-util offers integration into other (X)Emacs packages like - ;; compile.el, gud.el etc. Not necessary in Emacs 23. - (eval-after-load "tramp" - ;; We check whether `start-file-process' is an alias. - '(when (or (not (fboundp 'start-file-process)) - (symbolp (symbol-function 'start-file-process))) - (require 'tramp-util) - (add-hook 'tramp-unload-hook - '(lambda () - (when (featurep 'tramp-util) - (unload-feature 'tramp-util 'force)))))) - - ;; Make sure that we get integration with the VC package. When it - ;; is loaded, we need to pull in the integration module. Not - ;; necessary in Emacs 23. - (eval-after-load "vc" + ;; tramp-util offers integration into other (X)Emacs packages like + ;; compile.el, gud.el etc. Not necessary in Emacs 23. (eval-after-load "tramp" - ;; We check whether `start-file-process' is an alias. - '(when (or (not (fboundp 'start-file-process)) - (symbolp (symbol-function 'start-file-process))) - (require 'tramp-vc) + '(progn + (require 'tramp-util) (add-hook 'tramp-unload-hook '(lambda () - (when (featurep 'tramp-vc) - (unload-feature 'tramp-vc 'force))))))) + (when (featurep 'tramp-util) + (unload-feature 'tramp-util 'force)))))) + + ;; Make sure that we get integration with the VC package. When it + ;; is loaded, we need to pull in the integration module. Not + ;; necessary in Emacs 23. + (eval-after-load "vc" + (eval-after-load "tramp" + '(progn + (require 'tramp-vc) + (add-hook 'tramp-unload-hook + '(lambda () + (when (featurep 'tramp-vc) + (unload-feature 'tramp-vc 'force)))))))) ;; Avoid byte-compiler warnings if the byte-compiler supports this. ;; Currently, XEmacs supports this. @@ -176,7 +174,8 @@ (if (and (tramp-tramp-file-p name) (not (string-match - "[[*?]" (tramp-handle-file-remote-p name 'localname)))) + "[[*?]" (tramp-compat-funcall + 'file-remote-p name 'localname)))) (setq ad-return-value (list name)) ;; Otherwise, just run the original function. ad-do-it))) @@ -236,22 +235,23 @@ (tramp-compat-temporary-file-directory))) (extension (file-name-extension filename t)) result) - (if (fboundp 'make-temp-file) + (condition-case nil (setq result (tramp-compat-funcall 'make-temp-file prefix dir-flag extension)) - ;; We use our own implementation, taken from files.el. - (while - (condition-case () - (progn - (setq result (concat (make-temp-name prefix) extension)) - (if dir-flag - (make-directory result) - (write-region "" nil result nil 'silent)) - nil) - (file-already-exists t)) - ;; The file was somehow created by someone else between - ;; `make-temp-name' and `write-region', let's try again. - nil)) + (error + ;; We use our own implementation, taken from files.el. + (while + (condition-case () + (progn + (setq result (concat (make-temp-name prefix) extension)) + (if dir-flag + (make-directory result) + (write-region "" nil result nil 'silent)) + nil) + (file-already-exists t)) + ;; The file was somehow created by someone else between + ;; `make-temp-name' and `write-region', let's try again. + nil))) result)) ;; `most-positive-fixnum' does not exist in XEmacs. diff -r 681cd08dc0f7 -r e93288477c43 lisp/net/tramp.el --- a/lisp/net/tramp.el Thu Jun 10 22:43:47 2010 +0000 +++ b/lisp/net/tramp.el Sun Jun 13 22:57:55 2010 +0000 @@ -1065,7 +1065,7 @@ `("HISTFILE=$HOME/.tramp_history" "HISTSIZE=1" "LC_ALL=C" ,(format "TERM=%s" tramp-terminal-type) "EMACS=t" ;; Deprecated. - ,(format "INSIDE_EMACS=%s,tramp:%s" emacs-version tramp-version) + ,(format "INSIDE_EMACS='%s,tramp:%s'" emacs-version tramp-version) "CDPATH=" "HISTORY=" "MAIL=" "MAILCHECK=" "MAILPATH=" "autocorrect=" "correct=") @@ -1091,8 +1091,10 @@ (defcustom tramp-shell-prompt-pattern ;; Allow a prompt to start right after a ^M since it indeed would be - ;; displayed at the beginning of the line (and Zsh uses it). - "\\(?:^\\|\r\\)[^#$%>\n]*#?[#$%>] *\\(\e\\[[0-9;]*[a-zA-Z] *\\)*" + ;; displayed at the beginning of the line (and Zsh uses it). This + ;; regexp works only for GNU Emacs. + (concat (if (featurep 'xemacs) "" "\\(?:^\\|\r\\)") + "[^#$%>\n]*#?[#$%>] *\\(\e\\[[0-9;]*[a-zA-Z] *\\)*") "Regexp to match prompts from remote shell. Normally, Tramp expects you to configure `shell-prompt-pattern' correctly, but sometimes it happens that you are connecting to a @@ -5513,7 +5515,8 @@ ;; XEmacs only. 'dired-print-file 'dired-shell-call-process ;; nowhere yet. - 'executable-find 'start-process 'call-process)) + 'executable-find 'start-process + 'call-process 'call-process-region)) default-directory) ;; Unknown file primitive. (t (error "unknown file I/O primitive: %s" operation)))) @@ -8758,7 +8761,7 @@ exiting if process is running." (if (fboundp 'set-process-query-on-exit-flag) (tramp-compat-funcall 'set-process-query-on-exit-flag process flag) - (tramp-compat-funcall 'process-kill-without-query) process flag)) + (tramp-compat-funcall 'process-kill-without-query process flag))) ;; ------------------------------------------------------------ @@ -8914,7 +8917,7 @@ ;; rsync). ;; * Keep a second connection open for out-of-band methods like scp or ;; rsync. -;; * Support ptys in `tramp-handle-start-file-process'. (Bug#4604) +;; * Support ptys in `tramp-handle-start-file-process'. (Bug#4604, Bug#6360) ;; * IMHO, it's a drawback that currently Tramp doesn't support ;; Unicode in Dired file names by default. Is it possible to ;; improve Tramp to set LC_ALL to "C" only for commands where Tramp diff -r 681cd08dc0f7 -r e93288477c43 lisp/pcvs-defs.el --- a/lisp/pcvs-defs.el Thu Jun 10 22:43:47 2010 +0000 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,528 +0,0 @@ -;;; pcvs-defs.el --- variable definitions for PCL-CVS - -;; Copyright (C) 1991, 1992, 1993, 1994, 1995, 1996, 1997, 1998, 1999, -;; 2000, 2001, 2002, 2003, 2004, 2005, 2006, 2007, 2008, 2009, 2010 -;; Free Software Foundation, Inc. - -;; Author: Stefan Monnier -;; Keywords: pcl-cvs - -;; 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 3 of the License, 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. If not, see . - -;;; Commentary: - - -;;; Code: - -(eval-when-compile (require 'cl)) -(require 'pcvs-util) - -;;;; ------------------------------------------------------- -;;;; START OF THINGS TO CHECK WHEN INSTALLING - -(defvar cvs-program "cvs" - "*Name or full path of the cvs executable.") - -(defvar cvs-version - ;; With the divergence of the CVSNT codebase and version numbers, this is - ;; not really good any more. - (ignore-errors - (with-temp-buffer - (call-process cvs-program nil t nil "-v") - (goto-char (point-min)) - (when (re-search-forward "(CVS\\(NT\\)?) \\([0-9]+\\)\\.\\([0-9]+\\)" - nil t) - (cons (string-to-number (match-string 1)) - (string-to-number (match-string 2)))))) - "*Version of `cvs' installed on your system. -It must be in the (MAJOR . MINOR) format.") - -;; FIXME: this is only used by cvs-mode-diff-backup -(defvar cvs-diff-program (or (and (boundp 'diff-command) diff-command) "diff") - "*Name or full path of the best diff program you've got. -NOTE: there are some nasty bugs in the context diff variants of some vendor -versions, such as the one in SunOS-4.") - -;;;; END OF THINGS TO CHECK WHEN INSTALLING -;;;; -------------------------------------------------------- - -;;;; -;;;; User configuration variables: -;;;; -;;;; NOTE: these should be set in your ~/.emacs (or site-lisp/default.el) file. -;;;; - -(defgroup pcl-cvs nil - "Special support for the CVS versioning system." - :version "21.1" - :group 'tools - :prefix "cvs-") - -;; -;; cvsrc options -;; - -(defcustom cvs-cvsrc-file (convert-standard-filename "~/.cvsrc") - "Path to your cvsrc file." - :group 'pcl-cvs - :type '(file)) - -(defvar cvs-shared-start 4 - "Index of the first shared flag. -If set to 4, for instance, a numeric argument smaller than 4 will -select a non-shared flag, while a numeric argument greater than 3 -will select a shared-flag.") - -(defvar cvs-shared-flags (make-list cvs-shared-start nil) - "List of flags whose settings is shared among several commands.") - -(defvar cvs-cvsroot nil - "*Specifies where the (current) cvs master repository is. -Overrides the environment variable $CVSROOT by sending \" -d dir\" to -all CVS commands. This switch is useful if you have multiple CVS -repositories. It can be set interactively with \\[cvs-change-cvsroot.] -There is no need to set this if $CVSROOT is set to a correct value.") - -(defcustom cvs-auto-remove-handled nil - "If up-to-date files should be acknowledged automatically. -If T, they will be removed from the *cvs* buffer after every command. -If DELAYED, they will be removed from the *cvs* buffer before every command. -If STATUS, they will only be removed after a `cvs-mode-status' command. -Else, they will never be automatically removed from the *cvs* buffer." - :group 'pcl-cvs - :type '(choice (const nil) (const status) (const delayed) (const t))) - -(defcustom cvs-auto-remove-directories 'handled - "If ALL, directory entries will never be shown. -If HANDLED, only non-handled directories will be shown. -If EMPTY, only non-empty directories will be shown." - :group 'pcl-cvs - :type '(choice (const :tag "No" nil) (const all) (const handled) (const empty))) - -(defcustom cvs-auto-revert t - "Non-nil if changed files should automatically be reverted." - :group 'pcl-cvs - :type '(boolean)) - -(defcustom cvs-sort-ignore-file t - "Non-nil if `cvs-mode-ignore' should sort the .cvsignore automatically." - :group 'pcl-cvs - :type '(boolean)) - -(defcustom cvs-force-dir-tag t - "If non-nil, tagging can only be applied to directories. -Tagging should generally be applied a directory at a time, but sometimes it is -useful to be able to tag a single file. The normal way to do that is to use -`cvs-mode-force-command' so as to temporarily override the restrictions," - :group 'pcl-cvs - :type '(boolean)) - -(defcustom cvs-default-ignore-marks nil - "Non-nil if cvs mode commands should ignore any marked files. -Normally they run on the files that are marked (with `cvs-mode-mark'), -or the file under the cursor if no files are marked. If this variable -is set to a non-nil value they will by default run on the file on the -current line. See also `cvs-invert-ignore-marks'" - :group 'pcl-cvs - :type '(boolean)) - -(defvar cvs-diff-ignore-marks t) -(make-obsolete-variable 'cvs-diff-ignore-marks - 'cvs-invert-ignore-marks - "21.1") - -(defcustom cvs-invert-ignore-marks - (let ((l ())) - (unless (equal cvs-diff-ignore-marks cvs-default-ignore-marks) - (push "diff" l)) - (when (and cvs-force-dir-tag (not cvs-default-ignore-marks)) - (push "tag" l)) - l) - "List of cvs commands that invert the default ignore-mark behavior. -Commands in this set will use the opposite default from the one set -in `cvs-default-ignore-marks'." - :group 'pcl-cvs - :type '(set (const "diff") - (const "tag") - (const "ignore"))) - -(defcustom cvs-confirm-removals t - "Ask for confirmation before removing files. -Non-nil means that PCL-CVS will ask confirmation before removing files -except for files whose content can readily be recovered from the repository. -A value of `list' means that the list of files to be deleted will be -displayed when asking for confirmation." - :group 'pcl-cvs - :type '(choice (const list) - (const t) - (const nil))) - -(defcustom cvs-add-default-message nil - "Default message to use when adding files. -If set to nil, `cvs-mode-add' will always prompt for a message." - :group 'pcl-cvs - :type '(choice (const :tag "Prompt" nil) - (string))) - -(defvar cvs-diff-buffer-name "*cvs-diff*") -(make-obsolete-variable 'cvs-diff-buffer-name - 'cvs-buffer-name-alist - "21.1") - -(defcustom cvs-find-file-and-jump nil - "Jump to the modified area when finding a file. -If non-nil, `cvs-mode-file-file' will place the cursor at the beginning of -the modified area. If the file is not locally modified, this will obviously -have no effect." - :group 'pcl-cvs - :type '(boolean)) - -(defcustom cvs-buffer-name-alist - '(("diff" cvs-diff-buffer-name diff-mode) - ("status" "*cvs-info*" cvs-status-mode) - ("tree" "*cvs-info*" cvs-status-mode) - ("message" "*cvs-commit*" nil log-edit) - ("log" "*cvs-info*" log-view-mode)) - "Buffer name and mode to be used for each command. -This is a list of elements of the form - - (CMD BUFNAME MODE &optional POSTPROC) - -CMD is the name of the command. -BUFNAME is an expression that should evaluate to a string used as - a buffer name. It can use the variable CMD if it wants to. -MODE is the command to use to setup the buffer. -POSTPROC is a function that should be executed when the command terminates - -The CMD used for `cvs-mode-commit' is \"message\". For that special - case, POSTPROC is called just after MODE with special arguments." - :group 'pcl-cvs - :type '(repeat - (list (choice (const "diff") - (const "status") - (const "tree") - (const "message") - (const "log") - (string)) - (choice (const "*vc-diff*") - (const "*cvs-info*") - (const "*cvs-commit*") - (const (expand-file-name "*cvs-commit*")) - (const (format "*cvs-%s*" cmd)) - (const (expand-file-name (format "*cvs-%s*" cmd))) - (sexp :value "my-cvs-info-buffer") - (const nil)) - (choice (function-item diff-mode) - (function-item cvs-edit-mode) - (function-item cvs-status-mode) - function - (const nil)) - (set :inline t - (choice (function-item cvs-status-cvstrees) - (function-item cvs-status-trees) - function))))) - -(defvar cvs-buffer-name '(expand-file-name "*cvs*" dir) ;; "*cvs*" - "Name of the cvs buffer. -This expression will be evaluated in an environment where DIR is set to -the directory name of the cvs buffer.") - -(defvar cvs-temp-buffer-name - ;; Was '(expand-file-name " *cvs-tmp*" dir), but that causes them to - ;; become non-hidden if uniquification is done `forward'. - " *cvs-tmp*" - "*Name of the cvs temporary buffer. -Output from cvs is placed here for asynchronous commands.") - -(defcustom cvs-idiff-imerge-handlers - (if (fboundp 'ediff) - '(cvs-ediff-diff . cvs-ediff-merge) - '(cvs-emerge-diff . cvs-emerge-merge)) - "Pair of functions to be used for resp. diff'ing and merg'ing interactively." - :group 'pcl-cvs - :type '(choice (const :tag "Ediff" (cvs-ediff-diff . cvs-ediff-merge)) - (const :tag "Emerge" (cvs-emerge-diff . cvs-emerge-merge)))) - -(defvar cvs-mode-hook nil - "Run after `cvs-mode' was setup.") - - -;;;; -;;;; Internal variables, used in the process buffer. -;;;; - -(defvar cvs-postprocess nil - "(Buffer local) what to do once the process exits.") - -;;;; -;;;; Internal variables for the *cvs* buffer. -;;;; - -(defcustom cvs-reuse-cvs-buffer 'subdir - "When to reuse an existing cvs buffer. -Alternatives are: - CURRENT: just reuse the current buffer if it is a cvs buffer - SAMEDIR: reuse any cvs buffer displaying the same directory - SUBDIR: or reuse any cvs buffer displaying any sub- or super- directory - ALWAYS: reuse any cvs buffer." - :group 'pcl-cvs - :type '(choice (const always) (const subdir) (const samedir) (const current))) - -(defvar cvs-temp-buffer nil - "(Buffer local) The temporary buffer associated with this *cvs* buffer.") - -(defvar cvs-lock-file nil - "Full path to a lock file that CVS is waiting for (or was waiting for). -This variable is buffer local and only used in the *cvs* buffer.") - -(defvar cvs-lock-file-regexp "^#cvs\\.\\([trw]fl\\.[-.a-z0-9]+\\|lock\\)\\'" - "Regexp matching the possible names of locks in the CVS repository.") - -(defconst cvs-cursor-column 22 - "Column to position cursor in in `cvs-mode'.") - -;;;; -;;;; Global internal variables -;;;; - -(defconst cvs-vendor-branch "1.1.1" - "The default branch used by CVS for vendor code.") - -(easy-mmode-defmap cvs-mode-diff-map - '(("E" "imerge" . cvs-mode-imerge) - ("=" . cvs-mode-diff) - ("e" "idiff" . cvs-mode-idiff) - ("2" "other" . cvs-mode-idiff-other) - ("d" "diff" . cvs-mode-diff) - ("b" "backup" . cvs-mode-diff-backup) - ("h" "head" . cvs-mode-diff-head) - ("r" "repository" . cvs-mode-diff-repository) - ("y" "yesterday" . cvs-mode-diff-yesterday) - ("v" "vendor" . cvs-mode-diff-vendor)) - "Keymap for diff-related operations in `cvs-mode'." - :name "Diff") -;; This is necessary to allow correct handling of \\[cvs-mode-diff-map] -;; in substitute-command-keys. -(fset 'cvs-mode-diff-map cvs-mode-diff-map) - -(easy-mmode-defmap cvs-mode-map - ;;(define-prefix-command 'cvs-mode-map-diff-prefix) - ;;(define-prefix-command 'cvs-mode-map-control-c-prefix) - '(;; various - ;; (undo . cvs-mode-undo) - ("?" . cvs-help) - ("h" . cvs-help) - ("q" . cvs-bury-buffer) - ("z" . kill-this-buffer) - ("F" . cvs-mode-set-flags) - ;; ("\M-f" . cvs-mode-force-command) - ("!" . cvs-mode-force-command) - ("\C-c\C-c" . cvs-mode-kill-process) - ;; marking - ("m" . cvs-mode-mark) - ("M" . cvs-mode-mark-all-files) - ("S" . cvs-mode-mark-on-state) - ("u" . cvs-mode-unmark) - ("\C-?". cvs-mode-unmark-up) - ("%" . cvs-mode-mark-matching-files) - ("T" . cvs-mode-toggle-marks) - ("\M-\C-?" . cvs-mode-unmark-all-files) - ;; navigation keys - (" " . cvs-mode-next-line) - ("n" . cvs-mode-next-line) - ("p" . cvs-mode-previous-line) - ("\t" . cvs-mode-next-line) - ([backtab] . cvs-mode-previous-line) - ;; M- keys are usually those that operate on modules - ;;("\M-C". cvs-mode-rcs2log) ; i.e. "Create a ChangeLog" - ;;("\M-t". cvs-rtag) - ;;("\M-l". cvs-rlog) - ("\M-c". cvs-checkout) - ("\M-e". cvs-examine) - ("g" . cvs-mode-revert-buffer) - ("\M-u". cvs-update) - ("\M-s". cvs-status) - ;; diff commands - ("=" . cvs-mode-diff) - ("d" . cvs-mode-diff-map) - ;; keys that operate on individual files - ("\C-k" . cvs-mode-acknowledge) - ("A" . cvs-mode-add-change-log-entry-other-window) - ;;("B" . cvs-mode-byte-compile-files) - ("C" . cvs-mode-commit-setup) - ("O" . cvs-mode-update) - ("U" . cvs-mode-undo) - ("I" . cvs-mode-insert) - ("a" . cvs-mode-add) - ("b" . cvs-set-branch-prefix) - ("B" . cvs-set-secondary-branch-prefix) - ("c" . cvs-mode-commit) - ("e" . cvs-mode-examine) - ("f" . cvs-mode-find-file) - ("\C-m" . cvs-mode-find-file) - ("i" . cvs-mode-ignore) - ("l" . cvs-mode-log) - ("o" . cvs-mode-find-file-other-window) - ("r" . cvs-mode-remove) - ("s" . cvs-mode-status) - ("t" . cvs-mode-tag) - ("v" . cvs-mode-view-file) - ("x" . cvs-mode-remove-handled) - ;; cvstree bindings - ("+" . cvs-mode-tree) - ;; mouse bindings - ([mouse-2] . cvs-mode-find-file) - ([follow-link] . (lambda (pos) - (if (eq (get-char-property pos 'face) 'cvs-filename) t))) - ([(down-mouse-3)] . cvs-menu) - ;; dired-like bindings - ("\C-o" . cvs-mode-display-file) - ;; Emacs-21 toolbar - ;;([tool-bar item1] . (menu-item "Examine" cvs-examine :image (image :file "/usr/share/icons/xpaint.xpm" :type xpm))) - ;;([tool-bar item2] . (menu-item "Update" cvs-update :image (image :file "/usr/share/icons/mail1.xpm" :type xpm))) - ) - "Keymap for `cvs-mode'." - :dense t - :suppress t) - -(fset 'cvs-mode-map cvs-mode-map) - -(easy-menu-define cvs-menu cvs-mode-map "Menu used in `cvs-mode'." - '("CVS" - ["Open file" cvs-mode-find-file t] - ["Open in other window" cvs-mode-find-file-other-window t] - ["Display in other window" cvs-mode-display-file t] - ["Interactive merge" cvs-mode-imerge t] - ("View diff" - ["Interactive diff" cvs-mode-idiff t] - ["Current diff" cvs-mode-diff t] - ["Diff with head" cvs-mode-diff-head t] - ["Diff with vendor" cvs-mode-diff-vendor t] - ["Diff against yesterday" cvs-mode-diff-yesterday t] - ["Diff with backup" cvs-mode-diff-backup t]) - ["View log" cvs-mode-log t] - ["View status" cvs-mode-status t] - ["View tag tree" cvs-mode-tree t] - "----" - ["Insert" cvs-mode-insert] - ["Update" cvs-mode-update (cvs-enabledp 'update)] - ["Re-examine" cvs-mode-examine t] - ["Commit" cvs-mode-commit-setup (cvs-enabledp 'commit)] - ["Tag" cvs-mode-tag (cvs-enabledp (when cvs-force-dir-tag 'tag))] - ["Undo changes" cvs-mode-undo (cvs-enabledp 'undo)] - ["Add" cvs-mode-add (cvs-enabledp 'add)] - ["Remove" cvs-mode-remove (cvs-enabledp 'remove)] - ["Ignore" cvs-mode-ignore (cvs-enabledp 'ignore)] - ["Add ChangeLog" cvs-mode-add-change-log-entry-other-window t] - "----" - ["Mark" cvs-mode-mark t] - ["Mark all" cvs-mode-mark-all-files t] - ["Mark by regexp..." cvs-mode-mark-matching-files t] - ["Mark by state..." cvs-mode-mark-on-state t] - ["Unmark" cvs-mode-unmark t] - ["Unmark all" cvs-mode-unmark-all-files t] - ["Hide handled" cvs-mode-remove-handled t] - "----" - ["PCL-CVS Manual" (lambda () (interactive) - (info "(pcl-cvs)Top")) t] - "----" - ["Quit" cvs-mode-quit t])) - -;;;; -;;;; CVS-Minor mode -;;;; - -(defcustom cvs-minor-mode-prefix "\C-xc" - "Prefix key for the `cvs-mode' bindings in `cvs-minor-mode'." - :group 'pcl-cvs) - -(easy-mmode-defmap cvs-minor-mode-map - `((,cvs-minor-mode-prefix . cvs-mode-map) - ("e" . (menu-item nil cvs-mode-edit-log - :filter (lambda (x) (if (derived-mode-p 'log-view-mode) x))))) - "Keymap for `cvs-minor-mode', used in buffers related to PCL-CVS.") - -(defvar cvs-buffer nil - "(Buffer local) The *cvs* buffer associated with this buffer.") -(put 'cvs-buffer 'permanent-local t) -;;(make-variable-buffer-local 'cvs-buffer) - -(defvar cvs-minor-wrap-function nil - "Function to call when switching to the *cvs* buffer. -Takes two arguments: -- a *cvs* buffer. -- a zero-arg function which is guaranteed not to switch buffer. -It is expected to call the function.") -;;(make-variable-buffer-local 'cvs-minor-wrap-function) - -(defvar cvs-minor-current-files) -;;"Current files in a `cvs-minor-mode' buffer." -;; This should stay `void' because we want to be able to tell the difference -;; between an empty list and no list at all. - -(defconst cvs-pcl-cvs-dirchange-re "^pcl-cvs: descending directory \\(.*\\)$") - -;;;; -;;;; autoload the global menu -;;;; - -;;;###autoload -(defvar cvs-global-menu - (let ((m (make-sparse-keymap "PCL-CVS"))) - (define-key m [status] - `(menu-item ,(purecopy "Directory Status") cvs-status - :help ,(purecopy "A more verbose status of a workarea"))) - (define-key m [checkout] - `(menu-item ,(purecopy "Checkout Module") cvs-checkout - :help ,(purecopy "Check out a module from the repository"))) - (define-key m [update] - `(menu-item ,(purecopy "Update Directory") cvs-update - :help ,(purecopy "Fetch updates from the repository"))) - (define-key m [examine] - `(menu-item ,(purecopy "Examine Directory") cvs-examine - :help ,(purecopy "Examine the current state of a workarea"))) - (fset 'cvs-global-menu m))) - - -;; cvs-1.10 and above can take file arguments in other directories -;; while others need to be executed once per directory -(defvar cvs-execute-single-dir - (if (or (null cvs-version) - (or (>= (cdr cvs-version) 10) (> (car cvs-version) 1))) - ;; Supposedly some recent versions of CVS output some directory info - ;; as they recurse downthe tree, but it's not good enough in the case - ;; where we run "cvs status foo bar/foo". - '("status") - t) - "Whether cvs commands should be executed a directory at a time. -If a list, specifies for which commands the single-dir mode should be used. -If T, single-dir mode should be used for all operations. - -CVS versions before 1.10 did not allow passing them arguments in different -directories, so pcl-cvs checks what version you're using to determine -whether to use the new feature or not. -Sadly, even with a new cvs executable, if you connect to an older cvs server -\(typically a cvs-1.9 on the server), the old restriction applies. In such -a case the sanity check made by pcl-cvs fails and you will have to manually -set this variable to t (until the cvs server is upgraded). -When the above problem occurs, pcl-cvs should (hopefully) catch cvs' error -message and replace it with a message telling you to change this variable.") - -;; -(provide 'pcvs-defs) - -;; arch-tag: c7c701d0-d1d4-4aa9-a302-007bb03aca5e -;;; pcvs-defs.el ends here diff -r 681cd08dc0f7 -r e93288477c43 lisp/pcvs-info.el --- a/lisp/pcvs-info.el Thu Jun 10 22:43:47 2010 +0000 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,489 +0,0 @@ -;;; pcvs-info.el --- internal representation of a fileinfo entry - -;; Copyright (C) 1991, 1992, 1993, 1994, 1995, 1996, 1997, 1998, 1999, -;; 2000, 2001, 2002, 2003, 2004, 2005, 2006, 2007, 2008, 2009, 2010 -;; Free Software Foundation, Inc. - -;; Author: Stefan Monnier -;; Keywords: pcl-cvs - -;; 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 3 of the License, 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. If not, see . - -;;; Commentary: - -;; The cvs-fileinfo data structure: -;; -;; When the `cvs update' is ready we parse the output. Every file -;; that is affected in some way is added to the cookie collection as -;; a "fileinfo" (as defined below in cvs-create-fileinfo). - -;;; Code: - -(eval-when-compile (require 'cl)) -(require 'pcvs-util) -;;(require 'pcvs-defs) - -;;;; -;;;; config variables -;;;; - -(define-obsolete-variable-alias 'cvs-display-full-path - 'cvs-display-full-name "22.1") - -(defcustom cvs-display-full-name t - "Specifies how the filenames should be displayed in the listing. -If non-nil, their full filename name will be displayed, else only the -non-directory part." - :group 'pcl-cvs - :type '(boolean)) - -(defcustom cvs-allow-dir-commit nil - "Allow `cvs-mode-commit' on directories. -If you commit without any marked file and with the cursor positioned -on a directory entry, cvs would commit the whole directory. This seems -to confuse some users sometimes." - :group 'pcl-cvs - :type '(boolean)) - -;;;; -;;;; Faces for fontification -;;;; - -(defface cvs-header - '((((class color) (background dark)) - (:foreground "lightyellow" :weight bold)) - (((class color) (background light)) - (:foreground "blue4" :weight bold)) - (t (:weight bold))) - "PCL-CVS face used to highlight directory changes." - :group 'pcl-cvs) -(define-obsolete-face-alias 'cvs-header-face 'cvs-header "22.1") - -(defface cvs-filename - '((((class color) (background dark)) - (:foreground "lightblue")) - (((class color) (background light)) - (:foreground "blue4")) - (t ())) - "PCL-CVS face used to highlight file names." - :group 'pcl-cvs) -(define-obsolete-face-alias 'cvs-filename-face 'cvs-filename "22.1") - -(defface cvs-unknown - '((((class color) (background dark)) - (:foreground "red1")) - (((class color) (background light)) - (:foreground "red1")) - (t (:slant italic))) - "PCL-CVS face used to highlight unknown file status." - :group 'pcl-cvs) -(define-obsolete-face-alias 'cvs-unknown-face 'cvs-unknown "22.1") - -(defface cvs-handled - '((((class color) (background dark)) - (:foreground "pink")) - (((class color) (background light)) - (:foreground "pink")) - (t ())) - "PCL-CVS face used to highlight handled file status." - :group 'pcl-cvs) -(define-obsolete-face-alias 'cvs-handled-face 'cvs-handled "22.1") - -(defface cvs-need-action - '((((class color) (background dark)) - (:foreground "orange")) - (((class color) (background light)) - (:foreground "orange")) - (t (:slant italic))) - "PCL-CVS face used to highlight status of files needing action." - :group 'pcl-cvs) -(define-obsolete-face-alias 'cvs-need-action-face 'cvs-need-action "22.1") - -(defface cvs-marked - '((((min-colors 88) (class color) (background dark)) - (:foreground "green1" :weight bold)) - (((class color) (background dark)) - (:foreground "green" :weight bold)) - (((class color) (background light)) - (:foreground "green3" :weight bold)) - (t (:weight bold))) - "PCL-CVS face used to highlight marked file indicator." - :group 'pcl-cvs) -(define-obsolete-face-alias 'cvs-marked-face 'cvs-marked "22.1") - -(defface cvs-msg - '((t (:slant italic))) - "PCL-CVS face used to highlight CVS messages." - :group 'pcl-cvs) -(define-obsolete-face-alias 'cvs-msg-face 'cvs-msg "22.1") - -(defvar cvs-fi-up-to-date-face 'cvs-handled) -(defvar cvs-fi-unknown-face 'cvs-unknown) -(defvar cvs-fi-conflict-face 'font-lock-warning-face) - -;; There is normally no need to alter the following variable, but if -;; your site has installed CVS in a non-standard way you might have -;; to change it. - -(defvar cvs-bakprefix ".#" - "The prefix that CVS prepends to files when rcsmerge'ing.") - -(easy-mmode-defmap cvs-status-map - '(([(mouse-2)] . cvs-mode-toggle-mark)) - "Local keymap for text properties of status") - -;; Constructor: - -(defstruct (cvs-fileinfo - (:constructor nil) - (:copier nil) - (:constructor -cvs-create-fileinfo (type dir file full-log - &key marked subtype - merge - base-rev - head-rev)) - (:conc-name cvs-fileinfo->)) - marked ;; t/nil. - type ;; See below - subtype ;; See below - dir ;; Relative directory the file resides in. - ;; (concat dir file) should give a valid path. - file ;; The file name sans the directory. - base-rev ;; During status: This is the revision that the - ;; working file is based on. - head-rev ;; During status: This is the highest revision in - ;; the repository. - merge ;; A cons cell containing the (ancestor . head) revisions - ;; of the merge that resulted in the current file. - ;;removed ;; t if the file no longer exists. - full-log ;; The output from cvs, unparsed. - ;;mod-time ;; Not used. - - ;; In addition to the above, the following values can be extracted: - - ;; handled ;; t if this file doesn't require further action. - ;; full-name ;; The complete relative filename. - ;; pp-name ;; The printed file name - ;; backup-file;; For MERGED and CONFLICT files after a \"cvs update\", - ;; this is a full path to the backup file where the - ;; untouched version resides. - - ;; The meaning of the type field: - - ;; Value ---Used by--- Explanation - ;; update status - ;; NEED-UPDATE x file needs update - ;; MODIFIED x x modified by you, unchanged in repository - ;; MERGED x x successful merge - ;; ADDED x x added by you, not yet committed - ;; MISSING x rm'd, but not yet `cvs remove'd - ;; REMOVED x x removed by you, not yet committed - ;; NEED-MERGE x need merge - ;; CONFLICT x conflict when merging - ;; ;;MOD-CONFLICT x removed locally, changed in repository. - ;; DIRCHANGE x x A change of directory. - ;; UNKNOWN x An unknown file. - ;; UP-TO-DATE x The file is up-to-date. - ;; UPDATED x x file copied from repository - ;; PATCHED x x diff applied from repository - ;; COMMITTED x x cvs commit'd - ;; DEAD An entry that should be removed - ;; MESSAGE x x This is a special fileinfo that is used - ;; to display a text that should be in - ;; full-log." - ;; TEMP A temporary message that should be removed - ) -(defun cvs-create-fileinfo (type dir file msg &rest keys) - (cvs-check-fileinfo (apply #'-cvs-create-fileinfo type dir file msg keys))) - -;; Fake selectors: - -(defun cvs-fileinfo->full-name (fileinfo) - "Return the full path for the file that is described in FILEINFO." - (let ((dir (cvs-fileinfo->dir fileinfo))) - (if (eq (cvs-fileinfo->type fileinfo) 'DIRCHANGE) - (if (string= dir "") "." (directory-file-name dir)) - ;; Here, I use `concat' rather than `expand-file-name' because I want - ;; the resulting path to stay relative if `dir' is relative. - (concat dir (cvs-fileinfo->file fileinfo))))) -(define-obsolete-function-alias 'cvs-fileinfo->full-path - 'cvs-fileinfo->full-name "22.1") - -(defun cvs-fileinfo->pp-name (fi) - "Return the filename of FI as it should be displayed." - (if cvs-display-full-name - (cvs-fileinfo->full-name fi) - (cvs-fileinfo->file fi))) - -(defun cvs-fileinfo->backup-file (fileinfo) - "Construct the file name of the backup file for FILEINFO." - (let* ((dir (cvs-fileinfo->dir fileinfo)) - (file (cvs-fileinfo->file fileinfo)) - (default-directory (file-name-as-directory (expand-file-name dir))) - (files (directory-files "." nil - (concat "\\`" (regexp-quote cvs-bakprefix) - (regexp-quote file) "\\(\\.[0-9]+\\.[0-9]+\\)+\\'"))) - bf) - (dolist (f files) - (when (and (file-readable-p f) - (or (null bf) (file-newer-than-file-p f bf))) - (setq bf f))) - (concat dir bf))) - -;; (defun cvs-fileinfo->handled (fileinfo) -;; "Tell if this requires further action" -;; (memq (cvs-fileinfo->type fileinfo) '(UP-TO-DATE DEAD))) - - -;; Predicate: - -(defun cvs-check-fileinfo (fi) - "Check FI's conformance to some conventions." - (let ((check 'none) - (type (cvs-fileinfo->type fi)) - (subtype (cvs-fileinfo->subtype fi)) - (marked (cvs-fileinfo->marked fi)) - (dir (cvs-fileinfo->dir fi)) - (file (cvs-fileinfo->file fi)) - (base-rev (cvs-fileinfo->base-rev fi)) - (head-rev (cvs-fileinfo->head-rev fi)) - (full-log (cvs-fileinfo->full-log fi))) - (if (and (setq check 'marked) (memq marked '(t nil)) - (setq check 'base-rev) (or (null base-rev) (stringp base-rev)) - (setq check 'head-rev) (or (null head-rev) (stringp head-rev)) - (setq check 'full-log) (stringp full-log) - (setq check 'dir) - (and (stringp dir) - (not (file-name-absolute-p dir)) - (or (string= dir "") - (string= dir (file-name-as-directory dir)))) - (setq check 'file) - (and (stringp file) - (string= file (file-name-nondirectory file))) - (setq check 'type) (symbolp type) - (setq check 'consistency) - (case type - (DIRCHANGE (and (null subtype) (string= "." file))) - ((NEED-UPDATE ADDED MISSING DEAD MODIFIED MESSAGE UP-TO-DATE - REMOVED NEED-MERGE CONFLICT UNKNOWN MESSAGE) - t))) - fi - (error "Invalid :%s in cvs-fileinfo %s" check fi)))) - - -;;;; -;;;; State table to indicate what you can do when. -;;;; - -(defconst cvs-states - `((NEED-UPDATE update diff ignore) - (UP-TO-DATE update nil remove diff safe-rm revert) - (MODIFIED update commit undo remove diff merge diff-base) - (ADDED update commit remove) - (MISSING remove undo update safe-rm revert) - (REMOVED commit add undo safe-rm) - (NEED-MERGE update undo diff diff-base) - (CONFLICT merge remove undo commit diff diff-base) - (DIRCHANGE remove update diff ,(if cvs-allow-dir-commit 'commit) tag) - (UNKNOWN ignore add remove) - (DEAD ) - (MESSAGE)) - "Fileinfo state descriptions for pcl-cvs. -This is an assoc list. Each element consists of (STATE . FUNS) -- STATE (described in `cvs-create-fileinfo') is the key -- FUNS is the list of applicable operations. - The first one (if any) should be the \"default\" action. -Most of the actions have the obvious meaning. -`safe-rm' indicates that the file can be removed without losing - any information.") - -;;;; -;;;; Utility functions -;;;; - -(defun cvs-applicable-p (fi-or-type func) - "Check if FUNC is applicable to FI-OR-TYPE. -If FUNC is nil, always return t. -FI-OR-TYPE can either be a symbol (a fileinfo-type) or a fileinfo." - (let ((type (if (symbolp fi-or-type) fi-or-type - (cvs-fileinfo->type fi-or-type)))) - (and (not (eq type 'MESSAGE)) - (eq (car (memq func (cdr (assq type cvs-states)))) func)))) - -(defun cvs-add-face (str face &optional keymap &rest props) - (when keymap - (when (keymapp keymap) - (setq props (list* 'keymap keymap props))) - (setq props (list* 'mouse-face 'highlight props))) - (add-text-properties 0 (length str) (list* 'font-lock-face face props) str) - str) - -(defun cvs-fileinfo-pp (fileinfo) - "Pretty print FILEINFO. Insert a printed representation in current buffer. -For use by the cookie package." - (cvs-check-fileinfo fileinfo) - (let ((type (cvs-fileinfo->type fileinfo)) - (subtype (cvs-fileinfo->subtype fileinfo))) - (insert - (case type - (DIRCHANGE (concat "In directory " - (cvs-add-face (cvs-fileinfo->full-name fileinfo) - 'cvs-header t 'cvs-goal-column t) - ":")) - (MESSAGE - (cvs-add-face (format "Message: %s" (cvs-fileinfo->full-log fileinfo)) - 'cvs-msg)) - (t - (let* ((status (if (cvs-fileinfo->marked fileinfo) - (cvs-add-face "*" 'cvs-marked) - " ")) - (file (cvs-add-face (cvs-fileinfo->pp-name fileinfo) - 'cvs-filename t 'cvs-goal-column t)) - (base (or (cvs-fileinfo->base-rev fileinfo) "")) - (head (cvs-fileinfo->head-rev fileinfo)) - (type - (let ((str (case type - ;;(MOD-CONFLICT "Not Removed") - (DEAD "") - (t (capitalize (symbol-name type))))) - (face (let ((sym (intern - (concat "cvs-fi-" - (downcase (symbol-name type)) - "-face")))) - (or (and (boundp sym) (symbol-value sym)) - 'cvs-need-action)))) - (cvs-add-face str face cvs-status-map))) - (side (or - ;; maybe a subtype - (when subtype (downcase (symbol-name subtype))) - ;; or the head-rev - (when (and head (not (string= head base))) head) - ;; or nothing - ""))) - (format "%-11s %s %-11s %-11s %s" - side status type base file)))) - "\n"))) - - -(defun cvs-fileinfo-update (fi fi-new) - "Update FI with the information provided in FI-NEW." - (let ((type (cvs-fileinfo->type fi-new)) - (merge (cvs-fileinfo->merge fi-new))) - (setf (cvs-fileinfo->type fi) type) - (setf (cvs-fileinfo->subtype fi) (cvs-fileinfo->subtype fi-new)) - (setf (cvs-fileinfo->full-log fi) (cvs-fileinfo->full-log fi-new)) - (setf (cvs-fileinfo->base-rev fi) (cvs-fileinfo->base-rev fi-new)) - (setf (cvs-fileinfo->head-rev fi) (cvs-fileinfo->head-rev fi-new)) - (cond - (merge (setf (cvs-fileinfo->merge fi) merge)) - ((memq type '(UP-TO-DATE NEED-UPDATE)) - (setf (cvs-fileinfo->merge fi) nil))))) - -(defun cvs-fileinfo< (a b) - "Compare fileinfo A with fileinfo B and return t if A is `less'. -The ordering defined by this function is such that directories are -sorted alphabetically, and inside every directory the DIRCHANGE -fileinfo will appear first, followed by all files (alphabetically)." - (let ((subtypea (cvs-fileinfo->subtype a)) - (subtypeb (cvs-fileinfo->subtype b))) - (cond - ;; Sort according to directories. - ((string< (cvs-fileinfo->dir a) (cvs-fileinfo->dir b)) t) - ((not (string= (cvs-fileinfo->dir a) (cvs-fileinfo->dir b))) nil) - - ;; The DIRCHANGE entry is always first within the directory. - ((eq (cvs-fileinfo->type b) 'DIRCHANGE) nil) - ((eq (cvs-fileinfo->type a) 'DIRCHANGE) t) - - ;; All files are sorted by file name. - ((string< (cvs-fileinfo->file a) (cvs-fileinfo->file b)))))) - -;;; -;;; Look at CVS/Entries to quickly find a first approximation of the status -;;; - -(defun cvs-fileinfo-from-entries (dir &optional all) - "List of fileinfos for DIR, extracted from CVS/Entries. -Unless ALL is optional, returns only the files that are not up-to-date. -DIR can also be a file." - (let* ((singlefile - (cond - ((equal dir "") nil) - ((file-directory-p dir) (setq dir (file-name-as-directory dir)) nil) - (t (prog1 (file-name-nondirectory dir) - (setq dir (or (file-name-directory dir) "")))))) - (file (expand-file-name "CVS/Entries" dir)) - (fis nil)) - (if (not (file-readable-p file)) - (push (cvs-create-fileinfo (if singlefile 'UNKNOWN 'DIRCHANGE) - dir (or singlefile ".") "") fis) - (with-temp-buffer - (insert-file-contents file) - (goto-char (point-min)) - ;; Select the single file entry in case we're only interested in a file. - (cond - ((not singlefile) - (push (cvs-create-fileinfo 'DIRCHANGE dir "." "") fis)) - ((re-search-forward - (concat "^[^/]*/" (regexp-quote singlefile) "/.*") nil t) - (setq all t) - (goto-char (match-beginning 0)) - (narrow-to-region (point) (match-end 0))) - (t - (push (cvs-create-fileinfo 'UNKNOWN dir singlefile "") fis) - (narrow-to-region (point-min) (point-min)))) - (while (looking-at "\\([^/]*\\)/\\([^/]*\\)/\\([^/]*\\)/\\([^/]*\\)/") - (if (/= (match-beginning 1) (match-end 1)) - (setq fis (append (cvs-fileinfo-from-entries - (concat dir (file-name-as-directory - (match-string 2))) - all) - fis)) - (let ((f (match-string 2)) - (rev (match-string 3)) - (date (match-string 4)) - timestamp - (type 'MODIFIED) - (subtype nil)) - (cond - ((equal (substring rev 0 1) "-") - (setq type 'REMOVED rev (substring rev 1))) - ((not (file-exists-p (concat dir f))) (setq type 'MISSING)) - ((equal rev "0") (setq type 'ADDED rev nil)) - ((equal date "Result of merge") (setq subtype 'MERGED)) - ((let ((mtime (nth 5 (file-attributes (concat dir f)))) - (system-time-locale "C")) - (setq timestamp (format-time-string "%c" mtime 'utc)) - ;; Solaris sometimes uses "Wed Sep 05", not "Wed Sep 5". - ;; See "grep '[^a-z_]ctime' cvs/src/*.c" for reference. - (if (= (aref timestamp 8) ?0) - (setq timestamp (concat (substring timestamp 0 8) - " " (substring timestamp 9)))) - (equal timestamp date)) - (setq type (if all 'UP-TO-DATE))) - ((equal date (concat "Result of merge+" timestamp)) - (setq type 'CONFLICT))) - (when type - (push (cvs-create-fileinfo type dir f "" - :base-rev rev :subtype subtype) - fis)))) - (forward-line 1)))) - fis)) - -(provide 'pcvs-info) - -;; arch-tag: d85dde07-bdc2-400a-882f-92f398c7b0ba -;;; pcvs-info.el ends here diff -r 681cd08dc0f7 -r e93288477c43 lisp/pcvs-parse.el --- a/lisp/pcvs-parse.el Thu Jun 10 22:43:47 2010 +0000 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,538 +0,0 @@ -;;; pcvs-parse.el --- the CVS output parser - -;; Copyright (C) 1991, 1992, 1993, 1994, 1995, 1996, 1997, 1998, 1999, -;; 2000, 2001, 2002, 2003, 2004, 2005, 2006, 2007, 2008, 2009, 2010 Free Software Foundation, Inc. - -;; Author: Stefan Monnier -;; Keywords: pcl-cvs - -;; 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 3 of the License, 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. If not, see . - -;;; Commentary: - -;;; Bugs: - -;; - when merging a modified file, if the merge says that the file already -;; contained in the changes, it marks the file as `up-to-date' although -;; it might still contain further changes. -;; Example: merging a zero-change commit. - -;;; Code: - -(eval-when-compile (require 'cl)) - -(require 'pcvs-util) -(require 'pcvs-info) - -;; imported from pcvs.el -(defvar cvs-execute-single-dir) - -;; parse vars - -(defcustom cvs-update-prog-output-skip-regexp "$" - "A regexp that matches the end of the output from all cvs update programs. -That is, output from any programs that are run by CVS (by the flag -u -in the `modules' file - see cvs(5)) when `cvs update' is performed should -terminate with a line that this regexp matches. It is enough that -some part of the line is matched. - -The default (a single $) fits programs without output." - :group 'pcl-cvs - :type '(regexp :value "$")) - -(defcustom cvs-parse-ignored-messages - '("Executing ssh-askpass to query the password.*$" - ".*Remote host denied X11 forwarding.*$") - "A list of regexps matching messages that should be ignored by the parser. -Each regexp should match a whole set of lines and should hence be terminated -by `$'." - :group 'pcl-cvs - :type '(repeat regexp)) - -;; a few more defvars just to shut up the compiler -(defvar cvs-start) -(defvar cvs-current-dir) -(defvar cvs-current-subdir) -(defvar dont-change-disc) - -;;;; The parser - -(defconst cvs-parse-known-commands - '("status" "add" "commit" "update" "remove" "checkout" "ci") - "List of CVS commands whose output is understood by the parser.") - -(defun cvs-parse-buffer (parse-spec dont-change-disc &optional subdir) - "Parse current buffer according to PARSE-SPEC. -PARSE-SPEC is a function of no argument advancing the point and returning - either a fileinfo or t (if the matched text should be ignored) or - nil if it didn't match anything. -DONT-CHANGE-DISC just indicates whether the command was changing the disc - or not (useful to tell the difference between `cvs-examine' and `cvs-update' - output. -The path names should be interpreted as relative to SUBDIR (defaults - to the `default-directory'). -Return a list of collected entries, or t if an error occurred." - (goto-char (point-min)) - (let ((fileinfos ()) - (cvs-current-dir "") - (case-fold-search nil) - (cvs-current-subdir (or subdir ""))) - (while (not (or (eobp) (eq fileinfos t))) - (let ((ret (cvs-parse-run-table parse-spec))) - (cond - ;; it matched a known information message - ((cvs-fileinfo-p ret) (push ret fileinfos)) - ;; it didn't match anything at all (impossible) - ((and (consp ret) (cvs-fileinfo-p (car ret))) - (setq fileinfos (append ret fileinfos))) - ((null ret) (setq fileinfos t)) - ;; it matched something that should be ignored - (t nil)))) - (nreverse fileinfos))) - - -;; All those parsing macros/functions should return a success indicator -(defsubst cvs-parse-msg () (buffer-substring cvs-start (1- (point)))) - -;;(defsubst COLLECT (exp) (push exp *result*)) -;;(defsubst PROG (e) t) -;;(defmacro SEQ (&rest seqs) (cons 'and seqs)) - -(defmacro cvs-match (re &rest matches) - "Try to match RE and extract submatches. -If RE matches, advance the point until the line after the match and -then assign the variables as specified in MATCHES (via `setq')." - (cons 'cvs-do-match - (cons re (mapcar (lambda (match) - `(cons ',(first match) ,(second match))) - matches)))) - -(defun cvs-do-match (re &rest matches) - "Internal function for the `cvs-match' macro. -Match RE and if successful, execute MATCHES." - ;; Is it a match? - (when (looking-at re) - (goto-char (match-end 0)) - ;; Skip the newline (unless we already are at the end of the buffer). - (when (and (eolp) (< (point) (point-max))) (forward-char)) - ;; assign the matches - (dolist (match matches t) - (let ((val (cdr match))) - (set (car match) (if (integerp val) (match-string val) val)))))) - -(defmacro cvs-or (&rest alts) - "Try each one of the ALTS alternatives until one matches." - `(let ((-cvs-parse-point (point))) - ,(cons 'or - (mapcar (lambda (es) - `(or ,es (ignore (goto-char -cvs-parse-point)))) - alts)))) -(def-edebug-spec cvs-or t) - -;; This is how parser tables should be executed -(defun cvs-parse-run-table (parse-spec) - "Run PARSE-SPEC and provide sensible default behavior." - (unless (bolp) (forward-line 1)) ;this should never be needed - (let ((cvs-start (point))) - (cvs-or - (funcall parse-spec) - - (dolist (re cvs-parse-ignored-messages) - (when (cvs-match re) (return t))) - - ;; This is a parse error. Create a message-type fileinfo. - (and - (cvs-match ".*$") - (cvs-create-fileinfo 'MESSAGE cvs-current-dir " " - ;; (concat " Unknown msg: '" - (cvs-parse-msg) ;; "'") - :subtype 'ERROR))))) - - -(defun cvs-parsed-fileinfo (type path &optional directory &rest keys) - "Create a fileinfo. -TYPE can either be a type symbol or a cons of the form (TYPE . SUBTYPE). -PATH is the filename. -DIRECTORY influences the way PATH is interpreted: -- if it's a string, it denotes the directory in which PATH (which should then be - a plain file name with no directory component) resides. -- if it's nil, the PATH should not be trusted: if it has a directory - component, use it, else, assume it is relative to the current directory. -- else, the PATH should be trusted to be relative to the root - directory (i.e. if there is no directory component, it means the file - is inside the main directory). -The remaining KEYS are passed directly to `cvs-create-fileinfo'." - (let ((dir directory) - (file path)) - ;; only trust the directory if it's a string - (unless (stringp directory) - ;; else, if the directory is true, the path should be trusted - (setq dir (or (file-name-directory path) (if directory ""))) - (setq file (file-name-nondirectory path))) - - (let ((type (if (consp type) (car type) type)) - (subtype (if (consp type) (cdr type)))) - (when dir (setq cvs-current-dir dir)) - (apply 'cvs-create-fileinfo type - (concat cvs-current-subdir (or dir cvs-current-dir)) - file (cvs-parse-msg) :subtype subtype keys)))) - -;;;; CVS Process Parser Tables: -;;;; -;;;; The table for status and update could actually be merged since they -;;;; don't conflict. But they don't overlap much either. - -(defun cvs-parse-table () - "Table of message objects for `cvs-parse-process'." - (let (c file dir path base-rev subtype) - (cvs-or - - (cvs-parse-status) - (cvs-parse-merge) - (cvs-parse-commit) - - ;; this is not necessary because the fileinfo merging will remove - ;; such duplicate info and luckily the second info is the one we want. - ;; (and (cvs-match "M \\(.*\\)$" (path 1)) - ;; (cvs-parse-merge path)) - - ;; Normal file state indicator. - (and - (cvs-match "\\([MARCUPNJ?]\\) \\(.*\\)$" (c 1) (path 2)) - ;; M: The file is modified by the user, and untouched in the repository. - ;; A: The file is "cvs add"ed, but not "cvs ci"ed. - ;; R: The file is "cvs remove"ed, but not "cvs ci"ed. - ;; C: Conflict - ;; U: The file is copied from the repository. - ;; P: The file was patched from the repository. - ;; ?: Unknown file. - (let ((code (aref c 0))) - (cvs-parsed-fileinfo - (case code - (?M 'MODIFIED) - (?A 'ADDED) - (?R 'REMOVED) - (?? 'UNKNOWN) - (?C - (if (not dont-change-disc) 'CONFLICT - ;; This is ambiguous. We should look for conflict markers in the - ;; file to decide between CONFLICT and NEED-MERGE. With CVS-1.10 - ;; servers, this should not be necessary, because they return - ;; a complete merge output. - (with-temp-buffer - (ignore-errors (insert-file-contents path)) - (goto-char (point-min)) - (if (re-search-forward "^<<<<<<< " nil t) - 'CONFLICT 'NEED-MERGE)))) - (?J 'NEED-MERGE) ;not supported by standard CVS - ((?U ?P) - (if dont-change-disc 'NEED-UPDATE - (cons 'UP-TO-DATE (if (eq code ?U) 'UPDATED 'PATCHED))))) - path 'trust))) - - (and - (cvs-match "pcl-cvs: descending directory \\(.*\\)$" (dir 1)) - (setq cvs-current-subdir dir)) - - ;; A special cvs message - (and - (let ((case-fold-search t)) - (cvs-match "cvs[.a-z]* [a-z]+: ")) - (cvs-or - - ;; CVS is descending a subdirectory - ;; (status says `examining' while update says `updating') - (and - (cvs-match "\\(Examining\\|Updating\\) \\(.*\\)$" (dir 2)) - (let ((dir (if (string= "." dir) "" (file-name-as-directory dir)))) - (cvs-parsed-fileinfo 'DIRCHANGE "." dir))) - - ;; [-n update] A new (or pruned) directory appeared but isn't traversed - (and - (cvs-match "New directory `\\(.*\\)' -- ignored$" (dir 1)) - ;; (cvs-parsed-fileinfo 'MESSAGE " " (file-name-as-directory dir)) - ;; These messages either correspond to a true new directory - ;; that an update will bring in, or to a directory that's empty - ;; on the current branch (either because it only exists in other - ;; branches, or because it's been removed). - (if (ignore-errors - (with-temp-buffer - (ignore-errors - (insert-file-contents - (expand-file-name ".cvsignore" (file-name-directory dir)))) - (goto-char (point-min)) - (re-search-forward - (concat "^" (regexp-quote (file-name-nondirectory dir)) "/$") - nil t))) - t ;The user requested to ignore those messages. - (cvs-parsed-fileinfo '(NEED-UPDATE . NEW-DIR) dir t))) - - ;; File removed, since it is removed (by third party) in repository. - (and - (cvs-or - ;; some cvs versions output quotes around these files - (cvs-match "warning: `\\(.*\\)' is not (any longer) pertinent$" (file 1)) - (cvs-match "warning: \\(.*\\) is not (any longer) pertinent$" (file 1)) - (cvs-match "`\\(.*\\)' is no longer in the repository$" (file 1)) - (cvs-match "\\(.*\\) is no longer in the repository$" (file 1))) - (cvs-parsed-fileinfo - (if dont-change-disc '(NEED-UPDATE . REMOVED) 'DEAD) file)) - - ;; [add] - (and - (cvs-or - (cvs-match "scheduling file `\\(.*\\)' for addition.*$" (path 1)) - (cvs-match "re-adding file \\(.*\\) (in place of .*)$" (path 1))) - (cvs-parsed-fileinfo 'ADDED path)) - - ;; [add] this will also show up as a `U ' - (and - (cvs-match "`?\\(.*?\\)'?, version \\(.*\\), resurrected$" - (path 1) (base-rev 2)) - ;; FIXME: resurrection only brings back the original version, - ;; not the latest on the branch, so `up-to-date' is not always - ;; what we want. - (cvs-parsed-fileinfo '(UP-TO-DATE . RESURRECTED) path nil - :base-rev base-rev)) - - ;; [remove] - (and - (cvs-match "removed `\\(.*\\)'$" (path 1)) - (cvs-parsed-fileinfo 'DEAD path)) - - ;; [remove,merge] - (and - (cvs-match "scheduling `\\(.*\\)' for removal$" (file 1)) - (cvs-parsed-fileinfo 'REMOVED file)) - - ;; [update] File removed by you, but not cvs rm'd - (and - (cvs-match "warning: \\(.*\\) was lost$" (path 1)) - (cvs-match (concat "U " (regexp-quote path) "$")) - (cvs-parsed-fileinfo (if dont-change-disc - 'MISSING - '(UP-TO-DATE . UPDATED)) - path)) - - ;; Mode conflicts (rather than contents) - (and - (cvs-match "conflict: ") - (cvs-or - (cvs-match "removed \\(.*\\) was modified by second party$" - (path 1) (subtype 'REMOVED)) - (cvs-match "\\(.*\\) created independently by second party$" - (path 1) (subtype 'ADDED)) - (cvs-match "\\(.*\\) is modified but no longer in the repository$" - (path 1) (subtype 'MODIFIED))) - (cvs-match (concat "C " (regexp-quote path))) - (cvs-parsed-fileinfo (cons 'CONFLICT subtype) path)) - - ;; Messages that should be shown to the user - (and - (cvs-or - (cvs-match "move away \\(.*\\); it is in the way$" (file 1)) - (cvs-match "warning: new-born \\(.*\\) has disappeared$" (file 1)) - (cvs-match "sticky tag .* for file `\\(.*\\)' is not a branch$" - (file 1))) - (cvs-parsed-fileinfo 'MESSAGE file)) - - ;; File unknown. - (and (cvs-match "use `.+ add' to create an entry for \\(.*\\)$" (path 1)) - (cvs-parsed-fileinfo 'UNKNOWN path)) - - ;; [commit] - (and (cvs-match "Up-to-date check failed for `\\(.+\\)'$" (file 1)) - (cvs-parsed-fileinfo 'NEED-MERGE file)) - - ;; We use cvs-execute-multi-dir but cvs can't handle it - ;; Probably because the cvs-client can but the cvs-server can't - (and (cvs-match ".* files with '?/'? in their name.*$") - (not cvs-execute-single-dir) - (setq cvs-execute-single-dir t) - (cvs-create-fileinfo - 'MESSAGE "" " " - "*** Add (setq cvs-execute-single-dir t) to your .emacs *** - See the FAQ file or the variable's documentation for more info.")) - - ;; Cvs waits for a lock. Ignored: already handled by the process filter - (cvs-match "\\[..:..:..\\] \\(waiting for\\|obtained\\) .*lock in .*$") - ;; File you removed still exists. Ignore (will be noted as removed). - (cvs-match ".* should be removed and is still there$") - ;; just a note - (cvs-match "use ['`].+ commit' to \\sw+ th\\sw+ files? permanently$") - ;; [add,status] followed by a more complete status description anyway - (and (cvs-match "nothing known about \\(.*\\)$" (path 1)) - (cvs-parsed-fileinfo 'DEAD path 'trust)) - ;; [update] problem with patch - (cvs-match "checksum failure after patch to .*; will refetch$") - (cvs-match "refetching unpatchable files$") - ;; [commit] - (cvs-match "Rebuilding administrative file database$") - ;; ??? - (cvs-match "--> Using per-directory sticky tag `.*'") - - ;; CVS is running a *info program. - (and - (cvs-match "Executing.*$") - ;; Skip by any output the program may generate to stdout. - ;; Note that pcl-cvs will get seriously confused if the - ;; program prints anything to stderr. - (re-search-forward cvs-update-prog-output-skip-regexp)))) - - (and - (cvs-match "cvs[.ex]* \\[[a-z]+ aborted\\]:.*$") - (cvs-parsed-fileinfo 'MESSAGE "")) - - ;; sadly you can't do much with these since the path is in the repository - (cvs-match "Directory .* added to the repository$") - ))) - - -(defun cvs-parse-merge () - (let (path base-rev head-rev type) - ;; A merge (maybe with a conflict). - (and - (cvs-match "RCS file: .*$") - ;; Squirrel away info about the files that were retrieved for merging - (cvs-match "retrieving revision \\([0-9.]+\\)$" (base-rev 1)) - (cvs-match "retrieving revision \\([0-9.]+\\)$" (head-rev 1)) - (cvs-match "Merging differences between [0-9.]+ and [0-9.]+ into \\(.*\\)$" - (path 1)) - - ;; eat up potential conflict warnings - (cvs-or (cvs-match "\\(rcs\\)?merge:?\\( warning\\)?: \\(overlaps\\|conflicts\\) \\(or other problems \\)?during merge$" (type 'CONFLICT)) t) - (cvs-or - (and - (cvs-match "cvs[.ex]* [a-z]+: ") - (cvs-or - (cvs-match "conflicts found in \\(.*\\)$" (path 1) (type 'CONFLICT)) - (cvs-match "could not merge .*$") - (cvs-match "restoring \\(.*\\) from backup file .*$" (path 1)))) - t) - - ;; Is it a succesful merge? - ;; Figure out result of merging (ie, was there a conflict?) - (let ((qfile (regexp-quote path))) - (cvs-or - ;; Conflict - (and - (cvs-match (concat "C \\(.*" qfile "\\)$") (path 1) (type 'CONFLICT)) - ;; C might be followed by a "suprious" U for non-mergeable files - (cvs-or (cvs-match (concat "U \\(.*" qfile "\\)$")) t)) - ;; Successful merge - (cvs-match (concat "M \\(.*" qfile "\\)$") (path 1)) - ;; The file already contained the modifications - (cvs-match (concat "^\\(.*" qfile - "\\) already contains the differences between .*$") - (path 1) (type '(UP-TO-DATE . MERGED))) - t) - ;; FIXME: PATH might not be set yet. Sometimes the only path - ;; information is in `RCS file: ...' (yuck!!). - (cvs-parsed-fileinfo (if dont-change-disc 'NEED-MERGE - (or type '(MODIFIED . MERGED))) path nil - :merge (cons base-rev head-rev)))))) - -(defun cvs-parse-status () - (let (nofile path base-rev head-rev type) - (and - (cvs-match - "===================================================================$") - (cvs-match "File: \\(no file \\)?\\(.*[^ \t]\\)[ \t]+Status: " - (nofile 1) (path 2)) - (cvs-or - (cvs-match "Needs \\(Checkout\\|Patch\\)$" - (type (if nofile 'MISSING 'NEED-UPDATE))) - (cvs-match "Up-to-date$" - (type (if nofile '(UP-TO-DATE . REMOVED) 'UP-TO-DATE))) - (cvs-match "File had conflicts on merge$" (type 'MODIFIED)) - (cvs-match ".*[Cc]onflict.*$" (type 'CONFLICT)) - (cvs-match "Locally Added$" (type 'ADDED)) - (cvs-match "Locally Removed$" (type 'REMOVED)) - (cvs-match "Locally Modified$" (type 'MODIFIED)) - (cvs-match "Needs Merge$" (type 'NEED-MERGE)) - (cvs-match "Entry Invalid" (type '(NEED-MERGE . REMOVED))) - (cvs-match ".*$" (type 'UNKNOWN))) - (cvs-match "$") - (cvs-or - (cvs-match " *Version:[ \t]*\\([0-9.]+\\).*$" (base-rev 1)) - ;; NOTE: there's no date on the end of the following for server mode... - (cvs-match " *Working revision:[ \t]*-?\\([0-9.]+\\).*$" (base-rev 1)) - ;; Let's not get all worked up if the format changes a bit - (cvs-match " *Working revision:.*$")) - (cvs-or - (cvs-match " *RCS Version:[ \t]*\\([0-9.]+\\)[ \t]*.*$" (head-rev 1)) - (cvs-match " *Repository revision:[ \t]*\\([0-9.]+\\)[ \t]*\\(.*\\)$" - (head-rev 1)) - (cvs-match " *Repository revision:.*")) - (cvs-or (cvs-match " *Expansion option:.*") t) ;Optional CVSNT thingie. - (cvs-or (cvs-match " *Commit Identifier:.*") t) ;Optional CVSNT thingie. - (cvs-or - (and ;; Sometimes those fields are missing. - (cvs-match " *Sticky Tag:[ \t]*\\(.*\\)$") ; FIXME: use it. - (cvs-match " *Sticky Date:[ \t]*\\(.*\\)$") ; FIXME: use it. - (cvs-match " *Sticky Options:[ \t]*\\(.*\\)$")) ; FIXME: use it. - t) - (cvs-or (cvs-match " *Merge From:.*") t) ;Optional CVSNT thingie. - (cvs-match "$") - ;; ignore the tags-listing in the case of `status -v' - (cvs-or (cvs-match " *Existing Tags:\n\\(\t.*\n\\)*$") t) - (cvs-parsed-fileinfo type path nil - :base-rev base-rev - :head-rev head-rev)))) - -(defun cvs-parse-commit () - (let (path file base-rev subtype) - (cvs-or - - (and - (cvs-or - (cvs-match "\\(Checking in\\|Removing\\) \\(.*\\);$" (path 2)) - t) - (cvs-match ".*,v <-- \\(.*\\)$" (file 1)) - (cvs-or - ;; deletion - (cvs-match "new revision: delete; previous revision: \\([0-9.]*\\)$" - (subtype 'REMOVED) (base-rev 1)) - ;; addition - (cvs-match "initial revision: \\([0-9.]*\\)$" - (subtype 'ADDED) (base-rev 1)) - ;; update - (cvs-match "new revision: \\([0-9.]*\\); previous revision: .*$" - (subtype 'COMMITTED) (base-rev 1))) - (cvs-or (cvs-match "done$") t) - ;; In cvs-1.12.9 commit messages have been changed and became - ;; ambiguous. More specifically, the `path' above is not given. - ;; We assume here that in future releases the corresponding info will - ;; be put into `file'. - (progn - ;; Try to remove the temp files used by VC. - (vc-delete-automatic-version-backups (expand-file-name (or path file))) - ;; it's important here not to rely on the default directory management - ;; because `cvs commit' might begin by a series of Examining messages - ;; so the processing of the actual checkin messages might begin with - ;; a `current-dir' set to something different from "" - (cvs-parsed-fileinfo (cons 'UP-TO-DATE subtype) - (or path file) 'trust - :base-rev base-rev))) - - ;; useless message added before the actual addition: ignored - (cvs-match "RCS file: .*\ndone$")))) - - -(provide 'pcvs-parse) - -;; arch-tag: 35418375-1a23-40a0-957d-96b0262f91d6 -;;; pcvs-parse.el ends here diff -r 681cd08dc0f7 -r e93288477c43 lisp/pcvs-util.el --- a/lisp/pcvs-util.el Thu Jun 10 22:43:47 2010 +0000 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,371 +0,0 @@ -;;; pcvs-util.el --- utility functions for PCL-CVS -*- byte-compile-dynamic: t -*- - -;; Copyright (C) 1991, 1992, 1993, 1994, 1995, 1996, 1997, 1998, 1999, -;; 2000, 2001, 2002, 2003, 2004, 2005, 2006, 2007, 2008, 2009, 2010 Free Software Foundation, Inc. - -;; Author: Stefan Monnier -;; Keywords: pcl-cvs - -;; 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 3 of the License, 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. If not, see . - -;;; Commentary: - - -;;; Code: - -(eval-when-compile (require 'cl)) - -;;;; -;;;; list processing -;;;; - -(defsubst cvs-car (x) (if (consp x) (car x) x)) -(defalias 'cvs-cdr 'cdr-safe) -(defsubst cvs-append (&rest xs) - (apply 'append (mapcar (lambda (x) (if (listp x) x (list x))) xs))) - -(defsubst cvs-every (-cvs-every-f -cvs-every-l) - (while (consp -cvs-every-l) - (unless (funcall -cvs-every-f (pop -cvs-every-l)) - (setq -cvs-every-l t))) - (not -cvs-every-l)) - -(defun cvs-union (xs ys) - (let ((zs ys)) - (dolist (x xs zs) - (unless (member x ys) (push x zs))))) - -(defun cvs-map (-cvs-map-f &rest -cvs-map-ls) - (let ((accum ())) - (while (not (cvs-every 'null -cvs-map-ls)) - (push (apply -cvs-map-f (mapcar 'car -cvs-map-ls)) accum) - (setq -cvs-map-ls (mapcar 'cdr -cvs-map-ls))) - (nreverse accum))) - -(defun cvs-first (l &optional n) - (if (null n) (car l) - (when l - (let* ((nl (list (pop l))) - (ret nl)) - (while (and l (> n 1)) - (setcdr nl (list (pop l))) - (setq nl (cdr nl)) - (decf n)) - ret)))) - -(defun cvs-partition (p l) - "Partition a list L into two lists based on predicate P. -The function returns a `cons' cell where the `car' contains -elements of L for which P is true while the `cdr' contains -the other elements. The ordering among elements is maintained." - (let (car cdr) - (dolist (x l) - (if (funcall p x) (push x car) (push x cdr))) - (cons (nreverse car) (nreverse cdr)))) - -;;; -;;; frame, window, buffer handling -;;; - -(defun cvs-pop-to-buffer-same-frame (buf) - "Pop to BUF like `pop-to-buffer' but staying on the same frame. -If `pop-to-buffer' would have opened a new frame, this function would -try to split a new window instead." - (let ((pop-up-windows (or pop-up-windows pop-up-frames)) - (pop-up-frames nil)) - (or (let ((buf (get-buffer-window buf))) (and buf (select-window buf))) - (and pop-up-windows - (ignore-errors (select-window (split-window-vertically))) - (switch-to-buffer buf)) - (pop-to-buffer (current-buffer))))) - -(defun cvs-bury-buffer (buf &optional mainbuf) - "Hide the buffer BUF that was temporarily popped up. -BUF is assumed to be a temporary buffer used from the buffer MAINBUF." - (interactive (list (current-buffer))) - (save-current-buffer - (let ((win (if (eq buf (window-buffer (selected-window))) (selected-window) - (get-buffer-window buf t)))) - (when win - (if (window-dedicated-p win) - (condition-case () - (delete-window win) - (error (iconify-frame (window-frame win)))) -;;; (if (and mainbuf (get-buffer-window mainbuf)) -;;; ;; FIXME: if the buffer popped into a pre-existing window, -;;; ;; we don't want to delete that window. -;;; t ;;(delete-window win) -;;; ) - ))) - (with-current-buffer buf - (bury-buffer (unless (and (eq buf (window-buffer (selected-window))) - (not (window-dedicated-p (selected-window)))) - buf))) - (when mainbuf - (let ((mainwin (or (get-buffer-window mainbuf) - (get-buffer-window mainbuf 'visible)))) - (when mainwin (select-window mainwin)))))) - -(defun cvs-get-buffer-create (name &optional noreuse) - "Create a buffer NAME unless such a buffer already exists. -If the NAME looks like an absolute file name, the buffer will be created -with `create-file-buffer' and will probably get another name than NAME. -In such a case, the search for another buffer with the same name doesn't -use the buffer name but the buffer's `list-buffers-directory' variable. -If NOREUSE is non-nil, always return a new buffer." - (or (and (not (file-name-absolute-p name)) - (if noreuse (generate-new-buffer name) - (get-buffer-create name))) - (unless noreuse - (dolist (buf (buffer-list)) - (with-current-buffer buf - (when (equal name list-buffers-directory) - (return buf))))) - (with-current-buffer (create-file-buffer name) - (setq list-buffers-directory name) - (current-buffer)))) - -;;;; -;;;; string processing -;;;; - -(defun cvs-insert-strings (strings) - "Insert a list of STRINGS into the current buffer. -Uses columns to keep the listing readable but compact." - (when (consp strings) - (let* ((length (apply 'max (mapcar 'length strings))) - (wwidth (1- (window-width))) - (columns (min - ;; At least 2 columns; at least 2 spaces between columns. - (max 2 (/ wwidth (+ 2 length))) - ;; Don't allocate more columns than we can fill. - ;; Windows can't show less than 3 lines anyway. - (max 1 (/ (length strings) 2)))) - (colwidth (/ wwidth columns))) - ;; Use tab-width rather than indent-to. - (setq tab-width colwidth) - ;; The insertion should be "sensible" no matter what choices were made. - (dolist (str strings) - (unless (bolp) - (insert " \t") - (when (< wwidth (+ (max colwidth (length str)) (current-column))) - (delete-char -2) (insert "\n"))) - (insert str))))) - - -(defun cvs-file-to-string (file &optional oneline args) - "Read the content of FILE and return it as a string. -If ONELINE is t, only the first line (no \\n) will be returned. -If ARGS is non-nil, the file will be executed with ARGS as its -arguments. If ARGS is not a list, no argument will be passed." - (condition-case nil - (with-temp-buffer - (if args - (apply 'call-process - file nil t nil (when (listp args) args)) - (insert-file-contents file)) - (goto-char (point-min)) - (buffer-substring (point) - (if oneline (line-end-position) (point-max)))) - (file-error nil))) - -(defun cvs-string-prefix-p (str1 str2) - "Tell whether STR1 is a prefix of STR2." - (eq t (compare-strings str2 nil (length str1) str1 nil nil))) - -;;;; -;;;; file names -;;;; - -(defsubst cvs-expand-dir-name (d) - (file-name-as-directory (expand-file-name d))) - -;;;; -;;;; (interactive ) support function -;;;; - -(defstruct (cvs-qtypedesc - (:constructor nil) (:copier nil) - (:constructor cvs-qtypedesc-create - (str2obj obj2str &optional complete hist-sym require))) - str2obj - obj2str - hist-sym - complete - require) - - -(defconst cvs-qtypedesc-string1 (cvs-qtypedesc-create 'identity 'identity t)) -(defconst cvs-qtypedesc-string (cvs-qtypedesc-create 'identity 'identity)) -(defconst cvs-qtypedesc-strings - (cvs-qtypedesc-create 'split-string-and-unquote - 'combine-and-quote-strings nil)) - -(defun cvs-query-read (default prompt qtypedesc &optional hist-sym) - (let* ((qtypedesc (or qtypedesc cvs-qtypedesc-strings)) - (hist-sym (or hist-sym (cvs-qtypedesc-hist-sym qtypedesc))) - (complete (cvs-qtypedesc-complete qtypedesc)) - (completions (and (functionp complete) (funcall complete))) - (initval (funcall (cvs-qtypedesc-obj2str qtypedesc) default))) - (funcall (cvs-qtypedesc-str2obj qtypedesc) - (cond - ((null complete) (read-string prompt initval hist-sym)) - ((functionp complete) - (completing-read prompt completions - nil (cvs-qtypedesc-require qtypedesc) - initval hist-sym)) - (t initval))))) - -;;;; -;;;; Flags handling -;;;; - -(defstruct (cvs-flags - (:constructor nil) - (:constructor -cvs-flags-make - (desc defaults &optional qtypedesc hist-sym))) - defaults persist desc qtypedesc hist-sym) - -(defmacro cvs-flags-define (sym defaults - &optional desc qtypedesc hist-sym docstring) - `(defconst ,sym - (let ((bound (boundp ',sym))) - (if (and bound (cvs-flags-p ,sym)) ,sym - (let ((defaults ,defaults)) - (-cvs-flags-make ,desc - (if bound (cons ,sym (cdr defaults)) defaults) - ,qtypedesc ,hist-sym)))) - ,docstring)) - -(defun cvs-flags-query (sym &optional desc arg) - "Query flags based on SYM. -Optional argument DESC will be used for the prompt. -If ARG (or a prefix argument) is nil, just use the 0th default. -If it is a non-negative integer, use the corresponding default. -If it is a negative integer query for a new value of the corresponding - default and return that new value. -If it is \\[universal-argument], just query and return a value without - altering the defaults. -If it is \\[universal-argument] \\[universal-argument], behave just - as if a negative zero was provided." - (let* ((flags (symbol-value sym)) - (desc (or desc (cvs-flags-desc flags))) - (qtypedesc (cvs-flags-qtypedesc flags)) - (hist-sym (cvs-flags-hist-sym flags)) - (arg (if (eq arg 'noquery) 0 (or arg current-prefix-arg 0))) - (numarg (prefix-numeric-value arg)) - (defaults (cvs-flags-defaults flags)) - (permstr (if (< numarg 0) (format " (%sth default)" (- numarg))))) - ;; special case for universal-argument - (when (consp arg) - (setq permstr (if (> numarg 4) " (permanent)" "")) - (setq numarg 0)) - - ;; sanity check - (unless (< (abs numarg) (length defaults)) - (error "There is no %sth default" (abs numarg))) - - (if permstr - (let* ((prompt (format "%s%s: " desc permstr)) - (fs (cvs-query-read (nth (- numarg) (cvs-flags-defaults flags)) - prompt qtypedesc hist-sym))) - (when (not (equal permstr "")) - (setf (nth (- numarg) (cvs-flags-defaults flags)) fs)) - fs) - (nth numarg defaults)))) - -(defsubst cvs-flags-set (sym index value) - "Set SYM's INDEX'th setting to VALUE." - (setf (nth index (cvs-flags-defaults (symbol-value sym))) value)) - -;;;; -;;;; Prefix keys -;;;; - -(defconst cvs-prefix-number 10) - -(defsubst cvs-prefix-sym (sym) (intern (concat (symbol-name sym) "-cps"))) - -(defmacro cvs-prefix-define (sym docstring desc defaults - &optional qtypedesc hist-sym) - (let ((cps (cvs-prefix-sym sym))) - `(progn - (defvar ,sym nil ,(concat (or docstring "") " -See `cvs-prefix-set' for further description of the behavior.")) - (defvar ,cps - (let ((defaults ,defaults)) - ;; sanity ensurance - (unless (>= (length defaults) cvs-prefix-number) - (setq defaults (append defaults - (make-list (1- cvs-prefix-number) - (nth 0 defaults))))) - (-cvs-flags-make ,desc defaults ,qtypedesc ,hist-sym)))))) - -(defun cvs-prefix-make-local (sym) - (let ((cps (cvs-prefix-sym sym))) - (make-local-variable sym) - (set (make-local-variable cps) (copy-cvs-flags (symbol-value cps))))) - -(defun cvs-prefix-set (sym arg) - ;; we could distinguish between numeric and non-numeric prefix args instead of - ;; relying on that magic `4'. - "Set the cvs-prefix contained in SYM. -If ARG is between 0 and 9, it selects the corresponding default. -If ARG is negative (or \\[universal-argument] which corresponds to negative 0), - it queries the user and sets the -ARG'th default. -If ARG is greater than 9 (or \\[universal-argument] \\[universal-argument]), - the (ARG mod 10)'th prefix is made persistent. -If ARG is nil toggle the PREFIX's value between its 0th default and nil - and reset the persistence." - (let* ((prefix (symbol-value (cvs-prefix-sym sym))) - (numarg (if (integerp arg) arg 0)) - ;; (defs (cvs-flags-defaults prefix)) - ) - - ;; set persistence if requested - (when (> (prefix-numeric-value arg) 9) - (setf (cvs-flags-persist prefix) t) - (setq numarg (mod numarg 10))) - - ;; set the value - (set sym - (cond - ((null arg) - (setf (cvs-flags-persist prefix) nil) - (unless (symbol-value sym) (nth 0 (cvs-flags-defaults prefix)))) - - ((or (consp arg) (< numarg 0)) - (setf (nth (- numarg) (cvs-flags-defaults prefix)) - (cvs-query-read (nth (- numarg) (cvs-flags-defaults prefix)) - (format "%s: " (cvs-flags-desc prefix)) - (cvs-flags-qtypedesc prefix) - (cvs-flags-hist-sym prefix)))) - (t (nth numarg (cvs-flags-defaults prefix))))) - (force-mode-line-update))) - -(defun cvs-prefix-get (sym &optional read-only) - "Return the current value of the prefix SYM. -And reset it unless READ-ONLY is non-nil." - (prog1 (symbol-value sym) - (unless (or read-only - (cvs-flags-persist (symbol-value (cvs-prefix-sym sym)))) - (set sym nil) - (force-mode-line-update)))) - -(provide 'pcvs-util) - -;; arch-tag: 3b2588bb-2ae3-4f1f-bf5b-dea91b1f8a59 -;;; pcvs-util.el ends here diff -r 681cd08dc0f7 -r e93288477c43 lisp/pcvs.el --- a/lisp/pcvs.el Thu Jun 10 22:43:47 2010 +0000 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,2443 +0,0 @@ -;;; pcvs.el --- a front-end to CVS - -;; Copyright (C) 1991, 1992, 1993, 1994, 1995, 1996, 1997, 1998, 1999, -;; 2000, 2001, 2002, 2003, 2004, 2005, 2006, 2007, 2008, 2009, 2010 Free Software Foundation, Inc. - -;; Author: (The PCL-CVS Trust) pcl-cvs@cyclic.com -;; (Per Cederqvist) ceder@lysator.liu.se -;; (Greg A. Woods) woods@weird.com -;; (Jim Blandy) jimb@cyclic.com -;; (Karl Fogel) kfogel@floss.red-bean.com -;; (Jim Kingdon) kingdon@cyclic.com -;; (Stefan Monnier) monnier@cs.yale.edu -;; (Greg Klanderman) greg@alphatech.com -;; (Jari Aalto+mail.emacs) jari.aalto@poboxes.com -;; Maintainer: (Stefan Monnier) monnier@gnu.org -;; Keywords: CVS, version control, release management - -;; 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 3 of the License, 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. If not, see . - -;;; Commentary: - -;; PCL-CVS is a front-end to the CVS version control system. For people -;; familiar with VC, it is somewhat like VC-dired: it presents the status of -;; all the files in your working area and allows you to commit/update several -;; of them at a time. Compared to VC-dired, it is considerably better and -;; faster (but only for CVS). - -;; PCL-CVS was originally written by Per Cederqvist many years ago. This -;; version derives from the XEmacs-21 version, itself based on the 2.0b2 -;; version (last release from Per). It is a thorough rework. - -;; Contrary to what you'd expect, PCL-CVS is not a replacement for VC but only -;; for VC-dired. As such, I've tried to make PCL-CVS and VC interoperate -;; seamlessly (I also use VC). - -;; To use PCL-CVS just use `M-x cvs-examine RET RET'. -;; There is a TeXinfo manual, which can be helpful to get started. - -;;; Bugs: - -;; - Extracting an old version seems not to recognize encoding correctly. -;; That's probably because it's done via a process rather than a file. - -;;; Todo: - -;; ******** FIX THE DOCUMENTATION ********* -;; -;; - rework the displaying of error messages. -;; - allow to flush messages only -;; - allow to protect files like ChangeLog from flushing -;; - automatically cvs-mode-insert files from find-file-hook -;; (and don't flush them as long as they are visited) -;; - query the user for cvs-get-marked (for some cmds or if nothing's selected) -;; - don't return the first (resp last) FI if the cursor is before -;; (resp after) it. -;; - allow cvs-confirm-removals to force always confirmation. -;; - cvs-checkout should ask for a revision (with completion). -;; - removal confirmation should allow specifying another file name. -;; -;; - hide fileinfos without getting rid of them (will require ewok work). -;; - add toolbar entries -;; - marking -;; marking directories should jump to just after the dir. -;; allow (un)marking directories at a time with the mouse. -;; allow cvs-cmd-do to either clear the marks or not. -;; add a "marks active" notion, like transient-mark-mode does. -;; - liveness indicator -;; - indicate in docstring if the cmd understands the `b' prefix(es). -;; - call smerge-mode when opening CONFLICT files. -;; - have vc-checkin delegate to cvs-mode-commit when applicable -;; - higher-level CVS operations -;; cvs-mode-rename -;; cvs-mode-branch -;; - module-level commands -;; add support for parsing 'modules' file ("cvs co -c") -;; cvs-mode-rcs2log -;; cvs-rdiff -;; cvs-release -;; cvs-import -;; C-u M-x cvs-checkout should ask for a cvsroot -;; cvs-mode-handle-new-vendor-version -;; - checks out module, or alternately does update join -;; - does "cvs -n tag LAST_VENDOR" to find old files into *cvs* -;; cvs-export -;; (with completion on tag names and hooks to help generate full releases) -;; - display stickiness information. And current CVS/Tag as well. -;; - write 'cvs-mode-admin' to do arbitrary 'cvs admin' commands -;; Most interesting would be version removal and log message replacement. -;; The last one would be neat when called from log-view-mode. -;; - cvs-mode-incorporate -;; It would merge in the status from one *cvs* buffer into another. -;; This would be used to populate such a buffer that had been created with -;; a `cvs {update,status,checkout} -l'. -;; - cvs-mode-(i)diff-other-{file,buffer,cvs-buffer} -;; - offer the choice to kill the process when the user kills the cvs buffer. -;; right now, it's killed without further ado. -;; - make `cvs-mode-ignore' allow manually entering a pattern. -;; to which dir should it apply ? -;; - cvs-mode-ignore should try to remove duplicate entries. -;; - maybe poll/check CVS/Entries files to react to external `cvs' commands ? -;; - some kind of `cvs annotate' support ? -;; but vc-annotate can be used instead. -;; - proper `g' that passes safe args and uses either cvs-status or cvs-examine -;; maybe also use cvs-update depending on I-don't-know-what. -;; - add message-levels so that we can hide some levels of messages - -;;; Code: - -(eval-when-compile (require 'cl)) -(require 'ewoc) ;Ewoc was once cookie -(require 'pcvs-defs) -(require 'pcvs-util) -(require 'pcvs-parse) -(require 'pcvs-info) - - -;;;; -;;;; global vars -;;;; - -(defvar cvs-cookies) ;;nil - ;;"Handle for the cookie structure that is displayed in the *cvs* buffer.") -;;(make-variable-buffer-local 'cvs-cookies) - -;;;; -;;;; Dynamically scoped variables -;;;; - -(defvar cvs-from-vc nil "Bound to t inside VC advice.") - -;;;; -;;;; flags variables -;;;; - -(defun cvs-defaults (&rest defs) - (let ((defs (cvs-first defs cvs-shared-start))) - (append defs - (make-list (- cvs-shared-start (length defs)) (car defs)) - cvs-shared-flags))) - -;; For cvs flags, we need to add "-f" to override the cvsrc settings -;; we also want to evict the annoying -q and -Q options that hide useful -;; information from pcl-cvs. -(cvs-flags-define cvs-cvs-flags '(("-f"))) - -(cvs-flags-define cvs-checkout-flags (cvs-defaults '("-P"))) -(cvs-flags-define cvs-status-flags (cvs-defaults '("-v") nil)) -(cvs-flags-define cvs-log-flags (cvs-defaults nil)) -(cvs-flags-define cvs-diff-flags (cvs-defaults '("-u" "-N") '("-c" "-N") '("-u" "-b"))) -(cvs-flags-define cvs-tag-flags (cvs-defaults nil)) -(cvs-flags-define cvs-add-flags (cvs-defaults nil)) -(cvs-flags-define cvs-commit-flags (cvs-defaults nil)) -(cvs-flags-define cvs-remove-flags (cvs-defaults nil)) -;;(cvs-flags-define cvs-undo-flags (cvs-defaults nil)) -(cvs-flags-define cvs-update-flags (cvs-defaults '("-d" "-P"))) - -(defun cvs-reread-cvsrc () - "Reset the default arguments to those in the `cvs-cvsrc-file'." - (interactive) - (condition-case nil - (with-temp-buffer - (insert-file-contents cvs-cvsrc-file) - ;; fetch the values - (dolist (cmd '("cvs" "checkout" "status" "log" "diff" "tag" - "add" "commit" "remove" "update")) - (goto-char (point-min)) - (when (re-search-forward - (concat "^" cmd "\\(\\s-+\\(.*\\)\\)?$") nil t) - (let* ((sym (intern (concat "cvs-" cmd "-flags"))) - (val (split-string-and-unquote (or (match-string 2) "")))) - (cvs-flags-set sym 0 val)))) - ;; ensure that cvs doesn't have -q or -Q - (cvs-flags-set 'cvs-cvs-flags 0 - (cons "-f" - (cdr (cvs-partition - (lambda (x) (member x '("-q" "-Q" "-f"))) - (cvs-flags-query 'cvs-cvs-flags - nil 'noquery)))))) - (file-error nil))) - -;; initialize to cvsrc's default values -(cvs-reread-cvsrc) - - -;;;; -;;;; Mouse bindings and mode motion -;;;; - -(defvar cvs-minor-current-files) - -(defun cvs-menu (e) - "Popup the CVS menu." - (interactive "e") - (let ((cvs-minor-current-files - (list (ewoc-data (ewoc-locate - cvs-cookies (posn-point (event-end e))))))) - (popup-menu cvs-menu e))) - -(defvar cvs-mode-line-process nil - "Mode-line control for displaying info on cvs process status.") - - -;;;; -;;;; Query-Type-Descriptor for Tags -;;;; - -(autoload 'cvs-status-get-tags "cvs-status") -(defun cvs-tags-list () - "Return a list of acceptable tags, ready for completions." - (assert (cvs-buffer-p)) - (let ((marked (cvs-get-marked))) - (list* '("BASE") '("HEAD") - (when marked - (with-temp-buffer - (process-file cvs-program - nil ;no input - t ;output to current-buffer - nil ;don't update display while running - "status" - "-v" - (cvs-fileinfo->full-name (car marked))) - (goto-char (point-min)) - (let ((tags (cvs-status-get-tags))) - (when (listp tags) tags))))))) - -(defvar cvs-tag-history nil) -(defconst cvs-qtypedesc-tag - (cvs-qtypedesc-create 'identity 'identity 'cvs-tags-list 'cvs-tag-history)) - -;;;; - -(defun cvs-mode! (&optional -cvs-mode!-fun) - "Switch to the *cvs* buffer. -If -CVS-MODE!-FUN is provided, it is executed *cvs* being the current buffer - and with its window selected. Else, the *cvs* buffer is simply selected. --CVS-MODE!-FUN is called interactively if applicable and else with no argument." - (let* ((-cvs-mode!-buf (current-buffer)) - (cvsbuf (cond ((cvs-buffer-p) (current-buffer)) - ((and cvs-buffer (cvs-buffer-p cvs-buffer)) cvs-buffer) - (t (error "can't find the *cvs* buffer")))) - (-cvs-mode!-wrapper cvs-minor-wrap-function) - (-cvs-mode!-cont (lambda () - (save-current-buffer - (if (commandp -cvs-mode!-fun) - (call-interactively -cvs-mode!-fun) - (funcall -cvs-mode!-fun)))))) - (if (not -cvs-mode!-fun) (set-buffer cvsbuf) - (let ((cvs-mode!-buf (current-buffer)) - (cvs-mode!-owin (selected-window)) - (cvs-mode!-nwin (get-buffer-window cvsbuf 'visible))) - (unwind-protect - (progn - (set-buffer cvsbuf) - (when cvs-mode!-nwin (select-window cvs-mode!-nwin)) - (if -cvs-mode!-wrapper - (funcall -cvs-mode!-wrapper -cvs-mode!-buf -cvs-mode!-cont) - (funcall -cvs-mode!-cont))) - (set-buffer cvs-mode!-buf) - (when (and cvs-mode!-nwin (eq cvs-mode!-nwin (selected-window))) - ;; the selected window has not been changed by FUN - (select-window cvs-mode!-owin))))))) - -;;;; -;;;; Prefixes -;;;; - -(defvar cvs-branches (list cvs-vendor-branch "HEAD" "HEAD")) -(cvs-prefix-define cvs-branch-prefix - "Current selected branch." - "version" - (cons cvs-vendor-branch cvs-branches) - cvs-qtypedesc-tag) - -(defun cvs-set-branch-prefix (arg) - "Set the branch prefix to take action at the next command. -See `cvs-prefix-set' for a further the description of the behavior. -\\[universal-argument] 1 selects the vendor branch -and \\[universal-argument] 2 selects the HEAD." - (interactive "P") - (cvs-mode!) - (cvs-prefix-set 'cvs-branch-prefix arg)) - -(defun cvs-add-branch-prefix (flags &optional arg) - "Add branch selection argument if the branch prefix was set. -The argument is added (or not) to the list of FLAGS and is constructed -by appending the branch to ARG which defaults to \"-r\"." - (let ((branch (cvs-prefix-get 'cvs-branch-prefix))) - ;; deactivate the secondary prefix, even if not used. - (cvs-prefix-get 'cvs-secondary-branch-prefix) - (if branch (cons (concat (or arg "-r") branch) flags) flags))) - -(cvs-prefix-define cvs-secondary-branch-prefix - "Current secondary selected branch." - "version" - (cons cvs-vendor-branch cvs-branches) - cvs-qtypedesc-tag) - -(defun cvs-set-secondary-branch-prefix (arg) - "Set the branch prefix to take action at the next command. -See `cvs-prefix-set' for a further the description of the behavior. -\\[universal-argument] 1 selects the vendor branch -and \\[universal-argument] 2 selects the HEAD." - (interactive "P") - (cvs-mode!) - (cvs-prefix-set 'cvs-secondary-branch-prefix arg)) - -(defun cvs-add-secondary-branch-prefix (flags &optional arg) - "Add branch selection argument if the secondary branch prefix was set. -The argument is added (or not) to the list of FLAGS and is constructed -by appending the branch to ARG which defaults to \"-r\". -Since the `cvs-secondary-branch-prefix' is only active if the primary -prefix is active, it is important to read the secondary prefix before -the primay since reading the primary can deactivate it." - (let ((branch (and (cvs-prefix-get 'cvs-branch-prefix 'read-only) - (cvs-prefix-get 'cvs-secondary-branch-prefix)))) - (if branch (cons (concat (or arg "-r") branch) flags) flags))) - -;;;; - -(define-minor-mode cvs-minor-mode - "This mode is used for buffers related to a main *cvs* buffer. -All the `cvs-mode' buffer operations are simply rebound under -the \\[cvs-mode-map] prefix." - nil " CVS" - :group 'pcl-cvs) -(put 'cvs-minor-mode 'permanent-local t) - - -(defvar cvs-temp-buffers nil) -(defun cvs-temp-buffer (&optional cmd normal nosetup) - "Create a temporary buffer to run CMD in. -If CMD is a string, use it to lookup `cvs-buffer-name-alist' to find -the buffer name to be used and its `major-mode'. - -The selected window will not be changed. The new buffer will not maintain undo -information and will be read-only unless NORMAL is non-nil. It will be emptied -\(unless NOSETUP is non-nil\) and its `default-directory' will be inherited -from the current buffer." - (let* ((cvs-buf (current-buffer)) - (info (cdr (assoc cmd cvs-buffer-name-alist))) - (name (eval (nth 0 info))) - (mode (nth 1 info)) - (dir default-directory) - (buf (cond - (name (cvs-get-buffer-create name)) - ((and (bufferp cvs-temp-buffer) (buffer-live-p cvs-temp-buffer)) - cvs-temp-buffer) - (t - (set (make-local-variable 'cvs-temp-buffer) - (cvs-get-buffer-create - (eval cvs-temp-buffer-name) 'noreuse)))))) - - ;; handle the potential pre-existing process - (let ((proc (get-buffer-process buf))) - (when (and (not normal) (processp proc) - (memq (process-status proc) '(run stop))) - (if cmd - ;; When CMD is specified, the buffer is normally shown to the - ;; user, so interrupting the process is not harmful. - ;; Use `delete-process' rather than `kill-process' otherwise - ;; the pending output of the process will still get inserted - ;; after we erase the buffer. - (delete-process proc) - (error "Can not run two cvs processes simultaneously")))) - - (if (not name) (kill-local-variable 'other-window-scroll-buffer) - ;; Strangely, if no window is created, `display-buffer' ends up - ;; doing a `switch-to-buffer' which does a `set-buffer', hence - ;; the need for `save-excursion'. - (unless nosetup (save-excursion (display-buffer buf))) - ;; FIXME: this doesn't do the right thing if the user later on - ;; does a `find-file-other-window' and `scroll-other-window' - (set (make-local-variable 'other-window-scroll-buffer) buf)) - - (add-to-list 'cvs-temp-buffers buf) - - (with-current-buffer buf - (setq buffer-read-only nil) - (setq default-directory dir) - (unless nosetup - ;; Disable undo before calling erase-buffer since it may generate - ;; a very large and unwanted undo record. - (buffer-disable-undo) - (erase-buffer)) - (set (make-local-variable 'cvs-buffer) cvs-buf) - ;;(cvs-minor-mode 1) - (let ((lbd list-buffers-directory)) - (if (fboundp mode) (funcall mode) (fundamental-mode)) - (when lbd (setq list-buffers-directory lbd))) - (cvs-minor-mode 1) - ;;(set (make-local-variable 'cvs-buffer) cvs-buf) - (if normal - (buffer-enable-undo) - (setq buffer-read-only t) - (buffer-disable-undo)) - buf))) - -(defun cvs-mode-kill-buffers () - "Kill all the \"temporary\" buffers created by the *cvs* buffer." - (interactive) - (dolist (buf cvs-temp-buffers) (ignore-errors (kill-buffer buf)))) - -(defun cvs-make-cvs-buffer (dir &optional new) - "Create the *cvs* buffer for directory DIR. -If non-nil, NEW means to create a new buffer no matter what." - ;; the real cvs-buffer creation - (setq dir (cvs-expand-dir-name dir)) - (let* ((buffer-name (eval cvs-buffer-name)) - (buffer - (or (and (not new) - (eq cvs-reuse-cvs-buffer 'current) - (cvs-buffer-p) ;reuse the current buffer if possible - (current-buffer)) - ;; look for another cvs buffer visiting the same directory - (save-excursion - (unless new - (dolist (buffer (cons (current-buffer) (buffer-list))) - (set-buffer buffer) - (and (cvs-buffer-p) - (case cvs-reuse-cvs-buffer - (always t) - (subdir - (or (cvs-string-prefix-p default-directory dir) - (cvs-string-prefix-p dir default-directory))) - (samedir (string= default-directory dir))) - (return buffer))))) - ;; we really have to create a new buffer: - ;; we temporarily bind cwd to "" to prevent - ;; create-file-buffer from using directory info - ;; unless it is explicitly in the cvs-buffer-name. - (cvs-get-buffer-create buffer-name new)))) - (with-current-buffer buffer - (or - (and (string= dir default-directory) (cvs-buffer-p) - ;; just a refresh - (ignore-errors - (cvs-cleanup-collection cvs-cookies nil nil t) - (current-buffer))) - ;; setup from scratch - (progn - (setq default-directory dir) - (setq buffer-read-only nil) - (erase-buffer) - (insert "Repository : " (directory-file-name (cvs-get-cvsroot)) - "\nModule : " (cvs-get-module) - "\nWorking dir: " (abbreviate-file-name dir) - (if (not (file-readable-p "CVS/Tag")) "\n" - (let ((tag (cvs-file-to-string "CVS/Tag"))) - (cond - ((string-match "\\`T" tag) - (concat "\nTag : " (substring tag 1))) - ((string-match "\\`D" tag) - (concat "\nDate : " (substring tag 1))) - ("\n")))) - "\n") - (setq buffer-read-only t) - (cvs-mode) - (set (make-local-variable 'list-buffers-directory) buffer-name) - ;;(set (make-local-variable 'cvs-temp-buffer) (cvs-temp-buffer)) - (let ((cookies (ewoc-create 'cvs-fileinfo-pp "\n\n" "\n" t))) - (set (make-local-variable 'cvs-cookies) cookies) - (add-hook 'kill-buffer-hook - (lambda () - (ignore-errors (kill-buffer cvs-temp-buffer))) - nil t) - ;;(set-buffer buf) - buffer)))))) - -(defun* cvs-cmd-do (cmd dir flags fis new - &key cvsargs noexist dont-change-disc noshow) - (let* ((dir (file-name-as-directory - (abbreviate-file-name (expand-file-name dir)))) - (cvsbuf (cvs-make-cvs-buffer dir new))) - ;; Check that dir is under CVS control. - (unless (file-directory-p dir) - (error "%s is not a directory" dir)) - (unless (or noexist (file-directory-p (expand-file-name "CVS" dir)) - (file-expand-wildcards (expand-file-name "*/CVS" dir))) - (error "%s does not contain CVS controlled files" dir)) - - (set-buffer cvsbuf) - (cvs-mode-run cmd flags fis - :cvsargs cvsargs :dont-change-disc dont-change-disc) - - (if noshow cvsbuf - (let ((pop-up-windows nil)) (pop-to-buffer cvsbuf))))) -;; (funcall (if (and (boundp 'pop-up-frames) pop-up-frames) -;; 'pop-to-buffer 'switch-to-buffer) -;; cvsbuf)))) - -(defun cvs-run-process (args fis postprocess &optional single-dir) - (assert (cvs-buffer-p cvs-buffer)) - (save-current-buffer - (let ((procbuf (current-buffer)) - (cvsbuf cvs-buffer) - (single-dir (or single-dir (eq cvs-execute-single-dir t)))) - - (set-buffer procbuf) - (goto-char (point-max)) - (unless (bolp) (let ((inhibit-read-only t)) (insert "\n"))) - ;; find the set of files we'll process in this round - (let* ((dir+files+rest - (if (or (null fis) (not single-dir)) - ;; not single-dir mode: just process the whole thing - (list "" (mapcar 'cvs-fileinfo->full-name fis) nil) - ;; single-dir mode: extract the same-dir-elements - (let ((dir (cvs-fileinfo->dir (car fis)))) - ;; output the concerned dir so the parser can translate paths - (let ((inhibit-read-only t)) - (insert "pcl-cvs: descending directory " dir "\n")) - ;; loop to find the same-dir-elems - (do* ((files () (cons (cvs-fileinfo->file fi) files)) - (fis fis (cdr fis)) - (fi (car fis) (car fis))) - ((not (and fis (string= dir (cvs-fileinfo->dir fi)))) - (list dir files fis)))))) - (dir (nth 0 dir+files+rest)) - (files (nth 1 dir+files+rest)) - (rest (nth 2 dir+files+rest))) - - (add-hook 'kill-buffer-hook - (lambda () - (let ((proc (get-buffer-process (current-buffer)))) - (when (processp proc) - (set-process-filter proc nil) - ;; Abort postprocessing but leave the sentinel so it - ;; will update the list of running procs. - (process-put proc 'cvs-postprocess nil) - (interrupt-process proc)))) - nil t) - - ;; create the new process and setup the procbuffer correspondingly - (let* ((msg (cvs-header-msg args fis)) - (args (append (cvs-flags-query 'cvs-cvs-flags nil 'noquery) - (if cvs-cvsroot (list "-d" cvs-cvsroot)) - args - files)) - ;; If process-connection-type is nil and the repository - ;; is accessed via SSH, a bad interaction between libc, - ;; CVS and SSH can lead to garbled output. - ;; It might be a glibc-specific problem (but it can also happens - ;; under Mac OS X, it seems). - ;; It seems that using a pty can help circumvent the problem, - ;; but at the cost of screwing up when the process thinks it - ;; can ask for user input (such as password or host-key - ;; confirmation). A better workaround is to set CVS_RSH to - ;; an appropriate script, or to use a later version of CVS. - (process-connection-type nil) ; Use a pipe, not a pty. - (process - ;; the process will be run in the selected dir - (let ((default-directory (cvs-expand-dir-name dir))) - (apply 'start-file-process "cvs" procbuf cvs-program args)))) - ;; setup the process. - (process-put process 'cvs-buffer cvs-buffer) - (with-current-buffer cvs-buffer (cvs-update-header msg 'add)) - (process-put process 'cvs-header msg) - (process-put - process 'cvs-postprocess - (if (null rest) - ;; this is the last invocation - postprocess - ;; else, we have to register ourselves to be rerun on the rest - `(cvs-run-process ',args ',rest ',postprocess ',single-dir))) - (set-process-sentinel process 'cvs-sentinel) - (set-process-filter process 'cvs-update-filter) - (set-marker (process-mark process) (point-max)) - (ignore-errors (process-send-eof process)) ;close its stdin to avoid hangs - - ;; now finish setting up the cvs-buffer - (set-buffer cvsbuf) - (setq cvs-mode-line-process (symbol-name (process-status process))) - (force-mode-line-update))))) - - ;; The following line is said to improve display updates on some - ;; emacsen. It shouldn't be needed, but it does no harm. - (sit-for 0)) - -(defun cvs-header-msg (args fis) - (let* ((lastarg nil) - (args (mapcar (lambda (arg) - (cond - ;; filter out the largish commit message - ((and (eq lastarg nil) (string= arg "commit")) - (setq lastarg 'commit) arg) - ((and (eq lastarg 'commit) (string= arg "-m")) - (setq lastarg '-m) arg) - ((eq lastarg '-m) - (setq lastarg 'done) "") - ;; filter out the largish `admin -mrev:msg' message - ((and (eq lastarg nil) (string= arg "admin")) - (setq lastarg 'admin) arg) - ((and (eq lastarg 'admin) - (string-match "\\`-m[^:]*:" arg)) - (setq lastarg 'done) - (concat (match-string 0 arg) "")) - ;; Keep the rest as is. - (t arg))) - args))) - (concat cvs-program " " - (combine-and-quote-strings - (append (cvs-flags-query 'cvs-cvs-flags nil 'noquery) - (if cvs-cvsroot (list "-d" cvs-cvsroot)) - args - (mapcar 'cvs-fileinfo->full-name fis)))))) - -(defun cvs-update-header (cmd add) - (let* ((hf (ewoc-get-hf cvs-cookies)) - (str (car hf)) - (done "") - (tin (ewoc-nth cvs-cookies 0))) - ;; look for the first *real* fileinfo (to determine emptyness) - (while - (and tin - (memq (cvs-fileinfo->type (ewoc-data tin)) - '(MESSAGE DIRCHANGE))) - (setq tin (ewoc-next cvs-cookies tin))) - (if add - (progn - ;; Remove the default empty line, if applicable. - (if (not (string-match "." str)) (setq str "\n")) - (setq str (concat "-- Running " cmd " ...\n" str))) - (if (not (string-match - ;; FIXME: If `cmd' is large, this will bump into the - ;; compiled-regexp size limit. We could drop the "^" anchor - ;; and use search-forward to circumvent the problem. - (concat "^-- Running " (regexp-quote cmd) " \\.\\.\\.\n") str)) - (error "Internal PCL-CVS error while removing message") - (setq str (replace-match "" t t str)) - ;; Re-add the default empty line, if applicable. - (if (not (string-match "." str)) (setq str "\n\n")) - (setq done (concat "-- last cmd: " cmd " --\n")))) - ;; set the new header and footer - (ewoc-set-hf cvs-cookies - str (concat "\n--------------------- " - (if tin "End" "Empty") - " ---------------------\n" - done)))) - - -(defun cvs-sentinel (proc msg) - "Sentinel for the cvs update process. -This is responsible for parsing the output from the cvs update when -it is finished." - (when (memq (process-status proc) '(signal exit)) - (let ((cvs-postproc (process-get proc 'cvs-postprocess)) - (cvs-buf (process-get proc 'cvs-buffer)) - (procbuf (process-buffer proc))) - (unless (buffer-live-p cvs-buf) (setq cvs-buf nil)) - (unless (buffer-live-p procbuf) (setq procbuf nil)) - ;; Since the buffer and mode line will show that the - ;; process is dead, we can delete it now. Otherwise it - ;; will stay around until M-x list-processes. - (process-put proc 'postprocess nil) - (delete-process proc) - ;; Don't do anything if the main buffer doesn't exist any more. - (when cvs-buf - (with-current-buffer cvs-buf - (cvs-update-header (process-get proc 'cvs-header) nil) - (setq cvs-mode-line-process (symbol-name (process-status proc))) - (force-mode-line-update) - (when cvs-postproc - (if (null procbuf) - ;;(set-process-buffer proc nil) - (error "cvs' process buffer was killed") - (with-current-buffer procbuf - ;; Do the postprocessing like parsing and such. - (save-excursion (eval cvs-postproc))))))) - ;; Check whether something is left. - (when (and procbuf (not (get-buffer-process procbuf))) - (with-current-buffer procbuf - ;; IIRC, we enable undo again once the process is finished - ;; for cases where the output was inserted in *vc-diff* or - ;; in a file-like buffer. --Stef - (buffer-enable-undo) - (with-current-buffer (or cvs-buf (current-buffer)) - (message "CVS process has completed in %s" - (buffer-name)))))))) - -(defun cvs-parse-process (dcd &optional subdir old-fis) - "Parse the output of a cvs process. -DCD is the `dont-change-disc' flag to use when parsing that output. -SUBDIR is the subdirectory (if any) where this command was run. -OLD-FIS is the list of fileinfos on which the cvs command was applied and - which should be considered up-to-date if they are missing from the output." - (when (eq system-type 'darwin) - ;; Fixup the ^D^H^H inserted at beginning of buffer sometimes on MacOSX - ;; because of the call to `process-send-eof'. - (save-excursion - (goto-char (point-min)) - (while (re-search-forward "^\\^D+" nil t) - (let ((inhibit-read-only t)) - (delete-region (match-beginning 0) (match-end 0)))))) - (let* ((fileinfos (cvs-parse-buffer 'cvs-parse-table dcd subdir)) - last) - (with-current-buffer cvs-buffer - ;; Expand OLD-FIS to actual files. - (let ((fis nil)) - (dolist (fi old-fis) - (setq fis (if (eq (cvs-fileinfo->type fi) 'DIRCHANGE) - (nconc (ewoc-collect cvs-cookies 'cvs-dir-member-p - (cvs-fileinfo->dir fi)) - fis) - (cons fi fis)))) - (setq old-fis fis)) - ;; Drop OLD-FIS which were already up-to-date. - (let ((fis nil)) - (dolist (fi old-fis) - (unless (eq (cvs-fileinfo->type fi) 'UP-TO-DATE) (push fi fis))) - (setq old-fis fis)) - ;; Add the new fileinfos to the ewoc. - (dolist (fi fileinfos) - (setq last (cvs-addto-collection cvs-cookies fi last)) - ;; This FI was in the output, so remove it from OLD-FIS. - (setq old-fis (delq (ewoc-data last) old-fis))) - ;; Process the "silent output" (i.e. absence means up-to-date). - (dolist (fi old-fis) - (setf (cvs-fileinfo->type fi) 'UP-TO-DATE) - (setq last (cvs-addto-collection cvs-cookies fi last))) - (setq fileinfos (nconc old-fis fileinfos)) - ;; Clean up the ewoc as requested by the user. - (cvs-cleanup-collection cvs-cookies - (eq cvs-auto-remove-handled t) - cvs-auto-remove-directories - nil) - ;; Revert buffers if necessary. - (when (and cvs-auto-revert (not dcd) (not cvs-from-vc)) - (cvs-revert-if-needed fileinfos))))) - -(defmacro defun-cvs-mode (fun args docstring interact &rest body) - "Define a function to be used in a *cvs* buffer. -This will look for a *cvs* buffer and execute BODY in it. -Since the interactive arguments might need to be queried after -switching to the *cvs* buffer, the generic code is rather ugly, -but luckily we can often use simpler alternatives. - -FUN can be either a symbol (i.e. STYLE is nil) or a cons (FUN . STYLE). -ARGS and DOCSTRING are the normal argument list. -INTERACT is the interactive specification or nil for non-commands. - -STYLE can be either SIMPLE, NOARGS or DOUBLE. It's an error for it -to have any other value, unless other details of the function make it -clear what alternative to use. -- SIMPLE will get all the interactive arguments from the original buffer. -- NOARGS will get all the arguments from the *cvs* buffer and will - always behave as if called interactively. -- DOUBLE is the generic case." - (declare (debug (&define sexp lambda-list stringp ("interactive" interactive) def-body)) - (doc-string 3)) - (let ((style (cvs-cdr fun)) - (fun (cvs-car fun))) - (cond - ;; a trivial interaction, no need to move it - ((or (eq style 'SIMPLE) - (null (nth 1 interact)) - (stringp (nth 1 interact))) - `(defun ,fun ,args ,docstring ,interact - (cvs-mode! (lambda () ,@body)))) - - ;; fun is only called interactively: move all the args to the inner fun - ((eq style 'NOARGS) - `(defun ,fun () ,docstring (interactive) - (cvs-mode! (lambda ,args ,interact ,@body)))) - - ;; bad case - ((eq style 'DOUBLE) - (string-match ".*" docstring) - (let ((line1 (match-string 0 docstring)) - (fun-1 (intern (concat (symbol-name fun) "-1")))) - `(progn - (defun ,fun-1 ,args - ,(concat docstring "\nThis function only works within a *cvs* buffer. -For interactive use, use `" (symbol-name fun) "' instead.") - ,interact - ,@body) - (put ',fun-1 'definition-name ',fun) - (defun ,fun () - ,(concat line1 "\nWrapper function that switches to a *cvs* buffer -before calling the real function `" (symbol-name fun-1) "'.\n") - (interactive) - (cvs-mode! ',fun-1))))) - - (t (error "Unknown style %s in `defun-cvs-mode'" style))))) - -(defun-cvs-mode cvs-mode-kill-process () - "Kill the temporary buffer and associated process." - (interactive) - (when (and (bufferp cvs-temp-buffer) (buffer-live-p cvs-temp-buffer)) - (let ((proc (get-buffer-process cvs-temp-buffer))) - (when proc (delete-process proc))))) - -;; -;; Maintaining the collection in the face of updates -;; - -(defun cvs-addto-collection (c fi &optional tin) - "Add FI to C and return FI's corresponding tin. -FI is inserted in its proper place or maybe even merged with a preexisting - fileinfo if applicable. -TIN specifies an optional starting point." - (unless tin (setq tin (ewoc-nth c 0))) - (while (and tin (cvs-fileinfo< fi (ewoc-data tin))) - (setq tin (ewoc-prev c tin))) - (if (null tin) (ewoc-enter-first c fi) ;empty collection - (assert (not (cvs-fileinfo< fi (ewoc-data tin)))) - (let ((next-tin (ewoc-next c tin))) - (while (not (or (null next-tin) - (cvs-fileinfo< fi (ewoc-data next-tin)))) - (setq tin next-tin next-tin (ewoc-next c next-tin))) - (if (or (cvs-fileinfo< (ewoc-data tin) fi) - (eq (cvs-fileinfo->type fi) 'MESSAGE)) - ;; tin < fi < next-tin - (ewoc-enter-after c tin fi) - ;; fi == tin - (cvs-fileinfo-update (ewoc-data tin) fi) - (ewoc-invalidate c tin) - ;; Move cursor back to where it belongs. - (when (bolp) (cvs-move-to-goal-column)) - tin)))) - -(defcustom cvs-cleanup-functions nil - "Functions to tweak the cleanup process. -The functions are called with a single argument (a FILEINFO) and should -return a non-nil value if that fileinfo should be removed." - :group 'pcl-cvs - :type '(hook :options (cvs-cleanup-removed))) - -(defun cvs-cleanup-removed (fi) - "Non-nil if FI has been cvs-removed but still exists. -This is intended for use on `cvs-cleanup-functions' when you have cvs-removed -automatically generated files (which should hence not be under CVS control) -but can't commit the removal because the repository's owner doesn't understand -the problem." - (and (or (eq (cvs-fileinfo->type fi) 'REMOVED) - (and (eq (cvs-fileinfo->type fi) 'CONFLICT) - (eq (cvs-fileinfo->subtype fi) 'REMOVED))) - (file-exists-p (cvs-fileinfo->full-name fi)))) - -;; called at the following times: -;; - postparse ((eq cvs-auto-remove-handled t) cvs-auto-remove-directories nil) -;; - pre-run ((eq cvs-auto-remove-handled 'delayed) nil t) -;; - remove-handled (t (or cvs-auto-remove-directories 'handled) t) -;; - cvs-cmd-do (nil nil t) -;; - post-ignore (nil nil nil) -;; - acknowledge (nil nil nil) -;; - remove (nil nil nil) -(defun cvs-cleanup-collection (c rm-handled rm-dirs rm-msgs) - "Remove undesired entries. -C is the collection -RM-HANDLED if non-nil means remove handled entries. -RM-DIRS behaves like `cvs-auto-remove-directories'. -RM-MSGS if non-nil means remove messages." - (let (last-fi first-dir (rerun t)) - (while rerun - (setq rerun nil) - (setq first-dir t) - (setq last-fi (cvs-create-fileinfo 'DEAD "../" "" "")) ;place-holder - (ewoc-filter - c (lambda (fi) - (let* ((type (cvs-fileinfo->type fi)) - (subtype (cvs-fileinfo->subtype fi)) - (keep - (case type - ;; remove temp messages and keep the others - (MESSAGE (not (or rm-msgs (eq subtype 'TEMP)))) - ;; remove entries - (DEAD nil) - ;; handled also? - (UP-TO-DATE (not rm-handled)) - ;; keep the rest - (t (not (run-hook-with-args-until-success - 'cvs-cleanup-functions fi)))))) - - ;; mark dirs for removal - (when (and keep rm-dirs - (eq (cvs-fileinfo->type last-fi) 'DIRCHANGE) - (not (when first-dir (setq first-dir nil) t)) - (or (eq rm-dirs 'all) - (not (cvs-string-prefix-p - (cvs-fileinfo->dir last-fi) - (cvs-fileinfo->dir fi))) - (and (eq type 'DIRCHANGE) (eq rm-dirs 'empty)) - (eq subtype 'FOOTER))) - (setf (cvs-fileinfo->type last-fi) 'DEAD) - (setq rerun t)) - (when keep (setq last-fi fi))))) - ;; remove empty last dir - (when (and rm-dirs - (not first-dir) - (eq (cvs-fileinfo->type last-fi) 'DIRCHANGE)) - (setf (cvs-fileinfo->type last-fi) 'DEAD) - (setq rerun t))))) - -(defun cvs-get-cvsroot () - "Gets the CVSROOT for DIR." - (let ((cvs-cvsroot-file (expand-file-name "Root" "CVS"))) - (or (cvs-file-to-string cvs-cvsroot-file t) - cvs-cvsroot - (getenv "CVSROOT") - "?????"))) - -(defun cvs-get-module () - "Return the current CVS module. -This usually doesn't really work but is a handy initval in a prompt." - (let* ((repfile (expand-file-name "Repository" "CVS")) - (rep (cvs-file-to-string repfile t))) - (cond - ((null rep) "") - ((not (file-name-absolute-p rep)) rep) - (t - (let* ((root (cvs-get-cvsroot)) - (str (concat (file-name-as-directory (or root "/")) " || " rep))) - (if (and root (string-match "\\(.*\\) || \\1\\(.*\\)\\'" str)) - (match-string 2 str) - (file-name-nondirectory rep))))))) - - - -;;;; -;;;; running a "cvs checkout". -;;;; - -;;;###autoload -(defun cvs-checkout (modules dir flags &optional root) - "Run a 'cvs checkout MODULES' in DIR. -Feed the output to a *cvs* buffer, display it in the current window, -and run `cvs-mode' on it. - -With a prefix argument, prompt for cvs FLAGS to use." - (interactive - (let ((root (cvs-get-cvsroot))) - (if (or (null root) current-prefix-arg) - (setq root (read-string "CVS Root: "))) - (list (split-string-and-unquote - (read-string "Module(s): " (cvs-get-module))) - (read-directory-name "CVS Checkout Directory: " - nil default-directory nil) - (cvs-add-branch-prefix - (cvs-flags-query 'cvs-checkout-flags "cvs checkout flags")) - root))) - (when (eq flags t) - (setf flags (cvs-flags-query 'cvs-checkout-flags nil 'noquery))) - (let ((cvs-cvsroot root)) - (cvs-cmd-do "checkout" (or dir default-directory) - (append flags modules) nil 'new - :noexist t))) - -(defun-cvs-mode (cvs-mode-checkout . NOARGS) (dir) - "Run cvs checkout against the current branch. -The files are stored to DIR." - (interactive - (let* ((branch (cvs-prefix-get 'cvs-branch-prefix)) - (prompt (format "CVS Checkout Directory for `%s%s': " - (cvs-get-module) - (if branch (format " (branch: %s)" branch) - "")))) - (list (read-directory-name prompt nil default-directory nil)))) - (let ((modules (split-string-and-unquote (cvs-get-module))) - (flags (cvs-add-branch-prefix - (cvs-flags-query 'cvs-checkout-flags "cvs checkout flags"))) - (cvs-cvsroot (cvs-get-cvsroot))) - (cvs-checkout modules dir flags))) - -;;;; -;;;; The code for running a "cvs update" and friends in various ways. -;;;; - -(defun-cvs-mode (cvs-mode-revert-buffer . SIMPLE) - (&optional ignore-auto noconfirm) - "Rerun `cvs-examine' on the current directory with the default flags." - (interactive) - (cvs-examine default-directory t)) - -(defun cvs-query-directory (prompt) - "Read directory name, prompting with PROMPT. -If in a *cvs* buffer, don't prompt unless a prefix argument is given." - (if (and (cvs-buffer-p) - (not current-prefix-arg)) - default-directory - (read-directory-name prompt nil default-directory nil))) - -;;;###autoload -(defun cvs-quickdir (dir &optional flags noshow) - "Open a *cvs* buffer on DIR without running cvs. -With a prefix argument, prompt for a directory to use. -A prefix arg >8 (ex: \\[universal-argument] \\[universal-argument]), - prevents reuse of an existing *cvs* buffer. -Optional argument NOSHOW if non-nil means not to display the buffer. -FLAGS is ignored." - (interactive (list (cvs-query-directory "CVS quickdir (directory): "))) - ;; FIXME: code duplication with cvs-cmd-do and cvs-parse-process - (let* ((dir (file-name-as-directory - (abbreviate-file-name (expand-file-name dir)))) - (new (> (prefix-numeric-value current-prefix-arg) 8)) - (cvsbuf (cvs-make-cvs-buffer dir new)) - last) - ;; Check that dir is under CVS control. - (unless (file-directory-p dir) - (error "%s is not a directory" dir)) - (unless (file-directory-p (expand-file-name "CVS" dir)) - (error "%s does not contain CVS controlled files" dir)) - (set-buffer cvsbuf) - (dolist (fi (cvs-fileinfo-from-entries "")) - (setq last (cvs-addto-collection cvs-cookies fi last))) - (cvs-cleanup-collection cvs-cookies - (eq cvs-auto-remove-handled t) - cvs-auto-remove-directories - nil) - (if noshow cvsbuf - (let ((pop-up-windows nil)) (pop-to-buffer cvsbuf))))) - -;;;###autoload -(defun cvs-examine (directory flags &optional noshow) - "Run a `cvs -n update' in the specified DIRECTORY. -That is, check what needs to be done, but don't change the disc. -Feed the output to a *cvs* buffer and run `cvs-mode' on it. -With a prefix argument, prompt for a directory and cvs FLAGS to use. -A prefix arg >8 (ex: \\[universal-argument] \\[universal-argument]), - prevents reuse of an existing *cvs* buffer. -Optional argument NOSHOW if non-nil means not to display the buffer." - (interactive (list (cvs-query-directory "CVS Examine (directory): ") - (cvs-flags-query 'cvs-update-flags "cvs -n update flags"))) - (when (eq flags t) - (setf flags (cvs-flags-query 'cvs-update-flags nil 'noquery))) - (when find-file-visit-truename (setq directory (file-truename directory))) - (cvs-cmd-do "update" directory flags nil - (> (prefix-numeric-value current-prefix-arg) 8) - :cvsargs '("-n") - :noshow noshow - :dont-change-disc t)) - - -;;;###autoload -(defun cvs-update (directory flags) - "Run a `cvs update' in the current working DIRECTORY. -Feed the output to a *cvs* buffer and run `cvs-mode' on it. -With a \\[universal-argument] prefix argument, prompt for a directory to use. -A prefix arg >8 (ex: \\[universal-argument] \\[universal-argument]), - prevents reuse of an existing *cvs* buffer. -The prefix is also passed to `cvs-flags-query' to select the FLAGS - passed to cvs." - (interactive (list (cvs-query-directory "CVS Update (directory): ") - (cvs-flags-query 'cvs-update-flags "cvs update flags"))) - (when (eq flags t) - (setf flags (cvs-flags-query 'cvs-update-flags nil 'noquery))) - (cvs-cmd-do "update" directory flags nil - (> (prefix-numeric-value current-prefix-arg) 8))) - - -;;;###autoload -(defun cvs-status (directory flags &optional noshow) - "Run a `cvs status' in the current working DIRECTORY. -Feed the output to a *cvs* buffer and run `cvs-mode' on it. -With a prefix argument, prompt for a directory and cvs FLAGS to use. -A prefix arg >8 (ex: \\[universal-argument] \\[universal-argument]), - prevents reuse of an existing *cvs* buffer. -Optional argument NOSHOW if non-nil means not to display the buffer." - (interactive (list (cvs-query-directory "CVS Status (directory): ") - (cvs-flags-query 'cvs-status-flags "cvs status flags"))) - (when (eq flags t) - (setf flags (cvs-flags-query 'cvs-status-flags nil 'noquery))) - (cvs-cmd-do "status" directory flags nil - (> (prefix-numeric-value current-prefix-arg) 8) - :noshow noshow :dont-change-disc t)) - -(defun cvs-update-filter (proc string) - "Filter function for pcl-cvs. -This function gets the output that CVS sends to stdout. It inserts -the STRING into (process-buffer PROC) but it also checks if CVS is waiting -for a lock file. If so, it inserts a message cookie in the *cvs* buffer." - (save-match-data - (with-current-buffer (process-buffer proc) - (let ((inhibit-read-only t)) - (save-excursion - ;; Insert the text, moving the process-marker. - (goto-char (process-mark proc)) - (insert string) - (set-marker (process-mark proc) (point)) - ;; FIXME: Delete any old lock message - ;;(if (tin-nth cookies 1) - ;; (tin-delete cookies - ;; (tin-nth cookies 1))) - ;; Check if CVS is waiting for a lock. - (beginning-of-line 0) ;Move to beginning of last complete line. - (when (looking-at "^[ a-z]+: \\(.*waiting for .*lock in \\(.*\\)\\)$") - (let ((msg (match-string 1)) - (lock (match-string 2))) - (with-current-buffer cvs-buffer - (set (make-local-variable 'cvs-lock-file) lock) - ;; display the lock situation in the *cvs* buffer: - (ewoc-enter-last - cvs-cookies - (cvs-create-fileinfo - 'MESSAGE "" " " - (concat msg - (when (file-exists-p lock) - (substitute-command-keys - "\n\t(type \\[cvs-mode-delete-lock] to delete it)"))) - :subtype 'TEMP)) - (pop-to-buffer (current-buffer)) - (goto-char (point-max)) - (beep))))))))) - - -;;;; -;;;; The cvs-mode and its associated commands. -;;;; - -(cvs-prefix-define cvs-force-command "" "" '("/F") cvs-qtypedesc-string1) -(defun-cvs-mode cvs-mode-force-command (arg) - "Force the next cvs command to operate on all the selected files. -By default, cvs commands only operate on files on which the command -\"makes sense\". This overrides the safety feature on the next cvs command. -It actually behaves as a toggle. If prefixed by \\[universal-argument] \\[universal-argument], -the override will persist until the next toggle." - (interactive "P") - (cvs-prefix-set 'cvs-force-command arg)) - -(put 'cvs-mode 'mode-class 'special) -(define-derived-mode cvs-mode nil "CVS" - "Mode used for PCL-CVS, a frontend to CVS. -Full documentation is in the Texinfo file." - (setq mode-line-process - '("" cvs-force-command cvs-ignore-marks-modif - ":" (cvs-branch-prefix - ("" cvs-branch-prefix (cvs-secondary-branch-prefix - ("->" cvs-secondary-branch-prefix)))) - " " cvs-mode-line-process)) - (if buffer-file-name - (error "Use M-x cvs-quickdir to get a *cvs* buffer")) - (buffer-disable-undo) - ;;(set (make-local-variable 'goal-column) cvs-cursor-column) - (set (make-local-variable 'revert-buffer-function) 'cvs-mode-revert-buffer) - (setq truncate-lines t) - (cvs-prefix-make-local 'cvs-branch-prefix) - (cvs-prefix-make-local 'cvs-secondary-branch-prefix) - (cvs-prefix-make-local 'cvs-force-command) - (cvs-prefix-make-local 'cvs-ignore-marks-modif) - (make-local-variable 'cvs-mode-line-process) - (make-local-variable 'cvs-temp-buffers)) - - -(defun cvs-buffer-p (&optional buffer) - "Return whether the (by default current) BUFFER is a `cvs-mode' buffer." - (save-excursion - (if buffer (set-buffer buffer)) - (and (eq major-mode 'cvs-mode)))) - -(defun cvs-buffer-check () - "Check that the current buffer follows cvs-buffer's conventions." - (let ((buf (current-buffer)) - (check 'none)) - (or (and (setq check 'collection) - (eq (ewoc-buffer cvs-cookies) buf) - (setq check 'cvs-temp-buffer) - (or (null cvs-temp-buffer) - (null (buffer-live-p cvs-temp-buffer)) - (and (eq (with-current-buffer cvs-temp-buffer cvs-buffer) buf) - (equal (with-current-buffer cvs-temp-buffer - default-directory) - default-directory))) - t) - (error "Inconsistent %s in buffer %s" check (buffer-name buf))))) - - -(defun cvs-mode-quit () - "Quit PCL-CVS, killing the *cvs* buffer." - (interactive) - (and (y-or-n-p "Quit pcl-cvs? ") (kill-buffer (current-buffer)))) - -;; Give help.... - -(defun cvs-help () - "Display help for various PCL-CVS commands." - (interactive) - (if (eq last-command 'cvs-help) - (describe-function 'cvs-mode) ; would need minor-mode for log-edit-mode - (message "%s" - (substitute-command-keys - "`\\[cvs-help]':help `\\[cvs-mode-add]':add `\\[cvs-mode-commit]':commit \ -`\\[cvs-mode-diff-map]':diff* `\\[cvs-mode-log]':log \ -`\\[cvs-mode-remove]':remove `\\[cvs-mode-status]':status \ -`\\[cvs-mode-undo]':undo")))) - -;; Move around in the buffer - -(defun cvs-move-to-goal-column () - (let* ((eol (line-end-position)) - (fpos (next-single-property-change (point) 'cvs-goal-column nil eol))) - (when (< fpos eol) - (goto-char fpos)))) - -(defun-cvs-mode cvs-mode-previous-line (arg) - "Go to the previous line. -If a prefix argument is given, move by that many lines." - (interactive "p") - (ewoc-goto-prev cvs-cookies arg) - (cvs-move-to-goal-column)) - -(defun-cvs-mode cvs-mode-next-line (arg) - "Go to the next line. -If a prefix argument is given, move by that many lines." - (interactive "p") - (ewoc-goto-next cvs-cookies arg) - (cvs-move-to-goal-column)) - -;;;; -;;;; Mark handling -;;;; - -(defun-cvs-mode cvs-mode-mark (&optional arg) - "Mark the fileinfo on the current line. -If the fileinfo is a directory, all the contents of that directory are -marked instead. A directory can never be marked." - (interactive) - (let* ((tin (ewoc-locate cvs-cookies)) - (fi (ewoc-data tin))) - (if (eq (cvs-fileinfo->type fi) 'DIRCHANGE) - ;; it's a directory: let's mark all files inside - (ewoc-map - (lambda (f dir) - (when (cvs-dir-member-p f dir) - (setf (cvs-fileinfo->marked f) - (not (if (eq arg 'toggle) (cvs-fileinfo->marked f) arg))) - t)) ;Tell cookie to redisplay this cookie. - cvs-cookies - (cvs-fileinfo->dir fi)) - ;; not a directory: just do the obvious - (setf (cvs-fileinfo->marked fi) - (not (if (eq arg 'toggle) (cvs-fileinfo->marked fi) arg))) - (ewoc-invalidate cvs-cookies tin) - (cvs-mode-next-line 1)))) - -(defalias 'cvs-mouse-toggle-mark 'cvs-mode-toggle-mark) -(defun cvs-mode-toggle-mark (e) - "Toggle the mark of the entry at point." - (interactive (list last-input-event)) - (save-excursion - (posn-set-point (event-end e)) - (cvs-mode-mark 'toggle))) - -(defun-cvs-mode cvs-mode-unmark () - "Unmark the fileinfo on the current line." - (interactive) - (cvs-mode-mark t)) - -(defun-cvs-mode cvs-mode-mark-all-files () - "Mark all files." - (interactive) - (ewoc-map (lambda (cookie) - (unless (eq (cvs-fileinfo->type cookie) 'DIRCHANGE) - (setf (cvs-fileinfo->marked cookie) t))) - cvs-cookies)) - -(defun-cvs-mode (cvs-mode-mark-on-state . SIMPLE) (state) - "Mark all files in state STATE." - (interactive - (list - (let ((default - (condition-case nil - (downcase - (symbol-name - (cvs-fileinfo->type - (cvs-mode-marked nil nil :read-only t :one t :noquery t)))) - (error nil)))) - (intern - (upcase - (completing-read - (concat - "Mark files in state" (if default (concat " [" default "]")) ": ") - (mapcar (lambda (x) - (list (downcase (symbol-name (car x))))) - cvs-states) - nil t nil nil default)))))) - (ewoc-map (lambda (fi) - (when (eq (cvs-fileinfo->type fi) state) - (setf (cvs-fileinfo->marked fi) t))) - cvs-cookies)) - -(defun-cvs-mode cvs-mode-mark-matching-files (regex) - "Mark all files matching REGEX." - (interactive "sMark files matching: ") - (ewoc-map (lambda (cookie) - (when (and (not (eq (cvs-fileinfo->type cookie) 'DIRCHANGE)) - (string-match regex (cvs-fileinfo->file cookie))) - (setf (cvs-fileinfo->marked cookie) t))) - cvs-cookies)) - -(defun-cvs-mode cvs-mode-unmark-all-files () - "Unmark all files. -Directories are also unmarked, but that doesn't matter, since -they should always be unmarked." - (interactive) - (ewoc-map (lambda (cookie) - (setf (cvs-fileinfo->marked cookie) nil) - t) - cvs-cookies)) - -(defun-cvs-mode cvs-mode-unmark-up () - "Unmark the file on the previous line." - (interactive) - (let ((tin (ewoc-goto-prev cvs-cookies 1))) - (when tin - (setf (cvs-fileinfo->marked (ewoc-data tin)) nil) - (ewoc-invalidate cvs-cookies tin))) - (cvs-move-to-goal-column)) - -(defconst cvs-ignore-marks-alternatives - '(("toggle-marks" . "/TM") - ("force-marks" . "/FM") - ("ignore-marks" . "/IM"))) - -(cvs-prefix-define cvs-ignore-marks-modif - "Prefix to decide whether to ignore marks or not." - "active" - (mapcar 'cdr cvs-ignore-marks-alternatives) - (cvs-qtypedesc-create - (lambda (str) (cdr (assoc str cvs-ignore-marks-alternatives))) - (lambda (obj) (car (rassoc obj cvs-ignore-marks-alternatives))) - (lambda () cvs-ignore-marks-alternatives) - nil t)) - -(defun-cvs-mode cvs-mode-toggle-marks (arg) - "Toggle whether the next CVS command uses marks. -See `cvs-prefix-set' for further description of the behavior. -\\[universal-argument] 1 selects `force-marks', -\\[universal-argument] 2 selects `ignore-marks', -\\[universal-argument] 3 selects `toggle-marks'." - (interactive "P") - (cvs-prefix-set 'cvs-ignore-marks-modif arg)) - -(defun cvs-ignore-marks-p (cmd &optional read-only) - (let ((default (if (member cmd cvs-invert-ignore-marks) - (not cvs-default-ignore-marks) - cvs-default-ignore-marks)) - (modif (cvs-prefix-get 'cvs-ignore-marks-modif read-only))) - (cond - ((equal modif "/IM") t) - ((equal modif "/TM") (not default)) - ((equal modif "/FM") nil) - (t default)))) - -(defun cvs-mode-mark-get-modif (cmd) - (if (cvs-ignore-marks-p cmd 'read-only) "/IM" "/FM")) - -(defun cvs-get-marked (&optional ignore-marks ignore-contents) - "Return a list of all selected fileinfos. -If there are any marked tins, and IGNORE-MARKS is nil, return them. -Otherwise, if the cursor selects a directory, and IGNORE-CONTENTS is -nil, return all files in it, else return just the directory. -Otherwise return (a list containing) the file the cursor points to, or -an empty list if it doesn't point to a file at all." - (let ((fis nil)) - (dolist (fi (if (and (boundp 'cvs-minor-current-files) - (consp cvs-minor-current-files)) - (mapcar - (lambda (f) - (if (cvs-fileinfo-p f) f - (let ((f (file-relative-name f))) - (if (file-directory-p f) - (cvs-create-fileinfo - 'DIRCHANGE (file-name-as-directory f) "." "") - (let ((dir (file-name-directory f)) - (file (file-name-nondirectory f))) - (cvs-create-fileinfo - 'UNKNOWN (or dir "") file "")))))) - cvs-minor-current-files) - (or (and (not ignore-marks) - (ewoc-collect cvs-cookies 'cvs-fileinfo->marked)) - (list (ewoc-data (ewoc-locate cvs-cookies)))))) - - (if (or ignore-contents (not (eq (cvs-fileinfo->type fi) 'DIRCHANGE))) - (push fi fis) - ;; If a directory is selected, return members, if any. - (setq fis - (append (ewoc-collect - cvs-cookies 'cvs-dir-member-p (cvs-fileinfo->dir fi)) - fis)))) - (nreverse fis))) - -(defun* cvs-mode-marked (filter &optional cmd - &key read-only one file noquery) - "Get the list of marked FIS. -CMD is used to determine whether to use the marks or not. -Only files for which FILTER is applicable are returned. -If READ-ONLY is non-nil, the current toggling is left intact. -If ONE is non-nil, marks are ignored and a single FI is returned. -If FILE is non-nil, directory entries won't be selected." - (unless cmd (setq cmd (symbol-name filter))) - (let* ((fis (cvs-get-marked (or one (cvs-ignore-marks-p cmd read-only)) - (and (not file) - (cvs-applicable-p 'DIRCHANGE filter)))) - (force (cvs-prefix-get 'cvs-force-command)) - (fis (car (cvs-partition - (lambda (fi) (cvs-applicable-p fi (and (not force) filter))) - fis)))) - (when (and (or (null fis) (and one (cdr fis))) (not noquery)) - (message (if (null fis) - "`%s' is not applicable to any of the selected files." - "`%s' is only applicable to a single file.") cmd) - (sit-for 1) - (setq fis (list (cvs-insert-file - (read-file-name (format "File to %s: " cmd)))))) - (if one (car fis) fis))) - -(defun cvs-enabledp (filter) - "Determine whether FILTER applies to at least one of the selected files." - (ignore-errors (cvs-mode-marked filter nil :read-only t :noquery t))) - -(defun cvs-mode-files (&rest -cvs-mode-files-args) - (cvs-mode! - (lambda () - (mapcar 'cvs-fileinfo->full-name - (apply 'cvs-mode-marked -cvs-mode-files-args))))) - -;; -;; Interface between Log-Edit and PCL-CVS -;; - -(defun cvs-mode-commit-setup () - "Run `cvs-mode-commit' with setup." - (interactive) - (cvs-mode-commit 'force)) - -(defcustom cvs-mode-commit-hook nil - "Hook run after setting up the commit buffer." - :type 'hook - :options '(cvs-mode-diff) - :group 'pcl-cvs) - -(defun cvs-mode-commit (setup) - "Check in all marked files, or the current file. -The user will be asked for a log message in a buffer. -The buffer's mode and name is determined by the \"message\" setting - of `cvs-buffer-name-alist'. -The POSTPROC specified there (typically `log-edit') is then called, - passing it the SETUP argument." - (interactive "P") - ;; It seems that the save-excursion that happens if I use the better - ;; form of `(cvs-mode! (lambda ...))' screws up a couple things which - ;; end up being rather annoying (like log-edit-mode's message being - ;; displayed in the wrong minibuffer). - (cvs-mode!) - (let ((buf (cvs-temp-buffer "message" 'normal 'nosetup)) - (setupfun (or (nth 2 (cdr (assoc "message" cvs-buffer-name-alist))) - 'log-edit))) - (funcall setupfun 'cvs-do-commit setup - '((log-edit-listfun . cvs-commit-filelist) - (log-edit-diff-function . cvs-mode-diff)) buf) - (set (make-local-variable 'cvs-minor-wrap-function) 'cvs-commit-minor-wrap) - (run-hooks 'cvs-mode-commit-hook))) - -(defun cvs-commit-minor-wrap (buf f) - (let ((cvs-ignore-marks-modif (cvs-mode-mark-get-modif "commit"))) - (funcall f))) - -(defun cvs-commit-filelist () - (cvs-mode-files 'commit nil :read-only t :file t :noquery t)) - -(defun cvs-do-commit (flags) - "Do the actual commit, using the current buffer as the log message." - (interactive (list (cvs-flags-query 'cvs-commit-flags "cvs commit flags"))) - (let ((msg (buffer-substring-no-properties (point-min) (point-max)))) - (cvs-mode!) - ;;(pop-to-buffer cvs-buffer) - (cvs-mode-do "commit" (list* "-m" msg flags) 'commit))) - - -;;;; Editing existing commit log messages. - -(defun cvs-edit-log-text-at-point () - (save-excursion - (end-of-line) - (when (re-search-backward "^revision " nil t) - (forward-line 1) - (if (looking-at "date:") (forward-line 1)) - (if (looking-at "branches:") (forward-line 1)) - (buffer-substring - (point) - (if (re-search-forward - "^\\(-\\{28\\}\\|=\\{77\\}\\|revision [.0-9]+\\)$" - nil t) - (match-beginning 0) - (point)))))) - -(defvar cvs-edit-log-revision) -(defvar cvs-edit-log-files) (put 'cvs-edit-log-files 'permanent-local t) -(defun cvs-mode-edit-log (file rev &optional text) - "Edit the log message at point. -This is best called from a `log-view-mode' buffer." - (interactive - (list - (or (cvs-mode! (lambda () - (car (cvs-mode-files nil nil - :read-only t :file t :noquery t)))) - (read-string "File name: ")) - (or (cvs-mode! (lambda () (cvs-prefix-get 'cvs-branch-prefix))) - (read-string "Revision to edit: ")) - (cvs-edit-log-text-at-point))) - ;; It seems that the save-excursion that happens if I use the better - ;; form of `(cvs-mode! (lambda ...))' screws up a couple things which - ;; end up being rather annoying (like log-edit-mode's message being - ;; displayed in the wrong minibuffer). - (cvs-mode!) - (let ((buf (cvs-temp-buffer "message" 'normal 'nosetup)) - (setupfun (or (nth 2 (cdr (assoc "message" cvs-buffer-name-alist))) - 'log-edit))) - (with-current-buffer buf - ;; Set the filename before, so log-edit can correctly setup its - ;; log-edit-initial-files variable. - (set (make-local-variable 'cvs-edit-log-files) (list file))) - (funcall setupfun 'cvs-do-edit-log nil - '((log-edit-listfun . cvs-edit-log-filelist) - (log-edit-diff-function . cvs-mode-diff)) - buf) - (when text (erase-buffer) (insert text)) - (set (make-local-variable 'cvs-edit-log-revision) rev) - (set (make-local-variable 'cvs-minor-wrap-function) - 'cvs-edit-log-minor-wrap) - ;; (run-hooks 'cvs-mode-commit-hook) - )) - -(defun cvs-edit-log-minor-wrap (buf f) - (let ((cvs-branch-prefix (with-current-buffer buf cvs-edit-log-revision)) - (cvs-minor-current-files - (with-current-buffer buf cvs-edit-log-files)) - ;; FIXME: I need to force because the fileinfos are UNKNOWN - (cvs-force-command "/F")) - (funcall f))) - -(defun cvs-edit-log-filelist () - (if cvs-minor-wrap-function - (cvs-mode-files nil nil :read-only t :file t :noquery t) - cvs-edit-log-files)) - -(defun cvs-do-edit-log (rev) - "Do the actual commit, using the current buffer as the log message." - (interactive (list cvs-edit-log-revision)) - (let ((msg (buffer-substring-no-properties (point-min) (point-max)))) - (cvs-mode! - (lambda () - (cvs-mode-do "admin" (list (concat "-m" rev ":" msg)) nil))))) - - -;;;; -;;;; CVS Mode commands -;;;; - -(defun-cvs-mode (cvs-mode-insert . NOARGS) (file) - "Insert an entry for a specific file into the current listing. -This is typically used if the file is up-to-date (or has been added -outside of PCL-CVS) and one wants to do some operation on it." - (interactive - (list (read-file-name - "File to insert: " - ;; Can't use ignore-errors here because interactive - ;; specs aren't byte-compiled. - (condition-case nil - (file-name-as-directory - (expand-file-name - (cvs-fileinfo->dir - (cvs-mode-marked nil nil :read-only t :one t :noquery t)))) - (error nil))))) - (cvs-insert-file file)) - -(defun cvs-insert-file (file) - "Insert FILE (and its contents if it's a dir) and return its FI." - (let ((file (file-relative-name (directory-file-name file))) last) - (dolist (fi (cvs-fileinfo-from-entries file)) - (setq last (cvs-addto-collection cvs-cookies fi last))) - ;; There should have been at least one entry. - (goto-char (ewoc-location last)) - (ewoc-data last))) - -(defun cvs-mark-fis-dead (fis) - ;; Helper function, introduced because of the need for macro-expansion. - (dolist (fi fis) - (setf (cvs-fileinfo->type fi) 'DEAD))) - -(defun-cvs-mode (cvs-mode-add . SIMPLE) (flags) - "Add marked files to the cvs repository. -With prefix argument, prompt for cvs flags." - (interactive (list (cvs-flags-query 'cvs-add-flags "cvs add flags"))) - (let ((fis (cvs-mode-marked 'add)) - (needdesc nil) (dirs nil)) - ;; find directories and look for fis needing a description - (dolist (fi fis) - (cond - ((file-directory-p (cvs-fileinfo->full-name fi)) (push fi dirs)) - ((eq (cvs-fileinfo->type fi) 'UNKNOWN) (setq needdesc t)))) - ;; prompt for description if necessary - (let* ((msg (if (and needdesc - (or current-prefix-arg (not cvs-add-default-message))) - (read-from-minibuffer "Enter description: ") - (or cvs-add-default-message ""))) - (flags (list* "-m" msg flags)) - (postproc - ;; setup postprocessing for the directory entries - (when dirs - `((cvs-run-process (list "-n" "update") - ',dirs - '(cvs-parse-process t)) - (cvs-mark-fis-dead ',dirs))))) - (cvs-mode-run "add" flags fis :postproc postproc)))) - -(defun-cvs-mode (cvs-mode-diff . DOUBLE) (flags) - "Diff the selected files against the repository. -This command compares the files in your working area against the -revision which they are based upon." - (interactive - (list (cvs-add-branch-prefix - (cvs-add-secondary-branch-prefix - (cvs-flags-query 'cvs-diff-flags "cvs diff flags"))))) - (cvs-mode-do "diff" flags 'diff - :show t)) ;; :ignore-exit t - -(defun-cvs-mode (cvs-mode-diff-head . SIMPLE) (flags) - "Diff the selected files against the head of the current branch. -See ``cvs-mode-diff'' for more info." - (interactive (list (cvs-flags-query 'cvs-diff-flags "cvs diff flags"))) - (cvs-mode-diff-1 (cons "-rHEAD" flags))) - -(defun-cvs-mode (cvs-mode-diff-repository . SIMPLE) (flags) - "Diff the files for changes in the repository since last co/update/commit. -See ``cvs-mode-diff'' for more info." - (interactive (list (cvs-flags-query 'cvs-diff-flags "cvs diff flags"))) - (cvs-mode-diff-1 (cons "-rBASE" (cons "-rHEAD" flags)))) - -(defun-cvs-mode (cvs-mode-diff-yesterday . SIMPLE) (flags) - "Diff the selected files against yesterday's head of the current branch. -See ``cvs-mode-diff'' for more info." - (interactive (list (cvs-flags-query 'cvs-diff-flags "cvs diff flags"))) - (cvs-mode-diff-1 (cons "-Dyesterday" flags))) - -(defun-cvs-mode (cvs-mode-diff-vendor . SIMPLE) (flags) - "Diff the selected files against the head of the vendor branch. -See ``cvs-mode-diff'' for more info." - (interactive (list (cvs-flags-query 'cvs-diff-flags "cvs diff flags"))) - (cvs-mode-diff-1 (cons (concat "-r" cvs-vendor-branch) flags))) - -;; sadly, this is not provided by cvs, so we have to roll our own -(defun-cvs-mode (cvs-mode-diff-backup . SIMPLE) (flags) - "Diff the files against the backup file. -This command can be used on files that are marked with \"Merged\" -or \"Conflict\" in the *cvs* buffer." - (interactive (list (cvs-flags-query 'cvs-diff-flags "diff flags"))) - (unless (listp flags) (error "flags should be a list of strings")) - (save-some-buffers) - (let* ((marked (cvs-get-marked (cvs-ignore-marks-p "diff"))) - (fis (car (cvs-partition 'cvs-fileinfo->backup-file marked)))) - (unless (consp fis) - (error "No files with a backup file selected!")) - ;; let's extract some info into the environment for `buffer-name' - (let* ((dir (cvs-fileinfo->dir (car fis))) - (file (cvs-fileinfo->file (car fis)))) - (set-buffer (cvs-temp-buffer "diff"))) - (message "cvs diff backup...") - (cvs-execute-single-file-list fis 'cvs-diff-backup-extractor - cvs-diff-program flags)) - (message "cvs diff backup... Done.")) - -(defun cvs-diff-backup-extractor (fileinfo) - "Return the filename and the name of the backup file as a list. -Signal an error if there is no backup file." - (let ((backup-file (cvs-fileinfo->backup-file fileinfo))) - (unless backup-file - (error "%s has no backup file" (cvs-fileinfo->full-name fileinfo))) - (list backup-file (cvs-fileinfo->full-name fileinfo)))) - -;; -;; Emerge support -;; -(defun cvs-emerge-diff (b1 b2) (emerge-buffers b1 b2 b1)) -(defun cvs-emerge-merge (b1 b2 base out) - (emerge-buffers-with-ancestor b1 b2 base (find-file-noselect out))) - -;; -;; Ediff support -;; - -(defvar ediff-after-quit-destination-buffer) -(defvar ediff-after-quit-hook-internal) -(defvar cvs-transient-buffers) -(defun cvs-ediff-startup-hook () - (add-hook 'ediff-after-quit-hook-internal - `(lambda () - (cvs-ediff-exit-hook - ',ediff-after-quit-destination-buffer ',cvs-transient-buffers)) - nil 'local)) - -(defun cvs-ediff-exit-hook (cvs-buf tmp-bufs) - ;; kill the temp buffers (and their associated windows) - (dolist (tb tmp-bufs) - (when (and tb (buffer-live-p tb) (not (buffer-modified-p tb))) - (let ((win (get-buffer-window tb t))) - (kill-buffer tb) - (when (window-live-p win) (ignore-errors (delete-window win)))))) - ;; switch back to the *cvs* buffer - (when (and cvs-buf (buffer-live-p cvs-buf) - (not (get-buffer-window cvs-buf t))) - (ignore-errors (switch-to-buffer cvs-buf)))) - -(defun cvs-ediff-diff (b1 b2) - (let ((ediff-after-quit-destination-buffer (current-buffer)) - (startup-hook '(cvs-ediff-startup-hook))) - (ediff-buffers b1 b2 startup-hook 'ediff-revision))) - -(defun cvs-ediff-merge (b1 b2 base out) - (let ((ediff-after-quit-destination-buffer (current-buffer)) - (startup-hook '(cvs-ediff-startup-hook))) - (ediff-merge-buffers-with-ancestor - b1 b2 base startup-hook - 'ediff-merge-revisions-with-ancestor - out))) - -;; -;; Interactive merge/diff support. -;; - -(defun cvs-retrieve-revision (fileinfo rev) - "Retrieve the given REVision of the file in FILEINFO into a new buffer." - (let* ((file (cvs-fileinfo->full-name fileinfo)) - (buffile (concat file "." rev))) - (or (find-buffer-visiting buffile) - (with-current-buffer (create-file-buffer buffile) - (message "Retrieving revision %s..." rev) - ;; Discard stderr output to work around the CVS+SSH+libc - ;; problem when stdout and stderr are the same. - (let ((res - (let ((coding-system-for-read 'binary)) - (apply 'process-file cvs-program nil '(t nil) nil - "-q" "update" "-p" - ;; If `rev' is HEAD, don't pass it at all: - ;; the default behavior is to get the head - ;; of the current branch whereas "-r HEAD" - ;; stupidly gives you the head of the trunk. - (append (unless (equal rev "HEAD") (list "-r" rev)) - (list file)))))) - (when (and res (not (and (equal 0 res)))) - (error "Something went wrong retrieving revision %s: %s" rev res)) - ;; Figure out the encoding used and decode the byte-sequence - ;; into a sequence of chars. - (decode-coding-inserted-region - (point-min) (point-max) file t nil nil t) - ;; Set buffer-file-coding-system. - (after-insert-file-set-coding (buffer-size) t) - (set-buffer-modified-p nil) - (let ((buffer-file-name (expand-file-name file))) - (after-find-file)) - (toggle-read-only 1) - (message "Retrieving revision %s... Done" rev) - (current-buffer)))))) - -;; FIXME: The user should be able to specify ancestor/head/backup and we should -;; provide sensible defaults when merge info is unavailable (rather than rely -;; on smerge-ediff). Also provide sane defaults for need-merge files. -(defun-cvs-mode cvs-mode-imerge () - "Merge interactively appropriate revisions of the selected file." - (interactive) - (let ((fi (cvs-mode-marked 'merge nil :one t :file t))) - (let ((merge (cvs-fileinfo->merge fi)) - (file (cvs-fileinfo->full-name fi)) - (backup-file (cvs-fileinfo->backup-file fi))) - (if (not (and merge backup-file)) - (let ((buf (find-file-noselect file))) - (message "Missing merge info or backup file, using VC.") - (with-current-buffer buf - (smerge-ediff))) - (let* ((ancestor-buf (cvs-retrieve-revision fi (car merge))) - (head-buf (cvs-retrieve-revision fi (cdr merge))) - (backup-buf (let ((auto-mode-alist nil)) - (find-file-noselect backup-file))) - ;; this binding is used by cvs-ediff-startup-hook - (cvs-transient-buffers (list ancestor-buf backup-buf head-buf))) - (with-current-buffer backup-buf - (let ((buffer-file-name (expand-file-name file))) - (after-find-file))) - (funcall (cdr cvs-idiff-imerge-handlers) - backup-buf head-buf ancestor-buf file)))))) - -(cvs-flags-define cvs-idiff-version - (list "BASE" cvs-vendor-branch cvs-vendor-branch "BASE" "BASE") - "version: " cvs-qtypedesc-tag) - -(defun-cvs-mode (cvs-mode-idiff . NOARGS) (&optional rev1 rev2) - "Diff interactively current file to revisions." - (interactive - (let* ((rev1 (cvs-prefix-get 'cvs-branch-prefix)) - (rev2 (and rev1 (cvs-prefix-get 'cvs-secondary-branch-prefix)))) - (list (or rev1 (cvs-flags-query 'cvs-idiff-version)) - rev2))) - (let ((fi (cvs-mode-marked 'diff "idiff" :one t :file t))) - (let* ((file (cvs-fileinfo->full-name fi)) - (rev1-buf (cvs-retrieve-revision fi (or rev1 "BASE"))) - (rev2-buf (if rev2 (cvs-retrieve-revision fi rev2))) - ;; this binding is used by cvs-ediff-startup-hook - (cvs-transient-buffers (list rev1-buf rev2-buf))) - (funcall (car cvs-idiff-imerge-handlers) - rev1-buf (or rev2-buf (find-file-noselect file)))))) - -(defun-cvs-mode (cvs-mode-idiff-other . NOARGS) () - "Diff interactively current file to revisions." - (interactive) - (let* ((rev1 (cvs-prefix-get 'cvs-branch-prefix)) - (rev2 (and rev1 (cvs-prefix-get 'cvs-secondary-branch-prefix))) - (fis (cvs-mode-marked 'diff "idiff" :file t))) - (when (> (length fis) 2) - (error "idiff-other cannot be applied to more than 2 files at a time")) - (let* ((fi1 (car fis)) - (rev1-buf (if rev1 (cvs-retrieve-revision fi1 rev1) - (find-file-noselect (cvs-fileinfo->full-name fi1)))) - rev2-buf) - (if (cdr fis) - (let ((fi2 (nth 1 fis))) - (setq rev2-buf - (if rev2 (cvs-retrieve-revision fi2 rev2) - (find-file-noselect (cvs-fileinfo->full-name fi2))))) - (error "idiff-other doesn't know what other file/buffer to use")) - (let* (;; this binding is used by cvs-ediff-startup-hook - (cvs-transient-buffers (list rev1-buf rev2-buf))) - (funcall (car cvs-idiff-imerge-handlers) - rev1-buf rev2-buf))))) - - -(defun cvs-is-within-p (fis dir) - "Non-nil if buffer is inside one of FIS (in DIR)." - (when (stringp buffer-file-name) - (setq buffer-file-name (expand-file-name buffer-file-name)) - (let (ret) - (dolist (fi (or fis (list (cvs-create-fileinfo 'DIRCHANGE "" "." "")))) - (when (cvs-string-prefix-p - (expand-file-name (cvs-fileinfo->full-name fi) dir) - buffer-file-name) - (setq ret t))) - ret))) - -(defun* cvs-mode-run (cmd flags fis - &key (buf (cvs-temp-buffer)) - dont-change-disc cvsargs postproc) - "Generic cvs-mode- function. -Executes `cvs CVSARGS CMD FLAGS FIS'. -BUF is the buffer to be used for cvs' output. -DONT-CHANGE-DISC non-nil indicates that the command will not change the - contents of files. This is only used by the parser. -POSTPROC is a list of expressions to be evaluated at the very end (after - parsing if applicable). It will be prepended with `progn' if necessary." - (let ((def-dir default-directory)) - ;; Save the relevant buffers - (save-some-buffers nil (lambda () (cvs-is-within-p fis def-dir)))) - (unless (listp flags) (error "flags should be a list of strings")) - ;; Some w32 versions of CVS don't like an explicit . too much. - (when (and (car fis) (null (cdr fis)) - (eq (cvs-fileinfo->type (car fis)) 'DIRCHANGE) - ;; (equal (cvs-fileinfo->file (car fis)) ".") - (equal (cvs-fileinfo->dir (car fis)) "")) - (setq fis nil)) - (let* ((single-dir (or (not (listp cvs-execute-single-dir)) - (member cmd cvs-execute-single-dir))) - (parse (member cmd cvs-parse-known-commands)) - (args (append cvsargs (list cmd) flags)) - (after-mode (nth 2 (cdr (assoc cmd cvs-buffer-name-alist))))) - (cvs-cleanup-collection cvs-cookies ;cleanup remaining messages - (eq cvs-auto-remove-handled 'delayed) nil t) - (when (fboundp after-mode) - (setq postproc (append postproc `((,after-mode))))) - (when parse - (let ((old-fis - (when (member cmd '("status" "update")) ;FIXME: Yuck!! - ;; absence of `cvs update' output has a specific meaning. - (or fis (list (cvs-create-fileinfo 'DIRCHANGE "" "." "")))))) - (push `(cvs-parse-process ',dont-change-disc nil ',old-fis) postproc))) - (setq postproc (if (cdr postproc) (cons 'progn postproc) (car postproc))) - (with-current-buffer buf - (let ((inhibit-read-only t)) (erase-buffer)) - (message "Running cvs %s ..." cmd) - (cvs-run-process args fis postproc single-dir)))) - - -(defun* cvs-mode-do (cmd flags filter - &key show dont-change-disc cvsargs postproc) - "Generic cvs-mode- function. -Executes `cvs CVSARGS CMD FLAGS' on the selected files. -FILTER is passed to `cvs-applicable-p' to only apply the command to - files for which it makes sense. -SHOW indicates that CMD should be not be run in the default temp buffer and - should be shown to the user. The buffer and mode to be used is determined - by `cvs-buffer-name-alist'. -DONT-CHANGE-DISC non-nil indicates that the command will not change the - contents of files. This is only used by the parser." - (cvs-mode-run cmd flags (cvs-mode-marked filter cmd) - :buf (cvs-temp-buffer (when show cmd)) - :dont-change-disc dont-change-disc - :cvsargs cvsargs - :postproc postproc)) - -(defun-cvs-mode (cvs-mode-status . SIMPLE) (flags) - "Show cvs status for all marked files. -With prefix argument, prompt for cvs flags." - (interactive (list (cvs-flags-query 'cvs-status-flags "cvs status flags"))) - (cvs-mode-do "status" flags nil :dont-change-disc t :show t - :postproc (when (eq cvs-auto-remove-handled 'status) - `((with-current-buffer ,(current-buffer) - (cvs-mode-remove-handled)))))) - -(defun-cvs-mode (cvs-mode-tree . SIMPLE) (flags) - "Call cvstree using the file under the point as a keyfile." - (interactive (list (cvs-flags-query 'cvs-status-flags "cvs status flags"))) - (cvs-mode-run "status" (cons "-v" flags) (cvs-mode-marked nil "status") - :buf (cvs-temp-buffer "tree") - :dont-change-disc t - :postproc '((cvs-status-cvstrees)))) - -;; cvs log - -(defun-cvs-mode (cvs-mode-log . NOARGS) (flags) - "Display the cvs log of all selected files. -With prefix argument, prompt for cvs flags." - (interactive (list (cvs-add-branch-prefix - (cvs-flags-query 'cvs-log-flags "cvs log flags")))) - (cvs-mode-do "log" flags nil :show t)) - - -(defun-cvs-mode (cvs-mode-update . NOARGS) (flags) - "Update all marked files. -With a prefix argument, prompt for cvs flags." - (interactive - (list (cvs-add-branch-prefix - (cvs-add-secondary-branch-prefix - (cvs-flags-query 'cvs-update-flags "cvs update flags") - "-j") "-j"))) - (cvs-mode-do "update" flags 'update)) - - -(defun-cvs-mode (cvs-mode-examine . NOARGS) (flags) - "Re-examine all marked files. -With a prefix argument, prompt for cvs flags." - (interactive - (list (cvs-add-branch-prefix - (cvs-add-secondary-branch-prefix - (cvs-flags-query 'cvs-update-flags "cvs -n update flags") - "-j") "-j"))) - (cvs-mode-do "update" flags nil :cvsargs '("-n") :dont-change-disc t)) - - -(defun-cvs-mode cvs-mode-ignore (&optional pattern) - "Arrange so that CVS ignores the selected files. -This command ignores files that are not flagged as `Unknown'." - (interactive) - (dolist (fi (cvs-mode-marked 'ignore)) - (cvs-append-to-ignore (cvs-fileinfo->dir fi) (cvs-fileinfo->file fi) - (eq (cvs-fileinfo->subtype fi) 'NEW-DIR)) - (setf (cvs-fileinfo->type fi) 'DEAD)) - (cvs-cleanup-collection cvs-cookies nil nil nil)) - -(declare-function vc-editable-p "vc" (file)) -(declare-function vc-checkout "vc" (file &optional writable rev)) - -(defun cvs-append-to-ignore (dir str &optional old-dir) - "Add STR to the .cvsignore file in DIR. -If OLD-DIR is non-nil, then this is a directory that we don't want -to hear about anymore." - (with-current-buffer - (find-file-noselect (expand-file-name ".cvsignore" dir)) - (when (ignore-errors - (and buffer-read-only - (eq 'CVS (vc-backend buffer-file-name)) - (not (vc-editable-p buffer-file-name)))) - ;; CVSREAD=on special case - (vc-checkout buffer-file-name t)) - (goto-char (point-max)) - (unless (bolp) (insert "\n")) - (insert str (if old-dir "/\n" "\n")) - (if cvs-sort-ignore-file (sort-lines nil (point-min) (point-max))) - (save-buffer))) - - -(defun cvs-mode-find-file-other-window (e) - "Select a buffer containing the file in another window." - (interactive (list last-input-event)) - (cvs-mode-find-file e t)) - - -(defun cvs-mode-display-file (e) - "Show a buffer containing the file in another window." - (interactive (list last-input-event)) - (cvs-mode-find-file e 'dont-select)) - - -(defun cvs-mode-view-file (e) - "View the file." - (interactive (list last-input-event)) - (cvs-mode-find-file e nil t)) - - -(defun cvs-mode-view-file-other-window (e) - "View the file." - (interactive (list last-input-event)) - (cvs-mode-find-file e t t)) - - -(defun cvs-find-modif (fi) - (with-temp-buffer - (process-file cvs-program nil (current-buffer) nil - "-f" "diff" (cvs-fileinfo->file fi)) - (goto-char (point-min)) - (if (re-search-forward "^\\([0-9]+\\)" nil t) - (string-to-number (match-string 1)) - 1))) - - -(defun cvs-mode-find-file (e &optional other view) - "Select a buffer containing the file. -With a prefix, opens the buffer in an OTHER window." - (interactive (list last-input-event current-prefix-arg)) - ;; If the event moves point, check that it moves it to a valid location. - (when (and (/= (point) (progn (posn-set-point (event-end e)) (point))) - (not (memq (get-text-property (1- (line-end-position)) - 'font-lock-face) - '(cvs-header cvs-filename)))) - (error "Not a file name")) - (cvs-mode! - (lambda (&optional rev) - (interactive (list (cvs-prefix-get 'cvs-branch-prefix))) - (let* ((cvs-buf (current-buffer)) - (fi (cvs-mode-marked nil nil :one t))) - (if (eq (cvs-fileinfo->type fi) 'DIRCHANGE) - (let ((odir default-directory)) - (setq default-directory - (cvs-expand-dir-name (cvs-fileinfo->dir fi))) - (cond ((eq other 'dont-select) - (display-buffer (find-file-noselect default-directory))) - (other (dired-other-window default-directory)) - (t (dired default-directory))) - (set-buffer cvs-buf) - (setq default-directory odir)) - (let ((buf (if rev (cvs-retrieve-revision fi rev) - (find-file-noselect (cvs-fileinfo->full-name fi))))) - (funcall (cond ((eq other 'dont-select) 'display-buffer) - (other - (if view 'view-buffer-other-window - 'switch-to-buffer-other-window)) - (t (if view 'view-buffer 'switch-to-buffer))) - buf) - (when (and cvs-find-file-and-jump (cvs-applicable-p fi 'diff-base)) - (save-restriction - (widen) - (goto-char (point-min)) - (forward-line (1- (cvs-find-modif fi))))) - buf)))))) - - -(defun-cvs-mode (cvs-mode-undo . SIMPLE) (flags) - "Undo local changes to all marked files. -The file is removed and `cvs update FILE' is run." - ;;"With prefix argument, prompt for cvs FLAGS." - (interactive (list nil));; (cvs-flags-query 'cvs-undo-flags "undo flags") - (if current-prefix-arg (call-interactively 'cvs-mode-revert-to-rev) - (let* ((fis (cvs-do-removal 'undo "update" 'all)) - (removedp (lambda (fi) - (or (eq (cvs-fileinfo->type fi) 'REMOVED) - (and (eq (cvs-fileinfo->type fi) 'CONFLICT) - (eq (cvs-fileinfo->subtype fi) 'REMOVED))))) - (fis-split (cvs-partition removedp fis)) - (fis-removed (car fis-split)) - (fis-other (cdr fis-split))) - (if (null fis-other) - (when fis-removed (cvs-mode-run "add" nil fis-removed)) - (cvs-mode-run "update" flags fis-other - :postproc - (when fis-removed - `((with-current-buffer ,(current-buffer) - (cvs-mode-run "add" nil ',fis-removed))))))))) - - -(defun-cvs-mode (cvs-mode-revert-to-rev . NOARGS) (rev) - "Revert the selected files to an old revision." - (interactive - (list (or (cvs-prefix-get 'cvs-branch-prefix) - (let ((current-prefix-arg '(4))) - (cvs-flags-query 'cvs-idiff-version))))) - (let* ((fis (cvs-mode-marked 'revert "revert" :file t)) - (tag (concat "tmp_pcl_tag_" (make-temp-name ""))) - (untag `((with-current-buffer ,(current-buffer) - (cvs-mode-run "tag" (list "-d" ',tag) ',fis)))) - (update `((with-current-buffer ,(current-buffer) - (cvs-mode-run "update" (list "-j" ',tag "-j" ',rev) ',fis - :postproc ',untag))))) - (cvs-mode-run "tag" (list tag) fis :postproc update))) - - -(defun-cvs-mode cvs-mode-delete-lock () - "Delete the lock file that CVS is waiting for. -Note that this can be dangerous. You should only do this -if you are convinced that the process that created the lock is dead." - (interactive) - (let* ((default-directory (cvs-expand-dir-name cvs-lock-file)) - (locks (directory-files default-directory nil cvs-lock-file-regexp))) - (cond - ((not locks) (error "No lock files found")) - ((yes-or-no-p (concat "Really delete locks in " cvs-lock-file "? ")) - (dolist (lock locks) - (cond ((file-directory-p lock) (delete-directory lock)) - ((file-exists-p lock) (delete-file lock)))))))) - - -(defun-cvs-mode cvs-mode-remove-handled () - "Remove all lines that are handled. -Empty directories are removed." - (interactive) - (cvs-cleanup-collection cvs-cookies - t (or cvs-auto-remove-directories 'handled) t)) - - -(defun-cvs-mode cvs-mode-acknowledge () - "Remove all marked files from the buffer." - (interactive) - (dolist (fi (cvs-get-marked (cvs-ignore-marks-p "acknowledge") t)) - (setf (cvs-fileinfo->type fi) 'DEAD)) - (cvs-cleanup-collection cvs-cookies nil nil nil)) - -(defun cvs-do-removal (filter &optional cmd all) - "Remove files. -Returns a list of FIS that should be `cvs remove'd." - (let* ((files (cvs-mode-marked filter cmd :file t :read-only t)) - (fis (cdr (cvs-partition (lambda (fi) - (eq (cvs-fileinfo->type fi) 'UNKNOWN)) - (cvs-mode-marked filter cmd)))) - (silent (or (not cvs-confirm-removals) - (cvs-every (lambda (fi) - (or (not (file-exists-p - (cvs-fileinfo->full-name fi))) - (cvs-applicable-p fi 'safe-rm))) - files))) - (tmpbuf (cvs-temp-buffer))) - (when (and (not silent) (equal cvs-confirm-removals 'list)) - (with-current-buffer tmpbuf - (let ((inhibit-read-only t)) - (cvs-insert-strings (mapcar 'cvs-fileinfo->full-name fis)) - (cvs-pop-to-buffer-same-frame (current-buffer)) - (shrink-window-if-larger-than-buffer)))) - (if (not (or silent - (unwind-protect - (yes-or-no-p - (let ((nfiles (length files)) - (verb (if (eq filter 'undo) "Undo" "Delete"))) - (if (= 1 nfiles) - (format "%s file: \"%s\" ? " - verb - (cvs-fileinfo->file (car files))) - (format "%s %d files? " - verb - nfiles)))) - (cvs-bury-buffer tmpbuf cvs-buffer)))) - (progn (message "Aborting") nil) - (dolist (fi files) - (let* ((type (cvs-fileinfo->type fi)) - (file (cvs-fileinfo->full-name fi))) - (when (or all (eq type 'UNKNOWN)) - (when (file-exists-p file) (delete-file file)) - (unless all (setf (cvs-fileinfo->type fi) 'DEAD) t)))) - fis))) - -(defun-cvs-mode (cvs-mode-remove . SIMPLE) (flags) - "Remove all marked files. -With prefix argument, prompt for cvs flags." - (interactive (list (cvs-flags-query 'cvs-remove-flags "cvs remove flags"))) - (let ((fis (cvs-do-removal 'remove))) - (if fis (cvs-mode-run "remove" (cons "-f" flags) fis) - (cvs-cleanup-collection cvs-cookies nil nil nil)))) - - -(defvar cvs-tag-name "") -(defun-cvs-mode (cvs-mode-tag . SIMPLE) (tag &optional flags) - "Run `cvs tag TAG' on all selected files. -With prefix argument, prompt for cvs flags. -By default this can only be used on directories. -Use \\[cvs-mode-force-command] or change `cvs-force-dir-tag' if you need -to use it on individual files." - (interactive - (list (setq cvs-tag-name - (cvs-query-read cvs-tag-name "Tag name: " cvs-qtypedesc-tag)) - (cvs-flags-query 'cvs-tag-flags "tag flags"))) - (cvs-mode-do "tag" (append flags (list tag)) - (when cvs-force-dir-tag 'tag))) - -(defun-cvs-mode (cvs-mode-untag . SIMPLE) (tag &optional flags) - "Run `cvs tag -d TAG' on all selected files. -With prefix argument, prompt for cvs flags." - (interactive - (list (setq cvs-tag-name - (cvs-query-read cvs-tag-name "Tag to delete: " cvs-qtypedesc-tag)) - (cvs-flags-query 'cvs-tag-flags "tag flags"))) - (cvs-mode-do "tag" (append '("-d") flags (list tag)) - (when cvs-force-dir-tag 'tag))) - - -;; Byte compile files. - -(defun-cvs-mode cvs-mode-byte-compile-files () - "Run byte-compile-file on all selected files that end in '.el'." - (interactive) - (let ((marked (cvs-get-marked (cvs-ignore-marks-p "byte-compile")))) - (dolist (fi marked) - (let ((filename (cvs-fileinfo->full-name fi))) - (when (string-match "\\.el\\'" filename) - (byte-compile-file filename)))))) - -;; ChangeLog support. - -(defun-cvs-mode cvs-mode-add-change-log-entry-other-window () - "Add a ChangeLog entry in the ChangeLog of the current directory." - (interactive) - ;; Require `add-log' explicitly, because if it gets autoloaded when we call - ;; add-change-log-entry-other-window below, the - ;; add-log-buffer-file-name-function ends up unbound when we leave the `let'. - (require 'add-log) - (dolist (fi (cvs-mode-marked nil nil)) - (let* ((default-directory (cvs-expand-dir-name (cvs-fileinfo->dir fi))) - (add-log-buffer-file-name-function - (lambda () - (let ((file (expand-file-name (cvs-fileinfo->file fi)))) - (if (file-directory-p file) - ;; Be careful to use a directory name, otherwise add-log - ;; starts looking for a ChangeLog file in the - ;; parent dir. - (file-name-as-directory file) - file))))) - (kill-local-variable 'change-log-default-name) - (save-excursion (add-change-log-entry-other-window))))) - -;; interactive commands to set optional flags - -(defun cvs-mode-set-flags (flag) - "Ask for new setting of cvs-FLAG-flags." - (interactive - (list (completing-read - "Which flag: " - '("cvs" "diff" "update" "status" "log" "tag" ;"rtag" - "commit" "remove" "undo" "checkout") - nil t))) - (let* ((sym (intern (concat "cvs-" flag "-flags")))) - (let ((current-prefix-arg '(16))) - (cvs-flags-query sym (concat flag " flags"))))) - - -;;;; -;;;; Utilities for the *cvs* buffer -;;;; - -(defun cvs-dir-member-p (fileinfo dir) - "Return true if FILEINFO represents a file in directory DIR." - (and (not (eq (cvs-fileinfo->type fileinfo) 'DIRCHANGE)) - (cvs-string-prefix-p dir (cvs-fileinfo->dir fileinfo)))) - -(defun cvs-execute-single-file (fi extractor program constant-args) - "Internal function for `cvs-execute-single-file-list'." - (let* ((arg-list (funcall extractor fi)) - (inhibit-read-only t)) - - ;; Execute the command unless extractor returned t. - (when (listp arg-list) - (let* ((args (append constant-args arg-list))) - - (insert (format "=== %s %s\n\n" - program (split-string-and-unquote args))) - - ;; FIXME: return the exit status? - (apply 'process-file program nil t t args) - (goto-char (point-max)))))) - -;; FIXME: make this run in the background ala cvs-run-process... -(defun cvs-execute-single-file-list (fis extractor program constant-args) - "Run PROGRAM on all elements on FIS. -CONSTANT-ARGS is a list of strings to pass as arguments to PROGRAM. -The arguments given to the program will be CONSTANT-ARGS followed by -the list that EXTRACTOR returns. - -EXTRACTOR will be called once for each file on FIS. It is given -one argument, the cvs-fileinfo. It can return t, which means ignore -this file, or a list of arguments to send to the program." - (dolist (fi fis) - (cvs-execute-single-file fi extractor program constant-args))) - - -(defun cvs-revert-if-needed (fis) - (dolist (fileinfo fis) - (let* ((file (cvs-fileinfo->full-name fileinfo)) - (buffer (find-buffer-visiting file))) - ;; For a revert to happen the user must be editing the file... - (unless (or (null buffer) - (memq (cvs-fileinfo->type fileinfo) '(MESSAGE UNKNOWN)) - ;; FIXME: check whether revert is really needed. - ;; `(verify-visited-file-modtime buffer)' doesn't cut it - ;; because it only looks at the time stamp (it ignores - ;; read-write changes) which is not changed by `commit'. - (buffer-modified-p buffer)) - (with-current-buffer buffer - (ignore-errors - (revert-buffer 'ignore-auto 'dont-ask 'preserve-modes) - ;; `preserve-modes' avoids changing the (minor) modes. But we - ;; do want to reset the mode for VC, so we do it explicitly. - (vc-find-file-hook) - (when (eq (cvs-fileinfo->type fileinfo) 'CONFLICT) - (smerge-start-session)))))))) - - -(defun cvs-change-cvsroot (newroot) - "Change the cvsroot." - (interactive "DNew repository: ") - (if (or (file-directory-p (expand-file-name "CVSROOT" newroot)) - (y-or-n-p (concat "Warning: no CVSROOT found inside repository." - " Change cvs-cvsroot anyhow? "))) - (setq cvs-cvsroot newroot))) - -;;;; -;;;; useful global settings -;;;; - -;; -;; Hook to allow calling PCL-CVS by visiting the /CVS subdirectory -;; - -;;;###autoload -(defcustom cvs-dired-action 'cvs-quickdir - "The action to be performed when opening a CVS directory. -Sensible values are `cvs-examine', `cvs-status' and `cvs-quickdir'." - :group 'pcl-cvs - :type '(choice (const cvs-examine) (const cvs-status) (const cvs-quickdir))) - -;;;###autoload -(defcustom cvs-dired-use-hook '(4) - "Whether or not opening a CVS directory should run PCL-CVS. -A value of nil means never do it. -ALWAYS means to always do it unless a prefix argument is given to the - command that prompted the opening of the directory. -Anything else means to do it only if the prefix arg is equal to this value." - :group 'pcl-cvs - :type '(choice (const :tag "Never" nil) - (const :tag "Always" always) - (const :tag "Prefix" (4)))) - -;;;###autoload -(progn (defun cvs-dired-noselect (dir) - "Run `cvs-examine' if DIR is a CVS administrative directory. -The exact behavior is determined also by `cvs-dired-use-hook'." - (when (stringp dir) - (setq dir (directory-file-name dir)) - (when (and (string= "CVS" (file-name-nondirectory dir)) - (file-readable-p (expand-file-name "Entries" dir)) - cvs-dired-use-hook - (if (eq cvs-dired-use-hook 'always) - (not current-prefix-arg) - (equal current-prefix-arg cvs-dired-use-hook))) - (save-excursion - (funcall cvs-dired-action (file-name-directory dir) t t)))))) - -;; -;; hook into VC -;; - -(add-hook 'vc-post-command-functions 'cvs-vc-command-advice) - -(defun cvs-vc-command-advice (command files flags) - (when (and (equal command "cvs") - (progn - (while (and (stringp (car flags)) - (string-match "\\`-" (car flags))) - (pop flags)) - ;; don't parse output we don't understand. - (member (car flags) cvs-parse-known-commands)) - ;; Don't parse "update -p" output. - (not (and (member (car flags) '("update" "checkout")) - (let ((found-p nil)) - (dolist (flag flags found-p) - (if (equal flag "-p") (setq found-p t))))))) - (save-current-buffer - (let ((buffer (current-buffer)) - (dir default-directory) - (cvs-from-vc t)) - (dolist (cvs-buf (buffer-list)) - (set-buffer cvs-buf) - ;; look for a corresponding pcl-cvs buffer - (when (and (eq major-mode 'cvs-mode) - (cvs-string-prefix-p default-directory dir)) - (let ((subdir (substring dir (length default-directory)))) - (set-buffer buffer) - (set (make-local-variable 'cvs-buffer) cvs-buf) - ;; `cvs -q add file' produces no useful output :-( - (when (and (equal (car flags) "add") - (goto-char (point-min)) - (looking-at ".*to add this file permanently\n\\'")) - (dolist (file (if (listp files) files (list files))) - (insert "cvs add: scheduling file `" - (file-name-nondirectory file) - "' for addition\n"))) - ;; VC never (?) does `cvs -n update' so dcd=nil - ;; should probably always be the right choice. - (cvs-parse-process nil subdir)))))))) - -;; -;; Hook into write-buffer -;; - -(defun cvs-mark-buffer-changed () - (let* ((file (expand-file-name buffer-file-name)) - (version (and (fboundp 'vc-backend) - (eq (vc-backend file) 'CVS) - (vc-working-revision file)))) - (when version - (save-excursion - (dolist (cvs-buf (buffer-list)) - (set-buffer cvs-buf) - ;; look for a corresponding pcl-cvs buffer - (when (and (eq major-mode 'cvs-mode) - (cvs-string-prefix-p default-directory file)) - (let* ((file (substring file (length default-directory))) - (fi (cvs-create-fileinfo - (if (string= "0" version) - 'ADDED 'MODIFIED) - (or (file-name-directory file) "") - (file-name-nondirectory file) - "cvs-mark-buffer-changed"))) - (cvs-addto-collection cvs-cookies fi)))))))) - -(add-hook 'after-save-hook 'cvs-mark-buffer-changed) - - -(provide 'pcvs) - -;; arch-tag: 8e3a7494-0453-4389-9ab3-a557ce9fab61 -;;; pcvs.el ends here diff -r 681cd08dc0f7 -r e93288477c43 lisp/shell.el --- a/lisp/shell.el Thu Jun 10 22:43:47 2010 +0000 +++ b/lisp/shell.el Sun Jun 13 22:57:55 2010 +0000 @@ -340,6 +340,7 @@ (define-key shell-mode-map "\C-c\C-f" 'shell-forward-command) (define-key shell-mode-map "\C-c\C-b" 'shell-backward-command) (define-key shell-mode-map "\t" 'comint-dynamic-complete) + (define-key shell-mode-map (kbd "M-RET") 'shell-resync-dirs) (define-key shell-mode-map "\M-?" 'comint-dynamic-list-filename-completions) (define-key shell-mode-map [menu-bar completion] diff -r 681cd08dc0f7 -r e93288477c43 lisp/smerge-mode.el --- a/lisp/smerge-mode.el Thu Jun 10 22:43:47 2010 +0000 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,1231 +0,0 @@ -;;; smerge-mode.el --- Minor mode to resolve diff3 conflicts - -;; Copyright (C) 1999, 2000, 2001, 2002, 2003, 2004, 2005, 2006, -;; 2007, 2008, 2009, 2010 Free Software Foundation, Inc. - -;; Author: Stefan Monnier -;; Keywords: tools revision-control merge diff3 cvs conflict - -;; 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 3 of the License, 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. If not, see . - -;;; Commentary: - -;; Provides a lightweight alternative to emerge/ediff. -;; To use it, simply add to your .emacs the following lines: -;; -;; (autoload 'smerge-mode "smerge-mode" nil t) -;; -;; you can even have it turned on automatically with the following -;; piece of code in your .emacs: -;; -;; (defun sm-try-smerge () -;; (save-excursion -;; (goto-char (point-min)) -;; (when (re-search-forward "^<<<<<<< " nil t) -;; (smerge-mode 1)))) -;; (add-hook 'find-file-hook 'sm-try-smerge t) - -;;; Todo: - -;; - if requested, ask the user whether he wants to call ediff right away - -;;; Code: - -(eval-when-compile (require 'cl)) -(require 'diff-mode) ;For diff-auto-refine-mode. - - -;;; The real definition comes later. -(defvar smerge-mode) - -(defgroup smerge () - "Minor mode to highlight and resolve diff3 conflicts." - :group 'tools - :prefix "smerge-") - -(defcustom smerge-diff-buffer-name "*vc-diff*" - "Buffer name to use for displaying diffs." - :group 'smerge - :type '(choice - (const "*vc-diff*") - (const "*cvs-diff*") - (const "*smerge-diff*") - string)) - -(defcustom smerge-diff-switches - (append '("-d" "-b") - (if (listp diff-switches) diff-switches (list diff-switches))) - "A list of strings specifying switches to be passed to diff. -Used in `smerge-diff-base-mine' and related functions." - :group 'smerge - :type '(repeat string)) - -(defcustom smerge-auto-leave t - "Non-nil means to leave `smerge-mode' when the last conflict is resolved." - :group 'smerge - :type 'boolean) - -(defface smerge-mine - '((((min-colors 88) (background light)) - (:foreground "blue1")) - (((background light)) - (:foreground "blue")) - (((min-colors 88) (background dark)) - (:foreground "cyan1")) - (((background dark)) - (:foreground "cyan"))) - "Face for your code." - :group 'smerge) -(define-obsolete-face-alias 'smerge-mine-face 'smerge-mine "22.1") -(defvar smerge-mine-face 'smerge-mine) - -(defface smerge-other - '((((background light)) - (:foreground "darkgreen")) - (((background dark)) - (:foreground "lightgreen"))) - "Face for the other code." - :group 'smerge) -(define-obsolete-face-alias 'smerge-other-face 'smerge-other "22.1") -(defvar smerge-other-face 'smerge-other) - -(defface smerge-base - '((((min-colors 88) (background light)) - (:foreground "red1")) - (((background light)) - (:foreground "red")) - (((background dark)) - (:foreground "orange"))) - "Face for the base code." - :group 'smerge) -(define-obsolete-face-alias 'smerge-base-face 'smerge-base "22.1") -(defvar smerge-base-face 'smerge-base) - -(defface smerge-markers - '((((background light)) - (:background "grey85")) - (((background dark)) - (:background "grey30"))) - "Face for the conflict markers." - :group 'smerge) -(define-obsolete-face-alias 'smerge-markers-face 'smerge-markers "22.1") -(defvar smerge-markers-face 'smerge-markers) - -(defface smerge-refined-change - '((t :background "yellow")) - "Face used for char-based changes shown by `smerge-refine'." - :group 'smerge) - -(easy-mmode-defmap smerge-basic-map - `(("n" . smerge-next) - ("p" . smerge-prev) - ("r" . smerge-resolve) - ("a" . smerge-keep-all) - ("b" . smerge-keep-base) - ("o" . smerge-keep-other) - ("m" . smerge-keep-mine) - ("E" . smerge-ediff) - ("C" . smerge-combine-with-next) - ("R" . smerge-refine) - ("\C-m" . smerge-keep-current) - ("=" . ,(make-sparse-keymap "Diff")) - ("=<" "base-mine" . smerge-diff-base-mine) - ("=>" "base-other" . smerge-diff-base-other) - ("==" "mine-other" . smerge-diff-mine-other)) - "The base keymap for `smerge-mode'.") - -(defcustom smerge-command-prefix "\C-c^" - "Prefix for `smerge-mode' commands." - :group 'smerge - :type '(choice (const :tag "ESC" "\e") - (const :tag "C-c ^" "\C-c^" ) - (const :tag "none" "") - string)) - -(easy-mmode-defmap smerge-mode-map - `((,smerge-command-prefix . ,smerge-basic-map)) - "Keymap for `smerge-mode'.") - -(defvar smerge-check-cache nil) -(make-variable-buffer-local 'smerge-check-cache) -(defun smerge-check (n) - (condition-case nil - (let ((state (cons (point) (buffer-modified-tick)))) - (unless (equal (cdr smerge-check-cache) state) - (smerge-match-conflict) - (setq smerge-check-cache (cons (match-data) state))) - (nth (* 2 n) (car smerge-check-cache))) - (error nil))) - -(easy-menu-define smerge-mode-menu smerge-mode-map - "Menu for `smerge-mode'." - '("SMerge" - ["Next" smerge-next :help "Go to next conflict"] - ["Previous" smerge-prev :help "Go to previous conflict"] - "--" - ["Keep All" smerge-keep-all :help "Keep all three versions" - :active (smerge-check 1)] - ["Keep Current" smerge-keep-current :help "Use current (at point) version" - :active (and (smerge-check 1) (> (smerge-get-current) 0))] - "--" - ["Revert to Base" smerge-keep-base :help "Revert to base version" - :active (smerge-check 2)] - ["Keep Other" smerge-keep-other :help "Keep `other' version" - :active (smerge-check 3)] - ["Keep Yours" smerge-keep-mine :help "Keep your version" - :active (smerge-check 1)] - "--" - ["Diff Base/Mine" smerge-diff-base-mine - :help "Diff `base' and `mine' for current conflict" - :active (smerge-check 2)] - ["Diff Base/Other" smerge-diff-base-other - :help "Diff `base' and `other' for current conflict" - :active (smerge-check 2)] - ["Diff Mine/Other" smerge-diff-mine-other - :help "Diff `mine' and `other' for current conflict" - :active (smerge-check 1)] - "--" - ["Invoke Ediff" smerge-ediff - :help "Use Ediff to resolve the conflicts" - :active (smerge-check 1)] - ["Auto Resolve" smerge-resolve - :help "Try auto-resolution heuristics" - :active (smerge-check 1)] - ["Combine" smerge-combine-with-next - :help "Combine current conflict with next" - :active (smerge-check 1)] - )) - -(easy-menu-define smerge-context-menu nil - "Context menu for mine area in `smerge-mode'." - '(nil - ["Keep Current" smerge-keep-current :help "Use current (at point) version"] - ["Kill Current" smerge-kill-current :help "Remove current (at point) version"] - ["Keep All" smerge-keep-all :help "Keep all three versions"] - "---" - ["More..." (popup-menu smerge-mode-menu) :help "Show full SMerge mode menu"] - )) - -(defconst smerge-font-lock-keywords - '((smerge-find-conflict - (1 smerge-mine-face prepend t) - (2 smerge-base-face prepend t) - (3 smerge-other-face prepend t) - ;; FIXME: `keep' doesn't work right with syntactic fontification. - (0 smerge-markers-face keep) - (4 nil t t) - (5 nil t t))) - "Font lock patterns for `smerge-mode'.") - -(defconst smerge-begin-re "^<<<<<<< \\(.*\\)\n") -(defconst smerge-end-re "^>>>>>>> .*\n") -(defconst smerge-base-re "^||||||| .*\n") -(defconst smerge-other-re "^=======\n") - -(defvar smerge-conflict-style nil - "Keep track of which style of conflict is in use. -Can be nil if the style is undecided, or else: -- `diff3-E' -- `diff3-A'") - -;; Compiler pacifiers -(defvar font-lock-mode) -(defvar font-lock-keywords) - -;;;; -;;;; Actual code -;;;; - -;; Define smerge-next and smerge-prev -(easy-mmode-define-navigation smerge smerge-begin-re "conflict" nil nil - (if diff-auto-refine-mode - (condition-case nil (smerge-refine) (error nil)))) - -(defconst smerge-match-names ["conflict" "mine" "base" "other"]) - -(defun smerge-ensure-match (n) - (unless (match-end n) - (error "No `%s'" (aref smerge-match-names n)))) - -(defun smerge-auto-leave () - (when (and smerge-auto-leave - (save-excursion (goto-char (point-min)) - (not (re-search-forward smerge-begin-re nil t)))) - (when (and (listp buffer-undo-list) smerge-mode) - (push (list 'apply 'smerge-mode 1) buffer-undo-list)) - (smerge-mode -1))) - - -(defun smerge-keep-all () - "Concatenate all versions." - (interactive) - (smerge-match-conflict) - (let ((mb2 (or (match-beginning 2) (point-max))) - (me2 (or (match-end 2) (point-min)))) - (delete-region (match-end 3) (match-end 0)) - (delete-region (max me2 (match-end 1)) (match-beginning 3)) - (if (and (match-end 2) (/= (match-end 1) (match-end 3))) - (delete-region (match-end 1) (match-beginning 2))) - (delete-region (match-beginning 0) (min (match-beginning 1) mb2)) - (smerge-auto-leave))) - -(defun smerge-keep-n (n) - (smerge-remove-props (match-beginning 0) (match-end 0)) - ;; We used to use replace-match, but that did not preserve markers so well. - (delete-region (match-end n) (match-end 0)) - (delete-region (match-beginning 0) (match-beginning n))) - -(defun smerge-combine-with-next () - "Combine the current conflict with the next one." - ;; `smerge-auto-combine' relies on the finish position (at the beginning - ;; of the closing marker). - (interactive) - (smerge-match-conflict) - (let ((ends nil)) - (dolist (i '(3 2 1 0)) - (push (if (match-end i) (copy-marker (match-end i) t)) ends)) - (setq ends (apply 'vector ends)) - (goto-char (aref ends 0)) - (if (not (re-search-forward smerge-begin-re nil t)) - (error "No next conflict") - (smerge-match-conflict) - (let ((match-data (mapcar (lambda (m) (if m (copy-marker m))) - (match-data)))) - ;; First copy the in-between text in each alternative. - (dolist (i '(1 2 3)) - (when (aref ends i) - (goto-char (aref ends i)) - (insert-buffer-substring (current-buffer) - (aref ends 0) (car match-data)))) - (delete-region (aref ends 0) (car match-data)) - ;; Then move the second conflict's alternatives into the first. - (dolist (i '(1 2 3)) - (set-match-data match-data) - (when (and (aref ends i) (match-end i)) - (goto-char (aref ends i)) - (insert-buffer-substring (current-buffer) - (match-beginning i) (match-end i)))) - (delete-region (car match-data) (cadr match-data)) - ;; Free the markers. - (dolist (m match-data) (if m (move-marker m nil))) - (mapc (lambda (m) (if m (move-marker m nil))) ends))))) - -(defvar smerge-auto-combine-max-separation 2 - "Max number of lines between conflicts that should be combined.") - -(defun smerge-auto-combine () - "Automatically combine conflicts that are near each other." - (interactive) - (save-excursion - (goto-char (point-min)) - (while (smerge-find-conflict) - ;; 2 is 1 (default) + 1 (the begin markers). - (while (save-excursion - (smerge-find-conflict - (line-beginning-position - (+ 2 smerge-auto-combine-max-separation)))) - (forward-line -1) ;Go back inside the conflict. - (smerge-combine-with-next) - (forward-line 1) ;Move past the end of the conflict. - )))) - -(defvar smerge-resolve-function - (lambda () (error "Don't know how to resolve")) - "Mode-specific merge function. -The function is called with zero or one argument (non-nil if the resolution -function should only apply safe heuristics) and with the match data set -according to `smerge-match-conflict'.") -(add-to-list 'debug-ignored-errors "Don't know how to resolve") - -(defvar smerge-text-properties - `(help-echo "merge conflict: mouse-3 shows a menu" - ;; mouse-face highlight - keymap (keymap (down-mouse-3 . smerge-popup-context-menu)))) - -(defun smerge-remove-props (beg end) - (remove-overlays beg end 'smerge 'refine) - (remove-overlays beg end 'smerge 'conflict) - ;; Now that we use overlays rather than text-properties, this function - ;; does not cause refontification any more. It can be seen very clearly - ;; in buffers where jit-lock-contextually is not t, in which case deleting - ;; the "<<<<<<< foobar" leading line leaves the rest of the conflict - ;; highlighted as if it were still a valid conflict. Note that in many - ;; important cases (such as the previous example) we're actually called - ;; during font-locking so inhibit-modification-hooks is non-nil, so we - ;; can't just modify the buffer and expect font-lock to be triggered as in: - ;; (put-text-property beg end 'smerge-force-highlighting nil) - (with-silent-modifications - (remove-text-properties beg end '(fontified nil)))) - -(defun smerge-popup-context-menu (event) - "Pop up the Smerge mode context menu under mouse." - (interactive "e") - (if (and smerge-mode - (save-excursion (posn-set-point (event-end event)) (smerge-check 1))) - (progn - (posn-set-point (event-end event)) - (smerge-match-conflict) - (let ((i (smerge-get-current)) - o) - (if (<= i 0) - ;; Out of range - (popup-menu smerge-mode-menu) - ;; Install overlay. - (setq o (make-overlay (match-beginning i) (match-end i))) - (unwind-protect - (progn - (overlay-put o 'face 'highlight) - (sit-for 0) ;Display the new highlighting. - (popup-menu smerge-context-menu)) - ;; Delete overlay. - (delete-overlay o))))) - ;; There's no conflict at point, the text-props are just obsolete. - (save-excursion - (let ((beg (re-search-backward smerge-end-re nil t)) - (end (re-search-forward smerge-begin-re nil t))) - (smerge-remove-props (or beg (point-min)) (or end (point-max))) - (push event unread-command-events))))) - -(defun smerge-apply-resolution-patch (buf m0b m0e m3b m3e &optional m2b) - "Replace the conflict with a bunch of subconflicts. -BUF contains a plain diff between match-1 and match-3." - (let ((line 1) - (textbuf (current-buffer)) - (name1 (progn (goto-char m0b) - (buffer-substring (+ (point) 8) (line-end-position)))) - (name2 (when m2b (goto-char m2b) (forward-line -1) - (buffer-substring (+ (point) 8) (line-end-position)))) - (name3 (progn (goto-char m0e) (forward-line -1) - (buffer-substring (+ (point) 8) (line-end-position))))) - (smerge-remove-props m0b m0e) - (delete-region m3e m0e) - (delete-region m0b m3b) - (setq m3b m0b) - (setq m3e (- m3e (- m3b m0b))) - (goto-char m3b) - (with-current-buffer buf - (goto-char (point-min)) - (while (not (eobp)) - (if (not (looking-at "\\([0-9]+\\)\\(?:,\\([0-9]+\\)\\)?\\([acd]\\)\\([0-9]+\\)\\(?:,\\([0-9]+\\)\\)?$")) - (error "Unexpected patch hunk header: %s" - (buffer-substring (point) (line-end-position))) - (let* ((op (char-after (match-beginning 3))) - (startline (+ (string-to-number (match-string 1)) - ;; No clue why this is the way it is, but line - ;; numbers seem to be off-by-one for `a' ops. - (if (eq op ?a) 1 0))) - (endline (if (eq op ?a) startline - (1+ (if (match-end 2) - (string-to-number (match-string 2)) - startline)))) - (lines (- endline startline)) - (otherlines (cond - ((eq op ?d) nil) - ((null (match-end 5)) 1) - (t (- (string-to-number (match-string 5)) - (string-to-number (match-string 4)) -1)))) - othertext) - (forward-line 1) ;Skip header. - (forward-line lines) ;Skip deleted text. - (if (eq op ?c) (forward-line 1)) ;Skip separator. - (setq othertext - (if (null otherlines) "" - (let ((pos (point))) - (dotimes (i otherlines) (delete-char 2) (forward-line 1)) - (buffer-substring pos (point))))) - (with-current-buffer textbuf - (forward-line (- startline line)) - (insert "<<<<<<< " name1 "\n" othertext - (if name2 (concat "||||||| " name2 "\n") "") - "=======\n") - (forward-line lines) - (insert ">>>>>>> " name3 "\n") - (setq line endline)))))))) - -(defun smerge-resolve (&optional safe) - "Resolve the conflict at point intelligently. -This relies on mode-specific knowledge and thus only works in some -major modes. Uses `smerge-resolve-function' to do the actual work." - (interactive) - (smerge-match-conflict) - (smerge-remove-props (match-beginning 0) (match-end 0)) - (let ((md (match-data)) - (m0b (match-beginning 0)) - (m1b (match-beginning 1)) - (m2b (match-beginning 2)) - (m3b (match-beginning 3)) - (m0e (match-end 0)) - (m1e (match-end 1)) - (m2e (match-end 2)) - (m3e (match-end 3)) - (buf (generate-new-buffer " *smerge*")) - m b o) - (unwind-protect - (progn - (cond - ;; Trivial diff3 -A non-conflicts. - ((and (eq (match-end 1) (match-end 3)) - (eq (match-beginning 1) (match-beginning 3))) - (smerge-keep-n 3)) - ;; Mode-specific conflict resolution. - ((condition-case nil - (atomic-change-group - (if safe - (funcall smerge-resolve-function safe) - (funcall smerge-resolve-function)) - t) - (error nil)) - ;; Nothing to do: the resolution function has done it already. - nil) - ;; Non-conflict. - ((and (eq m1e m3e) (eq m1b m3b)) - (set-match-data md) (smerge-keep-n 3)) - ;; Refine a 2-way conflict using "diff -b". - ;; In case of a 3-way conflict with an empty base - ;; (i.e. 2 conflicting additions), we do the same, presuming - ;; that the 2 additions should be somehow merged rather - ;; than concatenated. - ((let ((lines (count-lines m3b m3e))) - (setq m (make-temp-file "smm")) - (write-region m1b m1e m nil 'silent) - (setq o (make-temp-file "smo")) - (write-region m3b m3e o nil 'silent) - (not (or (eq m1b m1e) (eq m3b m3e) - (and (not (zerop (call-process diff-command - nil buf nil "-b" o m))) - ;; TODO: We don't know how to do the refinement - ;; if there's a non-empty ancestor and m1 and m3 - ;; aren't just plain equal. - m2b (not (eq m2b m2e))) - (with-current-buffer buf - (goto-char (point-min)) - ;; Make sure there's some refinement. - (looking-at - (concat "1," (number-to-string lines) "c")))))) - (smerge-apply-resolution-patch buf m0b m0e m3b m3e m2b)) - ;; "Mere whitespace changes" conflicts. - ((when m2e - (setq b (make-temp-file "smb")) - (write-region m2b m2e b nil 'silent) - (with-current-buffer buf (erase-buffer)) - ;; Only minor whitespace changes made locally. - ;; BEWARE: pass "-c" 'cause the output is reused in the next test. - (zerop (call-process diff-command nil buf nil "-bc" b m))) - (set-match-data md) - (smerge-keep-n 3)) - ;; Try "diff -b BASE MINE | patch OTHER". - ((when (and (not safe) m2e b - ;; If the BASE is empty, this would just concatenate - ;; the two, which is rarely right. - (not (eq m2b m2e))) - ;; BEWARE: we're using here the patch of the previous test. - (with-current-buffer buf - (zerop (call-process-region - (point-min) (point-max) "patch" t nil nil - "-r" "/dev/null" "--no-backup-if-mismatch" - "-fl" o)))) - (save-restriction - (narrow-to-region m0b m0e) - (smerge-remove-props m0b m0e) - (insert-file-contents o nil nil nil t))) - ;; Try "diff -b BASE OTHER | patch MINE". - ((when (and (not safe) m2e b - ;; If the BASE is empty, this would just concatenate - ;; the two, which is rarely right. - (not (eq m2b m2e))) - (write-region m3b m3e o nil 'silent) - (call-process diff-command nil buf nil "-bc" b o) - (with-current-buffer buf - (zerop (call-process-region - (point-min) (point-max) "patch" t nil nil - "-r" "/dev/null" "--no-backup-if-mismatch" - "-fl" m)))) - (save-restriction - (narrow-to-region m0b m0e) - (smerge-remove-props m0b m0e) - (insert-file-contents m nil nil nil t))) - (t - (error "Don't know how to resolve")))) - (if (buffer-name buf) (kill-buffer buf)) - (if m (delete-file m)) - (if b (delete-file b)) - (if o (delete-file o)))) - (smerge-auto-leave)) - -(defun smerge-resolve-all () - "Perform automatic resolution on all conflicts." - (interactive) - (save-excursion - (goto-char (point-min)) - (while (re-search-forward smerge-begin-re nil t) - (condition-case nil - (progn - (smerge-match-conflict) - (smerge-resolve 'safe)) - (error nil))))) - -(defun smerge-batch-resolve () - ;; command-line-args-left is what is left of the command line. - (if (not noninteractive) - (error "`smerge-batch-resolve' is to be used only with -batch")) - (while command-line-args-left - (let ((file (pop command-line-args-left))) - (if (string-match "\\.rej\\'" file) - ;; .rej files should never contain diff3 markers, on the other hand, - ;; in Arch, .rej files are sometimes used to indicate that the - ;; main file has diff3 markers. So you can pass **/*.rej and - ;; it will DTRT. - (setq file (substring file 0 (match-beginning 0)))) - (message "Resolving conflicts in %s..." file) - (when (file-readable-p file) - (with-current-buffer (find-file-noselect file) - (smerge-resolve-all) - (save-buffer) - (kill-buffer (current-buffer))))))) - -(defun smerge-keep-base () - "Revert to the base version." - (interactive) - (smerge-match-conflict) - (smerge-ensure-match 2) - (smerge-keep-n 2) - (smerge-auto-leave)) - -(defun smerge-keep-other () - "Use \"other\" version." - (interactive) - (smerge-match-conflict) - ;;(smerge-ensure-match 3) - (smerge-keep-n 3) - (smerge-auto-leave)) - -(defun smerge-keep-mine () - "Keep your version." - (interactive) - (smerge-match-conflict) - ;;(smerge-ensure-match 1) - (smerge-keep-n 1) - (smerge-auto-leave)) - -(defun smerge-get-current () - (let ((i 3)) - (while (or (not (match-end i)) - (< (point) (match-beginning i)) - (>= (point) (match-end i))) - (decf i)) - i)) - -(defun smerge-keep-current () - "Use the current (under the cursor) version." - (interactive) - (smerge-match-conflict) - (let ((i (smerge-get-current))) - (if (<= i 0) (error "Not inside a version") - (smerge-keep-n i) - (smerge-auto-leave)))) - -(defun smerge-kill-current () - "Remove the current (under the cursor) version." - (interactive) - (smerge-match-conflict) - (let ((i (smerge-get-current))) - (if (<= i 0) (error "Not inside a version") - (let ((left nil)) - (dolist (n '(3 2 1)) - (if (and (match-end n) (/= (match-end n) (match-end i))) - (push n left))) - (if (and (cdr left) - (/= (match-end (car left)) (match-end (cadr left)))) - (ding) ;We don't know how to do that. - (smerge-keep-n (car left)) - (smerge-auto-leave)))))) - -(defun smerge-diff-base-mine () - "Diff 'base' and 'mine' version in current conflict region." - (interactive) - (smerge-diff 2 1)) - -(defun smerge-diff-base-other () - "Diff 'base' and 'other' version in current conflict region." - (interactive) - (smerge-diff 2 3)) - -(defun smerge-diff-mine-other () - "Diff 'mine' and 'other' version in current conflict region." - (interactive) - (smerge-diff 1 3)) - -(defun smerge-match-conflict () - "Get info about the conflict. Puts the info in the `match-data'. -The submatches contain: - 0: the whole conflict. - 1: your code. - 2: the base code. - 3: other code. -An error is raised if not inside a conflict." - (save-excursion - (condition-case nil - (let* ((orig-point (point)) - - (_ (forward-line 1)) - (_ (re-search-backward smerge-begin-re)) - - (start (match-beginning 0)) - (mine-start (match-end 0)) - (filename (or (match-string 1) "")) - - (_ (re-search-forward smerge-end-re)) - (_ (assert (< orig-point (match-end 0)))) - - (other-end (match-beginning 0)) - (end (match-end 0)) - - (_ (re-search-backward smerge-other-re start)) - - (mine-end (match-beginning 0)) - (other-start (match-end 0)) - - base-start base-end) - - ;; handle the various conflict styles - (cond - ((save-excursion - (goto-char mine-start) - (re-search-forward smerge-begin-re end t)) - ;; There's a nested conflict and we're after the beginning - ;; of the outer one but before the beginning of the inner one. - ;; Of course, maybe this is not a nested conflict but in that - ;; case it can only be something nastier that we don't know how - ;; to handle, so may as well arbitrarily decide to treat it as - ;; a nested conflict. --Stef - (error "There is a nested conflict")) - - ((re-search-backward smerge-base-re start t) - ;; a 3-parts conflict - (set (make-local-variable 'smerge-conflict-style) 'diff3-A) - (setq base-end mine-end) - (setq mine-end (match-beginning 0)) - (setq base-start (match-end 0))) - - ((string= filename (file-name-nondirectory - (or buffer-file-name ""))) - ;; a 2-parts conflict - (set (make-local-variable 'smerge-conflict-style) 'diff3-E)) - - ((and (not base-start) - (or (eq smerge-conflict-style 'diff3-A) - (equal filename "ANCESTOR") - (string-match "\\`[.0-9]+\\'" filename))) - ;; a same-diff conflict - (setq base-start mine-start) - (setq base-end mine-end) - (setq mine-start other-start) - (setq mine-end other-end))) - - (store-match-data (list start end - mine-start mine-end - base-start base-end - other-start other-end - (when base-start (1- base-start)) base-start - (1- other-start) other-start)) - t) - (search-failed (error "Point not in conflict region"))))) - -(add-to-list 'debug-ignored-errors "Point not in conflict region") - -(defun smerge-conflict-overlay (pos) - "Return the conflict overlay at POS if any." - (let ((ols (overlays-at pos)) - conflict) - (dolist (ol ols) - (if (and (eq (overlay-get ol 'smerge) 'conflict) - (> (overlay-end ol) pos)) - (setq conflict ol))) - conflict)) - -(defun smerge-find-conflict (&optional limit) - "Find and match a conflict region. Intended as a font-lock MATCHER. -The submatches are the same as in `smerge-match-conflict'. -Returns non-nil if a match is found between point and LIMIT. -Point is moved to the end of the conflict." - (let ((found nil) - (pos (point)) - conflict) - ;; First check to see if point is already inside a conflict, using - ;; the conflict overlays. - (while (and (not found) (setq conflict (smerge-conflict-overlay pos))) - ;; Check the overlay's validity and kill it if it's out of date. - (condition-case nil - (progn - (goto-char (overlay-start conflict)) - (smerge-match-conflict) - (goto-char (match-end 0)) - (if (<= (point) pos) - (error "Matching backward!") - (setq found t))) - (error (smerge-remove-props - (overlay-start conflict) (overlay-end conflict)) - (goto-char pos)))) - ;; If we're not already inside a conflict, look for the next conflict - ;; and add/update its overlay. - (while (and (not found) (re-search-forward smerge-begin-re limit t)) - (condition-case nil - (progn - (smerge-match-conflict) - (goto-char (match-end 0)) - (let ((conflict (smerge-conflict-overlay (1- (point))))) - (if conflict - ;; Update its location, just in case it got messed up. - (move-overlay conflict (match-beginning 0) (match-end 0)) - (setq conflict (make-overlay (match-beginning 0) (match-end 0) - nil 'front-advance nil)) - (overlay-put conflict 'evaporate t) - (overlay-put conflict 'smerge 'conflict) - (let ((props smerge-text-properties)) - (while props - (overlay-put conflict (pop props) (pop props)))))) - (setq found t)) - (error nil))) - found)) - -;;; Refined change highlighting - -(defvar smerge-refine-forward-function 'smerge-refine-forward - "Function used to determine an \"atomic\" element. -You can set it to `forward-char' to get char-level granularity. -Its behavior has mainly two restrictions: -- if this function encounters a newline, it's important that it stops right - after the newline. - This only matters if `smerge-refine-ignore-whitespace' is nil. -- it needs to be unaffected by changes performed by the `preproc' argument - to `smerge-refine-subst'. - This only matters if `smerge-refine-weight-hack' is nil.") - -(defvar smerge-refine-ignore-whitespace t - "If non-nil, indicate that `smerge-refine' should try to ignore change in whitespace.") - -(defvar smerge-refine-weight-hack t - "If non-nil, pass to diff as many lines as there are chars in the region. -I.e. each atomic element (e.g. word) will be copied as many times (on different -lines) as it has chars. This has two advantages: -- if `diff' tries to minimize the number *lines* (rather than chars) - added/removed, this adjust the weights so that adding/removing long - symbols is considered correspondingly more costly. -- `smerge-refine-forward-function' only needs to be called when chopping up - the regions, and `forward-char' can be used afterwards. -It has the following disadvantages: -- cannot use `diff -w' because the weighting causes added spaces in a line - to be represented as added copies of some line, so `diff -w' can't do the - right thing any more. -- may in degenerate cases take a 1KB input region and turn it into a 1MB - file to pass to diff.") - -(defun smerge-refine-forward (n) - (let ((case-fold-search nil) - (re "[[:upper:]]?[[:lower:]]+\\|[[:upper:]]+\\|[[:digit:]]+\\|.\\|\n")) - (when (and smerge-refine-ignore-whitespace - ;; smerge-refine-weight-hack causes additional spaces to - ;; appear as additional lines as well, so even if diff ignore - ;; whitespace changes, it'll report added/removed lines :-( - (not smerge-refine-weight-hack)) - (setq re (concat "[ \t]*\\(?:" re "\\)"))) - (dotimes (i n) - (unless (looking-at re) (error "Smerge refine internal error")) - (goto-char (match-end 0))))) - -(defun smerge-refine-chopup-region (beg end file &optional preproc) - "Chopup the region into small elements, one per line. -Save the result into FILE. -If non-nil, PREPROC is called with no argument in a buffer that contains -a copy of the text, just before chopping it up. It can be used to replace -chars to try and eliminate some spurious differences." - ;; We used to chop up char-by-char rather than word-by-word like ediff - ;; does. It had the benefit of simplicity and very fine results, but it - ;; often suffered from problem that diff would find correlations where - ;; there aren't any, so the resulting "change" didn't make much sense. - ;; You can still get this behavior by setting - ;; `smerge-refine-forward-function' to `forward-char'. - (let ((buf (current-buffer))) - (with-temp-buffer - (insert-buffer-substring buf beg end) - (when preproc (goto-char (point-min)) (funcall preproc)) - (when smerge-refine-ignore-whitespace - ;; It doesn't make much of a difference for diff-fine-highlight - ;; because we still have the _/+//! prefix anyway. Can still be - ;; useful in other circumstances. - (subst-char-in-region (point-min) (point-max) ?\n ?\s)) - (goto-char (point-min)) - (while (not (eobp)) - (funcall smerge-refine-forward-function 1) - (let ((s (if (prog2 (forward-char -1) (bolp) (forward-char 1)) - nil - (buffer-substring (line-beginning-position) (point))))) - ;; We add \n after each char except after \n, so we get - ;; one line per text char, where each line contains - ;; just one char, except for \n chars which are - ;; represented by the empty line. - (unless (eq (char-before) ?\n) (insert ?\n)) - ;; HACK ALERT!! - (if smerge-refine-weight-hack - (dotimes (i (1- (length s))) (insert s "\n"))))) - (unless (bolp) (error "Smerge refine internal error")) - (let ((coding-system-for-write 'emacs-mule)) - (write-region (point-min) (point-max) file nil 'nomessage))))) - -(defun smerge-refine-highlight-change (buf beg match-num1 match-num2 props) - (with-current-buffer buf - (goto-char beg) - (let* ((startline (- (string-to-number match-num1) 1)) - (beg (progn (funcall (if smerge-refine-weight-hack - 'forward-char - smerge-refine-forward-function) - startline) - (point))) - (end (progn (funcall (if smerge-refine-weight-hack - 'forward-char - smerge-refine-forward-function) - (if match-num2 - (- (string-to-number match-num2) - startline) - 1)) - (point)))) - (when smerge-refine-ignore-whitespace - (skip-chars-backward " \t\n" beg) (setq end (point)) - (goto-char beg) - (skip-chars-forward " \t\n" end) (setq beg (point))) - (when (> end beg) - (let ((ol (make-overlay - beg end nil - ;; Make them tend to shrink rather than spread when editing. - 'front-advance nil))) - (overlay-put ol 'evaporate t) - (dolist (x props) (overlay-put ol (car x) (cdr x))) - ol))))) - -(defun smerge-refine-subst (beg1 end1 beg2 end2 props &optional preproc) - "Show fine differences in the two regions BEG1..END1 and BEG2..END2. -PROPS is an alist of properties to put (via overlays) on the changes. -If non-nil, PREPROC is called with no argument in a buffer that contains -a copy of a region, just before preparing it to for `diff'. It can be -used to replace chars to try and eliminate some spurious differences." - (let* ((buf (current-buffer)) - (pos (point)) - (file1 (make-temp-file "diff1")) - (file2 (make-temp-file "diff2"))) - ;; Chop up regions into smaller elements and save into files. - (smerge-refine-chopup-region beg1 end1 file1 preproc) - (smerge-refine-chopup-region beg2 end2 file2 preproc) - - ;; Call diff on those files. - (unwind-protect - (with-temp-buffer - (let ((coding-system-for-read 'emacs-mule)) - (call-process diff-command nil t nil - (if (and smerge-refine-ignore-whitespace - (not smerge-refine-weight-hack)) - ;; Pass -a so diff treats it as a text file even - ;; if it contains \0 and such. - ;; Pass -d so as to get the smallest change, but - ;; also and more importantly because otherwise it - ;; may happen that diff doesn't behave like - ;; smerge-refine-weight-hack expects it to. - ;; See http://thread.gmane.org/gmane.emacs.devel/82685. - "-awd" "-ad") - file1 file2)) - ;; Process diff's output. - (goto-char (point-min)) - (let ((last1 nil) - (last2 nil)) - (while (not (eobp)) - (if (not (looking-at "\\([0-9]+\\)\\(?:,\\([0-9]+\\)\\)?\\([acd]\\)\\([0-9]+\\)\\(?:,\\([0-9]+\\)\\)?$")) - (error "Unexpected patch hunk header: %s" - (buffer-substring (point) (line-end-position)))) - (let ((op (char-after (match-beginning 3))) - (m1 (match-string 1)) - (m2 (match-string 2)) - (m4 (match-string 4)) - (m5 (match-string 5))) - (when (memq op '(?d ?c)) - (setq last1 - (smerge-refine-highlight-change buf beg1 m1 m2 props))) - (when (memq op '(?a ?c)) - (setq last2 - (smerge-refine-highlight-change buf beg2 m4 m5 props)))) - (forward-line 1) ;Skip hunk header. - (and (re-search-forward "^[0-9]" nil 'move) ;Skip hunk body. - (goto-char (match-beginning 0)))) - ;; (assert (or (null last1) (< (overlay-start last1) end1))) - ;; (assert (or (null last2) (< (overlay-start last2) end2))) - (if smerge-refine-weight-hack - (progn - ;; (assert (or (null last1) (<= (overlay-end last1) end1))) - ;; (assert (or (null last2) (<= (overlay-end last2) end2))) - ) - ;; smerge-refine-forward-function when calling in chopup may - ;; have stopped because it bumped into EOB whereas in - ;; smerge-refine-weight-hack it may go a bit further. - (if (and last1 (> (overlay-end last1) end1)) - (move-overlay last1 (overlay-start last1) end1)) - (if (and last2 (> (overlay-end last2) end2)) - (move-overlay last2 (overlay-start last2) end2)) - ))) - (goto-char pos) - (delete-file file1) - (delete-file file2)))) - -(defun smerge-refine (&optional part) - "Highlight the words of the conflict that are different. -For 3-way conflicts, highlights only two of the three parts. -A numeric argument PART can be used to specify which two parts; -repeating the command will highlight other two parts." - (interactive - (if (integerp current-prefix-arg) (list current-prefix-arg) - (smerge-match-conflict) - (let* ((prop (get-text-property (match-beginning 0) 'smerge-refine-part)) - (part (if (and (consp prop) - (eq (buffer-chars-modified-tick) (car prop))) - (cdr prop)))) - ;; If already highlighted, cycle. - (list (if (integerp part) (1+ (mod part 3))))))) - - (if (and (integerp part) (or (< part 1) (> part 3))) - (error "No conflict part nb %s" part)) - (smerge-match-conflict) - (remove-overlays (match-beginning 0) (match-end 0) 'smerge 'refine) - ;; Ignore `part' if not applicable, and default it if not provided. - (setq part (cond ((null (match-end 2)) 2) - ((eq (match-end 1) (match-end 3)) 1) - ((integerp part) part) - (t 2))) - (let ((n1 (if (eq part 1) 2 1)) - (n2 (if (eq part 3) 2 3))) - (smerge-ensure-match n1) - (smerge-ensure-match n2) - (with-silent-modifications - (put-text-property (match-beginning 0) (1+ (match-beginning 0)) - 'smerge-refine-part - (cons (buffer-chars-modified-tick) part))) - (smerge-refine-subst (match-beginning n1) (match-end n1) - (match-beginning n2) (match-end n2) - '((smerge . refine) - (face . smerge-refined-change))))) - -(defun smerge-diff (n1 n2) - (smerge-match-conflict) - (smerge-ensure-match n1) - (smerge-ensure-match n2) - (let ((name1 (aref smerge-match-names n1)) - (name2 (aref smerge-match-names n2)) - ;; Read them before the match-data gets clobbered. - (beg1 (match-beginning n1)) - (end1 (match-end n1)) - (beg2 (match-beginning n2)) - (end2 (match-end n2)) - (file1 (make-temp-file "smerge1")) - (file2 (make-temp-file "smerge2")) - (dir default-directory) - (file (if buffer-file-name (file-relative-name buffer-file-name))) - ;; We would want to use `emacs-mule-unix' for read&write, but we - ;; bump into problems with the coding-system used by diff to write - ;; the file names and the time stamps in the header. - ;; `buffer-file-coding-system' is not always correct either, but if - ;; the OS/user uses only one coding-system, then it works. - (coding-system-for-read buffer-file-coding-system)) - (write-region beg1 end1 file1 nil 'nomessage) - (write-region beg2 end2 file2 nil 'nomessage) - (unwind-protect - (with-current-buffer (get-buffer-create smerge-diff-buffer-name) - (setq default-directory dir) - (let ((inhibit-read-only t)) - (erase-buffer) - (let ((status - (apply 'call-process diff-command nil t nil - (append smerge-diff-switches - (list "-L" (concat name1 "/" file) - "-L" (concat name2 "/" file) - file1 file2))))) - (if (eq status 0) (insert "No differences found.\n")))) - (goto-char (point-min)) - (diff-mode) - (display-buffer (current-buffer) t)) - (delete-file file1) - (delete-file file2)))) - -;; compiler pacifiers -(defvar smerge-ediff-windows) -(defvar smerge-ediff-buf) -(defvar ediff-buffer-A) -(defvar ediff-buffer-B) -(defvar ediff-buffer-C) -(defvar ediff-ancestor-buffer) -(defvar ediff-quit-hook) -(declare-function ediff-cleanup-mess "ediff-util" nil) - -;;;###autoload -(defun smerge-ediff (&optional name-mine name-other name-base) - "Invoke ediff to resolve the conflicts. -NAME-MINE, NAME-OTHER, and NAME-BASE, if non-nil, are used for the -buffer names." - (interactive) - (let* ((buf (current-buffer)) - (mode major-mode) - ;;(ediff-default-variant 'default-B) - (config (current-window-configuration)) - (filename (file-name-nondirectory buffer-file-name)) - (mine (generate-new-buffer - (or name-mine (concat "*" filename " MINE*")))) - (other (generate-new-buffer - (or name-other (concat "*" filename " OTHER*")))) - base) - (with-current-buffer mine - (buffer-disable-undo) - (insert-buffer-substring buf) - (goto-char (point-min)) - (while (smerge-find-conflict) - (when (match-beginning 2) (setq base t)) - (smerge-keep-n 1)) - (buffer-enable-undo) - (set-buffer-modified-p nil) - (funcall mode)) - - (with-current-buffer other - (buffer-disable-undo) - (insert-buffer-substring buf) - (goto-char (point-min)) - (while (smerge-find-conflict) - (smerge-keep-n 3)) - (buffer-enable-undo) - (set-buffer-modified-p nil) - (funcall mode)) - - (when base - (setq base (generate-new-buffer - (or name-base (concat "*" filename " BASE*")))) - (with-current-buffer base - (buffer-disable-undo) - (insert-buffer-substring buf) - (goto-char (point-min)) - (while (smerge-find-conflict) - (if (match-end 2) - (smerge-keep-n 2) - (delete-region (match-beginning 0) (match-end 0)))) - (buffer-enable-undo) - (set-buffer-modified-p nil) - (funcall mode))) - - ;; the rest of the code is inspired from vc.el - ;; Fire up ediff. - (set-buffer - (if base - (ediff-merge-buffers-with-ancestor mine other base) - ;; nil 'ediff-merge-revisions-with-ancestor buffer-file-name) - (ediff-merge-buffers mine other))) - ;; nil 'ediff-merge-revisions buffer-file-name))) - - ;; Ediff is now set up, and we are in the control buffer. - ;; Do a few further adjustments and take precautions for exit. - (set (make-local-variable 'smerge-ediff-windows) config) - (set (make-local-variable 'smerge-ediff-buf) buf) - (set (make-local-variable 'ediff-quit-hook) - (lambda () - (let ((buffer-A ediff-buffer-A) - (buffer-B ediff-buffer-B) - (buffer-C ediff-buffer-C) - (buffer-Ancestor ediff-ancestor-buffer) - (buf smerge-ediff-buf) - (windows smerge-ediff-windows)) - (ediff-cleanup-mess) - (with-current-buffer buf - (erase-buffer) - (insert-buffer-substring buffer-C) - (kill-buffer buffer-A) - (kill-buffer buffer-B) - (kill-buffer buffer-C) - (when (bufferp buffer-Ancestor) (kill-buffer buffer-Ancestor)) - (set-window-configuration windows) - (message "Conflict resolution finished; you may save the buffer"))))) - (message "Please resolve conflicts now; exit ediff when done"))) - -(defun smerge-makeup-conflict (pt1 pt2 pt3 &optional pt4) - "Insert diff3 markers to make a new conflict. -Uses point and mark for two of the relevant positions and previous marks -for the other ones. -By default, makes up a 2-way conflict, -with a \\[universal-argument] prefix, makes up a 3-way conflict." - (interactive - (list (point) - (mark) - (progn (pop-mark) (mark)) - (when current-prefix-arg (pop-mark) (mark)))) - ;; Start from the end so as to avoid problems with pos-changes. - (destructuring-bind (pt1 pt2 pt3 &optional pt4) - (sort (list* pt1 pt2 pt3 (if pt4 (list pt4))) '>=) - (goto-char pt1) (beginning-of-line) - (insert ">>>>>>> OTHER\n") - (goto-char pt2) (beginning-of-line) - (insert "=======\n") - (goto-char pt3) (beginning-of-line) - (when pt4 - (insert "||||||| BASE\n") - (goto-char pt4) (beginning-of-line)) - (insert "<<<<<<< MINE\n")) - (if smerge-mode nil (smerge-mode 1)) - (smerge-refine)) - - -(defconst smerge-parsep-re - (concat smerge-begin-re "\\|" smerge-end-re "\\|" - smerge-base-re "\\|" smerge-other-re "\\|")) - -;;;###autoload -(define-minor-mode smerge-mode - "Minor mode to simplify editing output from the diff3 program. -\\{smerge-mode-map}" - :group 'smerge :lighter " SMerge" - (when (and (boundp 'font-lock-mode) font-lock-mode) - (save-excursion - (if smerge-mode - (font-lock-add-keywords nil smerge-font-lock-keywords 'append) - (font-lock-remove-keywords nil smerge-font-lock-keywords)) - (goto-char (point-min)) - (while (smerge-find-conflict) - (save-excursion - (font-lock-fontify-region (match-beginning 0) (match-end 0) nil))))) - (if (string-match (regexp-quote smerge-parsep-re) paragraph-separate) - (unless smerge-mode - (set (make-local-variable 'paragraph-separate) - (replace-match "" t t paragraph-separate))) - (when smerge-mode - (set (make-local-variable 'paragraph-separate) - (concat smerge-parsep-re paragraph-separate)))) - (unless smerge-mode - (smerge-remove-props (point-min) (point-max)))) - -;;;###autoload -(defun smerge-start-session () - "Turn on `smerge-mode' and move point to first conflict marker. -If no conflict maker is found, turn off `smerge-mode'." - (interactive) - (smerge-mode 1) - (condition-case nil - (unless (looking-at smerge-begin-re) - (smerge-next)) - (error (smerge-auto-leave)))) - -(provide 'smerge-mode) - -;; arch-tag: 605c8d1e-e43d-4943-a6f3-1bcc4333e690 -;;; smerge-mode.el ends here diff -r 681cd08dc0f7 -r e93288477c43 lisp/term/common-win.el --- a/lisp/term/common-win.el Thu Jun 10 22:43:47 2010 +0000 +++ b/lisp/term/common-win.el Sun Jun 13 22:57:55 2010 +0000 @@ -204,91 +204,168 @@ ;; of grey. (defvar x-colors - (purecopy - '("gray100" "gray99" "gray98" "gray97" "gray96" "gray95" "gray94" "gray93" "gray92" - "gray91" "gray90" "gray89" "gray88" "gray87" "gray86" "gray85" "gray84" "gray83" - "gray82" "gray81" "gray80" "gray79" "gray78" "gray77" "gray76" "gray75" "gray74" - "gray73" "gray72" "gray71" "gray70" "gray69" "gray68" "gray67" "gray66" "gray65" - "gray64" "gray63" "gray62" "gray61" "gray60" "gray59" "gray58" "gray57" "gray56" - "gray55" "gray54" "gray53" "gray52" "gray51" "gray50" "gray49" "gray48" "gray47" - "gray46" "gray45" "gray44" "gray43" "gray42" "gray41" "gray40" "gray39" "gray38" - "gray37" "gray36" "gray35" "gray34" "gray33" "gray32" "gray31" "gray30" "gray29" - "gray28" "gray27" "gray26" "gray25" "gray24" "gray23" "gray22" "gray21" "gray20" - "gray19" "gray18" "gray17" "gray16" "gray15" "gray14" "gray13" "gray12" "gray11" - "gray10" "gray9" "gray8" "gray7" "gray6" "gray5" "gray4" "gray3" "gray2" "gray1" - "gray0" "LightPink1" "LightPink2" "LightPink3" "LightPink4" "pink1" "pink2" "pink3" - "pink4" "PaleVioletRed1" "PaleVioletRed2" "PaleVioletRed3" "PaleVioletRed4" - "LavenderBlush1" "LavenderBlush2" "LavenderBlush3" "LavenderBlush4" "VioletRed1" - "VioletRed2" "VioletRed3" "VioletRed4" "HotPink1" "HotPink2" "HotPink3" "HotPink4" - "DeepPink1" "DeepPink2" "DeepPink3" "DeepPink4" "maroon1" "maroon2" "maroon3" - "maroon4" "orchid1" "orchid2" "orchid3" "orchid4" "plum1" "plum2" "plum3" "plum4" - "thistle1" "thistle2" "thistle3" "thistle4" "MediumOrchid1" "MediumOrchid2" - "MediumOrchid3" "MediumOrchid4" "DarkOrchid1" "DarkOrchid2" "DarkOrchid3" - "DarkOrchid4" "purple1" "purple2" "purple3" "purple4" "MediumPurple1" - "MediumPurple2" "MediumPurple3" "MediumPurple4" "SlateBlue1" "SlateBlue2" - "SlateBlue3" "SlateBlue4" "RoyalBlue1" "RoyalBlue2" "RoyalBlue3" "RoyalBlue4" - "LightSteelBlue1" "LightSteelBlue2" "LightSteelBlue3" "LightSteelBlue4" "SlateGray1" - "SlateGray2" "SlateGray3" "SlateGray4" "DodgerBlue1" "DodgerBlue2" "DodgerBlue3" - "DodgerBlue4" "SteelBlue1" "SteelBlue2" "SteelBlue3" "SteelBlue4" "SkyBlue1" - "SkyBlue2" "SkyBlue3" "SkyBlue4" "LightSkyBlue1" "LightSkyBlue2" "LightSkyBlue3" - "LightSkyBlue4" "LightBlue1" "LightBlue2" "LightBlue3" "LightBlue4" "CadetBlue1" - "CadetBlue2" "CadetBlue3" "CadetBlue4" "azure1" "azure2" "azure3" "azure4" - "LightCyan1" "LightCyan2" "LightCyan3" "LightCyan4" "PaleTurquoise1" - "PaleTurquoise2" "PaleTurquoise3" "PaleTurquoise4" "DarkSlateGray1" "DarkSlateGray2" - "DarkSlateGray3" "DarkSlateGray4" "aquamarine1" "aquamarine2" "aquamarine3" - "aquamarine4" "SeaGreen1" "SeaGreen2" "SeaGreen3" "SeaGreen4" "honeydew1" - "honeydew2" "honeydew3" "honeydew4" "DarkSeaGreen1" "DarkSeaGreen2" "DarkSeaGreen3" - "DarkSeaGreen4" "PaleGreen1" "PaleGreen2" "PaleGreen3" "PaleGreen4" - "DarkOliveGreen1" "DarkOliveGreen2" "DarkOliveGreen3" "DarkOliveGreen4" "OliveDrab1" - "OliveDrab2" "OliveDrab3" "OliveDrab4" "ivory1" "ivory2" "ivory3" "ivory4" - "LightYellow1" "LightYellow2" "LightYellow3" "LightYellow4" "khaki1" "khaki2" - "khaki3" "khaki4" "LemonChiffon1" "LemonChiffon2" "LemonChiffon3" "LemonChiffon4" - "LightGoldenrod1" "LightGoldenrod2" "LightGoldenrod3" "LightGoldenrod4" "cornsilk1" - "cornsilk2" "cornsilk3" "cornsilk4" "goldenrod1" "goldenrod2" "goldenrod3" - "goldenrod4" "DarkGoldenrod1" "DarkGoldenrod2" "DarkGoldenrod3" "DarkGoldenrod4" - "wheat1" "wheat2" "wheat3" "wheat4" "NavajoWhite1" "NavajoWhite2" "NavajoWhite3" - "NavajoWhite4" "burlywood1" "burlywood2" "burlywood3" "burlywood4" "AntiqueWhite1" - "AntiqueWhite2" "AntiqueWhite3" "AntiqueWhite4" "bisque1" "bisque2" "bisque3" - "bisque4" "tan1" "tan2" "tan3" "tan4" "PeachPuff1" "PeachPuff2" "PeachPuff3" - "PeachPuff4" "seashell1" "seashell2" "seashell3" "seashell4" "chocolate1" - "chocolate2" "chocolate3" "chocolate4" "sienna1" "sienna2" "sienna3" "sienna4" - "LightSalmon1" "LightSalmon2" "LightSalmon3" "LightSalmon4" "salmon1" "salmon2" - "salmon3" "salmon4" "coral1" "coral2" "coral3" "coral4" "tomato1" "tomato2" - "tomato3" "tomato4" "MistyRose1" "MistyRose2" "MistyRose3" "MistyRose4" "snow1" - "snow2" "snow3" "snow4" "RosyBrown1" "RosyBrown2" "RosyBrown3" "RosyBrown4" - "IndianRed1" "IndianRed2" "IndianRed3" "IndianRed4" "firebrick1" "firebrick2" - "firebrick3" "firebrick4" "brown1" "brown2" "brown3" "brown4" "magenta1" "magenta2" - "magenta3" "magenta4" "blue1" "blue2" "blue3" "blue4" "DeepSkyBlue1" "DeepSkyBlue2" - "DeepSkyBlue3" "DeepSkyBlue4" "turquoise1" "turquoise2" "turquoise3" "turquoise4" - "cyan1" "cyan2" "cyan3" "cyan4" "SpringGreen1" "SpringGreen2" "SpringGreen3" - "SpringGreen4" "green1" "green2" "green3" "green4" "chartreuse1" "chartreuse2" - "chartreuse3" "chartreuse4" "yellow1" "yellow2" "yellow3" "yellow4" "gold1" "gold2" - "gold3" "gold4" "orange1" "orange2" "orange3" "orange4" "DarkOrange1" "DarkOrange2" - "DarkOrange3" "DarkOrange4" "OrangeRed1" "OrangeRed2" "OrangeRed3" "OrangeRed4" - "red1" "red2" "red3" "red4" "lavender blush" "ghost white" "lavender" "alice blue" - "azure" "light cyan" "mint cream" "honeydew" "ivory" "light goldenrod yellow" - "light yellow" "beige" "floral white" "old lace" "blanched almond" "moccasin" - "papaya whip" "bisque" "antique white" "linen" "peach puff" "seashell" "misty rose" - "snow" "light pink" "pink" "hot pink" "deep pink" "maroon" "pale violet red" - "violet red" "medium violet red" "violet" "plum" "thistle" "orchid" "medium orchid" - "dark orchid" "purple" "blue violet" "medium purple" "light slate blue" - "medium slate blue" "slate blue" "dark slate blue" "midnight blue" "navy" - "dark blue" "light steel blue" "cornflower blue" "dodger blue" "royal blue" - "light slate gray" "slate gray" "dark slate gray" "steel blue" "cadet blue" - "light sky blue" "sky blue" "light blue" "powder blue" "pale turquoise" - "turquoise" "medium turquoise" "dark turquoise" "dark cyan" "aquamarine" - "medium aquamarine" "light sea green" - "medium sea green" "sea green" "dark sea green" "pale green" "lime green" - "dark green" "forest green" "light green" "green yellow" "yellow green" "olive drab" - "dark olive green" "lemon chiffon" "khaki" "dark khaki" "cornsilk" - "pale goldenrod" "light goldenrod" "goldenrod" "dark goldenrod" "wheat" - "navajo white" "tan" "burlywood" "sandy brown" "peru" "chocolate" "saddle brown" - "sienna" "rosy brown" "dark salmon" "coral" "tomato" "light salmon" "salmon" - "light coral" "indian red" "firebrick" "brown" "dark red" "magenta" - "dark magenta" "dark violet" "medium blue" "blue" "deep sky blue" - "cyan" "medium spring green" "spring green" "green" "lawn green" "chartreuse" - "yellow" "gold" "orange" "dark orange" "orange red" "red" "white" "white smoke" - "gainsboro" "light gray" "gray" "dark gray" "dim gray" "black" )) + (purecopy + '("gray100" "grey100" "gray99" "grey99" "gray98" "grey98" "gray97" + "grey97" "gray96" "grey96" "gray95" "grey95" "gray94" "grey94" + "gray93" "grey93" "gray92" "grey92" "gray91" "grey91" "gray90" + "grey90" "gray89" "grey89" "gray88" "grey88" "gray87" "grey87" + "gray86" "grey86" "gray85" "grey85" "gray84" "grey84" "gray83" + "grey83" "gray82" "grey82" "gray81" "grey81" "gray80" "grey80" + "gray79" "grey79" "gray78" "grey78" "gray77" "grey77" "gray76" + "grey76" "gray75" "grey75" "gray74" "grey74" "gray73" "grey73" + "gray72" "grey72" "gray71" "grey71" "gray70" "grey70" "gray69" + "grey69" "gray68" "grey68" "gray67" "grey67" "gray66" "grey66" + "gray65" "grey65" "gray64" "grey64" "gray63" "grey63" "gray62" + "grey62" "gray61" "grey61" "gray60" "grey60" "gray59" "grey59" + "gray58" "grey58" "gray57" "grey57" "gray56" "grey56" "gray55" + "grey55" "gray54" "grey54" "gray53" "grey53" "gray52" "grey52" + "gray51" "grey51" "gray50" "grey50" "gray49" "grey49" "gray48" + "grey48" "gray47" "grey47" "gray46" "grey46" "gray45" "grey45" + "gray44" "grey44" "gray43" "grey43" "gray42" "grey42" "gray41" + "grey41" "gray40" "grey40" "gray39" "grey39" "gray38" "grey38" + "gray37" "grey37" "gray36" "grey36" "gray35" "grey35" "gray34" + "grey34" "gray33" "grey33" "gray32" "grey32" "gray31" "grey31" + "gray30" "grey30" "gray29" "grey29" "gray28" "grey28" "gray27" + "grey27" "gray26" "grey26" "gray25" "grey25" "gray24" "grey24" + "gray23" "grey23" "gray22" "grey22" "gray21" "grey21" "gray20" + "grey20" "gray19" "grey19" "gray18" "grey18" "gray17" "grey17" + "gray16" "grey16" "gray15" "grey15" "gray14" "grey14" "gray13" + "grey13" "gray12" "grey12" "gray11" "grey11" "gray10" "grey10" + "gray9" "grey9" "gray8" "grey8" "gray7" "grey7" "gray6" "grey6" + "gray5" "grey5" "gray4" "grey4" "gray3" "grey3" "gray2" "grey2" + "gray1" "grey1" "gray0" "grey0" + "LightPink1" "LightPink2" "LightPink3" "LightPink4" + "pink1" "pink2" "pink3" "pink4" + "PaleVioletRed1" "PaleVioletRed2" "PaleVioletRed3" "PaleVioletRed4" + "LavenderBlush1" "LavenderBlush2" "LavenderBlush3" "LavenderBlush4" + "VioletRed1" "VioletRed2" "VioletRed3" "VioletRed4" + "HotPink1" "HotPink2" "HotPink3" "HotPink4" + "DeepPink1" "DeepPink2" "DeepPink3" "DeepPink4" + "maroon1" "maroon2" "maroon3" "maroon4" + "orchid1" "orchid2" "orchid3" "orchid4" + "plum1" "plum2" "plum3" "plum4" + "thistle1" "thistle2" "thistle3" "thistle4" + "MediumOrchid1" "MediumOrchid2" "MediumOrchid3" "MediumOrchid4" + "DarkOrchid1" "DarkOrchid2" "DarkOrchid3" "DarkOrchid4" + "purple1" "purple2" "purple3" "purple4" + "MediumPurple1" "MediumPurple2" "MediumPurple3" "MediumPurple4" + "SlateBlue1" "SlateBlue2" "SlateBlue3" "SlateBlue4" + "RoyalBlue1" "RoyalBlue2" "RoyalBlue3" "RoyalBlue4" + "LightSteelBlue1" "LightSteelBlue2" "LightSteelBlue3" "LightSteelBlue4" + "SlateGray1" "SlateGray2" "SlateGray3" "SlateGray4" + "DodgerBlue1" "DodgerBlue2" "DodgerBlue3" "DodgerBlue4" + "SteelBlue1" "SteelBlue2" "SteelBlue3" "SteelBlue4" + "SkyBlue1" "SkyBlue2" "SkyBlue3" "SkyBlue4" + "LightSkyBlue1" "LightSkyBlue2" "LightSkyBlue3" "LightSkyBlue4" + "LightBlue1" "LightBlue2" "LightBlue3" "LightBlue4" + "CadetBlue1" "CadetBlue2" "CadetBlue3" "CadetBlue4" + "azure1" "azure2" "azure3" "azure4" + "LightCyan1" "LightCyan2" "LightCyan3" "LightCyan4" + "PaleTurquoise1" "PaleTurquoise2" "PaleTurquoise3" "PaleTurquoise4" + "DarkSlateGray1" "DarkSlateGray2" "DarkSlateGray3" "DarkSlateGray4" + "aquamarine1" "aquamarine2" "aquamarine3" "aquamarine4" + "SeaGreen1" "SeaGreen2" "SeaGreen3" "SeaGreen4" + "honeydew1" "honeydew2" "honeydew3" "honeydew4" + "DarkSeaGreen1" "DarkSeaGreen2" "DarkSeaGreen3" "DarkSeaGreen4" + "PaleGreen1" "PaleGreen2" "PaleGreen3" "PaleGreen4" + "DarkOliveGreen1" "DarkOliveGreen2" "DarkOliveGreen3" "DarkOliveGreen4" + "OliveDrab1" "OliveDrab2" "OliveDrab3" "OliveDrab4" + "ivory1" "ivory2" "ivory3" "ivory4" + "LightYellow1" "LightYellow2" "LightYellow3" "LightYellow4" + "khaki1" "khaki2" "khaki3" "khaki4" + "LemonChiffon1" "LemonChiffon2" "LemonChiffon3" "LemonChiffon4" + "LightGoldenrod1" "LightGoldenrod2" "LightGoldenrod3" "LightGoldenrod4" + "cornsilk1" "cornsilk2" "cornsilk3" "cornsilk4" + "goldenrod1" "goldenrod2" "goldenrod3" "goldenrod4" + "DarkGoldenrod1" "DarkGoldenrod2" "DarkGoldenrod3" "DarkGoldenrod4" + "wheat1" "wheat2" "wheat3" "wheat4" + "NavajoWhite1" "NavajoWhite2" "NavajoWhite3" "NavajoWhite4" + "burlywood1" "burlywood2" "burlywood3" "burlywood4" + "AntiqueWhite1" "AntiqueWhite2" "AntiqueWhite3" "AntiqueWhite4" + "bisque1" "bisque2" "bisque3" "bisque4" + "tan1" "tan2" "tan3" "tan4" + "PeachPuff1" "PeachPuff2" "PeachPuff3" "PeachPuff4" + "seashell1" "seashell2" "seashell3" "seashell4" + "chocolate1" "chocolate2" "chocolate3" "chocolate4" + "sienna1" "sienna2" "sienna3" "sienna4" + "LightSalmon1" "LightSalmon2" "LightSalmon3" "LightSalmon4" + "salmon1" "salmon2" "salmon3" "salmon4" + "coral1" "coral2" "coral3" "coral4" + "tomato1" "tomato2" "tomato3" "tomato4" + "MistyRose1" "MistyRose2" "MistyRose3" "MistyRose4" + "snow1" "snow2" "snow3" "snow4" + "RosyBrown1" "RosyBrown2" "RosyBrown3" "RosyBrown4" + "IndianRed1" "IndianRed2" "IndianRed3" "IndianRed4" + "firebrick1" "firebrick2" "firebrick3" "firebrick4" + "brown1" "brown2" "brown3" "brown4" + "magenta1" "magenta2" "magenta3" "magenta4" + "blue1" "blue2" "blue3" "blue4" + "DeepSkyBlue1" "DeepSkyBlue2" "DeepSkyBlue3" "DeepSkyBlue4" + "turquoise1" "turquoise2" "turquoise3" "turquoise4" + "cyan1" "cyan2" "cyan3" "cyan4" + "SpringGreen1" "SpringGreen2" "SpringGreen3" "SpringGreen4" + "green1" "green2" "green3" "green4" + "chartreuse1" "chartreuse2" "chartreuse3" "chartreuse4" + "yellow1" "yellow2" "yellow3" "yellow4" + "gold1" "gold2" "gold3" "gold4" + "orange1" "orange2" "orange3" "orange4" + "DarkOrange1" "DarkOrange2" "DarkOrange3" "DarkOrange4" + "OrangeRed1" "OrangeRed2" "OrangeRed3" "OrangeRed4" + "red1" "red2" "red3" "red4" + "lavender blush" "LavenderBlush" "ghost white" "GhostWhite" + "lavender" "alice blue" "AliceBlue" "azure" "light cyan" + "LightCyan" "mint cream" "MintCream" "honeydew" "ivory" + "light goldenrod yellow" "LightGoldenrodYellow" "light yellow" + "LightYellow" "beige" "floral white" "FloralWhite" "old lace" + "OldLace" "blanched almond" "BlanchedAlmond" "moccasin" + "papaya whip" "PapayaWhip" "bisque" "antique white" + "AntiqueWhite" "linen" "peach puff" "PeachPuff" "seashell" + "misty rose" "MistyRose" "snow" "light pink" "LightPink" "pink" + "hot pink" "HotPink" "deep pink" "DeepPink" "maroon" + "pale violet red" "PaleVioletRed" "violet red" "VioletRed" + "medium violet red" "MediumVioletRed" "violet" "plum" "thistle" + "orchid" "medium orchid" "MediumOrchid" "dark orchid" + "DarkOrchid" "purple" "blue violet" "BlueViolet" "medium purple" + "MediumPurple" "light slate blue" "LightSlateBlue" + "medium slate blue" "MediumSlateBlue" "slate blue" "SlateBlue" + "dark slate blue" "DarkSlateBlue" "midnight blue" "MidnightBlue" + "navy" "navy blue" "NavyBlue" "dark blue" "DarkBlue" + "light steel blue" "LightSteelBlue" "cornflower blue" + "CornflowerBlue" "dodger blue" "DodgerBlue" "royal blue" + "RoyalBlue" "light slate gray" "light slate grey" + "LightSlateGray" "LightSlateGrey" "slate gray" "slate grey" + "SlateGray" "SlateGrey" "dark slate gray" "dark slate grey" + "DarkSlateGray" "DarkSlateGrey" "steel blue" "SteelBlue" + "cadet blue" "CadetBlue" "light sky blue" "LightSkyBlue" + "sky blue" "SkyBlue" "light blue" "LightBlue" "powder blue" + "PowderBlue" "pale turquoise" "PaleTurquoise" "turquoise" + "medium turquoise" "MediumTurquoise" "dark turquoise" + "DarkTurquoise" "dark cyan" "DarkCyan" "aquamarine" + "medium aquamarine" "MediumAquamarine" "light sea green" + "LightSeaGreen" "medium sea green" "MediumSeaGreen" "sea green" + "SeaGreen" "dark sea green" "DarkSeaGreen" "pale green" + "PaleGreen" "lime green" "LimeGreen" "dark green" "DarkGreen" + "forest green" "ForestGreen" "light green" "LightGreen" + "green yellow" "GreenYellow" "yellow green" "YellowGreen" + "olive drab" "OliveDrab" "dark olive green" "DarkOliveGreen" + "lemon chiffon" "LemonChiffon" "khaki" "dark khaki" "DarkKhaki" + "cornsilk" "pale goldenrod" "PaleGoldenrod" "light goldenrod" + "LightGoldenrod" "goldenrod" "dark goldenrod" "DarkGoldenrod" + "wheat" "navajo white" "NavajoWhite" "tan" "burlywood" + "sandy brown" "SandyBrown" "peru" "chocolate" "saddle brown" + "SaddleBrown" "sienna" "rosy brown" "RosyBrown" "dark salmon" + "DarkSalmon" "coral" "tomato" "light salmon" "LightSalmon" + "salmon" "light coral" "LightCoral" "indian red" "IndianRed" + "firebrick" "brown" "dark red" "DarkRed" "magenta" + "dark magenta" "DarkMagenta" "dark violet" "DarkViolet" + "medium blue" "MediumBlue" "blue" "deep sky blue" "DeepSkyBlue" + "cyan" "medium spring green" "MediumSpringGreen" "spring green" + "SpringGreen" "green" "lawn green" "LawnGreen" "chartreuse" + "yellow" "gold" "orange" "dark orange" "DarkOrange" "orange red" + "OrangeRed" "red" "white" "white smoke" "WhiteSmoke" "gainsboro" + "light gray" "light grey" "LightGray" "LightGrey" "gray" "grey" + "dark gray" "dark grey" "DarkGray" "DarkGrey" "dim gray" + "dim grey" "DimGray" "DimGrey" "black")) "List of basic colors available on color displays. For X, the list comes from the `rgb.txt' file,v 10.41 94/02/20. For Nextstep, this is a list of non-PANTONE colors returned by diff -r 681cd08dc0f7 -r e93288477c43 lisp/vc-annotate.el --- a/lisp/vc-annotate.el Thu Jun 10 22:43:47 2010 +0000 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,676 +0,0 @@ -;;; vc-annotate.el --- VC Annotate Support - -;; Copyright (C) 1997, 1998, 2000, 2001, 2002, 2003, 2004, 2005, 2006, -;; 2007, 2008, 2009, 2010 Free Software Foundation, Inc. - -;; Author: Martin Lorentzson -;; Maintainer: FSF -;; Keywords: tools - -;; 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 3 of the License, 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. If not, see . - -;;; Commentary: -;; - -(require 'vc-hooks) -(require 'vc) - -;;; Code: -(eval-when-compile - (require 'cl)) - -(defcustom vc-annotate-display-mode 'fullscale - "Which mode to color the output of \\[vc-annotate] with by default." - :type '(choice (const :tag "By Color Map Range" nil) - (const :tag "Scale to Oldest" scale) - (const :tag "Scale Oldest->Newest" fullscale) - (number :tag "Specify Fractional Number of Days" - :value "20.5")) - :group 'vc) - -(defcustom vc-annotate-color-map - (if (and (tty-display-color-p) (<= (display-color-cells) 8)) - ;; A custom sorted TTY colormap - (let* ((colors - (sort - (delq nil - (mapcar (lambda (x) - (if (not (or - (string-equal (car x) "white") - (string-equal (car x) "black") )) - (car x))) - (tty-color-alist))) - (lambda (a b) - (cond - ((or (string-equal a "red") (string-equal b "blue")) t) - ((or (string-equal b "red") (string-equal a "blue")) nil) - ((string-equal a "yellow") t) - ((string-equal b "yellow") nil) - ((string-equal a "cyan") t) - ((string-equal b "cyan") nil) - ((string-equal a "green") t) - ((string-equal b "green") nil) - ((string-equal a "magenta") t) - ((string-equal b "magenta") nil) - (t (string< a b)))))) - (date 20.) - (delta (/ (- 360. date) (1- (length colors))))) - (mapcar (lambda (x) - (prog1 - (cons date x) - (setq date (+ date delta)))) colors)) - ;; Normal colormap: hue stepped from 0-240deg, value=1., saturation=0.75 - '(( 20. . "#FF3F3F") - ( 40. . "#FF6C3F") - ( 60. . "#FF993F") - ( 80. . "#FFC63F") - (100. . "#FFF33F") - (120. . "#DDFF3F") - (140. . "#B0FF3F") - (160. . "#83FF3F") - (180. . "#56FF3F") - (200. . "#3FFF56") - (220. . "#3FFF83") - (240. . "#3FFFB0") - (260. . "#3FFFDD") - (280. . "#3FF3FF") - (300. . "#3FC6FF") - (320. . "#3F99FF") - (340. . "#3F6CFF") - (360. . "#3F3FFF"))) - "Association list of age versus color, for \\[vc-annotate]. -Ages are given in units of fractional days. Default is eighteen -steps using a twenty day increment, from red to blue. For TTY -displays with 8 or fewer colors, the default is red to blue with -all other colors between (excluding black and white)." - :type 'alist - :group 'vc) - -(defcustom vc-annotate-very-old-color "#3F3FFF" - "Color for lines older than the current color range in \\[vc-annotate]." - :type 'string - :group 'vc) - -(defcustom vc-annotate-background "black" - "Background color for \\[vc-annotate]. -Default color is used if nil." - :type '(choice (const :tag "Default background" nil) (color)) - :group 'vc) - -(defcustom vc-annotate-menu-elements '(2 0.5 0.1 0.01) - "Menu elements for the mode-specific menu of VC-Annotate mode. -List of factors, used to expand/compress the time scale. See `vc-annotate'." - :type '(repeat number) - :group 'vc) - -(defvar vc-annotate-mode-map - (let ((m (make-sparse-keymap))) - (define-key m "a" 'vc-annotate-revision-previous-to-line) - (define-key m "d" 'vc-annotate-show-diff-revision-at-line) - (define-key m "D" 'vc-annotate-show-changeset-diff-revision-at-line) - (define-key m "f" 'vc-annotate-find-revision-at-line) - (define-key m "j" 'vc-annotate-revision-at-line) - (define-key m "l" 'vc-annotate-show-log-revision-at-line) - (define-key m "n" 'vc-annotate-next-revision) - (define-key m "p" 'vc-annotate-prev-revision) - (define-key m "w" 'vc-annotate-working-revision) - (define-key m "v" 'vc-annotate-toggle-annotation-visibility) - m) - "Local keymap used for VC-Annotate mode.") - -;;; Annotate functionality - -;; Declare globally instead of additional parameter to -;; temp-buffer-show-function (not possible to pass more than one -;; parameter). The use of annotate-ratio is deprecated in favor of -;; annotate-mode, which replaces it with the more sensible "span-to -;; days", along with autoscaling support. -(defvar vc-annotate-ratio nil "Global variable.") - -;; internal buffer-local variables -(defvar vc-annotate-backend nil) -(defvar vc-annotate-parent-file nil) -(defvar vc-annotate-parent-rev nil) -(defvar vc-annotate-parent-display-mode nil) - -(defconst vc-annotate-font-lock-keywords - ;; The fontification is done by vc-annotate-lines instead of font-lock. - '((vc-annotate-lines))) - -(define-derived-mode vc-annotate-mode special-mode "Annotate" - "Major mode for output buffers of the `vc-annotate' command. - -You can use the mode-specific menu to alter the time-span of the used -colors. See variable `vc-annotate-menu-elements' for customizing the -menu items." - ;; Frob buffer-invisibility-spec so that if it is originally a naked t, - ;; it will become a list, to avoid initial annotations being invisible. - (add-to-invisibility-spec 'foo) - (remove-from-invisibility-spec 'foo) - (set (make-local-variable 'truncate-lines) t) - (set (make-local-variable 'font-lock-defaults) - '(vc-annotate-font-lock-keywords t)) - (hack-dir-local-variables-non-file-buffer)) - -(defun vc-annotate-toggle-annotation-visibility () - "Toggle whether or not the annotation is visible." - (interactive) - (funcall (if (memq 'vc-annotate-annotation buffer-invisibility-spec) - 'remove-from-invisibility-spec - 'add-to-invisibility-spec) - 'vc-annotate-annotation) - (force-window-update (current-buffer))) - -(defun vc-annotate-display-default (ratio) - "Display the output of \\[vc-annotate] using the default color range. -The color range is given by `vc-annotate-color-map', scaled by RATIO. -The current time is used as the offset." - (interactive (progn (kill-local-variable 'vc-annotate-color-map) '(1.0))) - (message "Redisplaying annotation...") - (vc-annotate-display ratio) - (message "Redisplaying annotation...done")) - -(defun vc-annotate-oldest-in-map (color-map) - "Return the oldest time in the COLOR-MAP." - ;; Since entries should be sorted, we can just use the last one. - (caar (last color-map))) - -(defun vc-annotate-get-time-set-line-props () - (let ((bol (point)) - (date (vc-call-backend vc-annotate-backend 'annotate-time)) - (inhibit-read-only t)) - (assert (>= (point) bol)) - (put-text-property bol (point) 'invisible 'vc-annotate-annotation) - date)) - -(defun vc-annotate-display-autoscale (&optional full) - "Highlight the output of \\[vc-annotate] using an autoscaled color map. -Autoscaling means that the map is scaled from the current time to the -oldest annotation in the buffer, or, with prefix argument FULL, to -cover the range from the oldest annotation to the newest." - (interactive "P") - (let ((newest 0.0) - (oldest 999999.) ;Any CVS users at the founding of Rome? - (current (vc-annotate-convert-time (current-time))) - date) - (message "Redisplaying annotation...") - ;; Run through this file and find the oldest and newest dates annotated. - (save-excursion - (goto-char (point-min)) - (while (not (eobp)) - (when (setq date (vc-annotate-get-time-set-line-props)) - (when (> date newest) - (setq newest date)) - (when (< date oldest) - (setq oldest date))) - (forward-line 1))) - (vc-annotate-display - (/ (- (if full newest current) oldest) - (vc-annotate-oldest-in-map vc-annotate-color-map)) - (if full newest)) - (message "Redisplaying annotation...done \(%s\)" - (if full - (format "Spanned from %.1f to %.1f days old" - (- current oldest) - (- current newest)) - (format "Spanned to %.1f days old" (- current oldest)))))) - -;; Menu -- Using easymenu.el -(easy-menu-define vc-annotate-mode-menu vc-annotate-mode-map - "VC Annotate Display Menu" - `("VC-Annotate" - ["By Color Map Range" (unless (null vc-annotate-display-mode) - (setq vc-annotate-display-mode nil) - (vc-annotate-display-select)) - :style toggle :selected (null vc-annotate-display-mode)] - ,@(let ((oldest-in-map (vc-annotate-oldest-in-map vc-annotate-color-map))) - (mapcar (lambda (element) - (let ((days (* element oldest-in-map))) - `[,(format "Span %.1f days" days) - (vc-annotate-display-select nil ,days) - :style toggle :selected - (eql vc-annotate-display-mode ,days) ])) - vc-annotate-menu-elements)) - ["Span ..." - (vc-annotate-display-select - nil (float (string-to-number (read-string "Span how many days? "))))] - "--" - ["Span to Oldest" - (unless (eq vc-annotate-display-mode 'scale) - (vc-annotate-display-select nil 'scale)) - :help - "Use an autoscaled color map from the oldest annotation to the current time" - :style toggle :selected - (eq vc-annotate-display-mode 'scale)] - ["Span Oldest->Newest" - (unless (eq vc-annotate-display-mode 'fullscale) - (vc-annotate-display-select nil 'fullscale)) - :help - "Use an autoscaled color map from the oldest to the newest annotation" - :style toggle :selected - (eq vc-annotate-display-mode 'fullscale)] - "--" - ["Toggle annotation visibility" vc-annotate-toggle-annotation-visibility - :help - "Toggle whether the annotation is visible or not"] - ["Annotate previous revision" vc-annotate-prev-revision - :help "Visit the annotation of the revision previous to this one"] - ["Annotate next revision" vc-annotate-next-revision - :help "Visit the annotation of the revision after this one"] - ["Annotate revision at line" vc-annotate-revision-at-line - :help - "Visit the annotation of the revision identified in the current line"] - ["Annotate revision previous to line" vc-annotate-revision-previous-to-line - :help "Visit the annotation of the revision before the revision at line"] - ["Annotate latest revision" vc-annotate-working-revision - :help "Visit the annotation of the working revision of this file"] - "--" - ["Show log of revision at line" vc-annotate-show-log-revision-at-line - :help "Visit the log of the revision at line"] - ["Show diff of revision at line" vc-annotate-show-diff-revision-at-line - :help "Visit the diff of the revision at line from its previous revision"] - ["Show changeset diff of revision at line" - vc-annotate-show-changeset-diff-revision-at-line - :enable - (eq 'repository (vc-call-backend ,vc-annotate-backend 'revision-granularity)) - :help "Visit the diff of the revision at line from its previous revision"] - ["Visit revision at line" vc-annotate-find-revision-at-line - :help "Visit the revision identified in the current line"])) - -(defun vc-annotate-display-select (&optional buffer mode) - "Highlight the output of \\[vc-annotate]. -By default, the current buffer is highlighted, unless overridden by -BUFFER. `vc-annotate-display-mode' specifies the highlighting mode to -use; you may override this using the second optional arg MODE." - (interactive) - (when mode (setq vc-annotate-display-mode mode)) - (pop-to-buffer (or buffer (current-buffer))) - (cond ((null vc-annotate-display-mode) - ;; The ratio is global, thus relative to the global color-map. - (kill-local-variable 'vc-annotate-color-map) - (vc-annotate-display-default (or vc-annotate-ratio 1.0))) - ;; One of the auto-scaling modes - ((eq vc-annotate-display-mode 'scale) - (vc-exec-after `(vc-annotate-display-autoscale))) - ((eq vc-annotate-display-mode 'fullscale) - (vc-exec-after `(vc-annotate-display-autoscale t))) - ((numberp vc-annotate-display-mode) ; A fixed number of days lookback - (vc-annotate-display-default - (/ vc-annotate-display-mode - (vc-annotate-oldest-in-map vc-annotate-color-map)))) - (t (error "No such display mode: %s" - vc-annotate-display-mode)))) - -;;;###autoload -(defun vc-annotate (file rev &optional display-mode buf move-point-to) - "Display the edit history of the current file using colors. - -This command creates a buffer that shows, for each line of the current -file, when it was last edited and by whom. Additionally, colors are -used to show the age of each line--blue means oldest, red means -youngest, and intermediate colors indicate intermediate ages. By -default, the time scale stretches back one year into the past; -everything that is older than that is shown in blue. - -With a prefix argument, this command asks two questions in the -minibuffer. First, you may enter a revision number; then the buffer -displays and annotates that revision instead of the working revision -\(type RET in the minibuffer to leave that default unchanged). Then, -you are prompted for the time span in days which the color range -should cover. For example, a time span of 20 days means that changes -over the past 20 days are shown in red to blue, according to their -age, and everything that is older than that is shown in blue. - -If MOVE-POINT-TO is given, move the point to that line. - -Customization variables: - -`vc-annotate-menu-elements' customizes the menu elements of the -mode-specific menu. `vc-annotate-color-map' and -`vc-annotate-very-old-color' define the mapping of time to colors. -`vc-annotate-background' specifies the background color." - (interactive - (save-current-buffer - (vc-ensure-vc-buffer) - (list buffer-file-name - (let ((def (vc-working-revision buffer-file-name))) - (if (null current-prefix-arg) def - (read-string - (format "Annotate from revision (default %s): " def) - nil nil def))) - (if (null current-prefix-arg) - vc-annotate-display-mode - (float (string-to-number - (read-string "Annotate span days (default 20): " - nil nil "20"))))))) - (vc-ensure-vc-buffer) - (setq vc-annotate-display-mode display-mode) ;Not sure why. --Stef - (let* ((temp-buffer-name (format "*Annotate %s (rev %s)*" (buffer-name) rev)) - (temp-buffer-show-function 'vc-annotate-display-select) - ;; If BUF is specified, we presume the caller maintains current line, - ;; so we don't need to do it here. This implementation may give - ;; strange results occasionally in the case of REV != WORKFILE-REV. - (current-line (or move-point-to (unless buf - (save-restriction - (widen) - (line-number-at-pos)))))) - (message "Annotating...") - ;; If BUF is specified it tells in which buffer we should put the - ;; annotations. This is used when switching annotations to another - ;; revision, so we should update the buffer's name. - (when buf (with-current-buffer buf - (rename-buffer temp-buffer-name t) - ;; In case it had to be uniquified. - (setq temp-buffer-name (buffer-name)))) - (with-output-to-temp-buffer temp-buffer-name - (let ((backend (vc-backend file)) - (coding-system-for-read buffer-file-coding-system)) - (vc-call-backend backend 'annotate-command file - (get-buffer temp-buffer-name) rev) - ;; we must setup the mode first, and then set our local - ;; variables before the show-function is called at the exit of - ;; with-output-to-temp-buffer - (with-current-buffer temp-buffer-name - (unless (equal major-mode 'vc-annotate-mode) - (vc-annotate-mode)) - (set (make-local-variable 'vc-annotate-backend) backend) - (set (make-local-variable 'vc-annotate-parent-file) file) - (set (make-local-variable 'vc-annotate-parent-rev) rev) - (set (make-local-variable 'vc-annotate-parent-display-mode) - display-mode)))) - - (with-current-buffer temp-buffer-name - (vc-exec-after - `(progn - ;; Ideally, we'd rather not move point if the user has already - ;; moved it elsewhere, but really point here is not the position - ;; of the user's cursor :-( - (when ,current-line ;(and (bobp)) - (goto-line ,current-line) - (setq vc-sentinel-movepoint (point))) - (unless (active-minibuffer-window) - (message "Annotating... done"))))))) - -(defun vc-annotate-prev-revision (prefix) - "Visit the annotation of the revision previous to this one. - -With a numeric prefix argument, annotate the revision that many -revisions previous." - (interactive "p") - (vc-annotate-warp-revision (- 0 prefix))) - -(defun vc-annotate-next-revision (prefix) - "Visit the annotation of the revision after this one. - -With a numeric prefix argument, annotate the revision that many -revisions after." - (interactive "p") - (vc-annotate-warp-revision prefix)) - -(defun vc-annotate-working-revision () - "Visit the annotation of the working revision of this file." - (interactive) - (if (not (equal major-mode 'vc-annotate-mode)) - (message "Cannot be invoked outside of a vc annotate buffer") - (let ((warp-rev (vc-working-revision vc-annotate-parent-file))) - (if (equal warp-rev vc-annotate-parent-rev) - (message "Already at revision %s" warp-rev) - (vc-annotate-warp-revision warp-rev))))) - -(defun vc-annotate-extract-revision-at-line () - "Extract the revision number of the current line. -Return a cons (REV . FILENAME)." - ;; This function must be invoked from a buffer in vc-annotate-mode - (let ((rev (vc-call-backend vc-annotate-backend - 'annotate-extract-revision-at-line))) - (if (or (null rev) (consp rev)) - rev - (cons rev vc-annotate-parent-file)))) - -(defun vc-annotate-revision-at-line () - "Visit the annotation of the revision identified in the current line." - (interactive) - (if (not (equal major-mode 'vc-annotate-mode)) - (message "Cannot be invoked outside of a vc annotate buffer") - (let ((rev-at-line (vc-annotate-extract-revision-at-line))) - (if (not rev-at-line) - (message "Cannot extract revision number from the current line") - (if (and (equal (car rev-at-line) vc-annotate-parent-rev) - (string= (cdr rev-at-line) vc-annotate-parent-file)) - (message "Already at revision %s" rev-at-line) - (vc-annotate-warp-revision (car rev-at-line) (cdr rev-at-line))))))) - -(defun vc-annotate-find-revision-at-line () - "Visit the revision identified in the current line." - (interactive) - (if (not (equal major-mode 'vc-annotate-mode)) - (message "Cannot be invoked outside of a vc annotate buffer") - (let ((rev-at-line (vc-annotate-extract-revision-at-line))) - (if (not rev-at-line) - (message "Cannot extract revision number from the current line") - (switch-to-buffer-other-window - (vc-find-revision (cdr rev-at-line) (car rev-at-line))))))) - -(defun vc-annotate-revision-previous-to-line () - "Visit the annotation of the revision before the revision at line." - (interactive) - (if (not (equal major-mode 'vc-annotate-mode)) - (message "Cannot be invoked outside of a vc annotate buffer") - (let* ((rev-at-line (vc-annotate-extract-revision-at-line)) - (prev-rev nil) - (rev (car rev-at-line)) - (fname (cdr rev-at-line))) - (if (not rev-at-line) - (message "Cannot extract revision number from the current line") - (setq prev-rev - (vc-call-backend vc-annotate-backend 'previous-revision - fname rev)) - (vc-annotate-warp-revision prev-rev fname))))) - -(defvar log-view-vc-backend) -(defvar log-view-vc-fileset) - -(defun vc-annotate-show-log-revision-at-line () - "Visit the log of the revision at line. -If the VC backend supports it, only show the log entry for the revision. -If a *vc-change-log* buffer exists and already shows a log for -the file in question, search for the log entry required and move point ." - (interactive) - (if (not (equal major-mode 'vc-annotate-mode)) - (message "Cannot be invoked outside of a vc annotate buffer") - (let ((rev-at-line (vc-annotate-extract-revision-at-line))) - (if (not rev-at-line) - (message "Cannot extract revision number from the current line") - (let ((backend vc-annotate-backend) - (log-buf (get-buffer "*vc-change-log*")) - pos) - (if (and - log-buf - ;; Look for a log buffer that already displays the correct file. - (with-current-buffer log-buf - (and (eq backend log-view-vc-backend) - (null (cdr log-view-vc-fileset)) - (string= (car log-view-vc-fileset) (cdr rev-at-line)) - ;; Check if the entry we require can be found. - (vc-call-backend - backend 'show-log-entry (car rev-at-line)) - (setq pos (point))))) - (progn - (pop-to-buffer log-buf) - (goto-char pos)) - ;; Ask the backend to display a single log entry. - (vc-print-log-internal - vc-annotate-backend (list (cdr rev-at-line)) - (car rev-at-line) t 1))))))) - -(defun vc-annotate-show-diff-revision-at-line-internal (filediff) - (if (not (equal major-mode 'vc-annotate-mode)) - (message "Cannot be invoked outside of a vc annotate buffer") - (let* ((rev-at-line (vc-annotate-extract-revision-at-line)) - (prev-rev nil) - (rev (car rev-at-line)) - (fname (cdr rev-at-line))) - (if (not rev-at-line) - (message "Cannot extract revision number from the current line") - (setq prev-rev - (vc-call-backend vc-annotate-backend 'previous-revision - fname rev)) - (if (not prev-rev) - (message "Cannot diff from any revision prior to %s" rev) - (save-window-excursion - (vc-diff-internal - nil - ;; The value passed here should follow what - ;; `vc-deduce-fileset' returns. - (list vc-annotate-backend - (if filediff - (list fname) - nil)) - prev-rev rev)) - (switch-to-buffer "*vc-diff*")))))) - -(defun vc-annotate-show-diff-revision-at-line () - "Visit the diff of the revision at line from its previous revision." - (interactive) - (vc-annotate-show-diff-revision-at-line-internal t)) - -(defun vc-annotate-show-changeset-diff-revision-at-line () - "Visit the diff of the revision at line from its previous revision for all files in the changeset." - (interactive) - (when (eq 'file (vc-call-backend vc-annotate-backend 'revision-granularity)) - (error "The %s backend does not support changeset diffs" vc-annotate-backend)) - (vc-annotate-show-diff-revision-at-line-internal nil)) - -(defun vc-annotate-warp-revision (revspec &optional file) - "Annotate the revision described by REVSPEC. - -If REVSPEC is a positive integer, warp that many revisions forward, -if possible, otherwise echo a warning message. If REVSPEC is a -negative integer, warp that many revisions backward, if possible, -otherwise echo a warning message. If REVSPEC is a string, then it -describes a revision number, so warp to that revision." - (if (not (equal major-mode 'vc-annotate-mode)) - (message "Cannot be invoked outside of a vc annotate buffer") - (let* ((buf (current-buffer)) - (oldline (line-number-at-pos)) - (revspeccopy revspec) - (newrev nil)) - (cond - ((and (integerp revspec) (> revspec 0)) - (setq newrev vc-annotate-parent-rev) - (while (and (> revspec 0) newrev) - (setq newrev (vc-call-backend vc-annotate-backend 'next-revision - (or file vc-annotate-parent-file) newrev)) - (setq revspec (1- revspec))) - (unless newrev - (message "Cannot increment %d revisions from revision %s" - revspeccopy vc-annotate-parent-rev))) - ((and (integerp revspec) (< revspec 0)) - (setq newrev vc-annotate-parent-rev) - (while (and (< revspec 0) newrev) - (setq newrev (vc-call-backend vc-annotate-backend 'previous-revision - (or file vc-annotate-parent-file) newrev)) - (setq revspec (1+ revspec))) - (unless newrev - (message "Cannot decrement %d revisions from revision %s" - (- 0 revspeccopy) vc-annotate-parent-rev))) - ((stringp revspec) (setq newrev revspec)) - (t (error "Invalid argument to vc-annotate-warp-revision"))) - (when newrev - (vc-annotate (or file vc-annotate-parent-file) newrev - vc-annotate-parent-display-mode - buf - ;; Pass the current line so that vc-annotate will - ;; place the point in the line. - (min oldline (progn (goto-char (point-max)) - (forward-line -1) - (line-number-at-pos)))))))) - -(defun vc-annotate-compcar (threshold a-list) - "Test successive cons cells of A-LIST against THRESHOLD. -Return the first cons cell with a car that is not less than THRESHOLD, -nil if no such cell exists." - (let ((i 1) - (tmp-cons (car a-list))) - (while (and tmp-cons (< (car tmp-cons) threshold)) - (setq tmp-cons (car (nthcdr i a-list))) - (setq i (+ i 1))) - tmp-cons)) ; Return the appropriate value - -(defun vc-annotate-convert-time (time) - "Convert a time value to a floating-point number of days. -The argument TIME is a list as returned by `current-time' or -`encode-time', only the first two elements of that list are considered." - (/ (+ (* (float (car time)) (lsh 1 16)) (cadr time)) 24 3600)) - -(defun vc-annotate-difference (&optional offset) - "Return the time span in days to the next annotation. -This calls the backend function annotate-time, and returns the -difference in days between the time returned and the current time, -or OFFSET if present." - (let ((next-time (vc-annotate-get-time-set-line-props))) - (when next-time - (- (or offset - (vc-call-backend vc-annotate-backend 'annotate-current-time)) - next-time)))) - -(defun vc-default-annotate-current-time (backend) - "Return the current time, encoded as fractional days." - (vc-annotate-convert-time (current-time))) - -(defvar vc-annotate-offset nil) - -(defun vc-annotate-display (ratio &optional offset) - "Highlight `vc-annotate' output in the current buffer. -RATIO is the expansion that should be applied to `vc-annotate-color-map'. -The annotations are relative to the current time, unless overridden by OFFSET." - (when (/= ratio 1.0) - (set (make-local-variable 'vc-annotate-color-map) - (mapcar (lambda (elem) (cons (* (car elem) ratio) (cdr elem))) - vc-annotate-color-map))) - (set (make-local-variable 'vc-annotate-offset) offset) - (font-lock-mode 1)) - -(defun vc-annotate-lines (limit) - (while (< (point) limit) - (let ((difference (vc-annotate-difference vc-annotate-offset)) - (start (point)) - (end (progn (forward-line 1) (point)))) - (when difference - (let* ((color (or (vc-annotate-compcar difference vc-annotate-color-map) - (cons nil vc-annotate-very-old-color))) - ;; substring from index 1 to remove any leading `#' in the name - (face-name (concat "vc-annotate-face-" - (if (string-equal - (substring (cdr color) 0 1) "#") - (substring (cdr color) 1) - (cdr color)))) - ;; Make the face if not done. - (face (or (intern-soft face-name) - (let ((tmp-face (make-face (intern face-name)))) - (set-face-foreground tmp-face (cdr color)) - (when vc-annotate-background - (set-face-background tmp-face - vc-annotate-background)) - tmp-face)))) ; Return the face - (put-text-property start end 'face face))))) - ;; Pretend to font-lock there were no matches. - nil) - -(provide 'vc-annotate) - -;; arch-tag: c3454a89-80e5-4ffd-8993-671b59612898 -;;; vc-annotate.el ends here diff -r 681cd08dc0f7 -r e93288477c43 lisp/vc-arch.el --- a/lisp/vc-arch.el Thu Jun 10 22:43:47 2010 +0000 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,641 +0,0 @@ -;;; vc-arch.el --- VC backend for the Arch version-control system - -;; Copyright (C) 2004, 2005, 2006, 2007, 2008, 2009, 2010 -;; Free Software Foundation, Inc. - -;; Author: FSF (see vc.el for full credits) -;; Maintainer: Stefan Monnier - -;; 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 3 of the License, 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. If not, see . - -;;; Commentary: - -;; The home page of the Arch version control system is at -;; -;; http://www.gnuarch.org/ -;; -;; This is derived from vc-mcvs.el as follows: -;; - cp vc-mcvs.el vc-arch.el and then M-% mcvs RET arch RET -;; -;; Then of course started the hacking. -;; -;; What has been partly tested: -;; - Open a file. -;; - C-x v = without any prefix arg. -;; - C-x v v to commit a change to a single file. - -;; Bugs: - -;; - *VC-log*'s initial content lacks the `Summary:' lines. -;; - All files under the tree are considered as "under Arch's control" -;; without regards to =tagging-method and such. -;; - Files are always considered as `edited'. -;; - C-x v l does not work. -;; - C-x v i does not work. -;; - C-x v ~ does not work. -;; - C-x v u does not work. -;; - C-x v s does not work. -;; - C-x v r does not work. -;; - VC directory listings do not work. -;; - And more... - -;;; Code: - -(eval-when-compile (require 'vc) (require 'cl)) - -;;; Properties of the backend - -(defun vc-arch-revision-granularity () 'repository) -(defun vc-arch-checkout-model (files) 'implicit) - -;;; -;;; Customization options -;;; - -;; It seems Arch diff does not accept many options, so this is not -;; very useful. It exists mainly so that the VC backends are all -;; consistent with regards to their treatment of diff switches. -(defcustom vc-arch-diff-switches t - "String or list of strings specifying switches for Arch diff under VC. -If nil, use the value of `vc-diff-switches'. If t, use no switches." - :type '(choice (const :tag "Unspecified" nil) - (const :tag "None" t) - (string :tag "Argument String") - (repeat :tag "Argument List" :value ("") string)) - :version "23.1" - :group 'vc) - -(define-obsolete-variable-alias 'vc-arch-command 'vc-arch-program "23.1") - -(defcustom vc-arch-program - (let ((candidates '("tla" "baz"))) - (while (and candidates (not (executable-find (car candidates)))) - (setq candidates (cdr candidates))) - (or (car candidates) "tla")) - "Name of the Arch executable." - :type 'string - :group 'vc) - -;; Clear up the cache to force vc-call to check again and discover -;; new functions when we reload this file. -(put 'Arch 'vc-functions nil) - -;;;###autoload (defun vc-arch-registered (file) -;;;###autoload (if (vc-find-root file "{arch}/=tagging-method") -;;;###autoload (progn -;;;###autoload (load "vc-arch") -;;;###autoload (vc-arch-registered file)))) - -(defun vc-arch-add-tagline () - "Add an `arch-tag' to the end of the current file." - (interactive) - (comment-normalize-vars) - (goto-char (point-max)) - (forward-comment -1) - (skip-chars-forward " \t\n") - (cond - ((not (bolp)) (insert "\n\n")) - ((not (eq ?\n (char-before (1- (point))))) (insert "\n"))) - (let ((beg (point)) - (idfile (and buffer-file-name - (expand-file-name - (concat ".arch-ids/" - (file-name-nondirectory buffer-file-name) - ".id") - (file-name-directory buffer-file-name))))) - (insert "arch-tag: ") - (if (and idfile (file-exists-p idfile)) - ;; If the file is unreadable, we do want to get an error here. - (progn - (insert-file-contents idfile) - (forward-line 1) - (delete-file idfile)) - (condition-case nil - (call-process "uuidgen" nil t) - (file-error (insert (format "%s <%s> %s" - (current-time-string) - user-mail-address - (+ (nth 2 (current-time)) - (buffer-size))))))) - (comment-region beg (point)))) - -(defconst vc-arch-tagline-re "^\\W*arch-tag:[ \t]*\\(.*[^ \t\n]\\)") - -(defmacro vc-with-current-file-buffer (file &rest body) - (declare (indent 2) (debug t)) - `(let ((-kill-buf- nil) - (-file- ,file)) - (with-current-buffer (or (find-buffer-visiting -file-) - (setq -kill-buf- (generate-new-buffer " temp"))) - ;; Avoid find-file-literally since it can do many undesirable extra - ;; things (among which, call us back into an infinite loop). - (if -kill-buf- (insert-file-contents -file-)) - (unwind-protect - (progn ,@body) - (if (buffer-live-p -kill-buf-) (kill-buffer -kill-buf-)))))) - -(defun vc-arch-file-source-p (file) - "Can return nil, `maybe' or a non-nil value. -Only the value `maybe' can be trusted :-(." - ;; FIXME: Check the tag and name of parent dirs. - (unless (string-match "\\`[,+]" (file-name-nondirectory file)) - (or (string-match "\\`{arch}/" - (file-relative-name file (vc-arch-root file))) - (file-exists-p - ;; Check the presence of an ID file. - (expand-file-name - (concat ".arch-ids/" (file-name-nondirectory file) ".id") - (file-name-directory file))) - ;; Check the presence of a tagline. - (vc-with-current-file-buffer file - (save-excursion - (goto-char (point-max)) - (or (re-search-backward vc-arch-tagline-re (- (point) 1000) t) - (progn - (goto-char (point-min)) - (re-search-forward vc-arch-tagline-re (+ (point) 1000) t))))) - ;; FIXME: check =tagging-method to see whether untagged files might - ;; be source or not. - (with-current-buffer - (find-file-noselect (expand-file-name "{arch}/=tagging-method" - (vc-arch-root file))) - (let ((untagged-source t)) ;Default is `names'. - (save-excursion - (goto-char (point-min)) - (if (re-search-forward "^[ \t]*\\(\\(tagline\\|implicit\\|names\\)\\|explicit\\)" nil t) - (setq untagged-source (match-end 2))) - (if (re-search-forward "^[ \t]*untagged-source[ \t]+\\(\\(source\\)\\|precious\\|backup\\|junk\\|unrecognized\\)" nil t) - (setq untagged-source (match-end 2)))) - (if untagged-source 'maybe)))))) - -(defun vc-arch-file-id (file) - ;; Don't include the kind of ID this is because it seems to be too messy. - (let ((idfile (expand-file-name - (concat ".arch-ids/" (file-name-nondirectory file) ".id") - (file-name-directory file)))) - (if (file-exists-p idfile) - (with-temp-buffer - (insert-file-contents idfile) - (looking-at ".*[^ \n\t]") - (match-string 0)) - (with-current-buffer (find-file-noselect file) - (save-excursion - (goto-char (point-max)) - (if (or (re-search-backward vc-arch-tagline-re (- (point) 1000) t) - (progn - (goto-char (point-min)) - (re-search-forward vc-arch-tagline-re (+ (point) 1000) t))) - (match-string 1) - (concat "./" (file-relative-name file (vc-arch-root file))))))))) - -(defun vc-arch-tagging-method (file) - (with-current-buffer - (find-file-noselect - (expand-file-name "{arch}/=tagging-method" (vc-arch-root file))) - (save-excursion - (goto-char (point-min)) - (if (re-search-forward - "^[ \t]*\\(tagline\\|implicit\\|names\\|explicit\\)" nil t) - (intern (match-string 1)) - 'names)))) - -(defun vc-arch-root (file) - "Return the root directory of an Arch project, if any." - (or (vc-file-getprop file 'arch-root) - ;; Check the =tagging-method, in case someone naively manually - ;; creates a {arch} directory somewhere. - (let ((root (vc-find-root file "{arch}/=tagging-method"))) - (when root - (vc-file-setprop - file 'arch-root root))))) - -(defun vc-arch-register (files &optional rev comment) - (if rev (error "Explicit initial revision not supported for Arch")) - (dolist (file files) - (let ((tagmet (vc-arch-tagging-method file))) - (if (and (memq tagmet '(tagline implicit)) comment-start) - (with-current-buffer (find-file-noselect file) - (if (buffer-modified-p) - (error "Save %s first" (buffer-name))) - (vc-arch-add-tagline) - (save-buffer))))) - (vc-arch-command nil 0 files "add")) - -(defun vc-arch-registered (file) - ;; Don't seriously check whether it's source or not. Checking would - ;; require running TLA, so it's better to not do it, so it also works if - ;; TLA is not installed. - (and (vc-arch-root file) - (vc-arch-file-source-p file))) - -(defun vc-arch-default-version (file) - (or (vc-file-getprop (vc-arch-root file) 'arch-default-version) - (let* ((root (vc-arch-root file)) - (f (expand-file-name "{arch}/++default-version" root))) - (if (file-readable-p f) - (vc-file-setprop - root 'arch-default-version - (with-temp-buffer - (insert-file-contents f) - ;; Strip the terminating newline. - (buffer-substring (point-min) (1- (point-max))))))))) - -(defun vc-arch-workfile-unchanged-p (file) - "Stub: arch workfiles are always considered to be in a changed state," - nil) - -(defun vc-arch-state (file) - ;; There's no checkout operation and merging is not done from VC - ;; so the only operation that's state dependent that VC supports is commit - ;; which is only activated if the file is `edited'. - (let* ((root (vc-arch-root file)) - (ver (vc-arch-default-version file)) - (pat (concat "\\`" (subst-char-in-string ?/ ?% ver))) - (dir (expand-file-name ",,inode-sigs/" - (expand-file-name "{arch}" root))) - (sigfile nil)) - (dolist (f (if (file-directory-p dir) (directory-files dir t pat))) - (if (or (not sigfile) (file-newer-than-file-p f sigfile)) - (setq sigfile f))) - (if (not sigfile) - 'edited ;We know nothing. - (let ((id (vc-arch-file-id file))) - (setq id (replace-regexp-in-string "[ \t]" "_" id)) - (with-current-buffer (find-file-noselect sigfile) - (goto-char (point-min)) - (while (and (search-forward id nil 'move) - (save-excursion - (goto-char (- (match-beginning 0) 2)) - ;; For `names', the lines start with `?./foo/bar'. - ;; For others there's 2 chars before the ./foo/bar. - (or (not (or (bolp) (looking-at "\n?"))) - ;; Ignore E_ entries used for foo.id files. - (looking-at "E_"))))) - (if (eobp) - ;; ID not found. - (if (equal (file-name-nondirectory sigfile) - (subst-char-in-string - ?/ ?% (vc-arch-working-revision file))) - 'added - ;; Might be `added' or `up-to-date' as well. - ;; FIXME: Check in the patch logs to find out. - 'edited) - ;; Found the ID, let's check the inode. - (if (not (re-search-forward - "\t.*mtime=\\([0-9]+\\):size=\\([0-9]+\\)" - (line-end-position) t)) - ;; Buh? Unexpected format. - 'edited - (let ((ats (file-attributes file))) - (if (and (eq (nth 7 ats) (string-to-number (match-string 2))) - (equal (format-time-string "%s" (nth 5 ats)) - (match-string 1))) - 'up-to-date - 'edited))))))))) - -(defun vc-arch-dir-status (dir callback) - "Run 'tla inventory' for DIR and pass results to CALLBACK. -CALLBACK expects (ENTRIES &optional MORE-TO-COME); see -`vc-dir-refresh'." - (let ((default-directory dir)) - (vc-arch-command t 'async nil "changes")) - ;; The updating could be done asynchronously. - (vc-exec-after - `(vc-arch-after-dir-status ',callback))) - -(defun vc-arch-after-dir-status (callback) - (let* ((state-map '(("M " . edited) - ("Mb" . edited) ;binary - ("D " . removed) - ("D/" . removed) ;directory - ("A " . added) - ("A/" . added) ;directory - ("=>" . renamed) - ("/>" . renamed) ;directory - ("lf" . symlink-to-file) - ("fl" . file-to-symlink) - ("--" . permissions-changed) - ("-/" . permissions-changed) ;directory - )) - (state-map-regexp (regexp-opt (mapcar 'car state-map) t)) - (entry-regexp (concat "^" state-map-regexp " \\(.*\\)$")) - result) - (goto-char (point-min)) - ;;(message "Got %s" (buffer-string)) - (while (re-search-forward entry-regexp nil t) - (let* ((state-string (match-string 1)) - (state (cdr (assoc state-string state-map))) - (filename (match-string 2))) - (push (list filename state) result))) - - (funcall callback result nil))) - -(defun vc-arch-working-revision (file) - (let* ((root (expand-file-name "{arch}" (vc-arch-root file))) - (defbranch (vc-arch-default-version file))) - (when (and defbranch (string-match "\\`\\(.+@[^/\n]+\\)/\\(\\(\\(.*?\\)\\(?:--.*\\)?\\)--.*\\)\\'" defbranch)) - (let* ((archive (match-string 1 defbranch)) - (category (match-string 4 defbranch)) - (branch (match-string 3 defbranch)) - (version (match-string 2 defbranch)) - (sealed nil) (rev-nb 0) - (rev nil) - logdir tmp) - (setq logdir (expand-file-name category root)) - (setq logdir (expand-file-name branch logdir)) - (setq logdir (expand-file-name version logdir)) - (setq logdir (expand-file-name archive logdir)) - (setq logdir (expand-file-name "patch-log" logdir)) - (dolist (file (if (file-directory-p logdir) (directory-files logdir))) - ;; Revision names go: base-0, patch-N, version-0, versionfix-M. - (when (and (eq (aref file 0) ?v) (not sealed)) - (setq sealed t rev-nb 0)) - (if (and (string-match "-\\([0-9]+\\)\\'" file) - (setq tmp (string-to-number (match-string 1 file))) - (or (not sealed) (eq (aref file 0) ?v)) - (>= tmp rev-nb)) - (setq rev-nb tmp rev file))) - ;; Use "none-000" if the tree hasn't yet been committed on the - ;; default branch. We'll then get "Arch:000[branch]" on the mode-line. - (concat defbranch "--" (or rev "none-000")))))) - - -(defcustom vc-arch-mode-line-rewrite - '(("\\`.*--\\(.*--.*\\)--\\(v?\\).*-\\([0-9]+\\)\\'" . "\\2\\3[\\1]")) - "Rewrite rules to shorten Arch's revision names on the mode-line." - :type '(repeat (cons regexp string)) - :group 'vc) - -(defun vc-arch-mode-line-string (file) - "Return string for placement in modeline by `vc-mode-line' for FILE." - (let ((rev (vc-working-revision file))) - (dolist (rule vc-arch-mode-line-rewrite) - (if (string-match (car rule) rev) - (setq rev (replace-match (cdr rule) t nil rev)))) - (format "Arch%c%s" - (case (vc-state file) - ((up-to-date needs-update) ?-) - (added ?@) - (t ?:)) - rev))) - -(defun vc-arch-diff3-rej-p (rej) - (let ((attrs (file-attributes rej))) - (and attrs (< (nth 7 attrs) 60) - (with-temp-buffer - (insert-file-contents rej) - (goto-char (point-min)) - (looking-at "Conflicts occured, diff3 conflict markers left in file\\."))))) - -(defun vc-arch-delete-rej-if-obsolete () - "For use in `after-save-hook'." - (save-excursion - (let ((rej (concat buffer-file-name ".rej"))) - (when (and buffer-file-name (vc-arch-diff3-rej-p rej)) - (unless (re-search-forward "^<<<<<<< " nil t) - ;; The .rej file is obsolete. - (condition-case nil (delete-file rej) (error nil)) - ;; Remove the hook so that it is not called multiple times. - (remove-hook 'after-save-hook 'vc-arch-delete-rej-if-obsolete t)))))) - -(defun vc-arch-find-file-hook () - (let ((rej (concat buffer-file-name ".rej"))) - (when (and buffer-file-name (file-exists-p rej)) - (if (vc-arch-diff3-rej-p rej) - (save-excursion - (goto-char (point-min)) - (if (not (re-search-forward "^<<<<<<< " nil t)) - ;; The .rej file is obsolete. - (condition-case nil (delete-file rej) (error nil)) - (smerge-mode 1) - (add-hook 'after-save-hook - 'vc-arch-delete-rej-if-obsolete nil t) - (message "There are unresolved conflicts in this file"))) - (message "There are unresolved conflicts in %s" - (file-name-nondirectory rej)))))) - -(defun vc-arch-checkin (files rev comment &optional extra-args-ignored) - (if rev (error "Committing to a specific revision is unsupported")) - ;; FIXME: This implementation probably only works for singleton filesets - (let ((summary (file-relative-name (car files) (vc-arch-root (car files))))) - ;; Extract a summary from the comment. - (when (or (string-match "\\`Summary:[ \t]*\\(.*[^ \t\n]\\)\\([ \t]*\n\\)*" comment) - (string-match "\\`[ \t]*\\(.*[^ \t\n]\\)[ \t]*\\(\n?\\'\\|\n\\([ \t]*\n\\)+\\)" comment)) - (setq summary (match-string 1 comment)) - (setq comment (substring comment (match-end 0)))) - (vc-arch-command nil 0 files "commit" "-s" summary "-L" comment "--" - (vc-switches 'Arch 'checkin)))) - -(defun vc-arch-diff (files &optional oldvers newvers buffer) - "Get a difference report using Arch between two versions of FILES." - ;; FIXME: This implementation only works for singleton filesets. To make - ;; it work for more cases, we have to either call `file-diffs' manually on - ;; each and every `file' in the fileset, or use `changes --diffs' (and - ;; variants) and maybe filter the output with `filterdiff' to only include - ;; the files in which we're interested. - (let ((file (car files))) - (if (and newvers - (vc-up-to-date-p file) - (equal newvers (vc-working-revision file))) - ;; Newvers is the base revision and the current file is unchanged, - ;; so we can diff with the current file. - (setq newvers nil)) - (if newvers - (error "Diffing specific revisions not implemented") - (let* (process-file-side-effects - (async (not vc-disable-async-diff)) - ;; Run the command from the root dir. - (default-directory (vc-arch-root file)) - (status - (vc-arch-command - (or buffer "*vc-diff*") - (if async 'async 1) - nil "file-diffs" - (vc-switches 'Arch 'diff) - (file-relative-name file) - (if (equal oldvers (vc-working-revision file)) - nil - oldvers)))) - (if async 1 status))))) ; async diff, pessimistic assumption. - -(defun vc-arch-delete-file (file) - (vc-arch-command nil 0 file "rm")) - -(defun vc-arch-rename-file (old new) - (vc-arch-command nil 0 new "mv" (file-relative-name old))) - -(defalias 'vc-arch-responsible-p 'vc-arch-root) - -(defun vc-arch-command (buffer okstatus file &rest flags) - "A wrapper around `vc-do-command' for use in vc-arch.el." - (apply 'vc-do-command (or buffer "*vc*") okstatus vc-arch-program file flags)) - -(defun vc-arch-init-revision () nil) - -;;; Completion of versions and revisions. - -(defun vc-arch--version-completion-table (root string) - (delq nil - (mapcar - (lambda (d) - (when (string-match "/\\([^/]+\\)/\\([^/]+\\)\\'" d) - (concat (match-string 2 d) "/" (match-string 1 d)))) - (let ((default-directory root)) - (file-expand-wildcards - (concat "*/*/" - (if (string-match "/" string) - (concat (substring string (match-end 0)) - "*/" (substring string 0 (match-beginning 0))) - (concat "*/" string)) - "*")))))) - -(defun vc-arch-revision-completion-table (files) - (lexical-let ((files files)) - (lambda (string pred action) - ;; FIXME: complete revision patches as well. - (let* ((root (expand-file-name "{arch}" (vc-arch-root (car files)))) - (table (vc-arch--version-completion-table root string))) - (complete-with-action action table string pred))))) - -;;; Trimming revision libraries. - -;; This code is not directly related to VC and there are many variants of -;; this functionality available as scripts, but I like this version better, -;; so maybe others will like it too. - -(defun vc-arch-trim-find-least-useful-rev (revs) - (let* ((first (pop revs)) - (second (pop revs)) - (third (pop revs)) - ;; We try to give more importance to recent revisions. The idea is - ;; that it's OK if checking out a revision 1000-patch-old is ten - ;; times slower than checking out a revision 100-patch-old. But at - ;; the same time a 2-patch-old rev isn't really ten times more - ;; important than a 20-patch-old, so we use an arbitrary constant - ;; "100" to reduce this effect for recent revisions. Making this - ;; constant a float has the side effect of causing the subsequent - ;; computations to be done as floats as well. - (max (+ 100.0 (car (or (car (last revs)) third)))) - (cost (lambda () (/ (- (car third) (car first)) (- max (car second))))) - (minrev second) - (mincost (funcall cost))) - (while revs - (setq first second) - (setq second third) - (setq third (pop revs)) - (when (< (funcall cost) mincost) - (setq minrev second) - (setq mincost (funcall cost)))) - minrev)) - -(defun vc-arch-trim-make-sentinel (revs) - (if (null revs) (lambda (proc msg) (message "VC-Arch trimming ... done")) - (lexical-let ((revs revs)) - (lambda (proc msg) - (message "VC-Arch trimming %s..." (file-name-nondirectory (car revs))) - (rename-file (car revs) (concat (car revs) "*rm*")) - (setq proc (start-process "vc-arch-trim" nil - "rm" "-rf" (concat (car revs) "*rm*"))) - (set-process-sentinel proc (vc-arch-trim-make-sentinel (cdr revs))))))) - -(defun vc-arch-trim-one-revlib (dir) - "Delete half of the revisions in the revision library." - (interactive "Ddirectory: ") - (let ((garbage (directory-files dir 'full "\\`,," 'nosort))) - (when garbage - (funcall (vc-arch-trim-make-sentinel garbage) nil nil))) - (let ((revs - (sort (delq nil - (mapcar - (lambda (f) - (when (string-match "-\\([0-9]+\\)\\'" f) - (cons (string-to-number (match-string 1 f)) f))) - (directory-files dir nil nil 'nosort))) - 'car-less-than-car)) - (subdirs nil)) - (when (cddr revs) - (dotimes (i (/ (length revs) 2)) - (let ((minrev (vc-arch-trim-find-least-useful-rev revs))) - (setq revs (delq minrev revs)) - (push minrev subdirs))) - (funcall (vc-arch-trim-make-sentinel - (mapcar (lambda (x) (expand-file-name (cdr x) dir)) subdirs)) - nil nil)))) - -(defun vc-arch-trim-revlib () - "Delete half of the revisions in the revision library." - (interactive) - (let ((rl-dir (with-output-to-string - (call-process vc-arch-program nil standard-output nil - "my-revision-library")))) - (while (string-match "\\(.*\\)\n" rl-dir) - (let ((dir (match-string 1 rl-dir))) - (setq rl-dir - (if (and (file-directory-p dir) (file-writable-p dir)) - dir - (substring rl-dir (match-end 0)))))) - (unless (file-writable-p rl-dir) - (error "No writable revlib directory found")) - (message "Revlib at %s" rl-dir) - (let* ((archives (directory-files rl-dir 'full "[^.]\\|...")) - (categories - (apply 'append - (mapcar (lambda (dir) - (when (file-directory-p dir) - (directory-files dir 'full "[^.]\\|..."))) - archives))) - (branches - (apply 'append - (mapcar (lambda (dir) - (when (file-directory-p dir) - (directory-files dir 'full "[^.]\\|..."))) - categories))) - (versions - (apply 'append - (mapcar (lambda (dir) - (when (file-directory-p dir) - (directory-files dir 'full "--.*--"))) - branches)))) - (mapc 'vc-arch-trim-one-revlib versions)) - )) - -(defvar vc-arch-extra-menu-map - (let ((map (make-sparse-keymap))) - (define-key map [add-tagline] - '(menu-item "Add tagline" vc-arch-add-tagline)) - map)) - -(defun vc-arch-extra-menu () vc-arch-extra-menu-map) - - -;;; Less obvious implementations. - -(defun vc-arch-find-revision (file rev buffer) - (let ((out (make-temp-file "vc-out"))) - (unwind-protect - (progn - (with-temp-buffer - (vc-arch-command (current-buffer) 1 nil "file-diffs" file rev) - (call-process-region (point-min) (point-max) - "patch" nil nil nil "-R" "-o" out file)) - (with-current-buffer buffer - (insert-file-contents out))) - (delete-file out)))) - -(provide 'vc-arch) - -;; arch-tag: a35c7c1c-5237-429d-88ef-3d718fd2e704 -;;; vc-arch.el ends here diff -r 681cd08dc0f7 -r e93288477c43 lisp/vc-bzr.el --- a/lisp/vc-bzr.el Thu Jun 10 22:43:47 2010 +0000 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,1057 +0,0 @@ -;;; vc-bzr.el --- VC backend for the bzr revision control system - -;; Copyright (C) 2006, 2007, 2008, 2009, 2010 Free Software Foundation, Inc. - -;; Author: Dave Love -;; Riccardo Murri -;; Keywords: tools -;; Created: Sept 2006 -;; Version: 2008-01-04 (Bzr revno 25) -;; URL: http://launchpad.net/vc-bzr - -;; 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 3 of the License, 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. If not, see . - -;;; Commentary: - -;; See concerning bzr. See -;; for alternate development -;; branches of `vc-bzr'. - -;; Load this library to register bzr support in VC. - -;; Known bugs -;; ========== - -;; When editing a symlink and *both* the symlink and its target -;; are bzr-versioned, `vc-bzr` presently runs `bzr status` on the -;; symlink, thereby not detecting whether the actual contents -;; (that is, the target contents) are changed. -;; See https://bugs.launchpad.net/vc-bzr/+bug/116607 - -;; For an up-to-date list of bugs, please see: -;; https://bugs.launchpad.net/vc-bzr/+bugs - -;;; Properties of the backend - -(defun vc-bzr-revision-granularity () 'repository) -(defun vc-bzr-checkout-model (files) 'implicit) - -;;; Code: - -(eval-when-compile - (require 'cl) - (require 'vc) ;; for vc-exec-after - (require 'vc-dir)) - -;; Clear up the cache to force vc-call to check again and discover -;; new functions when we reload this file. -(put 'Bzr 'vc-functions nil) - -(defgroup vc-bzr nil - "VC bzr backend." - :version "22.2" - :group 'vc) - -(defcustom vc-bzr-program "bzr" - "Name of the bzr command (excluding any arguments)." - :group 'vc-bzr - :type 'string) - -(defcustom vc-bzr-diff-switches nil - "String or list of strings specifying switches for bzr diff under VC. -If nil, use the value of `vc-diff-switches'. If t, use no switches." - :type '(choice (const :tag "Unspecified" nil) - (const :tag "None" t) - (string :tag "Argument String") - (repeat :tag "Argument List" :value ("") string)) - :group 'vc-bzr) - -(defcustom vc-bzr-log-switches nil - "String or list of strings specifying switches for bzr log under VC." - :type '(choice (const :tag "None" nil) - (string :tag "Argument String") - (repeat :tag "Argument List" :value ("") string)) - :group 'vc-bzr) - -;; since v0.9, bzr supports removing the progress indicators -;; by setting environment variable BZR_PROGRESS_BAR to "none". -(defun vc-bzr-command (bzr-command buffer okstatus file-or-list &rest args) - "Wrapper round `vc-do-command' using `vc-bzr-program' as COMMAND. -Invoke the bzr command adding `BZR_PROGRESS_BAR=none' and -`LC_MESSAGES=C' to the environment." - (let ((process-environment - (list* "BZR_PROGRESS_BAR=none" ; Suppress progress output (bzr >=0.9) - "LC_MESSAGES=C" ; Force English output - process-environment))) - (apply 'vc-do-command (or buffer "*vc*") okstatus vc-bzr-program - file-or-list bzr-command args))) - - -;;;###autoload -(defconst vc-bzr-admin-dirname ".bzr" - "Name of the directory containing Bzr repository status files.") -;;;###autoload -(defconst vc-bzr-admin-checkout-format-file - (concat vc-bzr-admin-dirname "/checkout/format")) -(defconst vc-bzr-admin-dirstate - (concat vc-bzr-admin-dirname "/checkout/dirstate")) -(defconst vc-bzr-admin-branch-format-file - (concat vc-bzr-admin-dirname "/branch/format")) -(defconst vc-bzr-admin-revhistory - (concat vc-bzr-admin-dirname "/branch/revision-history")) -(defconst vc-bzr-admin-lastrev - (concat vc-bzr-admin-dirname "/branch/last-revision")) - -;;;###autoload (defun vc-bzr-registered (file) -;;;###autoload (if (vc-find-root file vc-bzr-admin-checkout-format-file) -;;;###autoload (progn -;;;###autoload (load "vc-bzr") -;;;###autoload (vc-bzr-registered file)))) - -(defun vc-bzr-root (file) - "Return the root directory of the bzr repository containing FILE." - ;; Cache technique copied from vc-arch.el. - (or (vc-file-getprop file 'bzr-root) - (let ((root (vc-find-root file vc-bzr-admin-checkout-format-file))) - (when root (vc-file-setprop file 'bzr-root root))))) - -(require 'sha1) ;For sha1-program - -(defun vc-bzr-sha1 (file) - (with-temp-buffer - (set-buffer-multibyte nil) - (let ((prog sha1-program) - (args nil) - process-file-side-effects) - (when (consp prog) - (setq args (cdr prog)) - (setq prog (car prog))) - (apply 'process-file prog (file-relative-name file) t nil args) - (buffer-substring (point-min) (+ (point-min) 40))))) - -(defun vc-bzr-state-heuristic (file) - "Like `vc-bzr-state' but hopefully without running Bzr." - ;; `bzr status' was excrutiatingly slow with large histories and - ;; pending merges, so try to avoid using it until they fix their - ;; performance problems. - ;; This function tries first to parse Bzr internal file - ;; `checkout/dirstate', but it may fail if Bzr internal file format - ;; has changed. As a safeguard, the `checkout/dirstate' file is - ;; only parsed if it contains the string `#bazaar dirstate flat - ;; format 3' in the first line. - ;; If the `checkout/dirstate' file cannot be parsed, fall back to - ;; running `vc-bzr-state'." - (lexical-let ((root (vc-bzr-root file))) - (when root ; Short cut. - ;; This looks at internal files. May break if they change - ;; their format. - (lexical-let ((dirstate (expand-file-name vc-bzr-admin-dirstate root))) - (condition-case nil - (with-temp-buffer - (insert-file-contents dirstate) - (goto-char (point-min)) - (if (not (looking-at "#bazaar dirstate flat format 3")) - (vc-bzr-state file) ; Some other unknown format? - (let* ((relfile (file-relative-name file root)) - (reldir (file-name-directory relfile))) - (if (re-search-forward - (concat "^\0" - (if reldir (regexp-quote - (directory-file-name reldir))) - "\0" - (regexp-quote (file-name-nondirectory relfile)) - "\0" - "[^\0]*\0" ;id? - "\\([^\0]*\\)\0" ;"a/f/d", a=removed? - "\\([^\0]*\\)\0" ;sha1 (empty if conflicted)? - "\\([^\0]*\\)\0" ;size?p - "[^\0]*\0" ;"y/n", executable? - "[^\0]*\0" ;? - "\\([^\0]*\\)\0" ;"a/f/d" a=added? - "\\([^\0]*\\)\0" ;sha1 again? - "\\([^\0]*\\)\0" ;size again? - "[^\0]*\0" ;"y/n", executable again? - "[^\0]*\0" ;last revid? - ;; There are more fields when merges are pending. - ) - nil t) - ;; Apparently the second sha1 is the one we want: when - ;; there's a conflict, the first sha1 is absent (and the - ;; first size seems to correspond to the file with - ;; conflict markers). - (cond - ((eq (char-after (match-beginning 1)) ?a) 'removed) - ((eq (char-after (match-beginning 4)) ?a) 'added) - ((or (and (eq (string-to-number (match-string 3)) - (nth 7 (file-attributes file))) - (equal (match-string 5) - (vc-bzr-sha1 file))) - (and - ;; It looks like for lightweight - ;; checkouts \2 is empty and we need to - ;; look for size in \6. - (eq (match-beginning 2) (match-end 2)) - (eq (string-to-number (match-string 6)) - (nth 7 (file-attributes file))) - (equal (match-string 5) - (vc-bzr-sha1 file)))) - 'up-to-date) - (t 'edited)) - 'unregistered)))) - ;; Either the dirstate file can't be read, or the sha1 - ;; executable is missing, or ... - ;; In either case, recent versions of Bzr aren't that slow - ;; any more. - (error (vc-bzr-state file))))))) - - -(defun vc-bzr-registered (file) - "Return non-nil if FILE is registered with bzr." - (let ((state (vc-bzr-state-heuristic file))) - (not (memq state '(nil unregistered ignored))))) - -(defconst vc-bzr-state-words - "added\\|ignored\\|kind changed\\|modified\\|removed\\|renamed\\|unknown" - "Regexp matching file status words as reported in `bzr' output.") - -(defun vc-bzr-file-name-relative (filename) - "Return file name FILENAME stripped of the initial Bzr repository path." - (lexical-let* - ((filename* (expand-file-name filename)) - (rootdir (vc-bzr-root filename*))) - (when rootdir - (file-relative-name filename* rootdir)))) - -(defun vc-bzr-status (file) - "Return FILE status according to Bzr. -Return value is a cons (STATUS . WARNING), where WARNING is a -string or nil, and STATUS is one of the symbols: `added', -`ignored', `kindchanged', `modified', `removed', `renamed', `unknown', -which directly correspond to `bzr status' output, or 'unchanged -for files whose copy in the working tree is identical to the one -in the branch repository, or nil for files that are not -registered with Bzr. - -If any error occurred in running `bzr status', then return nil." - (with-temp-buffer - (let ((ret (condition-case nil - (vc-bzr-command "status" t 0 file) - (file-error nil))) ; vc-bzr-program not found. - (status 'unchanged)) - ;; the only secure status indication in `bzr status' output - ;; is a couple of lines following the pattern:: - ;; | : - ;; | - ;; if the file is up-to-date, we get no status report from `bzr', - ;; so if the regexp search for the above pattern fails, we consider - ;; the file to be up-to-date. - (goto-char (point-min)) - (when (re-search-forward - ;; bzr prints paths relative to the repository root. - (concat "^\\(" vc-bzr-state-words "\\):[ \t\n]+" - (regexp-quote (vc-bzr-file-name-relative file)) - ;; Bzr appends a '/' to directory names and - ;; '*' to executable files - (if (file-directory-p file) "/?" "\\*?") - "[ \t\n]*$") - nil t) - (lexical-let ((statusword (match-string 1))) - ;; Erase the status text that matched. - (delete-region (match-beginning 0) (match-end 0)) - (setq status - (intern (replace-regexp-in-string " " "" statusword))))) - (when status - (goto-char (point-min)) - (skip-chars-forward " \n\t") ;Throw away spaces. - (cons status - ;; "bzr" will output warnings and informational messages to - ;; stderr; due to Emacs' `vc-do-command' (and, it seems, - ;; `start-process' itself) limitations, we cannot catch stderr - ;; and stdout into different buffers. So, if there's anything - ;; left in the buffer after removing the above status - ;; keywords, let us just presume that any other message from - ;; "bzr" is a user warning, and display it. - (unless (eobp) (buffer-substring (point) (point-max)))))))) - -(defun vc-bzr-state (file) - (lexical-let ((result (vc-bzr-status file))) - (when (consp result) - (when (cdr result) - (message "Warnings in `bzr' output: %s" (cdr result))) - (cdr (assq (car result) - '((added . added) - (kindchanged . edited) - (renamed . edited) - (modified . edited) - (removed . removed) - (ignored . ignored) - (unknown . unregistered) - (unchanged . up-to-date))))))) - -(defun vc-bzr-resolve-when-done () - "Call \"bzr resolve\" if the conflict markers have been removed." - (save-excursion - (goto-char (point-min)) - (unless (re-search-forward "^<<<<<<< " nil t) - (vc-bzr-command "resolve" nil 0 buffer-file-name) - ;; Remove the hook so that it is not called multiple times. - (remove-hook 'after-save-hook 'vc-bzr-resolve-when-done t)))) - -(defun vc-bzr-find-file-hook () - (when (and buffer-file-name - ;; FIXME: We should check that "bzr status" says "conflict". - (file-exists-p (concat buffer-file-name ".BASE")) - (file-exists-p (concat buffer-file-name ".OTHER")) - (file-exists-p (concat buffer-file-name ".THIS")) - ;; If "bzr status" says there's a conflict but there are no - ;; conflict markers, it's not clear what we should do. - (save-excursion - (goto-char (point-min)) - (re-search-forward "^<<<<<<< " nil t))) - ;; TODO: the merge algorithm used in `bzr merge' is nicely configurable, - ;; but the one in `bzr pull' isn't, so it would be good to provide an - ;; elisp function to remerge from the .BASE/OTHER/THIS files. - (smerge-start-session) - (add-hook 'after-save-hook 'vc-bzr-resolve-when-done nil t) - (message "There are unresolved conflicts in this file"))) - -(defun vc-bzr-workfile-unchanged-p (file) - (eq 'unchanged (car (vc-bzr-status file)))) - -(defun vc-bzr-working-revision (file) - ;; Together with the code in vc-state-heuristic, this makes it possible - ;; to get the initial VC state of a Bzr file even if Bzr is not installed. - (lexical-let* - ((rootdir (vc-bzr-root file)) - (branch-format-file (expand-file-name vc-bzr-admin-branch-format-file - rootdir)) - (revhistory-file (expand-file-name vc-bzr-admin-revhistory rootdir)) - (lastrev-file (expand-file-name vc-bzr-admin-lastrev rootdir))) - ;; This looks at internal files to avoid forking a bzr process. - ;; May break if they change their format. - (if (and (file-exists-p branch-format-file) - ;; For lightweight checkouts (obtained with bzr checkout --lightweight) - ;; the branch-format-file does not contain the revision - ;; information, we need to look up the branch-format-file - ;; in the place where the lightweight checkout comes - ;; from. We only do that if it's a local file. - (let ((location-fname (expand-file-name - (concat vc-bzr-admin-dirname - "/branch/location") rootdir))) - ;; The existence of this file is how we distinguish - ;; lightweight checkouts. - (if (file-exists-p location-fname) - (with-temp-buffer - (insert-file-contents location-fname) - ;; If the lightweight checkout points to a - ;; location in the local file system, then we can - ;; look there for the version information. - (when (re-search-forward "file://\\(.+\\)" nil t) - (let ((l-c-parent-dir (match-string 1))) - (when (and (memq system-type '(ms-dos windows-nt)) - (string-match-p "^/[[:alpha:]]:" l-c-parent-dir)) - ;;; The non-Windows code takes a shortcut by using the host/path - ;;; separator slash as the start of the absolute path. That - ;;; does not work on Windows, so we must remove it (bug#5345) - (setq l-c-parent-dir (substring l-c-parent-dir 1))) - (setq branch-format-file - (expand-file-name vc-bzr-admin-branch-format-file - l-c-parent-dir)) - (setq lastrev-file - (expand-file-name vc-bzr-admin-lastrev l-c-parent-dir)) - ;; FIXME: maybe it's overkill to check if both these files exist. - (and (file-exists-p branch-format-file) - (file-exists-p lastrev-file))))) - t))) - (with-temp-buffer - (insert-file-contents branch-format-file) - (goto-char (point-min)) - (cond - ((or - (looking-at "Bazaar-NG branch, format 0.0.4") - (looking-at "Bazaar-NG branch format 5")) - ;; count lines in .bzr/branch/revision-history - (insert-file-contents revhistory-file) - (number-to-string (count-lines (line-end-position) (point-max)))) - ((or - (looking-at "Bazaar Branch Format 6 (bzr 0.15)") - (looking-at "Bazaar Branch Format 7 (needs bzr 1.6)")) - ;; revno is the first number in .bzr/branch/last-revision - (insert-file-contents lastrev-file) - (when (re-search-forward "[0-9]+" nil t) - (buffer-substring (match-beginning 0) (match-end 0)))))) - ;; fallback to calling "bzr revno" - (lexical-let* - ((result (vc-bzr-command-discarding-stderr - vc-bzr-program "revno" (file-relative-name file))) - (exitcode (car result)) - (output (cdr result))) - (cond - ((eq exitcode 0) (substring output 0 -1)) - (t nil)))))) - -(defun vc-bzr-create-repo () - "Create a new Bzr repository." - (vc-bzr-command "init" nil 0 nil)) - -(defun vc-bzr-init-revision (&optional file) - "Always return nil, as Bzr cannot register explicit versions." - nil) - -(defun vc-bzr-previous-revision (file rev) - (if (string-match "\\`[0-9]+\\'" rev) - (number-to-string (1- (string-to-number rev))) - (concat "before:" rev))) - -(defun vc-bzr-next-revision (file rev) - (if (string-match "\\`[0-9]+\\'" rev) - (number-to-string (1+ (string-to-number rev))) - (error "Don't know how to compute the next revision of %s" rev))) - -(defun vc-bzr-register (files &optional rev comment) - "Register FILE under bzr. -Signal an error unless REV is nil. -COMMENT is ignored." - (if rev (error "Can't register explicit revision with bzr")) - (vc-bzr-command "add" nil 0 files)) - -;; Could run `bzr status' in the directory and see if it succeeds, but -;; that's relatively expensive. -(defalias 'vc-bzr-responsible-p 'vc-bzr-root - "Return non-nil if FILE is (potentially) controlled by bzr. -The criterion is that there is a `.bzr' directory in the same -or a superior directory.") - -(defun vc-bzr-could-register (file) - "Return non-nil if FILE could be registered under bzr." - (and (vc-bzr-responsible-p file) ; shortcut - (condition-case () - (with-temp-buffer - (vc-bzr-command "add" t 0 file "--dry-run") - ;; The command succeeds with no output if file is - ;; registered (in bzr 0.8). - (goto-char (point-min)) - (looking-at "added ")) - (error)))) - -(defun vc-bzr-unregister (file) - "Unregister FILE from bzr." - (vc-bzr-command "remove" nil 0 file "--keep")) - -(declare-function log-edit-extract-headers "log-edit" (headers string)) - -(defun vc-bzr-checkin (files rev comment) - "Check FILE in to bzr with log message COMMENT. -REV non-nil gets an error." - (if rev (error "Can't check in a specific revision with bzr")) - (apply 'vc-bzr-command "commit" nil 0 - files (cons "-m" (log-edit-extract-headers '(("Author" . "--author") - ("Date" . "--commit-time") - ("Fixes" . "--fixes")) - comment)))) - -(defun vc-bzr-find-revision (file rev buffer) - "Fetch revision REV of file FILE and put it into BUFFER." - (with-current-buffer buffer - (if (and rev (stringp rev) (not (string= rev ""))) - (vc-bzr-command "cat" t 0 file "-r" rev) - (vc-bzr-command "cat" t 0 file)))) - -(defun vc-bzr-checkout (file &optional editable rev) - (if rev (error "Operation not supported") - ;; Else, there's nothing to do. - nil)) - -(defun vc-bzr-revert (file &optional contents-done) - (unless contents-done - (with-temp-buffer (vc-bzr-command "revert" t 0 file)))) - -(defvar log-view-message-re) -(defvar log-view-file-re) -(defvar log-view-font-lock-keywords) -(defvar log-view-current-tag-function) -(defvar log-view-per-file-logs) - -(define-derived-mode vc-bzr-log-view-mode log-view-mode "Bzr-Log-View" - (remove-hook 'log-view-mode-hook 'vc-bzr-log-view-mode) ;Deactivate the hack. - (require 'add-log) - (set (make-local-variable 'log-view-per-file-logs) nil) - (set (make-local-variable 'log-view-file-re) "\\`a\\`") - (set (make-local-variable 'log-view-message-re) - (if (eq vc-log-view-type 'short) - "^ *\\([0-9.]+\\): \\(.*?\\)[ \t]+\\([0-9]\\{4\\}-[0-9]\\{2\\}-[0-9]\\{2\\}\\)\\( \\[merge\\]\\)?" - "^ *\\(?:revno: \\([0-9.]+\\)\\|merged: .+\\)")) - (set (make-local-variable 'log-view-font-lock-keywords) - ;; log-view-font-lock-keywords is careful to use the buffer-local - ;; value of log-view-message-re only since Emacs-23. - (if (eq vc-log-view-type 'short) - (append `((,log-view-message-re - (1 'log-view-message-face) - (2 'change-log-name) - (3 'change-log-date) - (4 'change-log-list nil lax)))) - (append `((,log-view-message-re . 'log-view-message-face)) - ;; log-view-font-lock-keywords - '(("^ *\\(?:committer\\|author\\): \ -\\([^<(]+?\\)[ ]*[(<]\\([[:alnum:]_.+-]+@[[:alnum:]_.-]+\\)[>)]" - (1 'change-log-name) - (2 'change-log-email)) - ("^ *timestamp: \\(.*\\)" (1 'change-log-date-face))))))) - -(defun vc-bzr-print-log (files buffer &optional shortlog start-revision limit) - "Get bzr change log for FILES into specified BUFFER." - ;; `vc-do-command' creates the buffer, but we need it before running - ;; the command. - (vc-setup-buffer buffer) - ;; If the buffer exists from a previous invocation it might be - ;; read-only. - ;; FIXME: `vc-bzr-command' runs `bzr log' with `LC_MESSAGES=C', so - ;; the log display may not what the user wants - but I see no other - ;; way of getting the above regexps working. - (with-current-buffer buffer - (apply 'vc-bzr-command "log" buffer 'async files - (append - (when shortlog '("--line")) - (when start-revision (list (format "-r..%s" start-revision))) - (when limit (list "-l" (format "%s" limit))) - (if (stringp vc-bzr-log-switches) - (list vc-bzr-log-switches) - vc-bzr-log-switches))))) - -(defun vc-bzr-log-incoming (buffer remote-location) - (apply 'vc-bzr-command "missing" buffer 'async nil - (list "--theirs-only" (unless (string= remote-location "") remote-location)))) - -(defun vc-bzr-log-outgoing (buffer remote-location) - (apply 'vc-bzr-command "missing" buffer 'async nil - (list "--mine-only" (unless (string= remote-location "") remote-location)))) - -(defun vc-bzr-show-log-entry (revision) - "Find entry for patch name REVISION in bzr change log buffer." - (goto-char (point-min)) - (when revision - (let (case-fold-search - found) - (if (re-search-forward - ;; "revno:" can appear either at the beginning of a line, - ;; or indented. - (concat "^[ ]*-+\n[ ]*revno: " - ;; The revision can contain ".", quote it so that it - ;; does not interfere with regexp matching. - (regexp-quote revision) "$") nil t) - (progn - (beginning-of-line 0) - (setq found t)) - (goto-char (point-min))) - found))) - -(defun vc-bzr-diff (files &optional rev1 rev2 buffer) - "VC bzr backend for diff." - ;; `bzr diff' exits with code 1 if diff is non-empty. - (apply #'vc-bzr-command "diff" (or buffer "*vc-diff*") - (if vc-disable-async-diff 1 'async) files - "--diff-options" (mapconcat 'identity - (vc-switches 'bzr 'diff) - " ") - ;; This `when' is just an optimization because bzr-1.2 is *much* - ;; faster when the revision argument is not given. - (when (or rev1 rev2) - (list "-r" (format "%s..%s" - (or rev1 "revno:-1") - (or rev2 "")))))) - - -;; FIXME: vc-{next,previous}-revision need fixing in vc.el to deal with -;; straight integer revisions. - -(defun vc-bzr-delete-file (file) - "Delete FILE and delete it in the bzr repository." - (condition-case () - (delete-file file) - (file-error nil)) - (vc-bzr-command "remove" nil 0 file)) - -(defun vc-bzr-rename-file (old new) - "Rename file from OLD to NEW using `bzr mv'." - (vc-bzr-command "mv" nil 0 new old)) - -(defvar vc-bzr-annotation-table nil - "Internal use.") -(make-variable-buffer-local 'vc-bzr-annotation-table) - -(defun vc-bzr-annotate-command (file buffer &optional revision) - "Prepare BUFFER for `vc-annotate' on FILE. -Each line is tagged with the revision number, which has a `help-echo' -property containing author and date information." - (apply #'vc-bzr-command "annotate" buffer 'async file "--long" "--all" - (if revision (list "-r" revision))) - (lexical-let ((table (make-hash-table :test 'equal))) - (set-process-filter - (get-buffer-process buffer) - (lambda (proc string) - (when (process-buffer proc) - (with-current-buffer (process-buffer proc) - (setq string (concat (process-get proc :vc-left-over) string)) - (while (string-match "^\\( *[0-9.]+ *\\) \\([^\n ]+\\) +\\([0-9]\\{8\\}\\)\\( |.*\n\\)" string) - (let* ((rev (match-string 1 string)) - (author (match-string 2 string)) - (date (match-string 3 string)) - (key (substring string (match-beginning 0) - (match-beginning 4))) - (line (match-string 4 string)) - (tag (gethash key table)) - (inhibit-read-only t)) - (setq string (substring string (match-end 0))) - (unless tag - (setq tag - (propertize - (format "%s %-7.7s" rev author) - 'help-echo (format "Revision: %d, author: %s, date: %s" - (string-to-number rev) - author date) - 'mouse-face 'highlight)) - (puthash key tag table)) - (goto-char (process-mark proc)) - (insert tag line) - (move-marker (process-mark proc) (point)))) - (process-put proc :vc-left-over string))))))) - -(declare-function vc-annotate-convert-time "vc-annotate" (time)) - -(defun vc-bzr-annotate-time () - (when (re-search-forward "^ *[0-9.]+ +[^\n ]* +|" nil t) - (let ((prop (get-text-property (line-beginning-position) 'help-echo))) - (string-match "[0-9]+\\'" prop) - (let ((str (match-string-no-properties 0 prop))) - (vc-annotate-convert-time - (encode-time 0 0 0 - (string-to-number (substring str 6 8)) - (string-to-number (substring str 4 6)) - (string-to-number (substring str 0 4)))))))) - -(defun vc-bzr-annotate-extract-revision-at-line () - "Return revision for current line of annoation buffer, or nil. -Return nil if current line isn't annotated." - (save-excursion - (beginning-of-line) - (if (looking-at "^ *\\([0-9.]+\\) +[^\n ]* +|") - (match-string-no-properties 1)))) - -(defun vc-bzr-command-discarding-stderr (command &rest args) - "Execute shell command COMMAND (with ARGS); return its output and exitcode. -Return value is a cons (EXITCODE . OUTPUT), where EXITCODE is -the (numerical) exit code of the process, and OUTPUT is a string -containing whatever the process sent to its standard output -stream. Standard error output is discarded." - (with-temp-buffer - (cons - (apply #'process-file command nil (list (current-buffer) nil) nil args) - (buffer-substring (point-min) (point-max))))) - -(defstruct (vc-bzr-extra-fileinfo - (:copier nil) - (:constructor vc-bzr-create-extra-fileinfo (extra-name)) - (:conc-name vc-bzr-extra-fileinfo->)) - extra-name) ;; original name for rename targets, new name for - -(defun vc-bzr-dir-printer (info) - "Pretty-printer for the vc-dir-fileinfo structure." - (let ((extra (vc-dir-fileinfo->extra info))) - (vc-default-dir-printer 'Bzr info) - (when extra - (insert (propertize - (format " (renamed from %s)" - (vc-bzr-extra-fileinfo->extra-name extra)) - 'face 'font-lock-comment-face))))) - -;; FIXME: this needs testing, it's probably incomplete. -(defun vc-bzr-after-dir-status (update-function relative-dir) - (let ((status-str nil) - (translation '(("+N " . added) - ("-D " . removed) - (" M " . edited) ;; file text modified - (" *" . edited) ;; execute bit changed - (" M*" . edited) ;; text modified + execute bit changed - ;; FIXME: what about ignored files? - (" D " . missing) - ;; For conflicts, should we list the .THIS/.BASE/.OTHER? - ("C " . conflict) - ("? " . unregistered) - ;; No such state, but we need to distinguish this case. - ("R " . renamed) - ("RM " . renamed) - ;; For a non existent file FOO, the output is: - ;; bzr: ERROR: Path(s) do not exist: FOO - ("bzr" . not-found) - ;; If the tree is not up to date, bzr will print this warning: - ;; working tree is out of date, run 'bzr update' - ;; ignore it. - ;; FIXME: maybe this warning can be put in the vc-dir header... - ("wor" . not-found) - ;; Ignore "P " and "P." for pending patches. - ("P " . not-found) - ("P. " . not-found) - )) - (translated nil) - (result nil)) - (goto-char (point-min)) - (while (not (eobp)) - (setq status-str - (buffer-substring-no-properties (point) (+ (point) 3))) - (setq translated (cdr (assoc status-str translation))) - (cond - ((eq translated 'conflict) - ;; For conflicts the file appears twice in the listing: once - ;; with the M flag and once with the C flag, so take care - ;; not to add it twice to `result'. Ugly. - (let* ((file - (buffer-substring-no-properties - ;;For files with conflicts the format is: - ;;C Text conflict in FILENAME - ;; Bah. - (+ (point) 21) (line-end-position))) - (entry (assoc file result))) - (when entry - (setf (nth 1 entry) 'conflict)))) - ((eq translated 'renamed) - (re-search-forward "R[ M] \\(.*\\) => \\(.*\\)$" (line-end-position) t) - (let ((new-name (file-relative-name (match-string 2) relative-dir)) - (old-name (file-relative-name (match-string 1) relative-dir))) - (push (list new-name 'edited - (vc-bzr-create-extra-fileinfo old-name)) result))) - ;; do nothing for non existent files - ((eq translated 'not-found)) - (t - (push (list (file-relative-name - (buffer-substring-no-properties - (+ (point) 4) - (line-end-position)) relative-dir) - translated) result))) - (forward-line)) - (funcall update-function result))) - -(defun vc-bzr-dir-status (dir update-function) - "Return a list of conses (file . state) for DIR." - (vc-bzr-command "status" (current-buffer) 'async dir "-v" "-S") - (vc-exec-after - `(vc-bzr-after-dir-status (quote ,update-function) - ;; "bzr status" results are relative to - ;; the bzr root directory, NOT to the - ;; directory "bzr status" was invoked in. - ;; Ugh. - ;; We pass the relative directory here so - ;; that `vc-bzr-after-dir-status' can - ;; frob the results accordingly. - (file-relative-name ,dir (vc-bzr-root ,dir))))) - -(defun vc-bzr-dir-status-files (dir files default-state update-function) - "Return a list of conses (file . state) for DIR." - (apply 'vc-bzr-command "status" (current-buffer) 'async dir "-v" "-S" files) - (vc-exec-after - `(vc-bzr-after-dir-status (quote ,update-function) - (file-relative-name ,dir (vc-bzr-root ,dir))))) - -(defvar vc-bzr-shelve-map - (let ((map (make-sparse-keymap))) - ;; Turn off vc-dir marking - (define-key map [mouse-2] 'ignore) - - (define-key map [down-mouse-3] 'vc-bzr-shelve-menu) - (define-key map "\C-k" 'vc-bzr-shelve-delete-at-point) - (define-key map "=" 'vc-bzr-shelve-show-at-point) - (define-key map "\C-m" 'vc-bzr-shelve-show-at-point) - (define-key map "A" 'vc-bzr-shelve-apply-and-keep-at-point) - (define-key map "P" 'vc-bzr-shelve-apply-at-point) - (define-key map "S" 'vc-bzr-shelve-snapshot) - map)) - -(defvar vc-bzr-shelve-menu-map - (let ((map (make-sparse-keymap "Bzr Shelve"))) - (define-key map [de] - '(menu-item "Delete shelf" vc-bzr-shelve-delete-at-point - :help "Delete the current shelf")) - (define-key map [ap] - '(menu-item "Apply and keep shelf" vc-bzr-shelve-apply-and-keep-at-point - :help "Apply the current shelf and keep it")) - (define-key map [po] - '(menu-item "Apply and remove shelf (pop)" vc-bzr-shelve-apply-at-point - :help "Apply the current shelf and remove it")) - (define-key map [sh] - '(menu-item "Show shelve" vc-bzr-shelve-show-at-point - :help "Show the contents of the current shelve")) - map)) - -(defvar vc-bzr-extra-menu-map - (let ((map (make-sparse-keymap))) - (define-key map [bzr-sn] - '(menu-item "Shelve a snapshot" vc-bzr-shelve-snapshot - :help "Shelve the current state of the tree and keep the current state")) - (define-key map [bzr-sh] - '(menu-item "Shelve..." vc-bzr-shelve - :help "Shelve changes")) - map)) - -(defun vc-bzr-extra-menu () vc-bzr-extra-menu-map) - -(defun vc-bzr-extra-status-menu () vc-bzr-extra-menu-map) - -(defun vc-bzr-dir-extra-headers (dir) - (let* - ((str (with-temp-buffer - (vc-bzr-command "info" t 0 dir) - (buffer-string))) - (shelve (vc-bzr-shelve-list)) - (shelve-help-echo "Use M-x vc-bzr-shelve to create shelves") - (root-dir (vc-bzr-root dir)) - (pending-merge - ;; FIXME: looking for .bzr/checkout/merge-hashes is not a - ;; reliable method to detect pending merges, disable this - ;; until a proper solution is implemented. - (and nil - (file-exists-p - (expand-file-name ".bzr/checkout/merge-hashes" root-dir)))) - (pending-merge-help-echo - (format "A merge has been performed.\nA commit from the top-level directory (%s)\nis required before being able to check in anything else" root-dir)) - (light-checkout - (when (string-match ".+light checkout root: \\(.+\\)$" str) - (match-string 1 str))) - (light-checkout-branch - (when light-checkout - (when (string-match ".+checkout of branch: \\(.+\\)$" str) - (match-string 1 str))))) - (concat - (propertize "Parent branch : " 'face 'font-lock-type-face) - (propertize - (if (string-match "parent branch: \\(.+\\)$" str) - (match-string 1 str) - "None") - 'face 'font-lock-variable-name-face) - "\n" - (when light-checkout - (concat - (propertize "Light checkout root: " 'face 'font-lock-type-face) - (propertize light-checkout 'face 'font-lock-variable-name-face) - "\n")) - (when light-checkout-branch - (concat - (propertize "Checkout of branch : " 'face 'font-lock-type-face) - (propertize light-checkout-branch 'face 'font-lock-variable-name-face) - "\n")) - (when pending-merge - (concat - (propertize "Warning : " 'face 'font-lock-warning-face - 'help-echo pending-merge-help-echo) - (propertize "Pending merges, commit recommended before any other action" - 'help-echo pending-merge-help-echo - 'face 'font-lock-warning-face) - "\n")) - (if shelve - (concat - (propertize "Shelves :\n" 'face 'font-lock-type-face - 'help-echo shelve-help-echo) - (mapconcat - (lambda (x) - (propertize x - 'face 'font-lock-variable-name-face - 'mouse-face 'highlight - 'help-echo "mouse-3: Show shelve menu\nA: Apply and keep shelf\nP: Apply and remove shelf (pop)\nS: Snapshot to a shelf\nC-k: Delete shelf" - 'keymap vc-bzr-shelve-map)) - shelve "\n")) - (concat - (propertize "Shelves : " 'face 'font-lock-type-face - 'help-echo shelve-help-echo) - (propertize "No shelved changes" - 'help-echo shelve-help-echo - 'face 'font-lock-variable-name-face)))))) - -(defun vc-bzr-shelve (name) - "Create a shelve." - (interactive "sShelf name: ") - (let ((root (vc-bzr-root default-directory))) - (when root - (vc-bzr-command "shelve" nil 0 nil "--all" "-m" name) - (vc-resynch-buffer root t t)))) - -(defun vc-bzr-shelve-show (name) - "Show the contents of shelve NAME." - (interactive "sShelve name: ") - (vc-setup-buffer "*vc-diff*") - ;; FIXME: how can you show the contents of a shelf? - (vc-bzr-command "unshelve" "*vc-diff*" 'async nil "--preview" name) - (set-buffer "*vc-diff*") - (diff-mode) - (setq buffer-read-only t) - (pop-to-buffer (current-buffer))) - -(defun vc-bzr-shelve-apply (name) - "Apply shelve NAME and remove it afterwards." - (interactive "sApply (and remove) shelf: ") - (vc-bzr-command "unshelve" nil 0 nil "--apply" name) - (vc-resynch-buffer (vc-bzr-root default-directory) t t)) - -(defun vc-bzr-shelve-apply-and-keep (name) - "Apply shelve NAME and keep it afterwards." - (interactive "sApply (and keep) shelf: ") - (vc-bzr-command "unshelve" nil 0 nil "--apply" "--keep" name) - (vc-resynch-buffer (vc-bzr-root default-directory) t t)) - -(defun vc-bzr-shelve-snapshot () - "Create a stash with the current tree state." - (interactive) - (vc-bzr-command "shelve" nil 0 nil "--all" "-m" - (let ((ct (current-time))) - (concat - (format-time-string "Snapshot on %Y-%m-%d" ct) - (format-time-string " at %H:%M" ct)))) - (vc-bzr-command "unshelve" nil 0 nil "--apply" "--keep") - (vc-resynch-buffer (vc-bzr-root default-directory) t t)) - -(defun vc-bzr-shelve-list () - (with-temp-buffer - (vc-bzr-command "shelve" (current-buffer) 1 nil "--list" "-q") - (delete - "" - (split-string - (buffer-substring (point-min) (point-max)) - "\n")))) - -(defun vc-bzr-shelve-get-at-point (point) - (save-excursion - (goto-char point) - (beginning-of-line) - (if (looking-at "^ +\\([0-9]+\\):") - (match-string 1) - (error "Cannot find shelf at point")))) - -(defun vc-bzr-shelve-delete-at-point () - (interactive) - (let ((shelve (vc-bzr-shelve-get-at-point (point)))) - (when (y-or-n-p (format "Remove shelf %s ?" shelve)) - (vc-bzr-command "unshelve" nil 0 nil "--delete-only" shelve) - (vc-dir-refresh)))) - -(defun vc-bzr-shelve-show-at-point () - (interactive) - (vc-bzr-shelve-show (vc-bzr-shelve-get-at-point (point)))) - -(defun vc-bzr-shelve-apply-at-point () - (interactive) - (vc-bzr-shelve-apply (vc-bzr-shelve-get-at-point (point)))) - -(defun vc-bzr-shelve-apply-and-keep-at-point () - (interactive) - (vc-bzr-shelve-apply-and-keep (vc-bzr-shelve-get-at-point (point)))) - -(defun vc-bzr-shelve-menu (e) - (interactive "e") - (vc-dir-at-event e (popup-menu vc-bzr-shelve-menu-map e))) - -(defun vc-bzr-revision-table (files) - (let ((vc-bzr-revisions '()) - (default-directory (file-name-directory (car files)))) - (with-temp-buffer - (vc-bzr-command "log" t 0 files "--line") - (let ((start (point-min)) - (loglines (buffer-substring-no-properties (point-min) (point-max)))) - (while (string-match "^\\([0-9]+\\):" loglines) - (push (match-string 1 loglines) vc-bzr-revisions) - (setq start (+ start (match-end 0))) - (setq loglines (buffer-substring-no-properties start (point-max)))))) - vc-bzr-revisions)) - -(defun vc-bzr-conflicted-files (dir) - (let ((default-directory (vc-bzr-root dir)) - (files ())) - (with-temp-buffer - (vc-bzr-command "status" t 0 default-directory) - (goto-char (point-min)) - (when (re-search-forward "^conflicts:\n" nil t) - (while (looking-at " \\(?:Text conflict in \\(.*\\)\\|.*\\)\n") - (if (match-end 1) - (push (expand-file-name (match-string 1)) files)) - (goto-char (match-end 0))))) - files)) - -;;; Revision completion - -(eval-and-compile - (defconst vc-bzr-revision-keywords - '("revno" "revid" "last" "before" - "tag" "date" "ancestor" "branch" "submit"))) - -(defun vc-bzr-revision-completion-table (files) - (lexical-let ((files files)) - ;; What about using `files'?!? --Stef - (lambda (string pred action) - (cond - ((string-match "\\`\\(ancestor\\|branch\\|\\(revno:\\)?[-0-9]+:\\):" - string) - (completion-table-with-context (substring string 0 (match-end 0)) - (apply-partially - 'completion-table-with-predicate - 'completion-file-name-table - 'file-directory-p t) - (substring string (match-end 0)) - pred - action)) - ((string-match "\\`\\(before\\):" string) - (completion-table-with-context (substring string 0 (match-end 0)) - (vc-bzr-revision-completion-table files) - (substring string (match-end 0)) - pred - action)) - ((string-match "\\`\\(tag\\):" string) - (let ((prefix (substring string 0 (match-end 0))) - (tag (substring string (match-end 0))) - (table nil) - process-file-side-effects) - (with-temp-buffer - ;; "bzr-1.2 tags" is much faster with --show-ids. - (process-file vc-bzr-program nil '(t) nil "tags" "--show-ids") - ;; The output is ambiguous, unless we assume that revids do not - ;; contain spaces. - (goto-char (point-min)) - (while (re-search-forward "^\\(.*[^ \n]\\) +[^ \n]*$" nil t) - (push (match-string-no-properties 1) table))) - (completion-table-with-context prefix table tag pred action))) - - ((string-match "\\`\\([a-z]+\\):" string) - ;; no actual completion for the remaining keywords. - (completion-table-with-context (substring string 0 (match-end 0)) - (if (member (match-string 1 string) - vc-bzr-revision-keywords) - ;; If it's a valid keyword, - ;; use a non-empty table to - ;; indicate it. - '("") nil) - (substring string (match-end 0)) - pred - action)) - (t - ;; Could use completion-table-with-terminator, except that it - ;; currently doesn't work right w.r.t pcm and doesn't give - ;; the *Completions* output we want. - (complete-with-action action (eval-when-compile - (mapcar (lambda (s) (concat s ":")) - vc-bzr-revision-keywords)) - string pred)))))) - -(eval-after-load "vc" - '(add-to-list 'vc-directory-exclusion-list vc-bzr-admin-dirname t)) - -(provide 'vc-bzr) -;; arch-tag: 8101bad8-4e92-4e7d-85ae-d8e08b4e7c06 -;;; vc-bzr.el ends here diff -r 681cd08dc0f7 -r e93288477c43 lisp/vc-cvs.el --- a/lisp/vc-cvs.el Thu Jun 10 22:43:47 2010 +0000 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,1213 +0,0 @@ -;;; vc-cvs.el --- non-resident support for CVS version-control - -;; Copyright (C) 1995, 1998, 1999, 2000, 2001, 2002, 2003, 2004, 2005, -;; 2006, 2007, 2008, 2009, 2010 Free Software Foundation, Inc. - -;; Author: FSF (see vc.el for full credits) -;; Maintainer: Andre Spiegel - -;; 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 3 of the License, 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. If not, see . - -;;; Commentary: - -;;; Code: - -(eval-when-compile (require 'cl) (require 'vc)) - -;; Clear up the cache to force vc-call to check again and discover -;; new functions when we reload this file. -(put 'CVS 'vc-functions nil) - -;;; Properties of the backend. - -(defun vc-cvs-revision-granularity () 'file) - -(defun vc-cvs-checkout-model (files) - "CVS-specific version of `vc-checkout-model'." - (if (getenv "CVSREAD") - 'announce - (let* ((file (if (consp files) (car files) files)) - (attrib (file-attributes file))) - (or (vc-file-getprop file 'vc-checkout-model) - (vc-file-setprop - file 'vc-checkout-model - (if (and attrib ;; don't check further if FILE doesn't exist - ;; If the file is not writable (despite CVSREAD being - ;; undefined), this is probably because the file is being - ;; "watched" by other developers. - ;; (If vc-mistrust-permissions was t, we actually shouldn't - ;; trust this, but there is no other way to learn this from - ;; CVS at the moment (version 1.9).) - (string-match "r-..-..-." (nth 8 attrib))) - 'announce - 'implicit)))))) - -;;; -;;; Customization options -;;; - -(defcustom vc-cvs-global-switches nil - "Global switches to pass to any CVS command." - :type '(choice (const :tag "None" nil) - (string :tag "Argument String") - (repeat :tag "Argument List" - :value ("") - string)) - :version "22.1" - :group 'vc) - -(defcustom vc-cvs-register-switches nil - "Switches for registering a file into CVS. -A string or list of strings passed to the checkin program by -\\[vc-register]. If nil, use the value of `vc-register-switches'. -If t, use no switches." - :type '(choice (const :tag "Unspecified" nil) - (const :tag "None" t) - (string :tag "Argument String") - (repeat :tag "Argument List" :value ("") string)) - :version "21.1" - :group 'vc) - -(defcustom vc-cvs-diff-switches nil - "String or list of strings specifying switches for CVS diff under VC. -If nil, use the value of `vc-diff-switches'. If t, use no switches." - :type '(choice (const :tag "Unspecified" nil) - (const :tag "None" t) - (string :tag "Argument String") - (repeat :tag "Argument List" :value ("") string)) - :version "21.1" - :group 'vc) - -(defcustom vc-cvs-header (or (cdr (assoc 'CVS vc-header-alist)) '("\$Id\$")) - "Header keywords to be inserted by `vc-insert-headers'." - :version "21.1" - :type '(repeat string) - :group 'vc) - -(defcustom vc-cvs-use-edit t - "Non-nil means to use `cvs edit' to \"check out\" a file. -This is only meaningful if you don't use the implicit checkout model -\(i.e. if you have $CVSREAD set)." - :type 'boolean - :version "21.1" - :group 'vc) - -(defcustom vc-cvs-stay-local 'only-file - "Non-nil means use local operations when possible for remote repositories. -This avoids slow queries over the network and instead uses heuristics -and past information to determine the current status of a file. - -If value is the symbol `only-file' `vc-dir' will connect to the -server, but heuristics will be used to determine the status for -all other VC operations. - -The value can also be a regular expression or list of regular -expressions to match against the host name of a repository; then VC -only stays local for hosts that match it. Alternatively, the value -can be a list of regular expressions where the first element is the -symbol `except'; then VC always stays local except for hosts matched -by these regular expressions." - :type '(choice (const :tag "Always stay local" t) - (const :tag "Only for file operations" only-file) - (const :tag "Don't stay local" nil) - (list :format "\nExamine hostname and %v" - :tag "Examine hostname ..." - (set :format "%v" :inline t - (const :format "%t" :tag "don't" except)) - (regexp :format " stay local,\n%t: %v" - :tag "if it matches") - (repeat :format "%v%i\n" :inline t (regexp :tag "or")))) - :version "23.1" - :group 'vc) - -(defcustom vc-cvs-sticky-date-format-string "%c" - "Format string for mode-line display of sticky date. -Format is according to `format-time-string'. Only used if -`vc-cvs-sticky-tag-display' is t." - :type '(string) - :version "22.1" - :group 'vc) - -(defcustom vc-cvs-sticky-tag-display t - "Specify the mode-line display of sticky tags. -Value t means default display, nil means no display at all. If the -value is a function or macro, it is called with the sticky tag and -its' type as parameters, in that order. TYPE can have three different -values: `symbolic-name' (TAG is a string), `revision-number' (TAG is a -string) and `date' (TAG is a date as returned by `encode-time'). The -return value of the function or macro will be displayed as a string. - -Here's an example that will display the formatted date for sticky -dates and the word \"Sticky\" for sticky tag names and revisions. - - (lambda (tag type) - (cond ((eq type 'date) (format-time-string - vc-cvs-sticky-date-format-string tag)) - ((eq type 'revision-number) \"Sticky\") - ((eq type 'symbolic-name) \"Sticky\"))) - -Here's an example that will abbreviate to the first character only, -any text before the first occurrence of `-' for sticky symbolic tags. -If the sticky tag is a revision number, the word \"Sticky\" is -displayed. Date and time is displayed for sticky dates. - - (lambda (tag type) - (cond ((eq type 'date) (format-time-string \"%Y%m%d %H:%M\" tag)) - ((eq type 'revision-number) \"Sticky\") - ((eq type 'symbolic-name) - (condition-case nil - (progn - (string-match \"\\\\([^-]*\\\\)\\\\(.*\\\\)\" tag) - (concat (substring (match-string 1 tag) 0 1) \":\" - (substring (match-string 2 tag) 1 nil))) - (error tag))))) ; Fall-back to given tag name. - -See also variable `vc-cvs-sticky-date-format-string'." - :type '(choice boolean function) - :version "22.1" - :group 'vc) - -;;; -;;; Internal variables -;;; - - -;;; -;;; State-querying functions -;;; - -;;;###autoload (defun vc-cvs-registered (f) -;;;###autoload (when (file-readable-p (expand-file-name -;;;###autoload "CVS/Entries" (file-name-directory f))) -;;;###autoload (load "vc-cvs") -;;;###autoload (vc-cvs-registered f))) - -(defun vc-cvs-registered (file) - "Check if FILE is CVS registered." - (let ((dirname (or (file-name-directory file) "")) - (basename (file-name-nondirectory file)) - ;; make sure that the file name is searched case-sensitively - (case-fold-search nil)) - (if (file-readable-p (expand-file-name "CVS/Entries" dirname)) - (or (string= basename "") - (with-temp-buffer - (vc-cvs-get-entries dirname) - (goto-char (point-min)) - (cond ((re-search-forward - (concat "^/" (regexp-quote basename) "/[^/]") nil t) - (beginning-of-line) - (vc-cvs-parse-entry file) - t) - (t nil)))) - nil))) - -(defun vc-cvs-state (file) - "CVS-specific version of `vc-state'." - (if (vc-stay-local-p file 'CVS) - (let ((state (vc-file-getprop file 'vc-state))) - ;; If we should stay local, use the heuristic but only if - ;; we don't have a more precise state already available. - (if (memq state '(up-to-date edited nil)) - (vc-cvs-state-heuristic file) - state)) - (with-temp-buffer - (cd (file-name-directory file)) - (let (process-file-side-effects) - (vc-cvs-command t 0 file "status")) - (vc-cvs-parse-status t)))) - -(defun vc-cvs-state-heuristic (file) - "CVS-specific state heuristic." - ;; If the file has not changed since checkout, consider it `up-to-date'. - ;; Otherwise consider it `edited'. - (let ((checkout-time (vc-file-getprop file 'vc-checkout-time)) - (lastmod (nth 5 (file-attributes file)))) - (cond - ((equal checkout-time lastmod) 'up-to-date) - ((string= (vc-working-revision file) "0") 'added) - ((null checkout-time) 'unregistered) - (t 'edited)))) - -(defun vc-cvs-working-revision (file) - "CVS-specific version of `vc-working-revision'." - ;; There is no need to consult RCS headers under CVS, because we - ;; get the workfile version for free when we recognize that a file - ;; is registered in CVS. - (vc-cvs-registered file) - (vc-file-getprop file 'vc-working-revision)) - -(defun vc-cvs-mode-line-string (file) - "Return string for placement into the modeline for FILE. -Compared to the default implementation, this function does two things: -Handle the special case of a CVS file that is added but not yet -committed and support display of sticky tags." - (let* ((sticky-tag (vc-file-getprop file 'vc-cvs-sticky-tag)) - help-echo - (string - (let ((def-ml (vc-default-mode-line-string 'CVS file))) - (setq help-echo - (get-text-property 0 'help-echo def-ml)) - def-ml))) - (propertize - (if (zerop (length sticky-tag)) - string - (setq help-echo (format "%s on the '%s' branch" - help-echo sticky-tag)) - (concat string "[" sticky-tag "]")) - 'help-echo help-echo))) - - -;;; -;;; State-changing functions -;;; - -(defun vc-cvs-register (files &optional rev comment) - "Register FILES into the CVS version-control system. -COMMENT can be used to provide an initial description of FILES. -Passes either `vc-cvs-register-switches' or `vc-register-switches' -to the CVS command." - ;; Register the directories if needed. - (let (dirs) - (dolist (file files) - (and (not (vc-cvs-responsible-p file)) - (vc-cvs-could-register file) - (push (directory-file-name (file-name-directory file)) dirs))) - (if dirs (vc-cvs-register dirs))) - (apply 'vc-cvs-command nil 0 files - "add" - (and comment (string-match "[^\t\n ]" comment) - (concat "-m" comment)) - (vc-switches 'CVS 'register))) - -(defun vc-cvs-responsible-p (file) - "Return non-nil if CVS thinks it is responsible for FILE." - (file-directory-p (expand-file-name "CVS" - (if (file-directory-p file) - file - (file-name-directory file))))) - -(defun vc-cvs-could-register (file) - "Return non-nil if FILE could be registered in CVS. -This is only possible if CVS is managing FILE's directory or one of -its parents." - (let ((dir file)) - (while (and (stringp dir) - (not (equal dir (setq dir (file-name-directory dir)))) - dir) - (setq dir (if (file-exists-p - (expand-file-name "CVS/Entries" dir)) - t - (directory-file-name dir)))) - (eq dir t))) - -(defun vc-cvs-checkin (files rev comment &optional extra-args-ignored) - "CVS-specific version of `vc-backend-checkin'." - (unless (or (not rev) (vc-cvs-valid-revision-number-p rev)) - (if (not (vc-cvs-valid-symbolic-tag-name-p rev)) - (error "%s is not a valid symbolic tag name" rev) - ;; If the input revison is a valid symbolic tag name, we create it - ;; as a branch, commit and switch to it. - (apply 'vc-cvs-command nil 0 files "tag" "-b" (list rev)) - (apply 'vc-cvs-command nil 0 files "update" "-r" (list rev)) - (mapc (lambda (file) (vc-file-setprop file 'vc-cvs-sticky-tag rev)) - files))) - (let ((status (apply 'vc-cvs-command nil 1 files - "ci" (if rev (concat "-r" rev)) - (concat "-m" comment) - (vc-switches 'CVS 'checkin)))) - (set-buffer "*vc*") - (goto-char (point-min)) - (when (not (zerop status)) - ;; Check checkin problem. - (cond - ((re-search-forward "Up-to-date check failed" nil t) - (mapc (lambda (file) (vc-file-setprop file 'vc-state 'needs-merge)) - files) - (error "%s" (substitute-command-keys - (concat "Up-to-date check failed: " - "type \\[vc-next-action] to merge in changes")))) - (t - (pop-to-buffer (current-buffer)) - (goto-char (point-min)) - (shrink-window-if-larger-than-buffer) - (error "Check-in failed")))) - ;; Single-file commit? Then update the revision by parsing the buffer. - ;; Otherwise we can't necessarily tell what goes with what; clear - ;; its properties so they have to be refetched. - (if (= (length files) 1) - (vc-file-setprop - (car files) 'vc-working-revision - (vc-parse-buffer "^\\(new\\|initial\\) revision: \\([0-9.]+\\)" 2)) - (mapc 'vc-file-clearprops files)) - ;; Anyway, forget the checkout model of the file, because we might have - ;; guessed wrong when we found the file. After commit, we can - ;; tell it from the permissions of the file (see - ;; vc-cvs-checkout-model). - (mapc (lambda (file) (vc-file-setprop file 'vc-checkout-model nil)) - files) - - ;; if this was an explicit check-in (does not include creation of - ;; a branch), remove the sticky tag. - (if (and rev (not (vc-cvs-valid-symbolic-tag-name-p rev))) - (vc-cvs-command nil 0 files "update" "-A")))) - -(defun vc-cvs-find-revision (file rev buffer) - (apply 'vc-cvs-command - buffer 0 file - "-Q" ; suppress diagnostic output - "update" - (and rev (not (string= rev "")) - (concat "-r" rev)) - "-p" - (vc-switches 'CVS 'checkout))) - -(defun vc-cvs-checkout (file &optional editable rev) - "Checkout a revision of FILE into the working area. -EDITABLE non-nil means that the file should be writable. -REV is the revision to check out." - (message "Checking out %s..." file) - ;; Change buffers to get local value of vc-checkout-switches. - (with-current-buffer (or (get-file-buffer file) (current-buffer)) - (if (and (file-exists-p file) (not rev)) - ;; If no revision was specified, just make the file writable - ;; if necessary (using `cvs-edit' if requested). - (and editable (not (eq (vc-cvs-checkout-model (list file)) 'implicit)) - (if vc-cvs-use-edit - (vc-cvs-command nil 0 file "edit") - (set-file-modes file (logior (file-modes file) 128)) - (if (equal file buffer-file-name) (toggle-read-only -1)))) - ;; Check out a particular revision (or recreate the file). - (vc-file-setprop file 'vc-working-revision nil) - (apply 'vc-cvs-command nil 0 file - (and editable "-w") - "update" - (when rev - (unless (eq rev t) - ;; default for verbose checkout: clear the - ;; sticky tag so that the actual update will - ;; get the head of the trunk - (if (string= rev "") - "-A" - (concat "-r" rev)))) - (vc-switches 'CVS 'checkout))) - (vc-mode-line file 'CVS)) - (message "Checking out %s...done" file)) - -(defun vc-cvs-delete-file (file) - (vc-cvs-command nil 0 file "remove" "-f")) - -(defun vc-cvs-revert (file &optional contents-done) - "Revert FILE to the working revision on which it was based." - (vc-default-revert 'CVS file contents-done) - (unless (eq (vc-cvs-checkout-model (list file)) 'implicit) - (if vc-cvs-use-edit - (vc-cvs-command nil 0 file "unedit") - ;; Make the file read-only by switching off all w-bits - (set-file-modes file (logand (file-modes file) 3950))))) - -(defun vc-cvs-merge (file first-revision &optional second-revision) - "Merge changes into current working copy of FILE. -The changes are between FIRST-REVISION and SECOND-REVISION." - (vc-cvs-command nil 0 file - "update" "-kk" - (concat "-j" first-revision) - (concat "-j" second-revision)) - (vc-file-setprop file 'vc-state 'edited) - (with-current-buffer (get-buffer "*vc*") - (goto-char (point-min)) - (if (re-search-forward "conflicts during merge" nil t) - (progn - (vc-file-setprop file 'vc-state 'conflict) - ;; signal error - 1) - (vc-file-setprop file 'vc-state 'edited) - ;; signal success - 0))) - -(defun vc-cvs-merge-news (file) - "Merge in any new changes made to FILE." - (message "Merging changes into %s..." file) - ;; (vc-file-setprop file 'vc-working-revision nil) - (vc-file-setprop file 'vc-checkout-time 0) - (vc-cvs-command nil nil file "update") - ;; Analyze the merge result reported by CVS, and set - ;; file properties accordingly. - (with-current-buffer (get-buffer "*vc*") - (goto-char (point-min)) - ;; get new working revision - (if (re-search-forward - "^Merging differences between [0-9.]* and \\([0-9.]*\\) into" nil t) - (vc-file-setprop file 'vc-working-revision (match-string 1)) - (vc-file-setprop file 'vc-working-revision nil)) - ;; get file status - (prog1 - (if (eq (buffer-size) 0) - 0 ;; there were no news; indicate success - (if (re-search-forward - (concat "^\\([CMUP] \\)?" - (regexp-quote - (substring file (1+ (length (expand-file-name - "." default-directory))))) - "\\( already contains the differences between \\)?") - nil t) - (cond - ;; Merge successful, we are in sync with repository now - ((or (match-string 2) - (string= (match-string 1) "U ") - (string= (match-string 1) "P ")) - (vc-file-setprop file 'vc-state 'up-to-date) - (vc-file-setprop file 'vc-checkout-time - (nth 5 (file-attributes file))) - 0);; indicate success to the caller - ;; Merge successful, but our own changes are still in the file - ((string= (match-string 1) "M ") - (vc-file-setprop file 'vc-state 'edited) - 0);; indicate success to the caller - ;; Conflicts detected! - (t - (vc-file-setprop file 'vc-state 'conflict) - 1);; signal the error to the caller - ) - (pop-to-buffer "*vc*") - (error "Couldn't analyze cvs update result"))) - (message "Merging changes into %s...done" file)))) - -(defun vc-cvs-modify-change-comment (files rev comment) - "Modify the change comments for FILES on a specified REV. -Will fail unless you have administrative privileges on the repo." - (vc-cvs-command nil 0 files "admin" (concat "-m" rev ":" comment))) - -;;; -;;; History functions -;;; - -(declare-function vc-rcs-print-log-cleanup "vc-rcs" ()) - -(defun vc-cvs-print-log (files buffer &optional shortlog start-revision-ignored limit) - "Get change logs associated with FILES." - (require 'vc-rcs) - ;; It's just the catenation of the individual logs. - (vc-cvs-command - buffer - (if (vc-stay-local-p files 'CVS) 'async 0) - files "log") - (with-current-buffer buffer - (vc-exec-after (vc-rcs-print-log-cleanup))) - (when limit 'limit-unsupported)) - -(defun vc-cvs-comment-history (file) - "Get comment history of a file." - (vc-call-backend 'RCS 'comment-history file)) - -(defun vc-cvs-diff (files &optional oldvers newvers buffer) - "Get a difference report using CVS between two revisions of FILE." - (let* (process-file-side-effects - (async (and (not vc-disable-async-diff) - (vc-stay-local-p files 'CVS))) - (invoke-cvs-diff-list nil) - status) - ;; Look through the file list and see if any files have backups - ;; that can be used to do a plain "diff" instead of "cvs diff". - (dolist (file files) - (let ((ov oldvers) - (nv newvers)) - (when (or (not ov) (string-equal ov "")) - (setq ov (vc-working-revision file))) - (when (string-equal nv "") - (setq nv nil)) - (let ((file-oldvers (vc-version-backup-file file ov)) - (file-newvers (if (not nv) - file - (vc-version-backup-file file nv))) - (coding-system-for-read (vc-coding-system-for-diff file))) - (if (and file-oldvers file-newvers) - (progn - ;; This used to append diff-switches and vc-diff-switches, - ;; which was consistent with the vc-diff-switches doc at that - ;; time, but not with the actual behavior of any other VC diff. - (apply 'vc-do-command (or buffer "*vc-diff*") 1 "diff" nil - ;; Not a CVS diff, does not use vc-cvs-diff-switches. - (append (vc-switches nil 'diff) - (list (file-relative-name file-oldvers) - (file-relative-name file-newvers)))) - (setq status 0)) - (push file invoke-cvs-diff-list))))) - (when invoke-cvs-diff-list - (setq status (apply 'vc-cvs-command (or buffer "*vc-diff*") - (if async 'async 1) - invoke-cvs-diff-list "diff" - (and oldvers (concat "-r" oldvers)) - (and newvers (concat "-r" newvers)) - (vc-switches 'CVS 'diff)))) - (if async 1 status))) ; async diff, pessimistic assumption - -(defconst vc-cvs-annotate-first-line-re "^[0-9]") - -(defun vc-cvs-annotate-process-filter (process string) - (setq string (concat (process-get process 'output) string)) - (if (not (string-match vc-cvs-annotate-first-line-re string)) - ;; Still waiting for the first real line. - (process-put process 'output string) - (let ((vc-filter (process-get process 'vc-filter))) - (set-process-filter process vc-filter) - (funcall vc-filter process (substring string (match-beginning 0)))))) - -(defun vc-cvs-annotate-command (file buffer &optional revision) - "Execute \"cvs annotate\" on FILE, inserting the contents in BUFFER. -Optional arg REVISION is a revision to annotate from." - (vc-cvs-command buffer - (if (vc-stay-local-p file 'CVS) - 'async 0) - file "annotate" - (if revision (concat "-r" revision))) - ;; Strip the leading few lines. - (let ((proc (get-buffer-process buffer))) - (if proc - ;; If running asynchronously, use a process filter. - (progn - (process-put proc 'vc-filter (process-filter proc)) - (set-process-filter proc 'vc-cvs-annotate-process-filter)) - (with-current-buffer buffer - (goto-char (point-min)) - (re-search-forward vc-cvs-annotate-first-line-re) - (delete-region (point-min) (1- (point))))))) - -(declare-function vc-annotate-convert-time "vc-annotate" (time)) - -(defun vc-cvs-annotate-current-time () - "Return the current time, based at midnight of the current day, and -encoded as fractional days." - (vc-annotate-convert-time - (apply 'encode-time 0 0 0 (nthcdr 3 (decode-time (current-time)))))) - -(defun vc-cvs-annotate-time () - "Return the time of the next annotation (as fraction of days) -systime, or nil if there is none." - (let* ((bol (point)) - (cache (get-text-property bol 'vc-cvs-annotate-time)) - (inhibit-read-only t) - (inhibit-modification-hooks t)) - (cond - (cache) - ((looking-at - "^\\S-+\\s-+\\S-+\\s-+\\([0-9]+\\)-\\(\\sw+\\)-\\([0-9]+\\)): ") - (let ((day (string-to-number (match-string 1))) - (month (cdr (assq (intern (match-string 2)) - '((Jan . 1) (Feb . 2) (Mar . 3) - (Apr . 4) (May . 5) (Jun . 6) - (Jul . 7) (Aug . 8) (Sep . 9) - (Oct . 10) (Nov . 11) (Dec . 12))))) - (year (let ((tmp (string-to-number (match-string 3)))) - ;; Years 0..68 are 2000..2068. - ;; Years 69..99 are 1969..1999. - (+ (cond ((> 69 tmp) 2000) - ((> 100 tmp) 1900) - (t 0)) - tmp)))) - (put-text-property - bol (1+ bol) 'vc-cvs-annotate-time - (setq cache (cons - ;; Position at end makes for nicer overlay result. - ;; Don't put actual buffer pos here, but only relative - ;; distance, so we don't ever move backward in the - ;; goto-char below, even if the text is moved. - (- (match-end 0) (match-beginning 0)) - (vc-annotate-convert-time - (encode-time 0 0 0 day month year)))))))) - (when cache - (goto-char (+ bol (car cache))) ; Fontify from here to eol. - (cdr cache)))) ; days (float) - -(defun vc-cvs-annotate-extract-revision-at-line () - (save-excursion - (beginning-of-line) - (if (re-search-forward "^\\([0-9]+\\.[0-9]+\\(\\.[0-9]+\\)*\\) +(" - (line-end-position) t) - (match-string-no-properties 1) - nil))) - -(defun vc-cvs-previous-revision (file rev) - (vc-call-backend 'RCS 'previous-revision file rev)) - -(defun vc-cvs-next-revision (file rev) - (vc-call-backend 'RCS 'next-revision file rev)) - -;; FIXME: This should probably be replaced by code using cvs2cl. -(defun vc-cvs-update-changelog (files) - (vc-call-backend 'RCS 'update-changelog files)) - -;;; -;;; Tag system -;;; - -(defun vc-cvs-create-tag (dir name branchp) - "Assign to DIR's current revision a given NAME. -If BRANCHP is non-nil, the name is created as a branch (and the current -workspace is immediately moved to that new branch)." - (vc-cvs-command nil 0 dir "tag" "-c" (if branchp "-b") name) - (when branchp (vc-cvs-command nil 0 dir "update" "-r" name))) - -(defun vc-cvs-retrieve-tag (dir name update) - "Retrieve a tag at and below DIR. -NAME is the name of the tag; if it is empty, do a `cvs update'. -If UPDATE is non-nil, then update (resynch) any affected buffers." - (with-current-buffer (get-buffer-create "*vc*") - (let ((default-directory dir) - (sticky-tag)) - (erase-buffer) - (if (or (not name) (string= name "")) - (vc-cvs-command t 0 nil "update") - (vc-cvs-command t 0 nil "update" "-r" name) - (setq sticky-tag name)) - (when update - (goto-char (point-min)) - (while (not (eobp)) - (if (looking-at "\\([CMUP]\\) \\(.*\\)") - (let* ((file (expand-file-name (match-string 2) dir)) - (state (match-string 1)) - (buffer (find-buffer-visiting file))) - (when buffer - (cond - ((or (string= state "U") - (string= state "P")) - (vc-file-setprop file 'vc-state 'up-to-date) - (vc-file-setprop file 'vc-working-revision nil) - (vc-file-setprop file 'vc-checkout-time - (nth 5 (file-attributes file)))) - ((or (string= state "M") - (string= state "C")) - (vc-file-setprop file 'vc-state 'edited) - (vc-file-setprop file 'vc-working-revision nil) - (vc-file-setprop file 'vc-checkout-time 0))) - (vc-file-setprop file 'vc-cvs-sticky-tag sticky-tag) - (vc-resynch-buffer file t t)))) - (forward-line 1)))))) - - -;;; -;;; Miscellaneous -;;; - -(defun vc-cvs-make-version-backups-p (file) - "Return non-nil if version backups should be made for FILE." - (vc-stay-local-p file 'CVS)) - -(defun vc-cvs-check-headers () - "Check if the current file has any headers in it." - (save-excursion - (goto-char (point-min)) - (re-search-forward "\\$[A-Za-z\300-\326\330-\366\370-\377]+\ -\\(: [\t -#%-\176\240-\377]*\\)?\\$" nil t))) - - -;;; -;;; Internal functions -;;; - -(defun vc-cvs-command (buffer okstatus files &rest flags) - "A wrapper around `vc-do-command' for use in vc-cvs.el. -The difference to vc-do-command is that this function always invokes `cvs', -and that it passes `vc-cvs-global-switches' to it before FLAGS." - (apply 'vc-do-command (or buffer "*vc*") okstatus "cvs" files - (if (stringp vc-cvs-global-switches) - (cons vc-cvs-global-switches flags) - (append vc-cvs-global-switches - flags)))) - -(defun vc-cvs-stay-local-p (file) ;Back-compatibility. - (vc-stay-local-p file 'CVS)) - -(defun vc-cvs-repository-hostname (dirname) - "Hostname of the CVS server associated to workarea DIRNAME." - (let ((rootname (expand-file-name "CVS/Root" dirname))) - (when (file-readable-p rootname) - (with-temp-buffer - (let ((coding-system-for-read - (or file-name-coding-system - default-file-name-coding-system))) - (vc-insert-file rootname)) - (goto-char (point-min)) - (nth 2 (vc-cvs-parse-root - (buffer-substring (point) - (line-end-position)))))))) - -(defun vc-cvs-parse-uhp (path) - "parse user@host/path into (user@host /path)" - (if (string-match "\\([^/]+\\)\\(/.*\\)" path) - (list (match-string 1 path) (match-string 2 path)) - (list nil path))) - -(defun vc-cvs-parse-root (root) - "Split CVS ROOT specification string into a list of fields. -A CVS root specification of the form - [:METHOD:][[USER@]HOSTNAME]:?/path/to/repository -is converted to a normalized record with the following structure: - \(METHOD USER HOSTNAME CVS-ROOT). -The default METHOD for a CVS root of the form - /path/to/repository -is `local'. -The default METHOD for a CVS root of the form - [USER@]HOSTNAME:/path/to/repository -is `ext'. -For an empty string, nil is returned (invalid CVS root)." - ;; Split CVS root into colon separated fields (0-4). - ;; The `x:' makes sure, that leading colons are not lost; - ;; `HOST:/PATH' is then different from `:METHOD:/PATH'. - (let* ((root-list (cdr (split-string (concat "x:" root) ":"))) - (len (length root-list)) - ;; All syntactic varieties will get a proper METHOD. - (root-list - (cond - ((= len 0) - ;; Invalid CVS root - nil) - ((= len 1) - (let ((uhp (vc-cvs-parse-uhp (car root-list)))) - (cons (if (car uhp) "ext" "local") uhp))) - ((= len 2) - ;; [USER@]HOST:PATH => method `ext' - (and (not (equal (car root-list) "")) - (cons "ext" root-list))) - ((= len 3) - ;; :METHOD:PATH or :METHOD:USER@HOSTNAME/PATH - (cons (cadr root-list) - (vc-cvs-parse-uhp (caddr root-list)))) - (t - ;; :METHOD:[USER@]HOST:PATH - (cdr root-list))))) - (if root-list - (let ((method (car root-list)) - (uhost (or (cadr root-list) "")) - (root (nth 2 root-list)) - user host) - ;; Split USER@HOST - (if (string-match "\\(.*\\)@\\(.*\\)" uhost) - (setq user (match-string 1 uhost) - host (match-string 2 uhost)) - (setq host uhost)) - ;; Remove empty HOST - (and (equal host "") - (setq host)) - ;; Fix windows style CVS root `:local:C:\\project\\cvs\\some\\dir' - (and host - (equal method "local") - (setq root (concat host ":" root) host)) - ;; Normalize CVS root record - (list method user host root))))) - -;; XXX: This does not work correctly for subdirectories. "cvs status" -;; information is context sensitive, it contains lines like: -;; cvs status: Examining DIRNAME -;; and the file entries after that don't show the full path. -;; Because of this VC directory listings only show changed files -;; at the top level for CVS. -(defun vc-cvs-parse-status (&optional full) - "Parse output of \"cvs status\" command in the current buffer. -Set file properties accordingly. Unless FULL is t, parse only -essential information. Note that this can never set the 'ignored -state." - (let (file status missing) - (goto-char (point-min)) - (while (looking-at "? \\(.*\\)") - (setq file (expand-file-name (match-string 1))) - (vc-file-setprop file 'vc-state 'unregistered) - (forward-line 1)) - (when (re-search-forward "^File: " nil t) - (when (setq missing (looking-at "no file ")) - (goto-char (match-end 0))) - (cond - ((re-search-forward "\\=\\([^ \t]+\\)" nil t) - (setq file (expand-file-name (match-string 1))) - (setq status(if (re-search-forward "\\=[ \t]+Status: \\(.*\\)" nil t) - (match-string 1) "Unknown")) - (when (and full - (re-search-forward - "\\(RCS Version\\|RCS Revision\\|Repository revision\\):\ -\[\t ]+\\([0-9.]+\\)" - nil t)) - (vc-file-setprop file 'vc-latest-revision (match-string 2))) - (vc-file-setprop - file 'vc-state - (cond - ((string-match "Up-to-date" status) - (vc-file-setprop file 'vc-checkout-time - (nth 5 (file-attributes file))) - 'up-to-date) - ((string-match "Locally Modified" status) 'edited) - ((string-match "Needs Merge" status) 'needs-merge) - ((string-match "Needs \\(Checkout\\|Patch\\)" status) - (if missing 'missing 'needs-update)) - ((string-match "Locally Added" status) 'added) - ((string-match "Locally Removed" status) 'removed) - ((string-match "File had conflicts " status) 'conflict) - ((string-match "Unknown" status) 'unregistered) - (t 'edited)))))))) - -(defun vc-cvs-after-dir-status (update-function) - ;; Heavily inspired by vc-cvs-parse-status. AKA a quick hack. - ;; This needs a lot of testing. - (let ((status nil) - (status-str nil) - (file nil) - (result nil) - (missing nil) - (ignore-next nil) - (subdir default-directory)) - (goto-char (point-min)) - (while - ;; Look for either a file entry, an unregistered file, or a - ;; directory change. - (re-search-forward - "\\(^=+\n\\([^=c?\n].*\n\\|\n\\)+\\)\\|\\(\\(^?? .*\n\\)+\\)\\|\\(^cvs status: \\(Examining\\|nothing\\) .*\n\\)" - nil t) - ;; FIXME: get rid of narrowing here. - (narrow-to-region (match-beginning 0) (match-end 0)) - (goto-char (point-min)) - ;; The subdir - (when (looking-at "cvs status: Examining \\(.+\\)") - (setq subdir (expand-file-name (match-string 1)))) - ;; Unregistered files - (while (looking-at "? \\(.*\\)") - (setq file (file-relative-name - (expand-file-name (match-string 1) subdir))) - (push (list file 'unregistered) result) - (forward-line 1)) - (when (looking-at "cvs status: nothing known about") - ;; We asked about a non existent file. The output looks like this: - - ;; cvs status: nothing known about `lisp/v.diff' - ;; =================================================================== - ;; File: no file v.diff Status: Unknown - ;; - ;; Working revision: No entry for v.diff - ;; Repository revision: No revision control file - ;; - - ;; Due to narrowing in this iteration we only see the "cvs - ;; status:" line, so just set a flag so that we can ignore the - ;; file in the next iteration. - (setq ignore-next t)) - ;; A file entry. - (when (re-search-forward "^File: \\(no file \\)?\\(.*[^ \t]\\)[ \t]+Status: \\(.*\\)" nil t) - (setq missing (match-string 1)) - (setq file (file-relative-name - (expand-file-name (match-string 2) subdir))) - (setq status-str (match-string 3)) - (setq status - (cond - ((string-match "Up-to-date" status-str) 'up-to-date) - ((string-match "Locally Modified" status-str) 'edited) - ((string-match "Needs Merge" status-str) 'needs-merge) - ((string-match "Needs \\(Checkout\\|Patch\\)" status-str) - (if missing 'missing 'needs-update)) - ((string-match "Locally Added" status-str) 'added) - ((string-match "Locally Removed" status-str) 'removed) - ((string-match "File had conflicts " status-str) 'conflict) - ((string-match "Unknown" status-str) 'unregistered) - (t 'edited))) - (if ignore-next - (setq ignore-next nil) - (unless (eq status 'up-to-date) - (push (list file status) result)))) - (goto-char (point-max)) - (widen)) - (funcall update-function result)) - ;; Alternative implementation: use the "update" command instead of - ;; the "status" command. - ;; (let ((result nil) - ;; (translation '((?? . unregistered) - ;; (?A . added) - ;; (?C . conflict) - ;; (?M . edited) - ;; (?P . needs-merge) - ;; (?R . removed) - ;; (?U . needs-update)))) - ;; (goto-char (point-min)) - ;; (while (not (eobp)) - ;; (if (looking-at "^[ACMPRU?] \\(.*\\)$") - ;; (push (list (match-string 1) - ;; (cdr (assoc (char-after) translation))) - ;; result) - ;; (cond - ;; ((looking-at "cvs update: warning: \\(.*\\) was lost") - ;; ;; Format is: - ;; ;; cvs update: warning: FILENAME was lost - ;; ;; U FILENAME - ;; (push (list (match-string 1) 'missing) result) - ;; ;; Skip the "U" line - ;; (forward-line 1)) - ;; ((looking-at "cvs update: New directory `\\(.*\\)' -- ignored") - ;; (push (list (match-string 1) 'unregistered) result)))) - ;; (forward-line 1)) - ;; (funcall update-function result))) - ) - -;; Based on vc-cvs-dir-state-heuristic from Emacs 22. -;; FIXME does not mention unregistered files. -(defun vc-cvs-dir-status-heuristic (dir update-function &optional basedir) - "Find the CVS state of all files in DIR, using only local information." - (let (file basename status result dirlist) - (with-temp-buffer - (vc-cvs-get-entries dir) - (goto-char (point-min)) - (while (not (eobp)) - (if (looking-at "D/\\([^/]*\\)////") - (push (expand-file-name (match-string 1) dir) dirlist) - ;; CVS-removed files are not taken under VC control. - (when (looking-at "/\\([^/]*\\)/[^/-]") - (setq basename (match-string 1) - file (expand-file-name basename dir) - status (or (vc-file-getprop file 'vc-state) - (vc-cvs-parse-entry file t))) - (unless (eq status 'up-to-date) - (push (list (if basedir - (file-relative-name file basedir) - basename) - status) result)))) - (forward-line 1))) - (dolist (subdir dirlist) - (setq result (append result - (vc-cvs-dir-status-heuristic subdir nil - (or basedir dir))))) - (if basedir result - (funcall update-function result)))) - -(defun vc-cvs-dir-status (dir update-function) - "Create a list of conses (file . state) for DIR." - ;; FIXME check all files in DIR instead? - (let ((local (vc-stay-local-p dir 'CVS))) - (if (and local (not (eq local 'only-file))) - (vc-cvs-dir-status-heuristic dir update-function) - (vc-cvs-command (current-buffer) 'async dir "-f" "status") - ;; Alternative implementation: use the "update" command instead of - ;; the "status" command. - ;; (vc-cvs-command (current-buffer) 'async - ;; (file-relative-name dir) - ;; "-f" "-n" "update" "-d" "-P") - (vc-exec-after - `(vc-cvs-after-dir-status (quote ,update-function)))))) - -(defun vc-cvs-dir-status-files (dir files default-state update-function) - "Create a list of conses (file . state) for DIR." - (apply 'vc-cvs-command (current-buffer) 'async dir "-f" "status" files) - (vc-exec-after - `(vc-cvs-after-dir-status (quote ,update-function)))) - -(defun vc-cvs-file-to-string (file) - "Read the content of FILE and return it as a string." - (condition-case nil - (with-temp-buffer - (insert-file-contents file) - (goto-char (point-min)) - (buffer-substring (point) (point-max))) - (file-error nil))) - -(defun vc-cvs-dir-extra-headers (dir) - "Extract and represent per-directory properties of a CVS working copy." - (let ((repo - (condition-case nil - (with-temp-buffer - (insert-file-contents "CVS/Root") - (goto-char (point-min)) - (and (looking-at ":ext:") (delete-char 5)) - (concat (buffer-substring (point) (1- (point-max))) "\n")) - (file-error nil))) - (module - (condition-case nil - (with-temp-buffer - (insert-file-contents "CVS/Repository") - (goto-char (point-min)) - (skip-chars-forward "^\n") - (concat (buffer-substring (point-min) (point)) "\n")) - (file-error nil)))) - (concat - (cond (repo - (concat (propertize "Repository : " 'face 'font-lock-type-face) - (propertize repo 'face 'font-lock-variable-name-face))) - (t "")) - (cond (module - (concat (propertize "Module : " 'face 'font-lock-type-face) - (propertize module 'face 'font-lock-variable-name-face))) - (t "")) - (if (file-readable-p "CVS/Tag") - (let ((tag (vc-cvs-file-to-string "CVS/Tag"))) - (cond - ((string-match "\\`T" tag) - (concat (propertize "Tag : " 'face 'font-lock-type-face) - (propertize (substring tag 1) - 'face 'font-lock-variable-name-face))) - ((string-match "\\`D" tag) - (concat (propertize "Date : " 'face 'font-lock-type-face) - (propertize (substring tag 1) - 'face 'font-lock-variable-name-face))) - (t "")))) - - ;; In CVS, branch is a per-file property, not a per-directory property. - ;; We can't really do this here without making dangerous assumptions. - ;;(propertize "Branch: " 'face 'font-lock-type-face) - ;;(propertize "ADD CODE TO PRINT THE BRANCH NAME\n" - ;; 'face 'font-lock-warning-face) - ))) - -(defun vc-cvs-get-entries (dir) - "Insert the CVS/Entries file from below DIR into the current buffer. -This function ensures that the correct coding system is used for that, -which may not be the one that is used for the files' contents. -CVS/Entries should only be accessed through this function." - (let ((coding-system-for-read (or file-name-coding-system - default-file-name-coding-system))) - (vc-insert-file (expand-file-name "CVS/Entries" dir)))) - -(defun vc-cvs-valid-symbolic-tag-name-p (tag) - "Return non-nil if TAG is a valid symbolic tag name." - ;; According to the CVS manual, a valid symbolic tag must start with - ;; an uppercase or lowercase letter and can contain uppercase and - ;; lowercase letters, digits, `-', and `_'. - (and (string-match "^[a-zA-Z]" tag) - (not (string-match "[^a-z0-9A-Z-_]" tag)))) - -(defun vc-cvs-valid-revision-number-p (tag) - "Return non-nil if TAG is a valid revision number." - (and (string-match "^[0-9]" tag) - (not (string-match "[^0-9.]" tag)))) - -(defun vc-cvs-parse-sticky-tag (match-type match-tag) - "Parse and return the sticky tag as a string. -`match-data' is protected." - (let ((data (match-data)) - (tag) - (type (cond ((string= match-type "D") 'date) - ((string= match-type "T") - (if (vc-cvs-valid-symbolic-tag-name-p match-tag) - 'symbolic-name - 'revision-number)) - (t nil)))) - (unwind-protect - (progn - (cond - ;; Sticky Date tag. Convert to a proper date value (`encode-time') - ((eq type 'date) - (string-match - "\\([0-9]+\\)\\.\\([0-9]+\\)\\.\\([0-9]+\\)\\.\\([0-9]+\\)\\.\\([0-9]+\\)\\.\\([0-9]+\\)" - match-tag) - (let* ((year-tmp (string-to-number (match-string 1 match-tag))) - (month (string-to-number (match-string 2 match-tag))) - (day (string-to-number (match-string 3 match-tag))) - (hour (string-to-number (match-string 4 match-tag))) - (min (string-to-number (match-string 5 match-tag))) - (sec (string-to-number (match-string 6 match-tag))) - ;; Years 0..68 are 2000..2068. - ;; Years 69..99 are 1969..1999. - (year (+ (cond ((> 69 year-tmp) 2000) - ((> 100 year-tmp) 1900) - (t 0)) - year-tmp))) - (setq tag (encode-time sec min hour day month year)))) - ;; Sticky Tag name or revision number - ((eq type 'symbolic-name) (setq tag match-tag)) - ((eq type 'revision-number) (setq tag match-tag)) - ;; Default is no sticky tag at all - (t nil)) - (cond ((eq vc-cvs-sticky-tag-display nil) nil) - ((eq vc-cvs-sticky-tag-display t) - (cond ((eq type 'date) (format-time-string - vc-cvs-sticky-date-format-string - tag)) - ((eq type 'symbolic-name) tag) - ((eq type 'revision-number) tag) - (t nil))) - ((functionp vc-cvs-sticky-tag-display) - (funcall vc-cvs-sticky-tag-display tag type)) - (t nil))) - - (set-match-data data)))) - -(defun vc-cvs-parse-entry (file &optional set-state) - "Parse a line from CVS/Entries. -Compare modification time to that of the FILE, set file properties -accordingly. However, `vc-state' is set only if optional arg SET-STATE -is non-nil." - (cond - ;; entry for a "locally added" file (not yet committed) - ((looking-at "/[^/]+/0/") - (vc-file-setprop file 'vc-checkout-time 0) - (vc-file-setprop file 'vc-working-revision "0") - (if set-state (vc-file-setprop file 'vc-state 'added))) - ;; normal entry - ((looking-at - (concat "/[^/]+" - ;; revision - "/\\([^/]*\\)" - ;; timestamp and optional conflict field - "/\\([^/]*\\)/" - ;; options - "\\([^/]*\\)/" - ;; sticky tag - "\\(.\\|\\)" ;Sticky tag type (date or tag name, could be empty) - "\\(.*\\)")) ;Sticky tag - (vc-file-setprop file 'vc-working-revision (match-string 1)) - (vc-file-setprop file 'vc-cvs-sticky-tag - (vc-cvs-parse-sticky-tag (match-string 4) - (match-string 5))) - ;; Compare checkout time and modification time. - ;; This is intentionally different from the algorithm that CVS uses - ;; (which is based on textual comparison), because there can be problems - ;; generating a time string that looks exactly like the one from CVS. - (let* ((time (match-string 2)) - (mtime (nth 5 (file-attributes file))) - (parsed-time (progn (require 'parse-time) - (parse-time-string (concat time " +0000"))))) - (cond ((and (not (string-match "\\+" time)) - (car parsed-time) - (equal mtime (apply 'encode-time parsed-time))) - (vc-file-setprop file 'vc-checkout-time mtime) - (if set-state (vc-file-setprop file 'vc-state 'up-to-date))) - (t - (vc-file-setprop file 'vc-checkout-time 0) - (if set-state (vc-file-setprop file 'vc-state 'edited)))))))) - -;; Completion of revision names. -;; Just so I don't feel like I'm duplicating code from pcl-cvs, I'll use -;; `cvs log' so I can list all the revision numbers rather than only -;; tag names. - -(defun vc-cvs-revision-table (file) - (let (process-file-side-effects - (default-directory (file-name-directory file)) - (res nil)) - (with-temp-buffer - (vc-cvs-command t nil file "log") - (goto-char (point-min)) - (when (re-search-forward "^symbolic names:\n" nil t) - (while (looking-at "^ \\(.*\\): \\(.*\\)") - (push (cons (match-string 1) (match-string 2)) res) - (forward-line 1))) - (while (re-search-forward "^revision \\([0-9.]+\\)" nil t) - (push (match-string 1) res)) - res))) - -(defun vc-cvs-revision-completion-table (files) - (lexical-let ((files files) - table) - (setq table (lazy-completion-table - table (lambda () (vc-cvs-revision-table (car files))))) - table)) - - -(provide 'vc-cvs) - -;; arch-tag: 60e1402a-aa53-4607-927a-cf74f144b432 -;;; vc-cvs.el ends here diff -r 681cd08dc0f7 -r e93288477c43 lisp/vc-dav.el --- a/lisp/vc-dav.el Thu Jun 10 22:43:47 2010 +0000 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,189 +0,0 @@ -;;; vc-dav.el --- vc.el support for WebDAV - -;; Copyright (C) 2001, 2004, 2005, 2006, 2007, 2008, 2009, 2010 Free Software Foundation, Inc. - -;; Author: Bill Perry -;; Maintainer: Bill Perry -;; Keywords: url, vc - -;; 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 3 of the License, 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. If not, see . - - -;;; Commentary: - -;;; Todo: -;; -;; - Some methods need to be updated to match the current vc.el. -;; - rename "version" -> "revision" -;; - some methods need to take a fileset as a parameter instead of a -;; single file. - -;;; Code: - -(require 'url) -(require 'url-dav) - -;;; Required functions for a vc backend -(defun vc-dav-registered (url) - "Return t if URL is registered with a DAV aware server." - (url-dav-vc-registered url)) - -(defun vc-dav-state (url) - "Return the current version control state of URL. -For a list of possible values, see `vc-state'." - ;; Things we can support for WebDAV - ;; - ;; up-to-date - use lockdiscovery - ;; edited - check for an active lock by us - ;; USER - use lockdiscovery + owner - ;; - ;; These don't make sense for WebDAV - ;; needs-patch - ;; needs-merge - ;; unlocked-changes - (let ((locks (url-dav-active-locks url))) - (cond - ((null locks) 'up-to-date) - ((assoc url locks) - ;; SOMEBODY has a lock... let's find out who. - (setq locks (cdr (assoc url locks))) - (if (rassoc url-dav-lock-identifier locks) - ;; _WE_ have a lock - 'edited - (cdr (car locks))))))) - -(defun vc-dav-checkout-model (url) - "Indicate whether URL needs to be \"checked out\" before it can be edited. -See `vc-checkout-model' for a list of possible values." - ;; The only thing we can support with webdav is 'locking - 'locking) - -;; This should figure out the version # of the file somehow. What is -;; the most appropriate property in WebDAV to look at for this? -(defun vc-dav-workfile-version (url) - "Return the current workfile version of URL." - "Unknown") - -(defun vc-dav-register (url &optional rev comment) - "Register URL in the DAV backend." - ;; Do we need to do anything here? FIXME? - ) - -(defun vc-dav-checkin (url rev comment) - "Commit changes in URL to WebDAV. -If REV is non-nil, that should become the new revision number. -COMMENT is used as a check-in comment." - ;; This should PUT the resource and release any locks that we hold. - ) - -(defun vc-dav-checkout (url &optional editable rev destfile) - "Check out revision REV of URL into the working area. - -If EDITABLE is non-nil URL should be writable by the user and if -locking is used for URL, a lock should also be set. - -If REV is non-nil, that is the revision to check out. If REV is the -empty string, that means to check ou tht ehead of the trunk. - -If optional arg DESTFILE is given, it is an alternate filename to -write the contents to. -" - ;; This should LOCK the resource. - ) - -(defun vc-dav-revert (url &optional contents-done) - "Revert URL back to the current workfile version. - -If optional arg CONTENTS-DONE is non-nil, then the contents of FILE -have already been reverted from a version backup, and this function -only needs to update the status of URL within the backend. -" - ;; Should do a GET if !contents_done - ;; Should UNLOCK the file. - ) - -(defun vc-dav-print-log (url) - "Insert the revision log of URL into the *vc* buffer." - ) - -(defun vc-dav-diff (url &optional rev1 rev2) - "Insert the diff for URL into the *vc-diff* buffer. -If REV1 and REV2 are non-nil report differences from REV1 to REV2. -If REV1 is nil, use the current workfile version as the older version. -If REV2 is nil, use the current workfile contents as the nwer version. - -It should return a status of either 0 (no differences found), or -1 (either non-empty diff or the diff is run asynchronously). -" - ;; We should do this asynchronously... - ;; How would we do it at all, that is the question! - ) - - - -;;; Optional functions -;; Should be faster than vc-dav-state - but how? -(defun vc-dav-state-heuristic (url) - "Estimate the version control state of URL at visiting time." - (vc-dav-state url)) - -;; This should use url-dav-get-properties with a depth of `1' to get -;; all the properties. -(defun vc-dav-dir-state (url) - "find the version control state of all files in DIR in a fast way." - ) - -(defun vc-dav-workfile-unchanged-p (url) - "Return non-nil if URL is unchanged from its current workfile version." - ;; Probably impossible with webdav - ) - -(defun vc-dav-responsible-p (url) - "Return non-nil if DAV considers itself `responsible' for URL." - ;; Check for DAV support on the web server. - t) - -(defun vc-dav-could-register (url) - "Return non-nil if URL could be registered under this backend." - ;; Check for DAV support on the web server. - t) - -;;; Unimplemented functions -;; -;; vc-dav-latest-on-branch-p(URL) -;; Return non-nil if the current workfile version of FILE is the -;; latest on its branch. There are no branches in webdav yet. -;; -;; vc-dav-mode-line-string(url) -;; Return a dav-specific mode line string for URL. Are there any -;; specific states that we want exposed? -;; -;; vc-dav-dired-state-info(url) -;; Translate the `vc-state' property of URL into a string that can -;; be used in a vc-dired buffer. Are there any extra states that -;; we want exposed? -;; -;; vc-dav-receive-file(url rev) -;; Let this backend `receive' a file that is already registered -;; under another backend. The default just calls `register', which -;; should be sufficient for WebDAV. -;; -;; vc-dav-unregister(url) -;; Unregister URL. Not possible with WebDAV, other than by -;; deleting the resource. - -(provide 'vc-dav) - -;; arch-tag: 0a0fb9fe-8190-4c0a-a179-5c291d3a467e -;;; vc-dav.el ends here diff -r 681cd08dc0f7 -r e93288477c43 lisp/vc-dir.el --- a/lisp/vc-dir.el Thu Jun 10 22:43:47 2010 +0000 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,1256 +0,0 @@ -;;; vc-dir.el --- Directory status display under VC - -;; Copyright (C) 2007, 2008, 2009, 2010 -;; Free Software Foundation, Inc. - -;; Author: Dan Nicolaescu -;; Keywords: tools - -;; 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 3 of the License, 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. If not, see . - -;;; Credits: - -;; The original VC directory status implementation was based on dired. -;; This implementation was inspired by PCL-CVS. -;; Many people contributed comments, ideas and code to this -;; implementation. These include: -;; -;; Alexandre Julliard -;; Stefan Monnier -;; Tom Tromey - -;;; Commentary: -;; - -;;; Todo: see vc.el. - -(require 'vc-hooks) -(require 'vc) -(require 'tool-bar) -(require 'ewoc) - -;;; Code: -(eval-when-compile - (require 'cl)) - -(defcustom vc-dir-mode-hook nil - "Normal hook run by `vc-dir-mode'. -See `run-hooks'." - :type 'hook - :group 'vc) - -;; Used to store information for the files displayed in the directory buffer. -;; Each item displayed corresponds to one of these defstructs. -(defstruct (vc-dir-fileinfo - (:copier nil) - (:type list) ;So we can use `member' on lists of FIs. - (:constructor - ;; We could define it as an alias for `list'. - vc-dir-create-fileinfo (name state &optional extra marked directory)) - (:conc-name vc-dir-fileinfo->)) - name ;Keep it as first, for `member'. - state - ;; For storing backend specific information. - extra - marked - ;; To keep track of not updated files during a global refresh - needs-update - ;; To distinguish files and directories. - directory) - -(defvar vc-ewoc nil) - -(defvar vc-dir-process-buffer nil - "The buffer used for the asynchronous call that computes status.") - -(defvar vc-dir-backend nil - "The backend used by the current *vc-dir* buffer.") - -(defun vc-dir-move-to-goal-column () - ;; Used to keep the cursor on the file name column. - (beginning-of-line) - (unless (eolp) - ;; Must be in sync with vc-default-dir-printer. - (forward-char 25))) - -(defun vc-dir-prepare-status-buffer (bname dir backend &optional create-new) - "Find a buffer named BNAME showing DIR, or create a new one." - (setq dir (file-name-as-directory (expand-file-name dir))) - (let* ;; Look for another buffer name BNAME visiting the same directory. - ((buf (save-excursion - (unless create-new - (dolist (buffer vc-dir-buffers) - (when (buffer-live-p buffer) - (set-buffer buffer) - (when (and (derived-mode-p 'vc-dir-mode) - (eq vc-dir-backend backend) - (string= default-directory dir)) - (return buffer)))))))) - (or buf - ;; Create a new buffer named BNAME. - ;; We pass a filename to create-file-buffer because it is what - ;; the function expects, and also what uniquify needs (if active) - (with-current-buffer (create-file-buffer (expand-file-name bname dir)) - (cd dir) - (vc-setup-buffer (current-buffer)) - ;; Reset the vc-parent-buffer-name so that it does not appear - ;; in the mode-line. - (setq vc-parent-buffer-name nil) - (current-buffer))))) - -(defvar vc-dir-menu-map - (let ((map (make-sparse-keymap "VC-dir"))) - (define-key map [quit] - '(menu-item "Quit" quit-window - :help "Quit")) - (define-key map [kill] - '(menu-item "Kill Update Command" vc-dir-kill-dir-status-process - :enable (vc-dir-busy) - :help "Kill the command that updates the directory buffer")) - (define-key map [refresh] - '(menu-item "Refresh" revert-buffer - :enable (not (vc-dir-busy)) - :help "Refresh the contents of the directory buffer")) - (define-key map [remup] - '(menu-item "Hide up-to-date" vc-dir-hide-up-to-date - :help "Hide up-to-date items from display")) - ;; Movement. - (define-key map [sepmv] '("--")) - (define-key map [next-line] - '(menu-item "Next line" vc-dir-next-line - :help "Go to the next line" :keys "n")) - (define-key map [previous-line] - '(menu-item "Previous line" vc-dir-previous-line - :help "Go to the previous line")) - ;; Marking. - (define-key map [sepmrk] '("--")) - (define-key map [unmark-all] - '(menu-item "Unmark All" vc-dir-unmark-all-files - :help "Unmark all files that are in the same state as the current file\ -\nWith prefix argument unmark all files")) - (define-key map [unmark-previous] - '(menu-item "Unmark previous " vc-dir-unmark-file-up - :help "Move to the previous line and unmark the file")) - - (define-key map [mark-all] - '(menu-item "Mark All" vc-dir-mark-all-files - :help "Mark all files that are in the same state as the current file\ -\nWith prefix argument mark all files")) - (define-key map [unmark] - '(menu-item "Unmark" vc-dir-unmark - :help "Unmark the current file or all files in the region")) - - (define-key map [mark] - '(menu-item "Mark" vc-dir-mark - :help "Mark the current file or all files in the region")) - - (define-key map [sepopn] '("--")) - (define-key map [qr] - '(menu-item "Query Replace in Files..." vc-dir-query-replace-regexp - :help "Replace a string in the marked files")) - (define-key map [se] - '(menu-item "Search Files..." vc-dir-search - :help "Search a regexp in the marked files")) - (define-key map [ires] - '(menu-item "Isearch Regexp Files..." vc-dir-isearch-regexp - :help "Incremental search a regexp in the marked files")) - (define-key map [ise] - '(menu-item "Isearch Files..." vc-dir-isearch - :help "Incremental search a string in the marked files")) - (define-key map [open-other] - '(menu-item "Open in other window" vc-dir-find-file-other-window - :help "Find the file on the current line, in another window")) - (define-key map [open] - '(menu-item "Open file" vc-dir-find-file - :help "Find the file on the current line")) - (define-key map [sepvcdet] '("--")) - ;; FIXME: This needs a key binding. And maybe a better name - ;; ("Insert" like PCL-CVS uses does not sound that great either)... - (define-key map [ins] - '(menu-item "Show File" vc-dir-show-fileentry - :help "Show a file in the VC status listing even though it might be up to date")) - (define-key map [annotate] - '(menu-item "Annotate" vc-annotate - :help "Display the edit history of the current file using colors")) - (define-key map [diff] - '(menu-item "Compare with Base Version" vc-diff - :help "Compare file set with the base version")) - (define-key map [logo] - '(menu-item "Show Outgoing Log" vc-log-outgoing - :help "Show a log of changes that will be sent with a push operation")) - (define-key map [logi] - '(menu-item "Show Incoming Log" vc-log-incoming - :help "Show a log of changes that will be received with a pull operation")) - (define-key map [log] - '(menu-item "Show history" vc-print-log - :help "List the change log of the current file set in a window")) - (define-key map [rlog] - '(menu-item "Show Top of the Tree History " vc-print-root-log - :help "List the change log for the current tree in a window")) - ;; VC commands. - (define-key map [sepvccmd] '("--")) - (define-key map [update] - '(menu-item "Update to latest version" vc-update - :help "Update the current fileset's files to their tip revisions")) - (define-key map [revert] - '(menu-item "Revert to base version" vc-revert - :help "Revert working copies of the selected fileset to their repository contents.")) - (define-key map [next-action] - ;; FIXME: This really really really needs a better name! - ;; And a key binding too. - '(menu-item "Check In/Out" vc-next-action - :help "Do the next logical version control operation on the current fileset")) - (define-key map [register] - '(menu-item "Register" vc-register - :help "Register file set into the version control system")) - map) - "Menu for VC dir.") - -;; VC backends can use this to add mode-specific menu items to -;; vc-dir-menu-map. -(defun vc-dir-menu-map-filter (orig-binding) - (when (and (symbolp orig-binding) (fboundp orig-binding)) - (setq orig-binding (indirect-function orig-binding))) - (let ((ext-binding - (when (derived-mode-p 'vc-dir-mode) - (vc-call-backend vc-dir-backend 'extra-status-menu)))) - (if (null ext-binding) - orig-binding - (append orig-binding - '("----") - ext-binding)))) - -(defvar vc-dir-mode-map - (let ((map (make-sparse-keymap))) - ;; VC commands - (define-key map "v" 'vc-next-action) ;; C-x v v - (define-key map "=" 'vc-diff) ;; C-x v = - (define-key map "i" 'vc-register) ;; C-x v i - (define-key map "+" 'vc-update) ;; C-x v + - (define-key map "l" 'vc-print-log) ;; C-x v l - ;; More confusing than helpful, probably - ;;(define-key map "R" 'vc-revert) ;; u is taken by vc-dir-unmark. - ;;(define-key map "A" 'vc-annotate) ;; g is taken by revert-buffer - ;; bound by `special-mode'. - ;; Marking. - (define-key map "m" 'vc-dir-mark) - (define-key map "M" 'vc-dir-mark-all-files) - (define-key map "u" 'vc-dir-unmark) - (define-key map "U" 'vc-dir-unmark-all-files) - (define-key map "\C-?" 'vc-dir-unmark-file-up) - (define-key map "\M-\C-?" 'vc-dir-unmark-all-files) - ;; Movement. - (define-key map "n" 'vc-dir-next-line) - (define-key map " " 'vc-dir-next-line) - (define-key map "\t" 'vc-dir-next-directory) - (define-key map "p" 'vc-dir-previous-line) - (define-key map [backtab] 'vc-dir-previous-directory) - ;;; Rebind paragraph-movement commands. - (define-key map "\M-}" 'vc-dir-next-directory) - (define-key map "\M-{" 'vc-dir-previous-directory) - (define-key map [C-down] 'vc-dir-next-directory) - (define-key map [C-up] 'vc-dir-previous-directory) - ;; The remainder. - (define-key map "f" 'vc-dir-find-file) - (define-key map "\C-m" 'vc-dir-find-file) - (define-key map "o" 'vc-dir-find-file-other-window) - (define-key map "\C-c\C-c" 'vc-dir-kill-dir-status-process) - (define-key map [down-mouse-3] 'vc-dir-menu) - (define-key map [mouse-2] 'vc-dir-toggle-mark) - (define-key map [follow-link] 'mouse-face) - (define-key map "x" 'vc-dir-hide-up-to-date) - (define-key map [?\C-k] 'vc-dir-kill-line) - (define-key map "S" 'vc-dir-search) ;; FIXME: Maybe use A like dired? - (define-key map "Q" 'vc-dir-query-replace-regexp) - (define-key map (kbd "M-s a C-s") 'vc-dir-isearch) - (define-key map (kbd "M-s a M-C-s") 'vc-dir-isearch-regexp) - - ;; Hook up the menu. - (define-key map [menu-bar vc-dir-mode] - `(menu-item - ;; VC backends can use this to add mode-specific menu items to - ;; vc-dir-menu-map. - "VC-dir" ,vc-dir-menu-map :filter vc-dir-menu-map-filter)) - map) - "Keymap for directory buffer.") - -(defmacro vc-dir-at-event (event &rest body) - "Evaluate BODY with point located at event-start of EVENT. -If BODY uses EVENT, it should be a variable, - otherwise it will be evaluated twice." - (let ((posn (make-symbol "vc-dir-at-event-posn"))) - `(save-excursion - (unless (equal ,event '(tool-bar)) - (let ((,posn (event-start ,event))) - (set-buffer (window-buffer (posn-window ,posn))) - (goto-char (posn-point ,posn)))) - ,@body))) - -(defun vc-dir-menu (e) - "Popup the VC dir menu." - (interactive "e") - (vc-dir-at-event e (popup-menu vc-dir-menu-map e))) - -(defvar vc-dir-tool-bar-map - (let ((map (make-sparse-keymap))) - (tool-bar-local-item-from-menu 'vc-dir-find-file "open" - map vc-dir-mode-map) - (tool-bar-local-item "bookmark_add" - 'vc-dir-toggle-mark 'vc-dir-toggle-mark map - :help "Toggle mark on current item" - :label "Toggle Mark") - (tool-bar-local-item-from-menu 'vc-dir-previous-line "left-arrow" - map vc-dir-mode-map - :rtl "right-arrow") - (tool-bar-local-item-from-menu 'vc-dir-next-line "right-arrow" - map vc-dir-mode-map - :rtl "left-arrow") - (tool-bar-local-item-from-menu 'vc-print-log "info" - map vc-dir-mode-map) - (tool-bar-local-item-from-menu 'revert-buffer "refresh" - map vc-dir-mode-map) - (tool-bar-local-item-from-menu 'nonincremental-search-forward - "search" map nil - :label "Search") - (tool-bar-local-item-from-menu 'vc-dir-query-replace-regexp - "search-replace" map vc-dir-mode-map - :label "Replace") - (tool-bar-local-item-from-menu 'vc-dir-kill-dir-status-process "cancel" - map vc-dir-mode-map - :label "Cancel") - (tool-bar-local-item-from-menu 'quit-window "exit" - map vc-dir-mode-map) - map)) - -(defun vc-dir-node-directory (node) - ;; Compute the directory for NODE. - ;; If it's a directory node, get it from the node. - (let ((data (ewoc-data node))) - (or (vc-dir-fileinfo->directory data) - ;; Otherwise compute it from the file name. - (file-name-directory - (directory-file-name - (expand-file-name - (vc-dir-fileinfo->name data))))))) - -(defun vc-dir-update (entries buffer &optional noinsert) - "Update BUFFER's ewoc from the list of ENTRIES. -If NOINSERT, ignore elements on ENTRIES which are not in the ewoc." - ;; Add ENTRIES to the vc-dir buffer BUFFER. - (with-current-buffer buffer - ;; Insert the entries sorted by name into the ewoc. - ;; We assume the ewoc is sorted too, which should be the - ;; case if we always add entries with vc-dir-update. - (setq entries - ;; Sort: first files and then subdirectories. - ;; XXX: this is VERY inefficient, it computes the directory - ;; names too many times - (sort entries - (lambda (entry1 entry2) - (let ((dir1 (file-name-directory - (directory-file-name (expand-file-name (car entry1))))) - (dir2 (file-name-directory - (directory-file-name (expand-file-name (car entry2)))))) - (cond - ((string< dir1 dir2) t) - ((not (string= dir1 dir2)) nil) - ((string< (car entry1) (car entry2)))))))) - ;; Insert directory entries in the right places. - (let ((entry (car entries)) - (node (ewoc-nth vc-ewoc 0)) - (to-remove nil) - (dotname (file-relative-name default-directory))) - ;; Insert . if it is not present. - (unless node - (ewoc-enter-last - vc-ewoc (vc-dir-create-fileinfo - dotname nil nil nil default-directory)) - (setq node (ewoc-nth vc-ewoc 0))) - - (while (and entry node) - (let* ((entryfile (car entry)) - (entrydir (file-name-directory (directory-file-name - (expand-file-name entryfile)))) - (nodedir (vc-dir-node-directory node))) - (cond - ;; First try to find the directory. - ((string-lessp nodedir entrydir) - (setq node (ewoc-next vc-ewoc node))) - ((string-equal nodedir entrydir) - ;; Found the directory, find the place for the file name. - (let ((nodefile (vc-dir-fileinfo->name (ewoc-data node)))) - (cond - ((string= nodefile dotname) - (setq node (ewoc-next vc-ewoc node))) - ((string-lessp nodefile entryfile) - (setq node (ewoc-next vc-ewoc node))) - ((string-equal nodefile entryfile) - (if (nth 1 entry) - (progn - (setf (vc-dir-fileinfo->state (ewoc-data node)) (nth 1 entry)) - (setf (vc-dir-fileinfo->extra (ewoc-data node)) (nth 2 entry)) - (setf (vc-dir-fileinfo->needs-update (ewoc-data node)) nil) - (ewoc-invalidate vc-ewoc node)) - ;; If the state is nil, the file does not exist - ;; anymore, so remember the entry so we can remove - ;; it after we are done inserting all ENTRIES. - (push node to-remove)) - (setq entries (cdr entries)) - (setq entry (car entries)) - (setq node (ewoc-next vc-ewoc node))) - (t - (ewoc-enter-before vc-ewoc node - (apply 'vc-dir-create-fileinfo entry)) - (setq entries (cdr entries)) - (setq entry (car entries)))))) - (t - ;; We might need to insert a directory node if the - ;; previous node was in a different directory. - (let* ((rd (file-relative-name entrydir)) - (prev-node (ewoc-prev vc-ewoc node)) - (prev-dir (vc-dir-node-directory prev-node))) - (unless (string-equal entrydir prev-dir) - (ewoc-enter-before - vc-ewoc node (vc-dir-create-fileinfo rd nil nil nil entrydir)))) - ;; Now insert the node itself. - (ewoc-enter-before vc-ewoc node - (apply 'vc-dir-create-fileinfo entry)) - (setq entries (cdr entries) entry (car entries)))))) - ;; We're past the last node, all remaining entries go to the end. - (unless (or node noinsert) - (let ((lastdir (vc-dir-node-directory (ewoc-nth vc-ewoc -1)))) - (dolist (entry entries) - (let ((entrydir (file-name-directory - (directory-file-name (expand-file-name (car entry)))))) - ;; Insert a directory node if needed. - (unless (string-equal lastdir entrydir) - (setq lastdir entrydir) - (let ((rd (file-relative-name entrydir))) - (ewoc-enter-last - vc-ewoc (vc-dir-create-fileinfo rd nil nil nil entrydir)))) - ;; Now insert the node itself. - (ewoc-enter-last vc-ewoc - (apply 'vc-dir-create-fileinfo entry)))))) - (when to-remove - (let ((inhibit-read-only t)) - (apply 'ewoc-delete vc-ewoc (nreverse to-remove))))))) - -(defun vc-dir-busy () - (and (buffer-live-p vc-dir-process-buffer) - (get-buffer-process vc-dir-process-buffer))) - -(defun vc-dir-kill-dir-status-process () - "Kill the temporary buffer and associated process." - (interactive) - (when (buffer-live-p vc-dir-process-buffer) - (let ((proc (get-buffer-process vc-dir-process-buffer))) - (when proc (delete-process proc)) - (setq vc-dir-process-buffer nil) - (setq mode-line-process nil)))) - -(defun vc-dir-kill-query () - ;; Make sure that when the status buffer is killed the update - ;; process running in background is also killed. - (if (vc-dir-busy) - (when (y-or-n-p "Status update process running, really kill status buffer? ") - (vc-dir-kill-dir-status-process) - t) - t)) - -(defun vc-dir-next-line (arg) - "Go to the next line. -If a prefix argument is given, move by that many lines." - (interactive "p") - (with-no-warnings - (ewoc-goto-next vc-ewoc arg) - (vc-dir-move-to-goal-column))) - -(defun vc-dir-previous-line (arg) - "Go to the previous line. -If a prefix argument is given, move by that many lines." - (interactive "p") - (ewoc-goto-prev vc-ewoc arg) - (vc-dir-move-to-goal-column)) - -(defun vc-dir-next-directory () - "Go to the next directory." - (interactive) - (let ((orig (point))) - (if - (catch 'foundit - (while t - (let* ((next (ewoc-next vc-ewoc (ewoc-locate vc-ewoc)))) - (cond ((not next) - (throw 'foundit t)) - (t - (progn - (ewoc-goto-node vc-ewoc next) - (vc-dir-move-to-goal-column) - (if (vc-dir-fileinfo->directory (ewoc-data next)) - (throw 'foundit nil)))))))) - (goto-char orig)))) - -(defun vc-dir-previous-directory () - "Go to the previous directory." - (interactive) - (let ((orig (point))) - (if - (catch 'foundit - (while t - (let* ((prev (ewoc-prev vc-ewoc (ewoc-locate vc-ewoc)))) - (cond ((not prev) - (throw 'foundit t)) - (t - (progn - (ewoc-goto-node vc-ewoc prev) - (vc-dir-move-to-goal-column) - (if (vc-dir-fileinfo->directory (ewoc-data prev)) - (throw 'foundit nil)))))))) - (goto-char orig)))) - -(defun vc-dir-mark-unmark (mark-unmark-function) - (if (use-region-p) - (let ((firstl (line-number-at-pos (region-beginning))) - (lastl (line-number-at-pos (region-end)))) - (save-excursion - (goto-char (region-beginning)) - (while (<= (line-number-at-pos) lastl) - (funcall mark-unmark-function)))) - (funcall mark-unmark-function))) - -(defun vc-dir-parent-marked-p (arg) - ;; Return nil if none of the parent directories of arg is marked. - (let* ((argdir (vc-dir-node-directory arg)) - (arglen (length argdir)) - (crt arg) - data dir) - ;; Go through the predecessors, checking if any directory that is - ;; a parent is marked. - (while (setq crt (ewoc-prev vc-ewoc crt)) - (setq data (ewoc-data crt)) - (setq dir (vc-dir-node-directory crt)) - (when (and (vc-dir-fileinfo->directory data) - (vc-string-prefix-p dir argdir)) - (when (vc-dir-fileinfo->marked data) - (error "Cannot mark `%s', parent directory `%s' marked" - (vc-dir-fileinfo->name (ewoc-data arg)) - (vc-dir-fileinfo->name data))))) - nil)) - -(defun vc-dir-children-marked-p (arg) - ;; Return nil if none of the children of arg is marked. - (let* ((argdir-re (concat "\\`" (regexp-quote (vc-dir-node-directory arg)))) - (is-child t) - (crt arg) - data dir) - (while (and is-child (setq crt (ewoc-next vc-ewoc crt))) - (setq data (ewoc-data crt)) - (setq dir (vc-dir-node-directory crt)) - (if (string-match argdir-re dir) - (when (vc-dir-fileinfo->marked data) - (error "Cannot mark `%s', child `%s' marked" - (vc-dir-fileinfo->name (ewoc-data arg)) - (vc-dir-fileinfo->name data))) - ;; We are done, we got to an entry that is not a child of `arg'. - (setq is-child nil))) - nil)) - -(defun vc-dir-mark-file (&optional arg) - ;; Mark ARG or the current file and move to the next line. - (let* ((crt (or arg (ewoc-locate vc-ewoc))) - (file (ewoc-data crt)) - (isdir (vc-dir-fileinfo->directory file))) - (when (or (and isdir (not (vc-dir-children-marked-p crt))) - (and (not isdir) (not (vc-dir-parent-marked-p crt)))) - (setf (vc-dir-fileinfo->marked file) t) - (ewoc-invalidate vc-ewoc crt) - (unless (or arg (mouse-event-p last-command-event)) - (vc-dir-next-line 1))))) - -(defun vc-dir-mark () - "Mark the current file or all files in the region. -If the region is active, mark all the files in the region. -Otherwise mark the file on the current line and move to the next -line." - (interactive) - (vc-dir-mark-unmark 'vc-dir-mark-file)) - -(defun vc-dir-mark-all-files (arg) - "Mark all files with the same state as the current one. -With a prefix argument mark all files. -If the current entry is a directory, mark all child files. - -The commands operate on files that are on the same state. -This command is intended to make it easy to select all files that -share the same state." - (interactive "P") - (if arg - ;; Mark all files. - (progn - ;; First check that no directory is marked, we can't mark - ;; files in that case. - (ewoc-map - (lambda (filearg) - (when (and (vc-dir-fileinfo->directory filearg) - (vc-dir-fileinfo->marked filearg)) - (error "Cannot mark all files, directory `%s' marked" - (vc-dir-fileinfo->name filearg)))) - vc-ewoc) - (ewoc-map - (lambda (filearg) - (unless (vc-dir-fileinfo->marked filearg) - (setf (vc-dir-fileinfo->marked filearg) t) - t)) - vc-ewoc)) - (let ((data (ewoc-data (ewoc-locate vc-ewoc)))) - (if (vc-dir-fileinfo->directory data) - ;; It's a directory, mark child files. - (let ((crt (ewoc-locate vc-ewoc))) - (unless (vc-dir-children-marked-p crt) - (while (setq crt (ewoc-next vc-ewoc crt)) - (let ((crt-data (ewoc-data crt))) - (unless (vc-dir-fileinfo->directory crt-data) - (setf (vc-dir-fileinfo->marked crt-data) t) - (ewoc-invalidate vc-ewoc crt)))))) - ;; It's a file - (let ((state (vc-dir-fileinfo->state data)) - (crt (ewoc-nth vc-ewoc 0))) - (while crt - (let ((crt-data (ewoc-data crt))) - (when (and (not (vc-dir-fileinfo->marked crt-data)) - (eq (vc-dir-fileinfo->state crt-data) state) - (not (vc-dir-fileinfo->directory crt-data))) - (vc-dir-mark-file crt))) - (setq crt (ewoc-next vc-ewoc crt)))))))) - -(defun vc-dir-unmark-file () - ;; Unmark the current file and move to the next line. - (let* ((crt (ewoc-locate vc-ewoc)) - (file (ewoc-data crt))) - (setf (vc-dir-fileinfo->marked file) nil) - (ewoc-invalidate vc-ewoc crt) - (unless (mouse-event-p last-command-event) - (vc-dir-next-line 1)))) - -(defun vc-dir-unmark () - "Unmark the current file or all files in the region. -If the region is active, unmark all the files in the region. -Otherwise mark the file on the current line and move to the next -line." - (interactive) - (vc-dir-mark-unmark 'vc-dir-unmark-file)) - -(defun vc-dir-unmark-file-up () - "Move to the previous line and unmark the file." - (interactive) - ;; If we're on the first line, we won't move up, but we will still - ;; remove the mark. This seems a bit odd but it is what buffer-menu - ;; does. - (let* ((prev (ewoc-goto-prev vc-ewoc 1)) - (file (ewoc-data prev))) - (setf (vc-dir-fileinfo->marked file) nil) - (ewoc-invalidate vc-ewoc prev) - (vc-dir-move-to-goal-column))) - -(defun vc-dir-unmark-all-files (arg) - "Unmark all files with the same state as the current one. -With a prefix argument unmark all files. -If the current entry is a directory, unmark all the child files. - -The commands operate on files that are on the same state. -This command is intended to make it easy to deselect all files -that share the same state." - (interactive "P") - (if arg - (ewoc-map - (lambda (filearg) - (when (vc-dir-fileinfo->marked filearg) - (setf (vc-dir-fileinfo->marked filearg) nil) - t)) - vc-ewoc) - (let* ((crt (ewoc-locate vc-ewoc)) - (data (ewoc-data crt))) - (if (vc-dir-fileinfo->directory data) - ;; It's a directory, unmark child files. - (while (setq crt (ewoc-next vc-ewoc crt)) - (let ((crt-data (ewoc-data crt))) - (unless (vc-dir-fileinfo->directory crt-data) - (setf (vc-dir-fileinfo->marked crt-data) nil) - (ewoc-invalidate vc-ewoc crt)))) - ;; It's a file - (let ((crt-state (vc-dir-fileinfo->state (ewoc-data crt)))) - (ewoc-map - (lambda (filearg) - (when (and (vc-dir-fileinfo->marked filearg) - (eq (vc-dir-fileinfo->state filearg) crt-state)) - (setf (vc-dir-fileinfo->marked filearg) nil) - t)) - vc-ewoc)))))) - -(defun vc-dir-toggle-mark-file () - (let* ((crt (ewoc-locate vc-ewoc)) - (file (ewoc-data crt))) - (if (vc-dir-fileinfo->marked file) - (vc-dir-unmark-file) - (vc-dir-mark-file)))) - -(defun vc-dir-toggle-mark (e) - (interactive "e") - (vc-dir-at-event e (vc-dir-mark-unmark 'vc-dir-toggle-mark-file))) - -(defun vc-dir-delete-file () - "Delete the marked files, or the current file if no marks." - (interactive) - (mapc 'vc-delete-file (or (vc-dir-marked-files) - (list (vc-dir-current-file))))) - -(defun vc-dir-find-file () - "Find the file on the current line." - (interactive) - (find-file (vc-dir-current-file))) - -(defun vc-dir-find-file-other-window (&optional event) - "Find the file on the current line, in another window." - (interactive (list last-nonmenu-event)) - (if event (posn-set-point (event-end event))) - (find-file-other-window (vc-dir-current-file))) - -(defun vc-dir-isearch () - "Search for a string through all marked buffers using Isearch." - (interactive) - (multi-isearch-files - (mapcar 'car (vc-dir-marked-only-files-and-states)))) - -(defun vc-dir-isearch-regexp () - "Search for a regexp through all marked buffers using Isearch." - (interactive) - (multi-isearch-files-regexp - (mapcar 'car (vc-dir-marked-only-files-and-states)))) - -(defun vc-dir-search (regexp) - "Search through all marked files for a match for REGEXP. -For marked directories, use the files displayed from those directories. -Stops when a match is found. -To continue searching for next match, use command \\[tags-loop-continue]." - (interactive "sSearch marked files (regexp): ") - (tags-search regexp '(mapcar 'car (vc-dir-marked-only-files-and-states)))) - -(defun vc-dir-query-replace-regexp (from to &optional delimited) - "Do `query-replace-regexp' of FROM with TO, on all marked files. -For marked directories, use the files displayed from those directories. -If a directory is marked, then use the files displayed for that directory. -Third arg DELIMITED (prefix arg) means replace only word-delimited matches. -If you exit (\\[keyboard-quit], RET or q), you can resume the query replace -with the command \\[tags-loop-continue]." - ;; FIXME: this is almost a copy of `dired-do-replace-regexp'. This - ;; should probably be made generic and used in both places instead of - ;; duplicating it here. - (interactive - (let ((common - (query-replace-read-args - "Query replace regexp in marked files" t t))) - (list (nth 0 common) (nth 1 common) (nth 2 common)))) - (dolist (file (mapcar 'car (vc-dir-marked-only-files-and-states))) - (let ((buffer (get-file-buffer file))) - (if (and buffer (with-current-buffer buffer - buffer-read-only)) - (error "File `%s' is visited read-only" file)))) - (tags-query-replace from to delimited - '(mapcar 'car (vc-dir-marked-only-files-and-states)))) - -(defun vc-dir-current-file () - (let ((node (ewoc-locate vc-ewoc))) - (unless node - (error "No file available")) - (expand-file-name (vc-dir-fileinfo->name (ewoc-data node))))) - -(defun vc-dir-marked-files () - "Return the list of marked files." - (mapcar - (lambda (elem) (expand-file-name (vc-dir-fileinfo->name elem))) - (ewoc-collect vc-ewoc 'vc-dir-fileinfo->marked))) - -(defun vc-dir-marked-only-files-and-states () - "Return the list of conses (FILE . STATE) for the marked files. -For marked directories return the corresponding conses for the -child files." - (let ((crt (ewoc-nth vc-ewoc 0)) - result) - (while crt - (let ((crt-data (ewoc-data crt))) - (if (vc-dir-fileinfo->marked crt-data) - ;; FIXME: use vc-dir-child-files-and-states here instead of duplicating it. - (if (vc-dir-fileinfo->directory crt-data) - (let* ((dir (vc-dir-fileinfo->directory crt-data)) - (dirlen (length dir)) - data) - (while - (and (setq crt (ewoc-next vc-ewoc crt)) - (vc-string-prefix-p dir - (progn - (setq data (ewoc-data crt)) - (vc-dir-node-directory crt)))) - (unless (vc-dir-fileinfo->directory data) - (push - (cons (expand-file-name (vc-dir-fileinfo->name data)) - (vc-dir-fileinfo->state data)) - result)))) - (push (cons (expand-file-name (vc-dir-fileinfo->name crt-data)) - (vc-dir-fileinfo->state crt-data)) - result) - (setq crt (ewoc-next vc-ewoc crt))) - (setq crt (ewoc-next vc-ewoc crt))))) - (nreverse result))) - -(defun vc-dir-child-files-and-states () - "Return the list of conses (FILE . STATE) for child files of the current entry if it's a directory. -If it is a file, return the corresponding cons for the file itself." - (let* ((crt (ewoc-locate vc-ewoc)) - (crt-data (ewoc-data crt)) - result) - (if (vc-dir-fileinfo->directory crt-data) - (let* ((dir (vc-dir-fileinfo->directory crt-data)) - (dirlen (length dir)) - data) - (while - (and (setq crt (ewoc-next vc-ewoc crt)) - (vc-string-prefix-p dir (progn - (setq data (ewoc-data crt)) - (vc-dir-node-directory crt)))) - (unless (vc-dir-fileinfo->directory data) - (push - (cons (expand-file-name (vc-dir-fileinfo->name data)) - (vc-dir-fileinfo->state data)) - result)))) - (push - (cons (expand-file-name (vc-dir-fileinfo->name crt-data)) - (vc-dir-fileinfo->state crt-data)) result)) - (nreverse result))) - -(defun vc-dir-recompute-file-state (fname def-dir) - (let* ((file-short (file-relative-name fname def-dir)) - (remove-me-when-CVS-works - (when (eq vc-dir-backend 'CVS) - ;; FIXME: Warning: UGLY HACK. The CVS backend caches the state - ;; info, this forces the backend to update it. - (vc-call-backend vc-dir-backend 'registered fname))) - (state (vc-call-backend vc-dir-backend 'state fname)) - (extra (vc-call-backend vc-dir-backend - 'status-fileinfo-extra fname))) - (list file-short state extra))) - -(defun vc-dir-find-child-files (dirname) - ;; Give a DIRNAME string return the list of all child files shown in - ;; the current *vc-dir* buffer. - (let ((crt (ewoc-nth vc-ewoc 0)) - children - dname) - ;; Find DIR - (while (and crt (not (vc-string-prefix-p - dirname (vc-dir-node-directory crt)))) - (setq crt (ewoc-next vc-ewoc crt))) - (while (and crt (vc-string-prefix-p - dirname - (setq dname (vc-dir-node-directory crt)))) - (let ((data (ewoc-data crt))) - (unless (vc-dir-fileinfo->directory data) - (push (expand-file-name (vc-dir-fileinfo->name data)) children))) - (setq crt (ewoc-next vc-ewoc crt))) - children)) - -(defun vc-dir-resync-directory-files (dirname) - ;; Update the entries for all the child files of DIRNAME shown in - ;; the current *vc-dir* buffer. - (let ((files (vc-dir-find-child-files dirname)) - (ddir default-directory) - fileentries) - (when files - (dolist (crt files) - (push (vc-dir-recompute-file-state crt ddir) - fileentries)) - (vc-dir-update fileentries (current-buffer))))) - -(defun vc-dir-resynch-file (&optional fname) - "Update the entries for FNAME in any directory buffers that list it." - (let ((file (or fname (expand-file-name buffer-file-name))) - (drop '())) - (save-current-buffer - ;; look for a vc-dir buffer that might show this file. - (dolist (status-buf vc-dir-buffers) - (if (not (buffer-live-p status-buf)) - (push status-buf drop) - (set-buffer status-buf) - (if (not (derived-mode-p 'vc-dir-mode)) - (push status-buf drop) - (let ((ddir default-directory)) - (when (vc-string-prefix-p ddir file) - (if (file-directory-p file) - (progn - (vc-dir-resync-directory-files file) - (ewoc-set-hf vc-ewoc - (vc-dir-headers vc-dir-backend default-directory) "")) - (let ((state (vc-dir-recompute-file-state file ddir))) - (vc-dir-update - (list state) - status-buf (eq (cadr state) 'up-to-date)))))))))) - ;; Remove out-of-date entries from vc-dir-buffers. - (dolist (b drop) (setq vc-dir-buffers (delq b vc-dir-buffers))))) - -(defvar use-vc-backend) ;; dynamically bound - -(define-derived-mode vc-dir-mode special-mode "VC dir" - "Major mode for VC directory buffers. -Marking/Unmarking key bindings and actions: -m - mark a file/directory - - if the region is active, mark all the files in region. - Restrictions: - a file cannot be marked if any parent directory is marked - - a directory cannot be marked if any child file or - directory is marked -u - unmark a file/directory - - if the region is active, unmark all the files in region. -M - if the cursor is on a file: mark all the files with the same state as - the current file - - if the cursor is on a directory: mark all child files - - with a prefix argument: mark all files -U - if the cursor is on a file: unmark all the files with the same state - as the current file - - if the cursor is on a directory: unmark all child files - - with a prefix argument: unmark all files -mouse-2 - toggles the mark state - -VC commands -VC commands in the `C-x v' prefix can be used. -VC commands act on the marked entries. If nothing is marked, VC -commands act on the current entry. - -Search & Replace -S - searches the marked files -Q - does a query replace on the marked files -M-s a C-s - does an isearch on the marked files -M-s a C-M-s - does a regexp isearch on the marked files -If nothing is marked, these commands act on the current entry. -When a directory is current or marked, the Search & Replace -commands act on the child files of that directory that are displayed in -the *vc-dir* buffer. - -\\{vc-dir-mode-map}" - (set (make-local-variable 'vc-dir-backend) use-vc-backend) - (setq buffer-read-only t) - (when (boundp 'tool-bar-map) - (set (make-local-variable 'tool-bar-map) vc-dir-tool-bar-map)) - (let ((buffer-read-only nil)) - (erase-buffer) - (set (make-local-variable 'vc-dir-process-buffer) nil) - (set (make-local-variable 'vc-ewoc) (ewoc-create #'vc-dir-printer)) - (set (make-local-variable 'revert-buffer-function) - 'vc-dir-revert-buffer-function) - (setq list-buffers-directory (expand-file-name "*vc-dir*" default-directory)) - (add-to-list 'vc-dir-buffers (current-buffer)) - ;; Make sure that if the directory buffer is killed, the update - ;; process running in the background is also killed. - (add-hook 'kill-buffer-query-functions 'vc-dir-kill-query nil t) - (hack-dir-local-variables-non-file-buffer) - (vc-dir-refresh))) - -(defun vc-dir-headers (backend dir) - "Display the headers in the *VC dir* buffer. -It calls the `dir-extra-headers' backend method to display backend -specific headers." - (concat - ;; First layout the common headers. - (propertize "VC backend : " 'face 'font-lock-type-face) - (propertize (format "%s\n" backend) 'face 'font-lock-variable-name-face) - (propertize "Working dir: " 'face 'font-lock-type-face) - (propertize (format "%s\n" (abbreviate-file-name dir)) - 'face 'font-lock-variable-name-face) - ;; Then the backend specific ones. - (vc-call-backend backend 'dir-extra-headers dir) - "\n")) - -(defun vc-dir-refresh-files (files default-state) - "Refresh some files in the *VC-dir* buffer." - (let ((def-dir default-directory) - (backend vc-dir-backend)) - (vc-set-mode-line-busy-indicator) - ;; Call the `dir-status-file' backend function. - ;; `dir-status-file' is supposed to be asynchronous. - ;; It should compute the results, and then call the function - ;; passed as an argument in order to update the vc-dir buffer - ;; with the results. - (unless (buffer-live-p vc-dir-process-buffer) - (setq vc-dir-process-buffer - (generate-new-buffer (format " *VC-%s* tmp status" backend)))) - (lexical-let ((buffer (current-buffer))) - (with-current-buffer vc-dir-process-buffer - (cd def-dir) - (erase-buffer) - (vc-call-backend - backend 'dir-status-files def-dir files default-state - (lambda (entries &optional more-to-come) - ;; ENTRIES is a list of (FILE VC_STATE EXTRA) items. - ;; If MORE-TO-COME is true, then more updates will come from - ;; the asynchronous process. - (with-current-buffer buffer - (vc-dir-update entries buffer) - (unless more-to-come - (setq mode-line-process nil) - ;; Remove the ones that haven't been updated at all. - ;; Those not-updated are those whose state is nil because the - ;; file/dir doesn't exist and isn't versioned. - (ewoc-filter vc-ewoc - (lambda (info) - ;; The state for directory entries might - ;; have been changed to 'up-to-date, - ;; reset it, othewise it will be removed when doing 'x' - ;; next time. - ;; FIXME: There should be a more elegant way to do this. - (when (and (vc-dir-fileinfo->directory info) - (eq (vc-dir-fileinfo->state info) - 'up-to-date)) - (setf (vc-dir-fileinfo->state info) nil)) - - (not (vc-dir-fileinfo->needs-update info)))))))))))) - -(defun vc-dir-revert-buffer-function (&optional ignore-auto noconfirm) - (vc-dir-refresh)) - -(defun vc-dir-refresh () - "Refresh the contents of the *VC-dir* buffer. -Throw an error if another update process is in progress." - (interactive) - (if (vc-dir-busy) - (error "Another update process is in progress, cannot run two at a time") - (let ((def-dir default-directory) - (backend vc-dir-backend)) - (vc-set-mode-line-busy-indicator) - ;; Call the `dir-status' backend function. - ;; `dir-status' is supposed to be asynchronous. - ;; It should compute the results, and then call the function - ;; passed as an argument in order to update the vc-dir buffer - ;; with the results. - - ;; Create a buffer that can be used by `dir-status' and call - ;; `dir-status' with this buffer as the current buffer. Use - ;; `vc-dir-process-buffer' to remember this buffer, so that - ;; it can be used later to kill the update process in case it - ;; takes too long. - (unless (buffer-live-p vc-dir-process-buffer) - (setq vc-dir-process-buffer - (generate-new-buffer (format " *VC-%s* tmp status" backend)))) - ;; set the needs-update flag on all non-directory entries - (ewoc-map (lambda (info) - (unless (vc-dir-fileinfo->directory info) - (setf (vc-dir-fileinfo->needs-update info) t) nil)) - vc-ewoc) - (lexical-let ((buffer (current-buffer))) - (with-current-buffer vc-dir-process-buffer - (cd def-dir) - (erase-buffer) - (vc-call-backend - backend 'dir-status def-dir - (lambda (entries &optional more-to-come) - ;; ENTRIES is a list of (FILE VC_STATE EXTRA) items. - ;; If MORE-TO-COME is true, then more updates will come from - ;; the asynchronous process. - (with-current-buffer buffer - (vc-dir-update entries buffer) - (unless more-to-come - (let ((remaining - (ewoc-collect - vc-ewoc 'vc-dir-fileinfo->needs-update))) - (if remaining - (vc-dir-refresh-files - (mapcar 'vc-dir-fileinfo->name remaining) - 'up-to-date) - (setq mode-line-process nil))))))))) - (ewoc-set-hf vc-ewoc (vc-dir-headers backend def-dir) "")))) - -(defun vc-dir-show-fileentry (file) - "Insert an entry for a specific file into the current *VC-dir* listing. -This is typically used if the file is up-to-date (or has been added -outside of VC) and one wants to do some operation on it." - (interactive "fShow file: ") - (vc-dir-update (list (list (file-relative-name file) (vc-state file))) (current-buffer))) - -(defun vc-dir-hide-up-to-date () - "Hide up-to-date items from display." - (interactive) - (let ((crt (ewoc-nth vc-ewoc -1)) - (first (ewoc-nth vc-ewoc 0))) - ;; Go over from the last item to the first and remove the - ;; up-to-date files and directories with no child files. - (while (not (eq crt first)) - (let* ((data (ewoc-data crt)) - (dir (vc-dir-fileinfo->directory data)) - (next (ewoc-next vc-ewoc crt)) - (prev (ewoc-prev vc-ewoc crt)) - ;; ewoc-delete does not work without this... - (inhibit-read-only t)) - (when (or - ;; Remove directories with no child files. - (and dir - (or - ;; Nothing follows this directory. - (not next) - ;; Next item is a directory. - (vc-dir-fileinfo->directory (ewoc-data next)))) - ;; Remove files in the up-to-date state. - (eq (vc-dir-fileinfo->state data) 'up-to-date)) - (ewoc-delete vc-ewoc crt)) - (setq crt prev))))) - -(defun vc-dir-kill-line () - "Remove the current line from display." - (interactive) - (let ((crt (ewoc-locate vc-ewoc)) - (inhibit-read-only t)) - (ewoc-delete vc-ewoc crt))) - -(defun vc-dir-printer (fileentry) - (vc-call-backend vc-dir-backend 'dir-printer fileentry)) - -(defun vc-dir-deduce-fileset (&optional state-model-only-files) - (let ((marked (vc-dir-marked-files)) - files - only-files-list - state - model) - (if marked - (progn - (setq files marked) - (when state-model-only-files - (setq only-files-list (vc-dir-marked-only-files-and-states)))) - (let ((crt (vc-dir-current-file))) - (setq files (list crt)) - (when state-model-only-files - (setq only-files-list (vc-dir-child-files-and-states))))) - - (when state-model-only-files - (setq state (cdar only-files-list)) - ;; Check that all files are in a consistent state, since we use that - ;; state to decide which operation to perform. - (dolist (crt (cdr only-files-list)) - (unless (vc-compatible-state (cdr crt) state) - (error "When applying VC operations to multiple files, the files are required\nto be in similar VC states.\n%s in state %s clashes with %s in state %s" - (car crt) (cdr crt) (caar only-files-list) state))) - (setq only-files-list (mapcar 'car only-files-list)) - (when (and state (not (eq state 'unregistered))) - (setq model (vc-checkout-model vc-dir-backend only-files-list)))) - (list vc-dir-backend files only-files-list state model))) - -;;;###autoload -(defun vc-dir (dir &optional backend) - "Show the VC status for \"interesting\" files in and below DIR. -This allows you to mark files and perform VC operations on them. -The list omits files which are up to date, with no changes in your copy -or the repository, if there is nothing in particular to say about them. - -Preparing the list of file status takes time; when the buffer -first appears, it has only the first few lines of summary information. -The file lines appear later. - -Optional second argument BACKEND specifies the VC backend to use. -Interactively, a prefix argument means to ask for the backend. - -These are the commands available for use in the file status buffer: - -\\{vc-dir-mode-map}" - - (interactive - (list - ;; When you hit C-x v d in a visited VC file, - ;; the *vc-dir* buffer visits the directory under its truename; - ;; therefore it makes sense to always do that. - ;; Otherwise if you do C-x v d -> C-x C-f -> C-c v d - ;; you may get a new *vc-dir* buffer, different from the original - (file-truename (read-file-name "VC status for directory: " - default-directory default-directory t - nil #'file-directory-p)) - (if current-prefix-arg - (intern - (completing-read - "Use VC backend: " - (mapcar (lambda (b) (list (symbol-name b))) - vc-handled-backends) - nil t nil nil))))) - (unless backend - (setq backend (vc-responsible-backend dir))) - (let (pop-up-windows) ; based on cvs-examine; bug#6204 - (pop-to-buffer (vc-dir-prepare-status-buffer "*vc-dir*" dir backend))) - (if (derived-mode-p 'vc-dir-mode) - (vc-dir-refresh) - ;; FIXME: find a better way to pass the backend to `vc-dir-mode'. - (let ((use-vc-backend backend)) - (vc-dir-mode)))) - -(defun vc-default-dir-extra-headers (backend dir) - ;; Be loud by default to remind people to add code to display - ;; backend specific headers. - ;; XXX: change this to return nil before the release. - (concat - (propertize "Extra : " 'face 'font-lock-type-face) - (propertize "Please add backend specific headers here. It's easy!" - 'face 'font-lock-warning-face))) - -(defvar vc-dir-filename-mouse-map - (let ((map (make-sparse-keymap))) - (define-key map [mouse-2] 'vc-dir-find-file-other-window) - map) - "Local keymap for visiting a file.") - -(defun vc-default-dir-printer (backend fileentry) - "Pretty print FILEENTRY." - ;; If you change the layout here, change vc-dir-move-to-goal-column. - ;; VC backends can implement backend specific versions of this - ;; function. Changes here might need to be reflected in the - ;; vc-BACKEND-dir-printer functions. - (let* ((isdir (vc-dir-fileinfo->directory fileentry)) - (state (if isdir "" (vc-dir-fileinfo->state fileentry))) - (filename (vc-dir-fileinfo->name fileentry))) - (insert - (propertize - (format "%c" (if (vc-dir-fileinfo->marked fileentry) ?* ? )) - 'face 'font-lock-type-face) - " " - (propertize - (format "%-20s" state) - 'face (cond ((eq state 'up-to-date) 'font-lock-builtin-face) - ((memq state '(missing conflict)) 'font-lock-warning-face) - (t 'font-lock-variable-name-face)) - 'mouse-face 'highlight) - " " - (propertize - (format "%s" filename) - 'face - (if isdir 'font-lock-comment-delimiter-face 'font-lock-function-name-face) - 'help-echo - (if isdir - "Directory\nVC operations can be applied to it\nmouse-3: Pop-up menu" - "File\nmouse-3: Pop-up menu") - 'mouse-face 'highlight - 'keymap vc-dir-filename-mouse-map)))) - -(defun vc-default-extra-status-menu (backend) - nil) - -(defun vc-default-status-fileinfo-extra (backend file) - "Default absence of extra information returned for a file." - nil) - -(provide 'vc-dir) - -;; arch-tag: 0274a2e3-e8e9-4b1a-a73c-e8b9129d5d15 -;;; vc-dir.el ends here diff -r 681cd08dc0f7 -r e93288477c43 lisp/vc-dispatcher.el --- a/lisp/vc-dispatcher.el Thu Jun 10 22:43:47 2010 +0000 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,695 +0,0 @@ -;;; vc-dispatcher.el -- generic command-dispatcher facility. - -;; Copyright (C) 2008, 2009, 2010 -;; Free Software Foundation, Inc. - -;; Author: FSF (see below for full credits) -;; Maintainer: Eric S. Raymond -;; Keywords: tools - -;; 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 3 of the License, 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. If not, see . - -;;; Credits: - -;; Designed and implemented by Eric S. Raymond, originally as part of VC mode. -;; Stefan Monnier and Dan Nicolaescu contributed substantial work on the -;; vc-dir front end. - -;;; Commentary: - -;; Goals: -;; -;; There is a class of front-ending problems that Emacs might be used -;; to address that involves selecting sets of files, or possibly -;; directories, and passing the selection set to slave commands. The -;; prototypical example, from which this code is derived, is talking -;; to version-control systems. -;; -;; vc-dispatcher.el is written to decouple the UI issues in such front -;; ends from their application-specific logic. It also provides a -;; service layer for running the slave commands either synchronously -;; or asynchronously and managing the message/error logs from the -;; command runs. -;; -;; Similar UI problems can be expected to come up in applications -;; areas other than VCSes; IDEs and document search are two obvious ones. -;; This mode is intended to ensure that the Emacs interfaces for all such -;; beasts are consistent and carefully designed. But even if nothing -;; but VC ever uses it, getting the layer separation right will be -;; a valuable thing. - -;; Dispatcher's universe: -;; -;; The universe consists of the file tree rooted at the current -;; directory. The dispatcher's upper layer deduces some subset -;; of the file tree from the state of the currently visited buffer -;; and returns that subset, presumably to a client mode. -;; -;; The user may be looking at either of two different views; a buffer -;; visiting a file, or a directory buffer generated by vc-dispatcher. -;; -;; The lower layer of this mode runs commands in subprocesses, either -;; synchronously or asynchronously. Commands may be launched in one -;; of two ways: they may be run immediately, or the calling mode can -;; create a closure associated with a text-entry buffer, to be -;; executed when the user types C-c to ship the buffer contents. In -;; either case the command messages and error (if any) will remain -;; available in a status buffer. - -;; Special behavior of dispatcher directory buffers: -;; -;; In dispatcher directory buffers, facilities to perform basic -;; navigation and selection operations are provided by keymap and menu -;; entries that dispatcher sets up itself, so they'll be uniform -;; across all dispatcher-using client modes. Client modes are -;; expected to append to these to provide mode-specific bindings. -;; -;; The standard map associates a 'state' slot (that the client mode -;; may set) with each directory entry. The dispatcher knows nothing -;; about the semantics of individual states, but mark and unmark commands -;; treat all entries with the same state as the currently selected one as -;; a unit. - -;; The interface: -;; -;; The main interface to the lower level is vc-do-command. This launches a -;; command, synchronously or asynchronously, making the output available -;; in a command log buffer. Two other functions, (vc-start-logentry) and -;; (vc-finish-logentry), allow you to associate a command closure with an -;; annotation buffer so that when the user confirms the comment the closure -;; is run (with the comment as part of its context). -;; -;; The interface to the upper level has the two main entry points (vc-dir) -;; and (vc-dispatcher-selection-set) and a couple of convenience functions. -;; (vc-dir) sets up a dispatcher browsing buffer; (vc-dispatcher-selection-set) -;; returns a selection set of files, either the marked files in a browsing -;; buffer or the singleton set consisting of the file visited by the current -;; buffer (when that is appropriate). It also does what is needed to ensure -;; that on-disk files and the contents of their visiting Emacs buffers -;; coincide. -;; -;; When the client mode adds a local vc-mode-line-hook to a buffer, it -;; will be called with the buffer file name as argument whenever the -;; dispatcher resynchs the buffer. - -;; To do: -;; -;; - log buffers need font-locking. -;; - -;; General customization -(defcustom vc-logentry-check-hook nil - "Normal hook run by `vc-finish-logentry'. -Use this to impose your own rules on the entry in addition to any the -dispatcher client mode imposes itself." - :type 'hook - :group 'vc) - -(defcustom vc-delete-logbuf-window t - "If non-nil, delete the log buffer and window after each logical action. -If nil, bury that buffer instead. -This is most useful if you have multiple windows on a frame and would like to -preserve the setting." - :type 'boolean - :group 'vc) - -(defcustom vc-command-messages nil - "If non-nil, display run messages from back-end commands." - :type 'boolean - :group 'vc) - -(defcustom vc-suppress-confirm nil - "If non-nil, treat user as expert; suppress yes-no prompts on some things." - :type 'boolean - :group 'vc) - -;; Variables the user doesn't need to know about. - -(defvar vc-log-operation nil) -(defvar vc-log-after-operation-hook nil) -(defvar vc-log-fileset) - -;; In a log entry buffer, this is a local variable -;; that points to the buffer for which it was made -;; (either a file, or a directory buffer). -(defvar vc-parent-buffer nil) -(put 'vc-parent-buffer 'permanent-local t) -(defvar vc-parent-buffer-name nil) -(put 'vc-parent-buffer-name 'permanent-local t) - -;; Common command execution logic - -(defun vc-process-filter (p s) - "An alternative output filter for async process P. -One difference with the default filter is that this inserts S after markers. -Another is that undo information is not kept." - (let ((buffer (process-buffer p))) - (when (buffer-live-p buffer) - (with-current-buffer buffer - (save-excursion - (let ((buffer-undo-list t) - (inhibit-read-only t)) - (goto-char (process-mark p)) - (insert s) - (set-marker (process-mark p) (point)))))))) - -(defun vc-setup-buffer (buf) - "Prepare BUF for executing a slave command and make it current." - (let ((camefrom (current-buffer)) - (olddir default-directory)) - (set-buffer (get-buffer-create buf)) - (kill-all-local-variables) - (set (make-local-variable 'vc-parent-buffer) camefrom) - (set (make-local-variable 'vc-parent-buffer-name) - (concat " from " (buffer-name camefrom))) - (setq default-directory olddir) - (let ((buffer-undo-list t) - (inhibit-read-only t)) - (erase-buffer)))) - -(defvar vc-sentinel-movepoint) ;Dynamically scoped. - -(defun vc-process-sentinel (p s) - (let ((previous (process-get p 'vc-previous-sentinel)) - (buf (process-buffer p))) - ;; Impatient users sometime kill "slow" buffers; check liveness - ;; to avoid "error in process sentinel: Selecting deleted buffer". - (when (buffer-live-p buf) - (when previous (funcall previous p s)) - (with-current-buffer buf - (setq mode-line-process - (let ((status (process-status p))) - ;; Leave mode-line uncluttered, normally. - (unless (eq 'exit status) - (format " (%s)" status)))) - (let (vc-sentinel-movepoint) - ;; Normally, we want async code such as sentinels to not move point. - (save-excursion - (goto-char (process-mark p)) - (let ((cmds (process-get p 'vc-sentinel-commands))) - (process-put p 'vc-sentinel-commands nil) - (dolist (cmd cmds) - ;; Each sentinel may move point and the next one should be run - ;; at that new point. We could get the same result by having - ;; each sentinel read&set process-mark, but since `cmd' needs - ;; to work both for async and sync processes, this would be - ;; difficult to achieve. - (vc-exec-after cmd)))) - ;; But sometimes the sentinels really want to move point. - (when vc-sentinel-movepoint - (let ((win (get-buffer-window (current-buffer) 0))) - (if (not win) - (goto-char vc-sentinel-movepoint) - (with-selected-window win - (goto-char vc-sentinel-movepoint)))))))))) - -(defun vc-set-mode-line-busy-indicator () - (setq mode-line-process - (concat " " (propertize "[waiting...]" - 'face 'mode-line-emphasis - 'help-echo - "A command is in progress in this buffer")))) - -(defun vc-exec-after (code) - "Eval CODE when the current buffer's process is done. -If the current buffer has no process, just evaluate CODE. -Else, add CODE to the process' sentinel." - (let ((proc (get-buffer-process (current-buffer)))) - (cond - ;; If there's no background process, just execute the code. - ;; We used to explicitly call delete-process on exited processes, - ;; but this led to timing problems causing process output to be - ;; lost. Terminated processes get deleted automatically - ;; anyway. -- cyd - ((or (null proc) (eq (process-status proc) 'exit)) - ;; Make sure we've read the process's output before going further. - (when proc (accept-process-output proc)) - (eval code)) - ;; If a process is running, add CODE to the sentinel - ((eq (process-status proc) 'run) - (vc-set-mode-line-busy-indicator) - (let ((previous (process-sentinel proc))) - (unless (eq previous 'vc-process-sentinel) - (process-put proc 'vc-previous-sentinel previous)) - (set-process-sentinel proc 'vc-process-sentinel)) - (process-put proc 'vc-sentinel-commands - ;; We keep the code fragments in the order given - ;; so that vc-diff-finish's message shows up in - ;; the presence of non-nil vc-command-messages. - (append (process-get proc 'vc-sentinel-commands) - (list code)))) - (t (error "Unexpected process state")))) - nil) - -(defvar vc-post-command-functions nil - "Hook run at the end of `vc-do-command'. -Each function is called inside the buffer in which the command was run -and is passed 3 arguments: the COMMAND, the FILES and the FLAGS.") - -(defvar w32-quote-process-args) - -(defun vc-delistify (filelist) - "Smash a FILELIST into a file list string suitable for info messages." - ;; FIXME what about file names with spaces? - (if (not filelist) "." (mapconcat 'identity filelist " "))) - -;;;###autoload -(defun vc-do-command (buffer okstatus command file-or-list &rest flags) - "Execute a slave command, notifying user and checking for errors. -Output from COMMAND goes to BUFFER, or the current buffer if -BUFFER is t. If the destination buffer is not already current, -set it up properly and erase it. The command is considered -successful if its exit status does not exceed OKSTATUS (if -OKSTATUS is nil, that means to ignore error status, if it is -`async', that means not to wait for termination of the -subprocess; if it is t it means to ignore all execution errors). -FILE-OR-LIST is the name of a working file; it may be a list of -files or be nil (to execute commands that don't expect a file -name or set of files). If an optional list of FLAGS is present, -that is inserted into the command line before the filename. -Return the return value of the slave command in the synchronous -case, and the process object in the asynchronous case." - ;; FIXME: file-relative-name can return a bogus result because - ;; it doesn't look at the actual file-system to see if symlinks - ;; come into play. - (let* ((files - (mapcar (lambda (f) (file-relative-name (expand-file-name f))) - (if (listp file-or-list) file-or-list (list file-or-list)))) - (full-command - ;; What we're doing here is preparing a version of the command - ;; for display in a debug-progress message. If it's fewer than - ;; 20 characters display the entire command (without trailing - ;; newline). Otherwise display the first 20 followed by an ellipsis. - (concat (if (string= (substring command -1) "\n") - (substring command 0 -1) - command) - " " - (vc-delistify (mapcar (lambda (s) (if (> (length s) 20) (concat (substring s 0 2) "...") s)) flags)) - " " (vc-delistify files)))) - (save-current-buffer - (unless (or (eq buffer t) - (and (stringp buffer) - (string= (buffer-name) buffer)) - (eq buffer (current-buffer))) - (vc-setup-buffer buffer)) - ;; If there's some previous async process still running, just kill it. - (let ((oldproc (get-buffer-process (current-buffer)))) - ;; If we wanted to wait for oldproc to finish before doing - ;; something, we'd have used vc-eval-after. - ;; Use `delete-process' rather than `kill-process' because we don't - ;; want any of its output to appear from now on. - (when oldproc (delete-process oldproc))) - (let ((squeezed (remq nil flags)) - (inhibit-read-only t) - (status 0)) - (when files - (setq squeezed (nconc squeezed files))) - (let (;; Since some functions need to parse the output - ;; from external commands, set LC_MESSAGES to C. - (process-environment (cons "LC_MESSAGES=C" process-environment)) - (w32-quote-process-args t)) - (if (eq okstatus 'async) - ;; Run asynchronously. - (let ((proc - (let ((process-connection-type nil)) - (apply 'start-file-process command (current-buffer) - command squeezed)))) - (when vc-command-messages - (message "Running %s in background..." full-command)) - ;;(set-process-sentinel proc (lambda (p msg) (delete-process p))) - (set-process-filter proc 'vc-process-filter) - (setq status proc) - (when vc-command-messages - (vc-exec-after - `(message "Running %s in background... done" ',full-command)))) - ;; Run synchronously - (when vc-command-messages - (message "Running %s in foreground..." full-command)) - (let ((buffer-undo-list t)) - (setq status (apply 'process-file command nil t nil squeezed))) - (when (and (not (eq t okstatus)) - (or (not (integerp status)) - (and okstatus (< okstatus status)))) - (unless (eq ?\s (aref (buffer-name (current-buffer)) 0)) - (pop-to-buffer (current-buffer)) - (goto-char (point-min)) - (shrink-window-if-larger-than-buffer)) - (error "Running %s...FAILED (%s)" full-command - (if (integerp status) (format "status %d" status) status))) - (when vc-command-messages - (message "Running %s...OK = %d" full-command status)))) - (vc-exec-after - `(run-hook-with-args 'vc-post-command-functions - ',command ',file-or-list ',flags)) - status)))) - -;; These functions are used to ensure that the view the user sees is up to date -;; even if the dispatcher client mode has messed with file contents (as in, -;; for example, VCS keyword expansion). - -(declare-function view-mode-exit "view" (&optional return-to-alist exit-action all-win)) - -(defun vc-position-context (posn) - "Save a bit of the text around POSN in the current buffer. -Used to help us find the corresponding position again later -if markers are destroyed or corrupted." - ;; A lot of this was shamelessly lifted from Sebastian Kremer's - ;; rcs.el mode. - (list posn - (buffer-size) - (buffer-substring posn - (min (point-max) (+ posn 100))))) - -(defun vc-find-position-by-context (context) - "Return the position of CONTEXT in the current buffer. -If CONTEXT cannot be found, return nil." - (let ((context-string (nth 2 context))) - (if (equal "" context-string) - (point-max) - (save-excursion - (let ((diff (- (nth 1 context) (buffer-size)))) - (when (< diff 0) (setq diff (- diff))) - (goto-char (nth 0 context)) - (if (or (search-forward context-string nil t) - ;; Can't use search-backward since the match may continue - ;; after point. - (progn (goto-char (- (point) diff (length context-string))) - ;; goto-char doesn't signal an error at - ;; beginning of buffer like backward-char would - (search-forward context-string nil t))) - ;; to beginning of OSTRING - (- (point) (length context-string)))))))) - -(defun vc-context-matches-p (posn context) - "Return t if POSN matches CONTEXT, nil otherwise." - (let* ((context-string (nth 2 context)) - (len (length context-string)) - (end (+ posn len))) - (if (> end (1+ (buffer-size))) - nil - (string= context-string (buffer-substring posn end))))) - -(defun vc-buffer-context () - "Return a list (POINT-CONTEXT MARK-CONTEXT REPARSE). -Used by `vc-restore-buffer-context' to later restore the context." - (let ((point-context (vc-position-context (point))) - ;; Use mark-marker to avoid confusion in transient-mark-mode. - (mark-context (when (eq (marker-buffer (mark-marker)) (current-buffer)) - (vc-position-context (mark-marker)))) - ;; Make the right thing happen in transient-mark-mode. - (mark-active nil)) - (list point-context mark-context nil))) - -(defun vc-restore-buffer-context (context) - "Restore point/mark, and reparse any affected compilation buffers. -CONTEXT is that which `vc-buffer-context' returns." - (let ((point-context (nth 0 context)) - (mark-context (nth 1 context))) - ;; if necessary, restore point and mark - (if (not (vc-context-matches-p (point) point-context)) - (let ((new-point (vc-find-position-by-context point-context))) - (when new-point (goto-char new-point)))) - (and mark-active - mark-context - (not (vc-context-matches-p (mark) mark-context)) - (let ((new-mark (vc-find-position-by-context mark-context))) - (when new-mark (set-mark new-mark)))))) - -(defun vc-revert-buffer-internal (&optional arg no-confirm) - "Revert buffer, keeping point and mark where user expects them. -Try to be clever in the face of changes due to expanded version-control -key words. This is important for typeahead to work as expected. -ARG and NO-CONFIRM are passed on to `revert-buffer'." - (interactive "P") - (widen) - (let ((context (vc-buffer-context))) - ;; Use save-excursion here, because it may be able to restore point - ;; and mark properly even in cases where vc-restore-buffer-context - ;; would fail. However, save-excursion might also get it wrong -- - ;; in this case, vc-restore-buffer-context gives it a second try. - (save-excursion - ;; t means don't call normal-mode; - ;; that's to preserve various minor modes. - (revert-buffer arg no-confirm t)) - (vc-restore-buffer-context context))) - -(defvar vc-mode-line-hook nil) -(make-variable-buffer-local 'vc-mode-line-hook) -(put 'vc-mode-line-hook 'permanent-local t) - -(defun vc-resynch-window (file &optional keep noquery reset-vc-info) - "If FILE is in the current buffer, either revert or unvisit it. -The choice between revert (to see expanded keywords) and unvisit -depends on KEEP. NOQUERY if non-nil inhibits confirmation for -reverting. NOQUERY should be t *only* if it is known the only -difference between the buffer and the file is due to -modifications by the dispatcher client code, rather than user -editing!" - (and (string= buffer-file-name file) - (if keep - (when (file-exists-p file) - (when reset-vc-info - (vc-file-clearprops file)) - (vc-revert-buffer-internal t noquery) - - ;; VC operations might toggle the read-only state. In - ;; that case we need to adjust the `view-mode' status - ;; when `view-read-only' is non-nil. - (and view-read-only - (if (file-writable-p file) - (and view-mode - (let ((view-old-buffer-read-only nil)) - (view-mode-exit))) - (and (not view-mode) - (not (eq (get major-mode 'mode-class) 'special)) - (view-mode-enter)))) - - ;; FIXME: Why use a hook? Why pass it buffer-file-name? - (run-hook-with-args 'vc-mode-line-hook buffer-file-name)) - (kill-buffer (current-buffer))))) - -(declare-function vc-dir-resynch-file "vc-dir" (&optional fname)) -(declare-function vc-string-prefix-p "vc" (prefix string)) - -(defun vc-resynch-buffers-in-directory (directory &optional keep noquery reset-vc-info) - "Resync all buffers that visit files in DIRECTORY." - (dolist (buffer (buffer-list)) - (let ((fname (buffer-file-name buffer))) - (when (and fname (vc-string-prefix-p directory fname)) - (with-current-buffer buffer - (vc-resynch-buffer fname keep noquery reset-vc-info)))))) - -(defun vc-resynch-buffer (file &optional keep noquery reset-vc-info) - "If FILE is currently visited, resynch its buffer." - (if (string= buffer-file-name file) - (vc-resynch-window file keep noquery reset-vc-info) - (if (file-directory-p file) - (vc-resynch-buffers-in-directory file keep noquery reset-vc-info) - (let ((buffer (get-file-buffer file))) - (when buffer - (with-current-buffer buffer - (vc-resynch-window file keep noquery reset-vc-info)))))) - ;; Try to avoid unnecessary work, a *vc-dir* buffer is only present - ;; if this is true. - (when vc-dir-buffers - (vc-dir-resynch-file file))) - -(defun vc-buffer-sync (&optional not-urgent) - "Make sure the current buffer and its working file are in sync. -NOT-URGENT means it is ok to continue if the user says not to save." - (when (buffer-modified-p) - (if (or vc-suppress-confirm - (y-or-n-p (format "Buffer %s modified; save it? " (buffer-name)))) - (save-buffer) - (unless not-urgent - (error "Aborted"))))) - -;; Command closures - -;; Set up key bindings for use while editing log messages - -(defun vc-log-edit (fileset mode) - "Set up `log-edit' for use on FILE." - (setq default-directory - (with-current-buffer vc-parent-buffer default-directory)) - (log-edit 'vc-finish-logentry - nil - `((log-edit-listfun . (lambda () - ;; FIXME: Should expand the list - ;; for directories. - (mapcar 'file-relative-name - ',fileset))) - (log-edit-diff-function . (lambda () (vc-diff nil)))) - nil - mode) - (set (make-local-variable 'vc-log-fileset) fileset) - (set-buffer-modified-p nil) - (setq buffer-file-name nil)) - -(defun vc-start-logentry (files comment initial-contents msg logbuf mode action &optional after-hook) - "Accept a comment for an operation on FILES. -If COMMENT is nil, pop up a LOGBUF buffer, emit MSG, and set the -action on close to ACTION. If COMMENT is a string and -INITIAL-CONTENTS is non-nil, then COMMENT is used as the initial -contents of the log entry buffer. If COMMENT is a string and -INITIAL-CONTENTS is nil, do action immediately as if the user had -entered COMMENT. If COMMENT is t, also do action immediately with an -empty comment. Remember the file's buffer in `vc-parent-buffer' -\(current one if no file). Puts the log-entry buffer in major-mode -MODE, defaulting to `log-edit-mode' if MODE is nil. -AFTER-HOOK specifies the local value for `vc-log-after-operation-hook'." - (let ((parent - (if (vc-dispatcher-browsing) - ;; If we are called from a directory browser, the parent buffer is - ;; the current buffer. - (current-buffer) - (if (and files (equal (length files) 1)) - (get-file-buffer (car files)) - (current-buffer))))) - (if (and comment (not initial-contents)) - (set-buffer (get-buffer-create logbuf)) - (pop-to-buffer (get-buffer-create logbuf))) - (set (make-local-variable 'vc-parent-buffer) parent) - (set (make-local-variable 'vc-parent-buffer-name) - (concat " from " (buffer-name vc-parent-buffer))) - (vc-log-edit files mode) - (make-local-variable 'vc-log-after-operation-hook) - (when after-hook - (setq vc-log-after-operation-hook after-hook)) - (setq vc-log-operation action) - (when comment - (erase-buffer) - (when (stringp comment) (insert comment))) - (if (or (not comment) initial-contents) - (message "%s Type C-c C-c when done" msg) - (vc-finish-logentry (eq comment t))))) - -(declare-function vc-dir-move-to-goal-column "vc-dir" ()) -;; vc-finish-logentry is typically called from a log-edit buffer (see -;; vc-start-logentry). -(defun vc-finish-logentry (&optional nocomment) - "Complete the operation implied by the current log entry. -Use the contents of the current buffer as a check-in or registration -comment. If the optional arg NOCOMMENT is non-nil, then don't check -the buffer contents as a comment." - (interactive) - ;; Check and record the comment, if any. - (unless nocomment - (run-hooks 'vc-logentry-check-hook)) - ;; Sync parent buffer in case the user modified it while editing the comment. - ;; But not if it is a vc-dir buffer. - (with-current-buffer vc-parent-buffer - (or (vc-dispatcher-browsing) (vc-buffer-sync))) - (unless vc-log-operation - (error "No log operation is pending")) - - ;; save the parameters held in buffer-local variables - (let ((logbuf (current-buffer)) - (log-operation vc-log-operation) - ;; FIXME: When coming from VC-Dir, we should check that the - ;; set of selected files is still equal to vc-log-fileset, - ;; to avoid surprises. - (log-fileset vc-log-fileset) - (log-entry (buffer-string)) - (after-hook vc-log-after-operation-hook)) - (pop-to-buffer vc-parent-buffer) - ;; OK, do it to it - (save-excursion - (funcall log-operation - log-fileset - log-entry)) - ;; Remove checkin window (after the checkin so that if that fails - ;; we don't zap the log buffer and the typing therein). - ;; -- IMO this should be replaced with quit-window - (cond ((and logbuf vc-delete-logbuf-window) - (delete-windows-on logbuf (selected-frame)) - ;; Kill buffer and delete any other dedicated windows/frames. - (kill-buffer logbuf)) - (logbuf - (with-selected-window (or (get-buffer-window logbuf 0) - (selected-window)) - (with-current-buffer logbuf - (bury-buffer))))) - ;; Now make sure we see the expanded headers - (when log-fileset - (mapc - (lambda (file) (vc-resynch-buffer file vc-keep-workfiles t)) - log-fileset)) - (when (vc-dispatcher-browsing) - (vc-dir-move-to-goal-column)) - (run-hooks after-hook 'vc-finish-logentry-hook))) - -(defun vc-dispatcher-browsing () - "Are we in a directory browser buffer?" - (derived-mode-p 'vc-dir-mode)) - -;; These are unused. -;; (defun vc-dispatcher-in-fileset-p (fileset) -;; (let ((member nil)) -;; (while (and (not member) fileset) -;; (let ((elem (pop fileset))) -;; (if (if (file-directory-p elem) -;; (eq t (compare-strings buffer-file-name nil (length elem) -;; elem nil nil)) -;; (eq (current-buffer) (get-file-buffer elem))) -;; (setq member t)))) -;; member)) - -;; (defun vc-dispatcher-selection-set (&optional observer) -;; "Deduce a set of files to which to apply an operation. Return a cons -;; cell (SELECTION . FILESET), where SELECTION is what the user chose -;; and FILES is the flist with any directories replaced by the listed files -;; within them. - -;; If we're in a directory display, the fileset is the list of marked files (if -;; there is one) else the file on the current line. If not in a directory -;; display, but the current buffer visits a file, the fileset is a singleton -;; containing that file. Otherwise, throw an error." -;; (let ((selection -;; (cond -;; ;; Browsing with vc-dir -;; ((vc-dispatcher-browsing) -;; ;; If no files are marked, temporarily mark current file -;; ;; and choose on that basis (so we get subordinate files) -;; (if (not (vc-dir-marked-files)) -;; (prog2 -;; (vc-dir-mark-file) -;; (cons (vc-dir-marked-files) (vc-dir-marked-only-files)) -;; (vc-dir-unmark-all-files t)) -;; (cons (vc-dir-marked-files) (vc-dir-marked-only-files)))) -;; ;; Visiting an eligible file -;; ((buffer-file-name) -;; (cons (list buffer-file-name) (list buffer-file-name))) -;; ;; No eligible file -- if there's a parent buffer, deduce from there -;; ((and vc-parent-buffer (or (buffer-file-name vc-parent-buffer) -;; (with-current-buffer vc-parent-buffer -;; (vc-dispatcher-browsing)))) -;; (with-current-buffer vc-parent-buffer -;; (vc-dispatcher-selection-set))) -;; ;; No good set here, throw error -;; (t (error "No fileset is available here"))))) -;; ;; We assume, in order to avoid unpleasant surprises to the user, -;; ;; that a fileset is not in good shape to be handed to the user if the -;; ;; buffers visiting the fileset don't match the on-disk contents. -;; (unless observer -;; (save-some-buffers -;; nil (lambda () (vc-dispatcher-in-fileset-p (cdr selection))))) -;; selection)) - -(provide 'vc-dispatcher) - -;; arch-tag: 7d08b17f-5470-4799-914b-bfb9fcf6a246 -;;; vc-dispatcher.el ends here diff -r 681cd08dc0f7 -r e93288477c43 lisp/vc-git.el --- a/lisp/vc-git.el Thu Jun 10 22:43:47 2010 +0000 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,1031 +0,0 @@ -;;; vc-git.el --- VC backend for the git version control system - -;; Copyright (C) 2006, 2007, 2008, 2009, 2010 Free Software Foundation, Inc. - -;; Author: Alexandre Julliard -;; Keywords: tools - -;; 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 3 of the License, 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. If not, see . - -;;; Commentary: - -;; This file contains a VC backend for the git version control -;; system. -;; - -;;; Installation: - -;; To install: put this file on the load-path and add Git to the list -;; of supported backends in `vc-handled-backends'; the following line, -;; placed in your ~/.emacs, will accomplish this: -;; -;; (add-to-list 'vc-handled-backends 'Git) - -;;; Todo: -;; - check if more functions could use vc-git-command instead -;; of start-process. -;; - changelog generation - -;; Implement the rest of the vc interface. See the comment at the -;; beginning of vc.el. The current status is: -;; ("??" means: "figure out what to do about it") -;; -;; FUNCTION NAME STATUS -;; BACKEND PROPERTIES -;; * revision-granularity OK -;; STATE-QUERYING FUNCTIONS -;; * registered (file) OK -;; * state (file) OK -;; - state-heuristic (file) NOT NEEDED -;; * working-revision (file) OK -;; - latest-on-branch-p (file) NOT NEEDED -;; * checkout-model (files) OK -;; - workfile-unchanged-p (file) OK -;; - mode-line-string (file) OK -;; STATE-CHANGING FUNCTIONS -;; * create-repo () OK -;; * register (files &optional rev comment) OK -;; - init-revision (file) NOT NEEDED -;; - responsible-p (file) OK -;; - could-register (file) NOT NEEDED, DEFAULT IS GOOD -;; - receive-file (file rev) NOT NEEDED -;; - unregister (file) OK -;; * checkin (files rev comment) OK -;; * find-revision (file rev buffer) OK -;; * checkout (file &optional editable rev) OK -;; * revert (file &optional contents-done) OK -;; - rollback (files) COULD BE SUPPORTED -;; - merge (file rev1 rev2) It would be possible to merge -;; changes into a single file, but -;; when committing they wouldn't -;; be identified as a merge -;; by git, so it's probably -;; not a good idea. -;; - merge-news (file) see `merge' -;; - steal-lock (file &optional revision) NOT NEEDED -;; HISTORY FUNCTIONS -;; * print-log (files buffer &optional shortlog start-revision limit) OK -;; - log-view-mode () OK -;; - show-log-entry (revision) OK -;; - comment-history (file) ?? -;; - update-changelog (files) COULD BE SUPPORTED -;; * diff (file &optional rev1 rev2 buffer) OK -;; - revision-completion-table (files) OK -;; - annotate-command (file buf &optional rev) OK -;; - annotate-time () OK -;; - annotate-current-time () NOT NEEDED -;; - annotate-extract-revision-at-line () OK -;; TAG SYSTEM -;; - create-tag (dir name branchp) OK -;; - retrieve-tag (dir name update) OK -;; MISCELLANEOUS -;; - make-version-backups-p (file) NOT NEEDED -;; - repository-hostname (dirname) NOT NEEDED -;; - previous-revision (file rev) OK -;; - next-revision (file rev) OK -;; - check-headers () COULD BE SUPPORTED -;; - clear-headers () NOT NEEDED -;; - delete-file (file) OK -;; - rename-file (old new) OK -;; - find-file-hook () NOT NEEDED - -(eval-when-compile - (require 'cl) - (require 'vc) - (require 'vc-dir) - (require 'grep)) - -(defcustom vc-git-diff-switches t - "String or list of strings specifying switches for Git diff under VC. -If nil, use the value of `vc-diff-switches'. If t, use no switches." - :type '(choice (const :tag "Unspecified" nil) - (const :tag "None" t) - (string :tag "Argument String") - (repeat :tag "Argument List" :value ("") string)) - :version "23.1" - :group 'vc) - -(defvar vc-git-commits-coding-system 'utf-8 - "Default coding system for git commits.") - -;;; BACKEND PROPERTIES - -(defun vc-git-revision-granularity () 'repository) -(defun vc-git-checkout-model (files) 'implicit) - -;;; STATE-QUERYING FUNCTIONS - -;;;###autoload (defun vc-git-registered (file) -;;;###autoload "Return non-nil if FILE is registered with git." -;;;###autoload (if (vc-find-root file ".git") ; Short cut. -;;;###autoload (progn -;;;###autoload (load "vc-git") -;;;###autoload (vc-git-registered file)))) - -(defun vc-git-registered (file) - "Check whether FILE is registered with git." - (let ((dir (vc-git-root file))) - (when dir - (with-temp-buffer - (let* (process-file-side-effects - ;; Do not use the `file-name-directory' here: git-ls-files - ;; sometimes fails to return the correct status for relative - ;; path specs. - ;; See also: http://marc.info/?l=git&m=125787684318129&w=2 - (name (file-relative-name file dir)) - (str (ignore-errors - (cd dir) - (vc-git--out-ok "ls-files" "-c" "-z" "--" name) - ;; If result is empty, use ls-tree to check for deleted - ;; file. - (when (eq (point-min) (point-max)) - (vc-git--out-ok "ls-tree" "--name-only" "-z" "HEAD" - "--" name)) - (buffer-string)))) - (and str - (> (length str) (length name)) - (string= (substring str 0 (1+ (length name))) - (concat name "\0")))))))) - -(defun vc-git--state-code (code) - "Convert from a string to a added/deleted/modified state." - (case (string-to-char code) - (?M 'edited) - (?A 'added) - (?D 'removed) - (?U 'edited) ;; FIXME - (?T 'edited))) ;; FIXME - -(defun vc-git-state (file) - "Git-specific version of `vc-state'." - ;; FIXME: This can't set 'ignored or 'conflict yet - ;; The 'ignored state could be detected with `git ls-files -i -o - ;; --exclude-standard` It also can't set 'needs-update or - ;; 'needs-merge. The rough equivalent would be that upstream branch - ;; for current branch is in fast-forward state i.e. current branch - ;; is direct ancestor of corresponding upstream branch, and the file - ;; was modified upstream. But we can't check that without a network - ;; operation. - (if (not (vc-git-registered file)) - 'unregistered - (vc-git--call nil "add" "--refresh" "--" (file-relative-name file)) - (let ((diff (vc-git--run-command-string - file "diff-index" "-z" "HEAD" "--"))) - (if (and diff (string-match ":[0-7]\\{6\\} [0-7]\\{6\\} [0-9a-f]\\{40\\} [0-9a-f]\\{40\\} \\([ADMUT]\\)\0[^\0]+\0" - diff)) - (vc-git--state-code (match-string 1 diff)) - (if (vc-git--empty-db-p) 'added 'up-to-date))))) - -(defun vc-git-working-revision (file) - "Git-specific version of `vc-working-revision'." - (let* (process-file-side-effects - (str (with-output-to-string - (with-current-buffer standard-output - (vc-git--out-ok "symbolic-ref" "HEAD"))))) - (if (string-match "^\\(refs/heads/\\)?\\(.+\\)$" str) - (match-string 2 str) - str))) - -(defun vc-git-workfile-unchanged-p (file) - (eq 'up-to-date (vc-git-state file))) - -(defun vc-git-mode-line-string (file) - "Return string for placement into the modeline for FILE." - (let* ((branch (vc-git-working-revision file)) - (def-ml (vc-default-mode-line-string 'Git file)) - (help-echo (get-text-property 0 'help-echo def-ml))) - (if (zerop (length branch)) - (propertize - (concat def-ml "!") - 'help-echo (concat help-echo "\nNo current branch (detached HEAD)")) - (propertize def-ml - 'help-echo (concat help-echo "\nCurrent branch: " branch))))) - -(defstruct (vc-git-extra-fileinfo - (:copier nil) - (:constructor vc-git-create-extra-fileinfo - (old-perm new-perm &optional rename-state orig-name)) - (:conc-name vc-git-extra-fileinfo->)) - old-perm new-perm ;; Permission flags. - rename-state ;; Rename or copy state. - orig-name) ;; Original name for renames or copies. - -(defun vc-git-escape-file-name (name) - "Escape a file name if necessary." - (if (string-match "[\n\t\"\\]" name) - (concat "\"" - (mapconcat (lambda (c) - (case c - (?\n "\\n") - (?\t "\\t") - (?\\ "\\\\") - (?\" "\\\"") - (t (char-to-string c)))) - name "") - "\"") - name)) - -(defun vc-git-file-type-as-string (old-perm new-perm) - "Return a string describing the file type based on its permissions." - (let* ((old-type (lsh (or old-perm 0) -9)) - (new-type (lsh (or new-perm 0) -9)) - (str (case new-type - (?\100 ;; File. - (case old-type - (?\100 nil) - (?\120 " (type change symlink -> file)") - (?\160 " (type change subproject -> file)"))) - (?\120 ;; Symlink. - (case old-type - (?\100 " (type change file -> symlink)") - (?\160 " (type change subproject -> symlink)") - (t " (symlink)"))) - (?\160 ;; Subproject. - (case old-type - (?\100 " (type change file -> subproject)") - (?\120 " (type change symlink -> subproject)") - (t " (subproject)"))) - (?\110 nil) ;; Directory (internal, not a real git state). - (?\000 ;; Deleted or unknown. - (case old-type - (?\120 " (symlink)") - (?\160 " (subproject)"))) - (t (format " (unknown type %o)" new-type))))) - (cond (str (propertize str 'face 'font-lock-comment-face)) - ((eq new-type ?\110) "/") - (t "")))) - -(defun vc-git-rename-as-string (state extra) - "Return a string describing the copy or rename associated with INFO, -or an empty string if none." - (let ((rename-state (when extra - (vc-git-extra-fileinfo->rename-state extra)))) - (if rename-state - (propertize - (concat " (" - (if (eq rename-state 'copy) "copied from " - (if (eq state 'added) "renamed from " - "renamed to ")) - (vc-git-escape-file-name - (vc-git-extra-fileinfo->orig-name extra)) - ")") - 'face 'font-lock-comment-face) - ""))) - -(defun vc-git-permissions-as-string (old-perm new-perm) - "Format a permission change as string." - (propertize - (if (or (not old-perm) - (not new-perm) - (eq 0 (logand ?\111 (logxor old-perm new-perm)))) - " " - (if (eq 0 (logand ?\111 old-perm)) "+x" "-x")) - 'face 'font-lock-type-face)) - -(defun vc-git-dir-printer (info) - "Pretty-printer for the vc-dir-fileinfo structure." - (let* ((isdir (vc-dir-fileinfo->directory info)) - (state (if isdir "" (vc-dir-fileinfo->state info))) - (extra (vc-dir-fileinfo->extra info)) - (old-perm (when extra (vc-git-extra-fileinfo->old-perm extra))) - (new-perm (when extra (vc-git-extra-fileinfo->new-perm extra)))) - (insert - " " - (propertize (format "%c" (if (vc-dir-fileinfo->marked info) ?* ? )) - 'face 'font-lock-type-face) - " " - (propertize - (format "%-12s" state) - 'face (cond ((eq state 'up-to-date) 'font-lock-builtin-face) - ((eq state 'missing) 'font-lock-warning-face) - (t 'font-lock-variable-name-face)) - 'mouse-face 'highlight) - " " (vc-git-permissions-as-string old-perm new-perm) - " " - (propertize (vc-git-escape-file-name (vc-dir-fileinfo->name info)) - 'face (if isdir 'font-lock-comment-delimiter-face - 'font-lock-function-name-face) - 'help-echo - (if isdir - "Directory\nVC operations can be applied to it\nmouse-3: Pop-up menu" - "File\nmouse-3: Pop-up menu") - 'keymap vc-dir-filename-mouse-map - 'mouse-face 'highlight) - (vc-git-file-type-as-string old-perm new-perm) - (vc-git-rename-as-string state extra)))) - -(defun vc-git-after-dir-status-stage (stage files update-function) - "Process sentinel for the various dir-status stages." - (let (next-stage result) - (goto-char (point-min)) - (case stage - (update-index - (setq next-stage (if (vc-git--empty-db-p) 'ls-files-added - (if files 'ls-files-up-to-date 'diff-index)))) - (ls-files-added - (setq next-stage 'ls-files-unknown) - (while (re-search-forward "\\([0-7]\\{6\\}\\) [0-9a-f]\\{40\\} 0\t\\([^\0]+\\)\0" nil t) - (let ((new-perm (string-to-number (match-string 1) 8)) - (name (match-string 2))) - (push (list name 'added (vc-git-create-extra-fileinfo 0 new-perm)) - result)))) - (ls-files-up-to-date - (setq next-stage 'diff-index) - (while (re-search-forward "\\([0-7]\\{6\\}\\) [0-9a-f]\\{40\\} 0\t\\([^\0]+\\)\0" nil t) - (let ((perm (string-to-number (match-string 1) 8)) - (name (match-string 2))) - (push (list name 'up-to-date - (vc-git-create-extra-fileinfo perm perm)) - result)))) - (ls-files-unknown - (when files (setq next-stage 'ls-files-ignored)) - (while (re-search-forward "\\([^\0]*?\\)\0" nil t 1) - (push (list (match-string 1) 'unregistered - (vc-git-create-extra-fileinfo 0 0)) - result))) - (ls-files-ignored - (while (re-search-forward "\\([^\0]*?\\)\0" nil t 1) - (push (list (match-string 1) 'ignored - (vc-git-create-extra-fileinfo 0 0)) - result))) - (diff-index - (setq next-stage 'ls-files-unknown) - (while (re-search-forward - ":\\([0-7]\\{6\\}\\) \\([0-7]\\{6\\}\\) [0-9a-f]\\{40\\} [0-9a-f]\\{40\\} \\(\\([ADMUT]\\)\0\\([^\0]+\\)\\|\\([CR]\\)[0-9]*\0\\([^\0]+\\)\0\\([^\0]+\\)\\)\0" - nil t 1) - (let ((old-perm (string-to-number (match-string 1) 8)) - (new-perm (string-to-number (match-string 2) 8)) - (state (or (match-string 4) (match-string 6))) - (name (or (match-string 5) (match-string 7))) - (new-name (match-string 8))) - (if new-name ; Copy or rename. - (if (eq ?C (string-to-char state)) - (push (list new-name 'added - (vc-git-create-extra-fileinfo old-perm new-perm - 'copy name)) - result) - (push (list name 'removed - (vc-git-create-extra-fileinfo 0 0 - 'rename new-name)) - result) - (push (list new-name 'added - (vc-git-create-extra-fileinfo old-perm new-perm - 'rename name)) - result)) - (push (list name (vc-git--state-code state) - (vc-git-create-extra-fileinfo old-perm new-perm)) - result)))))) - (when result - (setq result (nreverse result)) - (when files - (dolist (entry result) (setq files (delete (car entry) files))) - (unless files (setq next-stage nil)))) - (when (or result (not next-stage)) - (funcall update-function result next-stage)) - (when next-stage - (vc-git-dir-status-goto-stage next-stage files update-function)))) - -(defun vc-git-dir-status-goto-stage (stage files update-function) - (erase-buffer) - (case stage - (update-index - (if files - (vc-git-command (current-buffer) 'async files "add" "--refresh" "--") - (vc-git-command (current-buffer) 'async nil - "update-index" "--refresh"))) - (ls-files-added - (vc-git-command (current-buffer) 'async files - "ls-files" "-z" "-c" "-s" "--")) - (ls-files-up-to-date - (vc-git-command (current-buffer) 'async files - "ls-files" "-z" "-c" "-s" "--")) - (ls-files-unknown - (vc-git-command (current-buffer) 'async files - "ls-files" "-z" "-o" "--directory" - "--no-empty-directory" "--exclude-standard" "--")) - (ls-files-ignored - (vc-git-command (current-buffer) 'async files - "ls-files" "-z" "-o" "-i" "--directory" - "--no-empty-directory" "--exclude-standard" "--")) - ;; --relative added in Git 1.5.5. - (diff-index - (vc-git-command (current-buffer) 'async files - "diff-index" "--relative" "-z" "-M" "HEAD" "--"))) - (vc-exec-after - `(vc-git-after-dir-status-stage ',stage ',files ',update-function))) - -(defun vc-git-dir-status (dir update-function) - "Return a list of (FILE STATE EXTRA) entries for DIR." - ;; Further things that would have to be fixed later: - ;; - how to handle unregistered directories - ;; - how to support vc-dir on a subdir of the project tree - (vc-git-dir-status-goto-stage 'update-index nil update-function)) - -(defun vc-git-dir-status-files (dir files default-state update-function) - "Return a list of (FILE STATE EXTRA) entries for FILES in DIR." - (vc-git-dir-status-goto-stage 'update-index files update-function)) - -(defvar vc-git-stash-map - (let ((map (make-sparse-keymap))) - ;; Turn off vc-dir marking - (define-key map [mouse-2] 'ignore) - - (define-key map [down-mouse-3] 'vc-git-stash-menu) - (define-key map "\C-k" 'vc-git-stash-delete-at-point) - (define-key map "=" 'vc-git-stash-show-at-point) - (define-key map "\C-m" 'vc-git-stash-show-at-point) - (define-key map "A" 'vc-git-stash-apply-at-point) - (define-key map "P" 'vc-git-stash-pop-at-point) - (define-key map "S" 'vc-git-stash-snapshot) - map)) - -(defvar vc-git-stash-menu-map - (let ((map (make-sparse-keymap "Git Stash"))) - (define-key map [de] - '(menu-item "Delete stash" vc-git-stash-delete-at-point - :help "Delete the current stash")) - (define-key map [ap] - '(menu-item "Apply stash" vc-git-stash-apply-at-point - :help "Apply the current stash and keep it in the stash list")) - (define-key map [po] - '(menu-item "Apply and remove stash (pop)" vc-git-stash-pop-at-point - :help "Apply the current stash and remove it")) - (define-key map [sh] - '(menu-item "Show stash" vc-git-stash-show-at-point - :help "Show the contents of the current stash")) - map)) - -(defun vc-git-dir-extra-headers (dir) - (let ((str (with-output-to-string - (with-current-buffer standard-output - (vc-git--out-ok "symbolic-ref" "HEAD")))) - (stash (vc-git-stash-list)) - (stash-help-echo "Use M-x vc-git-stash to create stashes.") - branch remote remote-url) - (if (string-match "^\\(refs/heads/\\)?\\(.+\\)$" str) - (progn - (setq branch (match-string 2 str)) - (setq remote - (with-output-to-string - (with-current-buffer standard-output - (vc-git--out-ok "config" - (concat "branch." branch ".remote"))))) - (when (string-match "\\([^\n]+\\)" remote) - (setq remote (match-string 1 remote))) - (when remote - (setq remote-url - (with-output-to-string - (with-current-buffer standard-output - (vc-git--out-ok "config" - (concat "remote." remote ".url")))))) - (when (string-match "\\([^\n]+\\)" remote-url) - (setq remote-url (match-string 1 remote-url)))) - (setq branch "not (detached HEAD)")) - ;; FIXME: maybe use a different face when nothing is stashed. - (concat - (propertize "Branch : " 'face 'font-lock-type-face) - (propertize branch - 'face 'font-lock-variable-name-face) - (when remote - (concat - "\n" - (propertize "Remote : " 'face 'font-lock-type-face) - (propertize remote-url - 'face 'font-lock-variable-name-face))) - "\n" - (if stash - (concat - (propertize "Stash :\n" 'face 'font-lock-type-face - 'help-echo stash-help-echo) - (mapconcat - (lambda (x) - (propertize x - 'face 'font-lock-variable-name-face - 'mouse-face 'highlight - 'help-echo "mouse-3: Show stash menu\nRET: Show stash\nA: Apply stash\nP: Apply and remove stash (pop)\nC-k: Delete stash" - 'keymap vc-git-stash-map)) - stash "\n")) - (concat - (propertize "Stash : " 'face 'font-lock-type-face - 'help-echo stash-help-echo) - (propertize "Nothing stashed" - 'help-echo stash-help-echo - 'face 'font-lock-variable-name-face)))))) - -;;; STATE-CHANGING FUNCTIONS - -(defun vc-git-create-repo () - "Create a new Git repository." - (vc-git-command nil 0 nil "init")) - -(defun vc-git-register (files &optional rev comment) - "Register FILES into the git version-control system." - (let (flist dlist) - (dolist (crt files) - (if (file-directory-p crt) - (push crt dlist) - (push crt flist))) - (when flist - (vc-git-command nil 0 flist "update-index" "--add" "--")) - (when dlist - (vc-git-command nil 0 dlist "add")))) - -(defalias 'vc-git-responsible-p 'vc-git-root) - -(defun vc-git-unregister (file) - (vc-git-command nil 0 file "rm" "-f" "--cached" "--")) - -(declare-function log-edit-extract-headers "log-edit" (headers string)) - -(defun vc-git-checkin (files rev comment) - (let ((coding-system-for-write vc-git-commits-coding-system)) - (apply 'vc-git-command nil 0 files - (nconc (list "commit" "-m") - (log-edit-extract-headers '(("Author" . "--author") - ("Date" . "--date")) - comment) - (list "--only" "--"))))) - -(defun vc-git-find-revision (file rev buffer) - (let* (process-file-side-effects - (coding-system-for-read 'binary) - (coding-system-for-write 'binary) - (fullname (substring - (vc-git--run-command-string - file "ls-files" "-z" "--full-name" "--") - 0 -1))) - (vc-git-command - buffer 0 - (concat (if rev rev "HEAD") ":" fullname) "cat-file" "blob"))) - -(defun vc-git-checkout (file &optional editable rev) - (vc-git-command nil 0 file "checkout" (or rev "HEAD"))) - -(defun vc-git-revert (file &optional contents-done) - "Revert FILE to the version stored in the git repository." - (if contents-done - (vc-git-command nil 0 file "update-index" "--") - (vc-git-command nil 0 file "reset" "-q" "--") - (vc-git-command nil nil file "checkout" "-q" "--"))) - -;;; HISTORY FUNCTIONS - -(defun vc-git-print-log (files buffer &optional shortlog start-revision limit) - "Get change log associated with FILES. -Note that using SHORTLOG requires at least Git version 1.5.6, -for the --graph option." - (let ((coding-system-for-read vc-git-commits-coding-system)) - ;; `vc-do-command' creates the buffer, but we need it before running - ;; the command. - (vc-setup-buffer buffer) - ;; If the buffer exists from a previous invocation it might be - ;; read-only. - (let ((inhibit-read-only t)) - (with-current-buffer - buffer - (apply 'vc-git-command buffer - 'async files - (append - '("log" "--no-color") - (when shortlog - '("--graph" "--decorate" "--date=short" - "--pretty=tformat:%d%h %ad %s" "--abbrev-commit")) - (when limit (list "-n" (format "%s" limit))) - (when start-revision (list start-revision)) - '("--"))))))) - -(defun vc-git-log-outgoing (buffer remote-location) - (interactive) - (vc-git-command - buffer 0 nil - "log" - "--no-color" "--graph" "--decorate" "--date=short" - "--pretty=tformat:%d%h %ad %s" "--abbrev-commit" - (concat (if (string= remote-location "") - "@{upstream}" - remote-location) - "..HEAD"))) - -(defun vc-git-log-incoming (buffer remote-location) - (interactive) - (vc-git-command nil 0 nil "fetch") - (vc-git-command - buffer 0 nil - "log" - "--no-color" "--graph" "--decorate" "--date=short" - "--pretty=tformat:%d%h %ad %s" "--abbrev-commit" - (concat "HEAD.." (if (string= remote-location "") - "@{upstream}" - remote-location)))) - -(defvar log-view-message-re) -(defvar log-view-file-re) -(defvar log-view-font-lock-keywords) -(defvar log-view-per-file-logs) - -(define-derived-mode vc-git-log-view-mode log-view-mode "Git-Log-View" - (require 'add-log) ;; We need the faces add-log. - ;; Don't have file markers, so use impossible regexp. - (set (make-local-variable 'log-view-file-re) "\\`a\\`") - (set (make-local-variable 'log-view-per-file-logs) nil) - (set (make-local-variable 'log-view-message-re) - (if (not (eq vc-log-view-type 'long)) - "^\\(?:[*/\\| ]+ \\)?\\(?: ([^)]+)\\)?\\([0-9a-z]+\\) \\([-a-z0-9]+\\) \\(.*\\)" - "^commit *\\([0-9a-z]+\\)")) - (set (make-local-variable 'log-view-font-lock-keywords) - (if (not (eq vc-log-view-type 'long)) - '( - ;; Same as log-view-message-re, except that we don't - ;; want the shy group for the tag name. - ("^\\(?:[*/\\| ]+ \\)?\\( ([^)]+)\\)?\\([0-9a-z]+\\) \\([-a-z0-9]+\\) \\(.*\\)" - (1 'highlight nil lax) - (2 'change-log-acknowledgement) - (3 'change-log-date))) - (append - `((,log-view-message-re (1 'change-log-acknowledgement))) - ;; Handle the case: - ;; user: foo@bar - '(("^Author:[ \t]+\\([A-Za-z0-9_.+-]+@[A-Za-z0-9_.-]+\\)" - (1 'change-log-email)) - ;; Handle the case: - ;; user: FirstName LastName - ("^Author:[ \t]+\\([^<(]+?\\)[ \t]*[(<]\\([A-Za-z0-9_.+-]+@[A-Za-z0-9_.-]+\\)[>)]" - (1 'change-log-name) - (2 'change-log-email)) - ("^ +\\(?:\\(?:[Aa]cked\\|[Ss]igned-[Oo]ff\\)-[Bb]y:\\)[ \t]+\\([A-Za-z0-9_.+-]+@[A-Za-z0-9_.-]+\\)" - (1 'change-log-name)) - ("^ +\\(?:\\(?:[Aa]cked\\|[Ss]igned-[Oo]ff\\)-[Bb]y:\\)[ \t]+\\([^<(]+?\\)[ \t]*[(<]\\([A-Za-z0-9_.+-]+@[A-Za-z0-9_.-]+\\)[>)]" - (1 'change-log-name) - (2 'change-log-email)) - ("^Merge: \\([0-9a-z]+\\) \\([0-9a-z]+\\)" - (1 'change-log-acknowledgement) - (2 'change-log-acknowledgement)) - ("^Date: \\(.+\\)" (1 'change-log-date)) - ("^summary:[ \t]+\\(.+\\)" (1 'log-view-message))))))) - - -(defun vc-git-show-log-entry (revision) - "Move to the log entry for REVISION. -REVISION may have the form BRANCH, BRANCH~N, -or BRANCH^ (where \"^\" can be repeated)." - (goto-char (point-min)) - (prog1 - (when revision - (search-forward - (format "\ncommit %s" revision) nil t - (cond ((string-match "~\\([0-9]\\)\\'" revision) - (1+ (string-to-number (match-string 1 revision)))) - ((string-match "\\^+\\'" revision) - (1+ (length (match-string 0 revision)))) - (t nil)))) - (beginning-of-line))) - -(defun vc-git-diff (files &optional rev1 rev2 buffer) - "Get a difference report using Git between two revisions of FILES." - (let (process-file-side-effects) - (apply #'vc-git-command (or buffer "*vc-diff*") 1 files - (if (and rev1 rev2) "diff-tree" "diff-index") - "--exit-code" - (append (vc-switches 'git 'diff) - (list "-p" (or rev1 "HEAD") rev2 "--"))))) - -(defun vc-git-revision-table (files) - ;; What about `files'?!? --Stef - (let (process-file-side-effects - (table (list "HEAD"))) - (with-temp-buffer - (vc-git-command t nil nil "for-each-ref" "--format=%(refname)") - (goto-char (point-min)) - (while (re-search-forward "^refs/\\(heads\\|tags\\|remotes\\)/\\(.*\\)$" - nil t) - (push (match-string 2) table))) - table)) - -(defun vc-git-revision-completion-table (files) - (lexical-let ((files files) - table) - (setq table (lazy-completion-table - table (lambda () (vc-git-revision-table files)))) - table)) - -(defun vc-git-annotate-command (file buf &optional rev) - (let ((name (file-relative-name file))) - (vc-git-command buf 'async name "blame" "--date=iso" "-C" "-C" rev))) - -(declare-function vc-annotate-convert-time "vc-annotate" (time)) - -(defun vc-git-annotate-time () - (and (re-search-forward "[0-9a-f]+[^()]+(.* \\([0-9]+\\)-\\([0-9]+\\)-\\([0-9]+\\) \\([0-9]+\\):\\([0-9]+\\):\\([0-9]+\\) \\([-+0-9]+\\) +[0-9]+) " nil t) - (vc-annotate-convert-time - (apply #'encode-time (mapcar (lambda (match) - (string-to-number (match-string match))) - '(6 5 4 3 2 1 7)))))) - -(defun vc-git-annotate-extract-revision-at-line () - (save-excursion - (move-beginning-of-line 1) - (when (looking-at "\\([0-9a-f^][0-9a-f]+\\) \\(\\([^(]+\\) \\)?") - (let ((revision (match-string-no-properties 1))) - (if (match-beginning 2) - (cons revision (expand-file-name (match-string-no-properties 3) - (vc-git-root default-directory))) - revision))))) - -;;; TAG SYSTEM - -(defun vc-git-create-tag (dir name branchp) - (let ((default-directory dir)) - (and (vc-git-command nil 0 nil "update-index" "--refresh") - (if branchp - (vc-git-command nil 0 nil "checkout" "-b" name) - (vc-git-command nil 0 nil "tag" name))))) - -(defun vc-git-retrieve-tag (dir name update) - (let ((default-directory dir)) - (vc-git-command nil 0 nil "checkout" name) - ;; FIXME: update buffers if `update' is true - )) - - -;;; MISCELLANEOUS - -(defun vc-git-previous-revision (file rev) - "Git-specific version of `vc-previous-revision'." - (if file - (let* ((default-directory (file-name-directory (expand-file-name file))) - (file (file-name-nondirectory file)) - (prev-rev (with-temp-buffer - (and - (vc-git--out-ok "rev-list" "-2" rev "--" file) - (goto-char (point-max)) - (bolp) - (zerop (forward-line -1)) - (not (bobp)) - (buffer-substring-no-properties - (point) - (1- (point-max))))))) - (or (vc-git-symbolic-commit prev-rev) prev-rev)) - (with-temp-buffer - (and - (vc-git--out-ok "rev-parse" (concat rev "^")) - (buffer-substring-no-properties (point-min) (+ (point-min) 40)))))) - -(defun vc-git-next-revision (file rev) - "Git-specific version of `vc-next-revision'." - (let* ((default-directory (file-name-directory - (expand-file-name file))) - (file (file-name-nondirectory file)) - (current-rev - (with-temp-buffer - (and - (vc-git--out-ok "rev-list" "-1" rev "--" file) - (goto-char (point-max)) - (bolp) - (zerop (forward-line -1)) - (bobp) - (buffer-substring-no-properties - (point) - (1- (point-max)))))) - (next-rev - (and current-rev - (with-temp-buffer - (and - (vc-git--out-ok "rev-list" "HEAD" "--" file) - (goto-char (point-min)) - (search-forward current-rev nil t) - (zerop (forward-line -1)) - (buffer-substring-no-properties - (point) - (progn (forward-line 1) (1- (point))))))))) - (or (vc-git-symbolic-commit next-rev) next-rev))) - -(defun vc-git-delete-file (file) - (vc-git-command nil 0 file "rm" "-f" "--")) - -(defun vc-git-rename-file (old new) - (vc-git-command nil 0 (list old new) "mv" "-f" "--")) - -(defvar vc-git-extra-menu-map - (let ((map (make-sparse-keymap))) - (define-key map [git-grep] - '(menu-item "Git grep..." vc-git-grep - :help "Run the `git grep' command")) - (define-key map [git-sn] - '(menu-item "Stash a snapshot" vc-git-stash-snapshot - :help "Stash the current state of the tree and keep the current state")) - (define-key map [git-st] - '(menu-item "Create Stash..." vc-git-stash - :help "Stash away changes")) - (define-key map [git-ss] - '(menu-item "Show Stash..." vc-git-stash-show - :help "Show stash contents")) - map)) - -(defun vc-git-extra-menu () vc-git-extra-menu-map) - -(defun vc-git-extra-status-menu () vc-git-extra-menu-map) - -(defun vc-git-root (file) - (vc-find-root file ".git")) - -;; Derived from `lgrep'. -(defun vc-git-grep (regexp &optional files dir) - "Run git grep, searching for REGEXP in FILES in directory DIR. -The search is limited to file names matching shell pattern FILES. -FILES may use abbreviations defined in `grep-files-aliases', e.g. -entering `ch' is equivalent to `*.[ch]'. - -With \\[universal-argument] prefix, you can edit the constructed shell command line -before it is executed. -With two \\[universal-argument] prefixes, directly edit and run `grep-command'. - -Collect output in a buffer. While git grep runs asynchronously, you -can use \\[next-error] (M-x next-error), or \\\\[compile-goto-error] \ -in the grep output buffer, -to go to the lines where grep found matches. - -This command shares argument histories with \\[rgrep] and \\[grep]." - (interactive - (progn - (grep-compute-defaults) - (cond - ((equal current-prefix-arg '(16)) - (list (read-from-minibuffer "Run: " "git grep" - nil nil 'grep-history) - nil)) - (t (let* ((regexp (grep-read-regexp)) - (files (grep-read-files regexp)) - (dir (read-directory-name "In directory: " - nil default-directory t))) - (list regexp files dir)))))) - (require 'grep) - (when (and (stringp regexp) (> (length regexp) 0)) - (let ((command regexp)) - (if (null files) - (if (string= command "git grep") - (setq command nil)) - (setq dir (file-name-as-directory (expand-file-name dir))) - (setq command - (grep-expand-template "git grep -n -e -- " regexp files)) - (when command - (if (equal current-prefix-arg '(4)) - (setq command - (read-from-minibuffer "Confirm: " - command nil nil 'grep-history)) - (add-to-history 'grep-history command)))) - (when command - (let ((default-directory dir) - (compilation-environment '("PAGER="))) - ;; Setting process-setup-function makes exit-message-function work - ;; even when async processes aren't supported. - (compilation-start command 'grep-mode)) - (if (eq next-error-last-buffer (current-buffer)) - (setq default-directory dir)))))) - -(defun vc-git-stash (name) - "Create a stash." - (interactive "sStash name: ") - (let ((root (vc-git-root default-directory))) - (when root - (vc-git--call nil "stash" "save" name) - (vc-resynch-buffer root t t)))) - -(defun vc-git-stash-show (name) - "Show the contents of stash NAME." - (interactive "sStash name: ") - (vc-setup-buffer "*vc-git-stash*") - (vc-git-command "*vc-git-stash*" 'async nil "stash" "show" "-p" name) - (set-buffer "*vc-git-stash*") - (diff-mode) - (setq buffer-read-only t) - (pop-to-buffer (current-buffer))) - -(defun vc-git-stash-apply (name) - "Apply stash NAME." - (interactive "sApply stash: ") - (vc-git-command "*vc-git-stash*" 0 nil "stash" "apply" "-q" name) - (vc-resynch-buffer (vc-git-root default-directory) t t)) - -(defun vc-git-stash-pop (name) - "Pop stash NAME." - (interactive "sPop stash: ") - (vc-git-command "*vc-git-stash*" 0 nil "stash" "pop" "-q" name) - (vc-resynch-buffer (vc-git-root default-directory) t t)) - -(defun vc-git-stash-snapshot () - "Create a stash with the current tree state." - (interactive) - (vc-git--call nil "stash" "save" - (let ((ct (current-time))) - (concat - (format-time-string "Snapshot on %Y-%m-%d" ct) - (format-time-string " at %H:%M" ct)))) - (vc-git-command "*vc-git-stash*" 0 nil "stash" "apply" "-q" "stash@{0}") - (vc-resynch-buffer (vc-git-root default-directory) t t)) - -(defun vc-git-stash-list () - (delete - "" - (split-string - (replace-regexp-in-string - "^stash@" " " (vc-git--run-command-string nil "stash" "list")) - "\n"))) - -(defun vc-git-stash-get-at-point (point) - (save-excursion - (goto-char point) - (beginning-of-line) - (if (looking-at "^ +\\({[0-9]+}\\):") - (match-string 1) - (error "Cannot find stash at point")))) - -(defun vc-git-stash-delete-at-point () - (interactive) - (let ((stash (vc-git-stash-get-at-point (point)))) - (when (y-or-n-p (format "Remove stash %s ? " stash)) - (vc-git--run-command-string nil "stash" "drop" (format "stash@%s" stash)) - (vc-dir-refresh)))) - -(defun vc-git-stash-show-at-point () - (interactive) - (vc-git-stash-show (format "stash@%s" (vc-git-stash-get-at-point (point))))) - -(defun vc-git-stash-apply-at-point () - (interactive) - (vc-git-stash-apply (format "stash@%s" (vc-git-stash-get-at-point (point))))) - -(defun vc-git-stash-pop-at-point () - (interactive) - (vc-git-stash-pop (format "stash@%s" (vc-git-stash-get-at-point (point))))) - -(defun vc-git-stash-menu (e) - (interactive "e") - (vc-dir-at-event e (popup-menu vc-git-stash-menu-map e))) - - -;;; Internal commands - -(defun vc-git-command (buffer okstatus file-or-list &rest flags) - "A wrapper around `vc-do-command' for use in vc-git.el. -The difference to vc-do-command is that this function always invokes `git'." - (apply 'vc-do-command (or buffer "*vc*") okstatus "git" file-or-list flags)) - -(defun vc-git--empty-db-p () - "Check if the git db is empty (no commit done yet)." - (let (process-file-side-effects) - (not (eq 0 (vc-git--call nil "rev-parse" "--verify" "HEAD"))))) - -(defun vc-git--call (buffer command &rest args) - ;; We don't need to care the arguments. If there is a file name, it - ;; is always a relative one. This works also for remote - ;; directories. - (apply 'process-file "git" nil buffer nil command args)) - -(defun vc-git--out-ok (command &rest args) - (zerop (apply 'vc-git--call '(t nil) command args))) - -(defun vc-git--run-command-string (file &rest args) - "Run a git command on FILE and return its output as string. -FILE can be nil." - (let* ((ok t) - (str (with-output-to-string - (with-current-buffer standard-output - (unless (apply 'vc-git--out-ok - (if file - (append args (list (file-relative-name - file))) - args)) - (setq ok nil)))))) - (and ok str))) - -(defun vc-git-symbolic-commit (commit) - "Translate COMMIT string into symbolic form. -Returns nil if not possible." - (and commit - (let ((name (with-temp-buffer - (and - (vc-git--out-ok "name-rev" "--name-only" commit) - (goto-char (point-min)) - (= (forward-line 2) 1) - (bolp) - (buffer-substring-no-properties (point-min) - (1- (point-max))))))) - (and name (not (string= name "undefined")) name)))) - -(provide 'vc-git) - -;; arch-tag: bd10664a-0e5b-48f5-a877-6c17b135be12 -;;; vc-git.el ends here diff -r 681cd08dc0f7 -r e93288477c43 lisp/vc-hg.el --- a/lisp/vc-hg.el Thu Jun 10 22:43:47 2010 +0000 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,630 +0,0 @@ -;;; vc-hg.el --- VC backend for the mercurial version control system - -;; Copyright (C) 2006, 2007, 2008, 2009, 2010 Free Software Foundation, Inc. - -;; Author: Ivan Kanis -;; Keywords: tools - -;; 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 3 of the License, 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. If not, see . - -;;; Commentary: - -;; This is a mercurial version control backend - -;;; Thanks: - -;;; Bugs: - -;;; Installation: - -;;; Todo: - -;; 1) Implement the rest of the vc interface. See the comment at the -;; beginning of vc.el. The current status is: - -;; FUNCTION NAME STATUS -;; BACKEND PROPERTIES -;; * revision-granularity OK -;; STATE-QUERYING FUNCTIONS -;; * registered (file) OK -;; * state (file) OK -;; - state-heuristic (file) NOT NEEDED -;; - dir-status (dir update-function) OK -;; - dir-status-files (dir files ds uf) OK -;; - dir-extra-headers (dir) OK -;; - dir-printer (fileinfo) OK -;; * working-revision (file) OK -;; - latest-on-branch-p (file) ?? -;; * checkout-model (files) OK -;; - workfile-unchanged-p (file) OK -;; - mode-line-string (file) NOT NEEDED -;; STATE-CHANGING FUNCTIONS -;; * register (files &optional rev comment) OK -;; * create-repo () OK -;; - init-revision () NOT NEEDED -;; - responsible-p (file) OK -;; - could-register (file) OK -;; - receive-file (file rev) ?? PROBABLY NOT NEEDED -;; - unregister (file) COMMENTED OUT, MAY BE INCORRECT -;; * checkin (files rev comment) OK -;; * find-revision (file rev buffer) OK -;; * checkout (file &optional editable rev) OK -;; * revert (file &optional contents-done) OK -;; - rollback (files) ?? PROBABLY NOT NEEDED -;; - merge (file rev1 rev2) NEEDED -;; - merge-news (file) NEEDED -;; - steal-lock (file &optional revision) NOT NEEDED -;; HISTORY FUNCTIONS -;; * print-log (files buffer &optional shortlog start-revision limit) OK -;; - log-view-mode () OK -;; - show-log-entry (revision) NOT NEEDED, DEFAULT IS GOOD -;; - comment-history (file) NOT NEEDED -;; - update-changelog (files) NOT NEEDED -;; * diff (files &optional rev1 rev2 buffer) OK -;; - revision-completion-table (files) OK? -;; - annotate-command (file buf &optional rev) OK -;; - annotate-time () OK -;; - annotate-current-time () NOT NEEDED -;; - annotate-extract-revision-at-line () OK -;; TAG SYSTEM -;; - create-tag (dir name branchp) NEEDED -;; - retrieve-tag (dir name update) NEEDED -;; MISCELLANEOUS -;; - make-version-backups-p (file) ?? -;; - repository-hostname (dirname) ?? -;; - previous-revision (file rev) OK -;; - next-revision (file rev) OK -;; - check-headers () ?? -;; - clear-headers () ?? -;; - delete-file (file) TEST IT -;; - rename-file (old new) OK -;; - find-file-hook () PROBABLY NOT NEEDED - -;; 2) Implement Stefan Monnier's advice: -;; vc-hg-registered and vc-hg-state -;; Both of those functions should be super extra careful to fail gracefully in -;; unexpected circumstances. The reason this is important is that any error -;; there will prevent the user from even looking at the file :-( -;; Ideally, just like in vc-arch and vc-cvs, checking that the file is under -;; mercurial's control and extracting the current revision should be done -;; without even using `hg' (this way even if you don't have `hg' installed, -;; Emacs is able to tell you this file is under mercurial's control). - -;;; History: -;; - -;;; Code: - -(eval-when-compile - (require 'cl) - (require 'vc) - (require 'vc-dir)) - -;;; Customization options - -(defcustom vc-hg-global-switches nil - "Global switches to pass to any Hg command." - :type '(choice (const :tag "None" nil) - (string :tag "Argument String") - (repeat :tag "Argument List" :value ("") string)) - :version "22.2" - :group 'vc) - -(defcustom vc-hg-diff-switches t ; Hg doesn't support common args like -u - "String or list of strings specifying switches for Hg diff under VC. -If nil, use the value of `vc-diff-switches'. If t, use no switches." - :type '(choice (const :tag "Unspecified" nil) - (const :tag "None" t) - (string :tag "Argument String") - (repeat :tag "Argument List" :value ("") string)) - :version "23.1" - :group 'vc) - - -;;; Properties of the backend - -(defun vc-hg-revision-granularity () 'repository) -(defun vc-hg-checkout-model (files) 'implicit) - -;;; State querying functions - -;;;###autoload (defun vc-hg-registered (file) -;;;###autoload "Return non-nil if FILE is registered with hg." -;;;###autoload (if (vc-find-root file ".hg") ; short cut -;;;###autoload (progn -;;;###autoload (load "vc-hg") -;;;###autoload (vc-hg-registered file)))) - -;; Modeled after the similar function in vc-bzr.el -(defun vc-hg-registered (file) - "Return non-nil if FILE is registered with hg." - (when (vc-hg-root file) ; short cut - (let ((state (vc-hg-state file))) ; expensive - (and state (not (memq state '(ignored unregistered))))))) - -(defun vc-hg-state (file) - "Hg-specific version of `vc-state'." - (let* - ((status nil) - (default-directory (file-name-directory file)) - (out - (with-output-to-string - (with-current-buffer - standard-output - (setq status - (condition-case nil - ;; Ignore all errors. - (let ((process-environment - ;; Avoid localization of messages so we - ;; can parse the output. - (append (list "TERM=dumb" "LANGUAGE=C" "HGRCPATH=") - process-environment))) - (process-file - "hg" nil t nil - "status" "-A" (file-relative-name file))) - ;; Some problem happened. E.g. We can't find an `hg' - ;; executable. - (error nil))))))) - (when (eq 0 status) - (when (null (string-match ".*: No such file or directory$" out)) - (let ((state (aref out 0))) - (cond - ((eq state ?=) 'up-to-date) - ((eq state ?A) 'added) - ((eq state ?M) 'edited) - ((eq state ?I) 'ignored) - ((eq state ?R) 'removed) - ((eq state ?!) 'missing) - ((eq state ??) 'unregistered) - ((eq state ?C) 'up-to-date) ;; Older mercurials use this - (t 'up-to-date))))))) - -(defun vc-hg-working-revision (file) - "Hg-specific version of `vc-working-revision'." - (let* - ((status nil) - (default-directory (file-name-directory file)) - ;; Avoid localization of messages so we can parse the output. - (avoid-local-env (append (list "TERM=dumb" "LANGUAGE=C" "HGRCPATH=") - process-environment)) - (out - (with-output-to-string - (with-current-buffer - standard-output - (setq status - (condition-case nil - (let ((process-environment avoid-local-env)) - ;; Ignore all errors. - (process-file - "hg" nil t nil - "parents" "--template" "{rev}" (file-relative-name file))) - ;; Some problem happened. E.g. We can't find an `hg' - ;; executable. - (error nil))))))) - (if (eq 0 status) - out - ;; Check if the file is in the 'added state, the above hg - ;; command does not distinguish between 'added and 'unregistered. - (setq status - (condition-case nil - (let ((process-environment avoid-local-env)) - (process-file - "hg" nil nil nil - ;; We use "log" here, if there's a faster command - ;; that returns true for an 'added file and false - ;; for an 'unregistered one, we could use that. - "log" "-l1" (file-relative-name file))) - ;; Some problem happened. E.g. We can't find an `hg' - ;; executable. - (error nil))) - (when (eq 0 status) "0")))) - -;;; History functions - -(defcustom vc-hg-log-switches nil - "String or list of strings specifying switches for hg log under VC." - :type '(choice (const :tag "None" nil) - (string :tag "Argument String") - (repeat :tag "Argument List" :value ("") string)) - :group 'vc-hg) - -(defun vc-hg-print-log (files buffer &optional shortlog start-revision limit) - "Get change log associated with FILES." - ;; `vc-do-command' creates the buffer, but we need it before running - ;; the command. - (vc-setup-buffer buffer) - ;; If the buffer exists from a previous invocation it might be - ;; read-only. - (let ((inhibit-read-only t)) - (with-current-buffer - buffer - (apply 'vc-hg-command buffer 0 files "log" - (nconc - (when start-revision (list (format "-r%s:" start-revision))) - (when limit (list "-l" (format "%s" limit))) - (when shortlog (list "--style" "compact")) - vc-hg-log-switches))))) - -(defvar log-view-message-re) -(defvar log-view-file-re) -(defvar log-view-font-lock-keywords) -(defvar log-view-per-file-logs) - -(define-derived-mode vc-hg-log-view-mode log-view-mode "Hg-Log-View" - (require 'add-log) ;; we need the add-log faces - (set (make-local-variable 'log-view-file-re) "\\`a\\`") - (set (make-local-variable 'log-view-per-file-logs) nil) - (set (make-local-variable 'log-view-message-re) - (if (eq vc-log-view-type 'short) - "^\\([0-9]+\\)\\(\\[.*\\]\\)? +\\([0-9a-z]\\{12\\}\\) +\\(\\(?:[0-9]+\\)-\\(?:[0-9]+\\)-\\(?:[0-9]+\\) \\(?:[0-9]+\\):\\(?:[0-9]+\\) \\(?:[-+0-9]+\\)\\) +\\(.*\\)$" - "^changeset:[ \t]*\\([0-9]+\\):\\(.+\\)")) - (set (make-local-variable 'log-view-font-lock-keywords) - (if (eq vc-log-view-type 'short) - (append `((,log-view-message-re - (1 'log-view-message-face) - (2 'highlight nil lax) - (3 'log-view-message-face) - (4 'change-log-date) - (5 'change-log-name)))) - (append - log-view-font-lock-keywords - '( - ;; Handle the case: - ;; user: FirstName LastName - ("^user:[ \t]+\\([^<(]+?\\)[ \t]*[(<]\\([A-Za-z0-9_.+-]+@[A-Za-z0-9_.-]+\\)[>)]" - (1 'change-log-name) - (2 'change-log-email)) - ;; Handle the cases: - ;; user: foo@bar - ;; and - ;; user: foo - ("^user:[ \t]+\\([A-Za-z0-9_.+-]+\\(?:@[A-Za-z0-9_.-]+\\)?\\)" - (1 'change-log-email)) - ("^date: \\(.+\\)" (1 'change-log-date)) - ("^tag: +\\([^ ]+\\)$" (1 'highlight)) - ("^summary:[ \t]+\\(.+\\)" (1 'log-view-message))))))) - -(defun vc-hg-diff (files &optional oldvers newvers buffer) - "Get a difference report using hg between two revisions of FILES." - (let* ((firstfile (car files)) - (working (and firstfile (vc-working-revision firstfile)))) - (when (and (equal oldvers working) (not newvers)) - (setq oldvers nil)) - (when (and (not oldvers) newvers) - (setq oldvers working)) - (apply #'vc-hg-command (or buffer "*vc-diff*") nil files "diff" - (append - (vc-switches 'hg 'diff) - (when oldvers - (if newvers - (list "-r" oldvers "-r" newvers) - (list "-r" oldvers))))))) - -(defun vc-hg-revision-table (files) - (let ((default-directory (file-name-directory (car files)))) - (with-temp-buffer - (vc-hg-command t nil files "log" "--template" "{rev} ") - (split-string - (buffer-substring-no-properties (point-min) (point-max)))))) - -;; Modeled after the similar function in vc-cvs.el -(defun vc-hg-revision-completion-table (files) - (lexical-let ((files files) - table) - (setq table (lazy-completion-table - table (lambda () (vc-hg-revision-table files)))) - table)) - -(defun vc-hg-annotate-command (file buffer &optional revision) - "Execute \"hg annotate\" on FILE, inserting the contents in BUFFER. -Optional arg REVISION is a revision to annotate from." - (vc-hg-command buffer 0 file "annotate" "-d" "-n" "--follow" - (when revision (concat "-r" revision)))) - -(declare-function vc-annotate-convert-time "vc-annotate" (time)) - -;; The format for one line output by "hg annotate -d -n" looks like this: -;;215 Wed Jun 20 21:22:58 2007 -0700: CONTENTS -;; i.e: VERSION_NUMBER DATE: CONTENTS -;; If the user has set the "--follow" option, the output looks like: -;;215 Wed Jun 20 21:22:58 2007 -0700 foo.c: CONTENTS -;; i.e. VERSION_NUMBER DATE FILENAME: CONTENTS -(defconst vc-hg-annotate-re - "^[ \t]*\\([0-9]+\\) \\(.\\{30\\}\\)\\(?:\\(: \\)\\|\\(?: +\\(.+\\): \\)\\)") - -(defun vc-hg-annotate-time () - (when (looking-at vc-hg-annotate-re) - (goto-char (match-end 0)) - (vc-annotate-convert-time - (date-to-time (match-string-no-properties 2))))) - -(defun vc-hg-annotate-extract-revision-at-line () - (save-excursion - (beginning-of-line) - (when (looking-at vc-hg-annotate-re) - (if (match-beginning 3) - (match-string-no-properties 1) - (cons (match-string-no-properties 1) - (expand-file-name (match-string-no-properties 4) - (vc-hg-root default-directory))))))) - -(defun vc-hg-previous-revision (file rev) - (let ((newrev (1- (string-to-number rev)))) - (when (>= newrev 0) - (number-to-string newrev)))) - -(defun vc-hg-next-revision (file rev) - (let ((newrev (1+ (string-to-number rev))) - (tip-revision - (with-temp-buffer - (vc-hg-command t 0 nil "tip") - (goto-char (point-min)) - (re-search-forward "^changeset:[ \t]*\\([0-9]+\\):") - (string-to-number (match-string-no-properties 1))))) - ;; We don't want to exceed the maximum possible revision number, ie - ;; the tip revision. - (when (<= newrev tip-revision) - (number-to-string newrev)))) - -;; Modeled after the similar function in vc-bzr.el -(defun vc-hg-delete-file (file) - "Delete FILE and delete it in the hg repository." - (condition-case () - (delete-file file) - (file-error nil)) - (vc-hg-command nil 0 file "remove" "--after" "--force")) - -;; Modeled after the similar function in vc-bzr.el -(defun vc-hg-rename-file (old new) - "Rename file from OLD to NEW using `hg mv'." - (vc-hg-command nil 0 new "mv" old)) - -(defun vc-hg-register (files &optional rev comment) - "Register FILES under hg. -REV is ignored. -COMMENT is ignored." - (vc-hg-command nil 0 files "add")) - -(defun vc-hg-create-repo () - "Create a new Mercurial repository." - (vc-hg-command nil 0 nil "init")) - -(defalias 'vc-hg-responsible-p 'vc-hg-root) - -;; Modeled after the similar function in vc-bzr.el -(defun vc-hg-could-register (file) - "Return non-nil if FILE could be registered under hg." - (and (vc-hg-responsible-p file) ; shortcut - (condition-case () - (with-temp-buffer - (vc-hg-command t nil file "add" "--dry-run")) - ;; The command succeeds with no output if file is - ;; registered. - (error)))) - -;; FIXME: This would remove the file. Is that correct? -;; (defun vc-hg-unregister (file) -;; "Unregister FILE from hg." -;; (vc-hg-command nil nil file "remove")) - -(declare-function log-edit-extract-headers "log-edit" (headers string)) - -(defun vc-hg-checkin (files rev comment) - "Hg-specific version of `vc-backend-checkin'. -REV is ignored." - (apply 'vc-hg-command nil 0 files - (nconc (list "commit" "-m") - (log-edit-extract-headers '(("Author" . "--user") - ("Date" . "--date")) - comment)))) - -(defun vc-hg-find-revision (file rev buffer) - (let ((coding-system-for-read 'binary) - (coding-system-for-write 'binary)) - (if rev - (vc-hg-command buffer 0 file "cat" "-r" rev) - (vc-hg-command buffer 0 file "cat")))) - -;; Modeled after the similar function in vc-bzr.el -(defun vc-hg-checkout (file &optional editable rev) - "Retrieve a revision of FILE. -EDITABLE is ignored. -REV is the revision to check out into WORKFILE." - (let ((coding-system-for-read 'binary) - (coding-system-for-write 'binary)) - (with-current-buffer (or (get-file-buffer file) (current-buffer)) - (if rev - (vc-hg-command t 0 file "cat" "-r" rev) - (vc-hg-command t 0 file "cat"))))) - -;; Modeled after the similar function in vc-bzr.el -(defun vc-hg-workfile-unchanged-p (file) - (eq 'up-to-date (vc-hg-state file))) - -;; Modeled after the similar function in vc-bzr.el -(defun vc-hg-revert (file &optional contents-done) - (unless contents-done - (with-temp-buffer (vc-hg-command t 0 file "revert")))) - -;;; Hg specific functionality. - -(defvar vc-hg-extra-menu-map - (let ((map (make-sparse-keymap))) - map)) - -(defun vc-hg-extra-menu () vc-hg-extra-menu-map) - -(defun vc-hg-extra-status-menu () vc-hg-extra-menu-map) - -(defvar log-view-vc-backend) - -(defstruct (vc-hg-extra-fileinfo - (:copier nil) - (:constructor vc-hg-create-extra-fileinfo (rename-state extra-name)) - (:conc-name vc-hg-extra-fileinfo->)) - rename-state ;; rename or copy state - extra-name) ;; original name for copies and rename targets, new name for - -(declare-function vc-default-dir-printer "vc-dir" (backend fileentry)) - -(defun vc-hg-dir-printer (info) - "Pretty-printer for the vc-dir-fileinfo structure." - (let ((extra (vc-dir-fileinfo->extra info))) - (vc-default-dir-printer 'Hg info) - (when extra - (insert (propertize - (format " (%s %s)" - (case (vc-hg-extra-fileinfo->rename-state extra) - ('copied "copied from") - ('renamed-from "renamed from") - ('renamed-to "renamed to")) - (vc-hg-extra-fileinfo->extra-name extra)) - 'face 'font-lock-comment-face))))) - -(defun vc-hg-after-dir-status (update-function) - (let ((status-char nil) - (file nil) - (translation '((?= . up-to-date) - (?C . up-to-date) - (?A . added) - (?R . removed) - (?M . edited) - (?I . ignored) - (?! . missing) - (? . copy-rename-line) - (?? . unregistered))) - (translated nil) - (result nil) - (last-added nil) - (last-line-copy nil)) - (goto-char (point-min)) - (while (not (eobp)) - (setq translated (cdr (assoc (char-after) translation))) - (setq file - (buffer-substring-no-properties (+ (point) 2) - (line-end-position))) - (cond ((not translated) - (setq last-line-copy nil)) - ((eq translated 'up-to-date) - (setq last-line-copy nil)) - ((eq translated 'copy-rename-line) - ;; For copied files the output looks like this: - ;; A COPIED_FILE_NAME - ;; ORIGINAL_FILE_NAME - (setf (nth 2 last-added) - (vc-hg-create-extra-fileinfo 'copied file)) - (setq last-line-copy t)) - ((and last-line-copy (eq translated 'removed)) - ;; For renamed files the output looks like this: - ;; A NEW_FILE_NAME - ;; ORIGINAL_FILE_NAME - ;; R ORIGINAL_FILE_NAME - ;; We need to adjust the previous entry to not think it is a copy. - (setf (vc-hg-extra-fileinfo->rename-state (nth 2 last-added)) - 'renamed-from) - (push (list file translated - (vc-hg-create-extra-fileinfo - 'renamed-to (nth 0 last-added))) result) - (setq last-line-copy nil)) - (t - (setq last-added (list file translated nil)) - (push last-added result) - (setq last-line-copy nil))) - (forward-line)) - (funcall update-function result))) - -(defun vc-hg-dir-status (dir update-function) - (vc-hg-command (current-buffer) 'async dir "status" "-C") - (vc-exec-after - `(vc-hg-after-dir-status (quote ,update-function)))) - -(defun vc-hg-dir-status-files (dir files default-state update-function) - (apply 'vc-hg-command (current-buffer) 'async dir "status" "-C" files) - (vc-exec-after - `(vc-hg-after-dir-status (quote ,update-function)))) - -(defun vc-hg-dir-extra-header (name &rest commands) - (concat (propertize name 'face 'font-lock-type-face) - (propertize - (with-temp-buffer - (apply 'vc-hg-command (current-buffer) 0 nil commands) - (buffer-substring-no-properties (point-min) (1- (point-max)))) - 'face 'font-lock-variable-name-face))) - -(defun vc-hg-dir-extra-headers (dir) - "Generate extra status headers for a Mercurial tree." - (let ((default-directory dir)) - (concat - (vc-hg-dir-extra-header "Root : " "root") "\n" - (vc-hg-dir-extra-header "Branch : " "id" "-b") "\n" - (vc-hg-dir-extra-header "Tags : " "id" "-t") ; "\n" - ;; these change after each commit - ;; (vc-hg-dir-extra-header "Local num : " "id" "-n") "\n" - ;; (vc-hg-dir-extra-header "Global id : " "id" "-i") - ))) - -(defun vc-hg-log-incoming (buffer remote-location) - (vc-hg-command buffer 1 nil "incoming" "-n" (unless (string= remote-location "") - remote-location))) - -(defun vc-hg-log-outgoing (buffer remote-location) - (vc-hg-command buffer 1 nil "outgoing" "-n" (unless (string= remote-location "") - remote-location))) - -(declare-function log-view-get-marked "log-view" ()) - -;; XXX maybe also add key bindings for these functions. -(defun vc-hg-push () - (interactive) - (let ((marked-list (log-view-get-marked))) - (if marked-list - (apply #'vc-hg-command - nil 0 nil - "push" - (apply 'nconc - (mapcar (lambda (arg) (list "-r" arg)) marked-list))) - (error "No log entries selected for push")))) - -(defun vc-hg-pull () - (interactive) - (let ((marked-list (log-view-get-marked))) - (if marked-list - (apply #'vc-hg-command - nil 0 nil - "pull" - (apply 'nconc - (mapcar (lambda (arg) (list "-r" arg)) marked-list))) - (error "No log entries selected for pull")))) - -;;; Internal functions - -(defun vc-hg-command (buffer okstatus file-or-list &rest flags) - "A wrapper around `vc-do-command' for use in vc-hg.el. -The difference to vc-do-command is that this function always invokes `hg', -and that it passes `vc-hg-global-switches' to it before FLAGS." - (apply 'vc-do-command (or buffer "*vc*") okstatus "hg" file-or-list - (if (stringp vc-hg-global-switches) - (cons vc-hg-global-switches flags) - (append vc-hg-global-switches - flags)))) - -(defun vc-hg-root (file) - (vc-find-root file ".hg")) - -(provide 'vc-hg) - -;; arch-tag: bd094dc5-715a-434f-a331-37b9fb7cd954 -;;; vc-hg.el ends here diff -r 681cd08dc0f7 -r e93288477c43 lisp/vc-hooks.el --- a/lisp/vc-hooks.el Thu Jun 10 22:43:47 2010 +0000 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,1055 +0,0 @@ -;;; vc-hooks.el --- resident support for version-control - -;; Copyright (C) 1992, 1993, 1994, 1995, 1996, 1998, 1999, 2000, 2001, -;; 2002, 2003, 2004, 2005, 2006, 2007, 2008, 2009, 2010 -;; Free Software Foundation, Inc. - -;; Author: FSF (see vc.el for full credits) -;; Maintainer: Andre Spiegel - -;; 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 3 of the License, 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. If not, see . - -;;; Commentary: - -;; This is the always-loaded portion of VC. It takes care of -;; VC-related activities that are done when you visit a file, so that -;; vc.el itself is loaded only when you use a VC command. See the -;; commentary of vc.el. - -;;; Code: - -(eval-when-compile - (require 'cl)) - -;; Customization Variables (the rest is in vc.el) - -(defvar vc-ignore-vc-files nil) -(make-obsolete-variable 'vc-ignore-vc-files - "set `vc-handled-backends' to nil to disable VC." - "21.1") - -(defvar vc-master-templates ()) -(make-obsolete-variable 'vc-master-templates - "to define master templates for a given BACKEND, use -vc-BACKEND-master-templates. To enable or disable VC for a given -BACKEND, use `vc-handled-backends'." - "21.1") - -(defvar vc-header-alist ()) -(make-obsolete-variable 'vc-header-alist 'vc-BACKEND-header "21.1") - -(defcustom vc-ignore-dir-regexp - ;; Stop SMB, automounter, AFS, and DFS host lookups. - locate-dominating-stop-dir-regexp - "Regexp matching directory names that are not under VC's control. -The default regexp prevents fruitless and time-consuming attempts -to determine the VC status in directories in which filenames are -interpreted as hostnames." - :type 'regexp - :group 'vc) - -(defcustom vc-handled-backends '(RCS CVS SVN SCCS Bzr Git Hg Mtn Arch) - ;; RCS, CVS, SVN and SCCS come first because they are per-dir - ;; rather than per-tree. RCS comes first because of the multibackend - ;; support intended to use RCS for local commits (with a remote CVS server). - "List of version control backends for which VC will be used. -Entries in this list will be tried in order to determine whether a -file is under that sort of version control. -Removing an entry from the list prevents VC from being activated -when visiting a file managed by that backend. -An empty list disables VC altogether." - :type '(repeat symbol) - :version "23.1" - :group 'vc) - -;; Note: we don't actually have a darcs back end yet. -;; Also, Meta-CVS (corresponsding to MCVS) is unsupported. -(defcustom vc-directory-exclusion-list (purecopy '("SCCS" "RCS" "CVS" "MCVS" - ".svn" ".git" ".hg" ".bzr" - "_MTN" "_darcs" "{arch}")) - "List of directory names to be ignored when walking directory trees." - :type '(repeat string) - :group 'vc) - -(defcustom vc-make-backup-files nil - "If non-nil, backups of registered files are made as with other files. -If nil (the default), files covered by version control don't get backups." - :type 'boolean - :group 'vc - :group 'backup) - -(defcustom vc-follow-symlinks 'ask - "What to do if visiting a symbolic link to a file under version control. -Editing such a file through the link bypasses the version control system, -which is dangerous and probably not what you want. - -If this variable is t, VC follows the link and visits the real file, -telling you about it in the echo area. If it is `ask', VC asks for -confirmation whether it should follow the link. If nil, the link is -visited and a warning displayed." - :type '(choice (const :tag "Ask for confirmation" ask) - (const :tag "Visit link and warn" nil) - (const :tag "Follow link" t)) - :group 'vc) - -(defcustom vc-display-status t - "If non-nil, display revision number and lock status in modeline. -Otherwise, not displayed." - :type 'boolean - :group 'vc) - - -(defcustom vc-consult-headers t - "If non-nil, identify work files by searching for version headers." - :type 'boolean - :group 'vc) - -(defcustom vc-keep-workfiles t - "If non-nil, don't delete working files after registering changes. -If the back-end is CVS, workfiles are always kept, regardless of the -value of this flag." - :type 'boolean - :group 'vc) - -(defcustom vc-mistrust-permissions nil - "If non-nil, don't assume permissions/ownership track version-control status. -If nil, do rely on the permissions. -See also variable `vc-consult-headers'." - :type 'boolean - :group 'vc) - -(defun vc-mistrust-permissions (file) - "Internal access function to variable `vc-mistrust-permissions' for FILE." - (or (eq vc-mistrust-permissions 't) - (and vc-mistrust-permissions - (funcall vc-mistrust-permissions - (vc-backend-subdirectory-name file))))) - -(defcustom vc-stay-local 'only-file - "Non-nil means use local operations when possible for remote repositories. -This avoids slow queries over the network and instead uses heuristics -and past information to determine the current status of a file. - -If value is the symbol `only-file' `vc-dir' will connect to the -server, but heuristics will be used to determine the status for -all other VC operations. - -The value can also be a regular expression or list of regular -expressions to match against the host name of a repository; then VC -only stays local for hosts that match it. Alternatively, the value -can be a list of regular expressions where the first element is the -symbol `except'; then VC always stays local except for hosts matched -by these regular expressions." - :type '(choice - (const :tag "Always stay local" t) - (const :tag "Only for file operations" only-file) - (const :tag "Don't stay local" nil) - (list :format "\nExamine hostname and %v" :tag "Examine hostname ..." - (set :format "%v" :inline t (const :format "%t" :tag "don't" except)) - (regexp :format " stay local,\n%t: %v" :tag "if it matches") - (repeat :format "%v%i\n" :inline t (regexp :tag "or")))) - :version "23.1" - :group 'vc) - -(defun vc-stay-local-p (file &optional backend) - "Return non-nil if VC should stay local when handling FILE. -This uses the `repository-hostname' backend operation. -If FILE is a list of files, return non-nil if any of them -individually should stay local." - (if (listp file) - (delq nil (mapcar (lambda (arg) (vc-stay-local-p arg backend)) file)) - (setq backend (or backend (vc-backend file))) - (let* ((sym (vc-make-backend-sym backend 'stay-local)) - (stay-local (if (boundp sym) (symbol-value sym) vc-stay-local))) - (if (symbolp stay-local) stay-local - (let ((dirname (if (file-directory-p file) - (directory-file-name file) - (file-name-directory file)))) - (eq 'yes - (or (vc-file-getprop dirname 'vc-stay-local-p) - (vc-file-setprop - dirname 'vc-stay-local-p - (let ((hostname (vc-call-backend - backend 'repository-hostname dirname))) - (if (not hostname) - 'no - (let ((default t)) - (if (eq (car-safe stay-local) 'except) - (setq default nil stay-local (cdr stay-local))) - (when (consp stay-local) - (setq stay-local - (mapconcat 'identity stay-local "\\|"))) - (if (if (string-match stay-local hostname) - default (not default)) - 'yes 'no)))))))))))) - -;;; This is handled specially now. -;; Tell Emacs about this new kind of minor mode -;; (add-to-list 'minor-mode-alist '(vc-mode vc-mode)) - -;;;###autoload -(put 'vc-mode 'risky-local-variable t) -(make-variable-buffer-local 'vc-mode) -(put 'vc-mode 'permanent-local t) - -(defun vc-mode (&optional arg) - ;; Dummy function for C-h m - "Version Control minor mode. -This minor mode is automatically activated whenever you visit a file under -control of one of the revision control systems in `vc-handled-backends'. -VC commands are globally reachable under the prefix `\\[vc-prefix-map]': -\\{vc-prefix-map}") - -(defmacro vc-error-occurred (&rest body) - `(condition-case nil (progn ,@body nil) (error t))) - -;; We need a notion of per-file properties because the version -;; control state of a file is expensive to derive --- we compute -;; them when the file is initially found, keep them up to date -;; during any subsequent VC operations, and forget them when -;; the buffer is killed. - -(defvar vc-file-prop-obarray (make-vector 17 0) - "Obarray for per-file properties.") - -(defvar vc-touched-properties nil) - -(defun vc-file-setprop (file property value) - "Set per-file VC PROPERTY for FILE to VALUE." - (if (and vc-touched-properties - (not (memq property vc-touched-properties))) - (setq vc-touched-properties (append (list property) - vc-touched-properties))) - (put (intern file vc-file-prop-obarray) property value)) - -(defun vc-file-getprop (file property) - "Get per-file VC PROPERTY for FILE." - (get (intern file vc-file-prop-obarray) property)) - -(defun vc-file-clearprops (file) - "Clear all VC properties of FILE." - (setplist (intern file vc-file-prop-obarray) nil)) - - -;; We keep properties on each symbol naming a backend as follows: -;; * `vc-functions': an alist mapping vc-FUNCTION to vc-BACKEND-FUNCTION. - -(defun vc-make-backend-sym (backend sym) - "Return BACKEND-specific version of VC symbol SYM." - (intern (concat "vc-" (downcase (symbol-name backend)) - "-" (symbol-name sym)))) - -(defun vc-find-backend-function (backend fun) - "Return BACKEND-specific implementation of FUN. -If there is no such implementation, return the default implementation; -if that doesn't exist either, return nil." - (let ((f (vc-make-backend-sym backend fun))) - (if (fboundp f) f - ;; Load vc-BACKEND.el if needed. - (require (intern (concat "vc-" (downcase (symbol-name backend))))) - (if (fboundp f) f - (let ((def (vc-make-backend-sym 'default fun))) - (if (fboundp def) (cons def backend) nil)))))) - -(defun vc-call-backend (backend function-name &rest args) - "Call for BACKEND the implementation of FUNCTION-NAME with the given ARGS. -Calls - - (apply 'vc-BACKEND-FUN ARGS) - -if vc-BACKEND-FUN exists (after trying to find it in vc-BACKEND.el) -and else calls - - (apply 'vc-default-FUN BACKEND ARGS) - -It is usually called via the `vc-call' macro." - (let ((f (assoc function-name (get backend 'vc-functions)))) - (if f (setq f (cdr f)) - (setq f (vc-find-backend-function backend function-name)) - (push (cons function-name f) (get backend 'vc-functions))) - (cond - ((null f) - (error "Sorry, %s is not implemented for %s" function-name backend)) - ((consp f) (apply (car f) (cdr f) args)) - (t (apply f args))))) - -(defmacro vc-call (fun file &rest args) - "A convenience macro for calling VC backend functions. -Functions called by this macro must accept FILE as the first argument. -ARGS specifies any additional arguments. FUN should be unquoted. -BEWARE!! FILE is evaluated twice!!" - `(vc-call-backend (vc-backend ,file) ',fun ,file ,@args)) - -(defsubst vc-parse-buffer (pattern i) - "Find PATTERN in the current buffer and return its Ith submatch." - (goto-char (point-min)) - (if (re-search-forward pattern nil t) - (match-string i))) - -(defun vc-insert-file (file &optional limit blocksize) - "Insert the contents of FILE into the current buffer. - -Optional argument LIMIT is a regexp. If present, the file is inserted -in chunks of size BLOCKSIZE (default 8 kByte), until the first -occurrence of LIMIT is found. Anything from the start of that occurrence -to the end of the buffer is then deleted. The function returns -non-nil if FILE exists and its contents were successfully inserted." - (erase-buffer) - (when (file-exists-p file) - (if (not limit) - (insert-file-contents file) - (unless blocksize (setq blocksize 8192)) - (let ((filepos 0)) - (while - (and (< 0 (cadr (insert-file-contents - file nil filepos (incf filepos blocksize)))) - (progn (beginning-of-line) - (let ((pos (re-search-forward limit nil 'move))) - (when pos (delete-region (match-beginning 0) - (point-max))) - (not pos))))))) - (set-buffer-modified-p nil) - t)) - -(defun vc-find-root (file witness) - "Find the root of a checked out project. -The function walks up the directory tree from FILE looking for WITNESS. -If WITNESS if not found, return nil, otherwise return the root." - (let ((locate-dominating-stop-dir-regexp - (or vc-ignore-dir-regexp locate-dominating-stop-dir-regexp))) - (locate-dominating-file file witness))) - -;; Access functions to file properties -;; (Properties should be _set_ using vc-file-setprop, but -;; _retrieved_ only through these functions, which decide -;; if the property is already known or not. A property should -;; only be retrieved by vc-file-getprop if there is no -;; access function.) - -;; properties indicating the backend being used for FILE - -(defun vc-registered (file) - "Return non-nil if FILE is registered in a version control system. - -This function performs the check each time it is called. To rely -on the result of a previous call, use `vc-backend' instead. If the -file was previously registered under a certain backend, then that -backend is tried first." - (let (handler) - (cond - ((and (file-name-directory file) - (string-match vc-ignore-dir-regexp (file-name-directory file))) - nil) - ((and (boundp 'file-name-handler-alist) - (setq handler (find-file-name-handler file 'vc-registered))) - ;; handler should set vc-backend and return t if registered - (funcall handler 'vc-registered file)) - (t - ;; There is no file name handler. - ;; Try vc-BACKEND-registered for each handled BACKEND. - (catch 'found - (let ((backend (vc-file-getprop file 'vc-backend))) - (mapc - (lambda (b) - (and (vc-call-backend b 'registered file) - (vc-file-setprop file 'vc-backend b) - (throw 'found t))) - (if (or (not backend) (eq backend 'none)) - vc-handled-backends - (cons backend vc-handled-backends)))) - ;; File is not registered. - (vc-file-setprop file 'vc-backend 'none) - nil))))) - -(defun vc-backend (file-or-list) - "Return the version control type of FILE-OR-LIST, nil if it's not registered. -If the argument is a list, the files must all have the same back end." - ;; `file' can be nil in several places (typically due to the use of - ;; code like (vc-backend buffer-file-name)). - (cond ((stringp file-or-list) - (let ((property (vc-file-getprop file-or-list 'vc-backend))) - ;; Note that internally, Emacs remembers unregistered - ;; files by setting the property to `none'. - (cond ((eq property 'none) nil) - (property) - ;; vc-registered sets the vc-backend property - (t (if (vc-registered file-or-list) - (vc-file-getprop file-or-list 'vc-backend) - nil))))) - ((and file-or-list (listp file-or-list)) - (vc-backend (car file-or-list))) - (t - nil))) - - -(defun vc-backend-subdirectory-name (file) - "Return where the repository for the current directory is kept." - (symbol-name (vc-backend file))) - -(defun vc-name (file) - "Return the master name of FILE. -If the file is not registered, or the master name is not known, return nil." - ;; TODO: This should ultimately become obsolete, at least up here - ;; in vc-hooks. - (or (vc-file-getprop file 'vc-name) - ;; force computation of the property by calling - ;; vc-BACKEND-registered explicitly - (let ((backend (vc-backend file))) - (if (and backend - (vc-call-backend backend 'registered file)) - (vc-file-getprop file 'vc-name))))) - -(defun vc-checkout-model (backend files) - "Indicate how FILES are checked out. - -If FILES are not registered, this function always returns nil. -For registered files, the possible values are: - - 'implicit FILES are always writable, and checked out `implicitly' - when the user saves the first changes to the file. - - 'locking FILES are read-only if up-to-date; user must type - \\[vc-next-action] before editing. Strict locking - is assumed. - - 'announce FILES are read-only if up-to-date; user must type - \\[vc-next-action] before editing. But other users - may be editing at the same time." - (vc-call-backend backend 'checkout-model files)) - -(defun vc-user-login-name (file) - "Return the name under which the user accesses the given FILE." - (or (and (eq (string-match tramp-file-name-regexp file) 0) - ;; tramp case: execute "whoami" via tramp - (let ((default-directory (file-name-directory file)) - process-file-side-effects) - (with-temp-buffer - (if (not (zerop (process-file "whoami" nil t))) - ;; fall through if "whoami" didn't work - nil - ;; remove trailing newline - (delete-region (1- (point-max)) (point-max)) - (buffer-string))))) - ;; normal case - (user-login-name) - ;; if user-login-name is nil, return the UID as a string - (number-to-string (user-uid)))) - -(defun vc-state (file &optional backend) - "Return the version control state of FILE. - -If FILE is not registered, this function always returns nil. -For registered files, the value returned is one of: - - 'up-to-date The working file is unmodified with respect to the - latest version on the current branch, and not locked. - - 'edited The working file has been edited by the user. If - locking is used for the file, this state means that - the current version is locked by the calling user. - This status should *not* be reported for files - which have a changed mtime but the same content - as the repo copy. - - USER The current version of the working file is locked by - some other USER (a string). - - 'needs-update The file has not been edited by the user, but there is - a more recent version on the current branch stored - in the repository. - - 'needs-merge The file has been edited by the user, and there is also - a more recent version on the current branch stored in - the repository. This state can only occur if locking - is not used for the file. - - 'unlocked-changes The working version of the file is not locked, - but the working file has been changed with respect - to that version. This state can only occur for files - with locking; it represents an erroneous condition that - should be resolved by the user (vc-next-action will - prompt the user to do it). - - 'added Scheduled to go into the repository on the next commit. - Often represented by vc-working-revision = \"0\" in VCSes - with monotonic IDs like Subversion and Mercurial. - - 'removed Scheduled to be deleted from the repository on next commit. - - 'conflict The file contains conflicts as the result of a merge. - For now the conflicts are text conflicts. In the - future this might be extended to deal with metadata - conflicts too. - - 'missing The file is not present in the file system, but the VC - system still tracks it. - - 'ignored The file showed up in a dir-status listing with a flag - indicating the version-control system is ignoring it, - Note: This property is not set reliably (some VCSes - don't have useful directory-status commands) so assume - that any file with vc-state nil might be ignorable - without VC knowing it. - - 'unregistered The file is not under version control. - -A return of nil from this function means we have no information on the -status of this file." - ;; Note: in Emacs 22 and older, return of nil meant the file was - ;; unregistered. This is potentially a source of - ;; backward-compatibility bugs. - - ;; FIXME: New (sub)states needed (?): - ;; - `copied' and `moved' (might be handled by `removed' and `added') - (or (vc-file-getprop file 'vc-state) - (when (> (length file) 0) ;Why?? --Stef - (setq backend (or backend (vc-backend file))) - (when backend - (vc-state-refresh file backend))))) - -(defun vc-state-refresh (file backend) - "Quickly recompute the `state' of FILE." - (vc-file-setprop - file 'vc-state - (vc-call-backend backend 'state-heuristic file))) - -(defsubst vc-up-to-date-p (file) - "Convenience function that checks whether `vc-state' of FILE is `up-to-date'." - (eq (vc-state file) 'up-to-date)) - -(defun vc-default-state-heuristic (backend file) - "Default implementation of vc-BACKEND-state-heuristic. -It simply calls the real state computation function `vc-BACKEND-state' -and does not employ any heuristic at all." - (vc-call-backend backend 'state file)) - -(defun vc-workfile-unchanged-p (file) - "Return non-nil if FILE has not changed since the last checkout." - (let ((checkout-time (vc-file-getprop file 'vc-checkout-time)) - (lastmod (nth 5 (file-attributes file)))) - ;; This is a shortcut for determining when the workfile is - ;; unchanged. It can fail under some circumstances; see the - ;; discussion in bug#694. - (if (and checkout-time - ;; Tramp and Ange-FTP return this when they don't know the time. - (not (equal lastmod '(0 0)))) - (equal checkout-time lastmod) - (let ((unchanged (vc-call workfile-unchanged-p file))) - (vc-file-setprop file 'vc-checkout-time (if unchanged lastmod 0)) - unchanged)))) - -(defun vc-default-workfile-unchanged-p (backend file) - "Check if FILE is unchanged by diffing against the repository version. -Return non-nil if FILE is unchanged." - (zerop (condition-case err - ;; If the implementation supports it, let the output - ;; go to *vc*, not *vc-diff*, since this is an internal call. - (vc-call-backend backend 'diff (list file) nil nil "*vc*") - (wrong-number-of-arguments - ;; If this error came from the above call to vc-BACKEND-diff, - ;; try again without the optional buffer argument (for - ;; backward compatibility). Otherwise, resignal. - (if (or (not (eq (cadr err) - (indirect-function - (vc-find-backend-function backend 'diff)))) - (not (eq (caddr err) 4))) - (signal (car err) (cdr err)) - (vc-call-backend backend 'diff (list file))))))) - -(defun vc-working-revision (file &optional backend) - "Return the repository version from which FILE was checked out. -If FILE is not registered, this function always returns nil." - (or (vc-file-getprop file 'vc-working-revision) - (progn - (setq backend (or backend (vc-backend file))) - (when backend - (vc-file-setprop file 'vc-working-revision - (vc-call-backend backend 'working-revision file)))))) - -;; Backward compatibility. -(define-obsolete-function-alias - 'vc-workfile-version 'vc-working-revision "23.1") -(defun vc-default-working-revision (backend file) - (message - "`working-revision' not found: using the old `workfile-version' instead") - (vc-call-backend backend 'workfile-version file)) - -(defun vc-default-registered (backend file) - "Check if FILE is registered in BACKEND using vc-BACKEND-master-templates." - (let ((sym (vc-make-backend-sym backend 'master-templates))) - (unless (get backend 'vc-templates-grabbed) - (put backend 'vc-templates-grabbed t) - (set sym (append (delq nil - (mapcar - (lambda (template) - (and (consp template) - (eq (cdr template) backend) - (car template))) - (with-no-warnings - vc-master-templates))) - (symbol-value sym)))) - (let ((result (vc-check-master-templates file (symbol-value sym)))) - (if (stringp result) - (vc-file-setprop file 'vc-name result) - nil)))) ; Not registered - -(defun vc-possible-master (s dirname basename) - (cond - ((stringp s) (format s dirname basename)) - ((functionp s) - ;; The template is a function to invoke. If the - ;; function returns non-nil, that means it has found a - ;; master. For backward compatibility, we also handle - ;; the case that the function throws a 'found atom - ;; and a pair (cons MASTER-FILE BACKEND). - (let ((result (catch 'found (funcall s dirname basename)))) - (if (consp result) (car result) result))))) - -(defun vc-check-master-templates (file templates) - "Return non-nil if there is a master corresponding to FILE. - -TEMPLATES is a list of strings or functions. If an element is a -string, it must be a control string as required by `format', with two -string placeholders, such as \"%sRCS/%s,v\". The directory part of -FILE is substituted for the first placeholder, the basename of FILE -for the second. If a file with the resulting name exists, it is taken -as the master of FILE, and returned. - -If an element of TEMPLATES is a function, it is called with the -directory part and the basename of FILE as arguments. It should -return non-nil if it finds a master; that value is then returned by -this function." - (let ((dirname (or (file-name-directory file) "")) - (basename (file-name-nondirectory file))) - (catch 'found - (mapcar - (lambda (s) - (let ((trial (vc-possible-master s dirname basename))) - (when (and trial (file-exists-p trial) - ;; Make sure the file we found with name - ;; TRIAL is not the source file itself. - ;; That can happen with RCS-style names if - ;; the file name is truncated (e.g. to 14 - ;; chars). See if either directory or - ;; attributes differ. - (or (not (string= dirname - (file-name-directory trial))) - (not (equal (file-attributes file) - (file-attributes trial))))) - (throw 'found trial)))) - templates)))) - -(defun vc-toggle-read-only (&optional verbose) - "Change read-only status of current buffer, perhaps via version control. - -If the buffer is visiting a file registered with version control, -throw an error, because this is not a safe or really meaningful operation -on any version-control system newer than RCS. - -Otherwise, just change the read-only flag of the buffer. - -If you bind this function to \\[toggle-read-only], then Emacs -will properly intercept all attempts to toggle the read-only flag -on version-controlled buffer." - (interactive "P") - (if (vc-backend buffer-file-name) - (error "Toggling the readability of a version controlled file is likely to wreak havoc") - (toggle-read-only))) - -(defun vc-default-make-version-backups-p (backend file) - "Return non-nil if unmodified versions should be backed up locally. -The default is to switch off this feature." - nil) - -(defun vc-version-backup-file-name (file &optional rev manual regexp) - "Return a backup file name for REV or the current version of FILE. -If MANUAL is non-nil it means that a name for backups created by -the user should be returned; if REGEXP is non-nil that means to return -a regexp for matching all such backup files, regardless of the version." - (if regexp - (concat (regexp-quote (file-name-nondirectory file)) - "\\.~.+" (unless manual "\\.") "~") - (expand-file-name (concat (file-name-nondirectory file) - ".~" (subst-char-in-string - ?/ ?_ (or rev (vc-working-revision file))) - (unless manual ".") "~") - (file-name-directory file)))) - -(defun vc-delete-automatic-version-backups (file) - "Delete all existing automatic version backups for FILE." - (condition-case nil - (mapc - 'delete-file - (directory-files (or (file-name-directory file) default-directory) t - (vc-version-backup-file-name file nil nil t))) - ;; Don't fail when the directory doesn't exist. - (file-error nil))) - -(defun vc-make-version-backup (file) - "Make a backup copy of FILE, which is assumed in sync with the repository. -Before doing that, check if there are any old backups and get rid of them." - (unless (and (fboundp 'msdos-long-file-names) - (not (with-no-warnings (msdos-long-file-names)))) - (vc-delete-automatic-version-backups file) - (condition-case nil - (copy-file file (vc-version-backup-file-name file) - nil 'keep-date) - ;; It's ok if it doesn't work (e.g. directory not writable), - ;; since this is just for efficiency. - (file-error - (message - (concat "Warning: Cannot make version backup; " - "diff/revert therefore not local")))))) - -(defun vc-before-save () - "Function to be called by `basic-save-buffer' (in files.el)." - ;; If the file on disk is still in sync with the repository, - ;; and version backups should be made, copy the file to - ;; another name. This enables local diffs and local reverting. - (let ((file buffer-file-name) - backend) - (ignore-errors ;Be careful not to prevent saving the file. - (and (setq backend (vc-backend file)) - (vc-up-to-date-p file) - (eq (vc-checkout-model backend (list file)) 'implicit) - (vc-call-backend backend 'make-version-backups-p file) - (vc-make-version-backup file))))) - -(declare-function vc-dir-resynch-file "vc-dir" (&optional fname)) - -(defvar vc-dir-buffers nil "List of vc-dir buffers.") - -(defun vc-after-save () - "Function to be called by `basic-save-buffer' (in files.el)." - ;; If the file in the current buffer is under version control, - ;; up-to-date, and locking is not used for the file, set - ;; the state to 'edited and redisplay the mode line. - (let* ((file buffer-file-name) - (backend (vc-backend file))) - (and backend - (or (and (equal (vc-file-getprop file 'vc-checkout-time) - (nth 5 (file-attributes file))) - ;; File has been saved in the same second in which - ;; it was checked out. Clear the checkout-time - ;; to avoid confusion. - (vc-file-setprop file 'vc-checkout-time nil)) - t) - (eq (vc-checkout-model backend (list file)) 'implicit) - (vc-state-refresh file backend) - (vc-mode-line file backend)) - ;; Try to avoid unnecessary work, a *vc-dir* buffer is - ;; present if this is true. - (when vc-dir-buffers - (vc-dir-resynch-file file)))) - -(defvar vc-menu-entry - `(menu-item ,(purecopy "Version Control") vc-menu-map - :filter vc-menu-map-filter)) - -(when (boundp 'menu-bar-tools-menu) - ;; We do not need to worry here about the placement of this entry - ;; because menu-bar.el has already created the proper spot for us - ;; and this will simply use it. - (define-key menu-bar-tools-menu [vc] vc-menu-entry)) - -(defconst vc-mode-line-map - (let ((map (make-sparse-keymap))) - (define-key map [mode-line down-mouse-1] vc-menu-entry) - map)) - -(defun vc-mode-line (file &optional backend) - "Set `vc-mode' to display type of version control for FILE. -The value is set in the current buffer, which should be the buffer -visiting FILE. -If BACKEND is passed use it as the VC backend when computing the result." - (interactive (list buffer-file-name)) - (setq backend (or backend (vc-backend file))) - (if (not backend) - (setq vc-mode nil) - (let* ((ml-string (vc-call-backend backend 'mode-line-string file)) - (ml-echo (get-text-property 0 'help-echo ml-string))) - (setq vc-mode - (concat - " " - (if (null vc-display-status) - (symbol-name backend) - (propertize - ml-string - 'mouse-face 'mode-line-highlight - 'help-echo - (concat (or ml-echo - (format "File under the %s version control system" - backend)) - "\nmouse-1: Version Control menu") - 'local-map vc-mode-line-map))))) - ;; If the user is root, and the file is not owner-writable, - ;; then pretend that we can't write it - ;; even though we can (because root can write anything). - ;; This way, even root cannot modify a file that isn't locked. - (and (equal file buffer-file-name) - (not buffer-read-only) - (zerop (user-real-uid)) - (zerop (logand (file-modes buffer-file-name) 128)) - (setq buffer-read-only t))) - (force-mode-line-update) - backend) - -(defun vc-default-mode-line-string (backend file) - "Return string for placement in modeline by `vc-mode-line' for FILE. -Format: - - \"BACKEND-REV\" if the file is up-to-date - \"BACKEND:REV\" if the file is edited (or locked by the calling user) - \"BACKEND:LOCKER:REV\" if the file is locked by somebody else - -This function assumes that the file is registered." - (let* ((backend-name (symbol-name backend)) - (state (vc-state file backend)) - (state-echo nil) - (rev (vc-working-revision file backend))) - (propertize - (cond ((or (eq state 'up-to-date) - (eq state 'needs-update)) - (setq state-echo "Up to date file") - (concat backend-name "-" rev)) - ((stringp state) - (setq state-echo (concat "File locked by" state)) - (concat backend-name ":" state ":" rev)) - ((eq state 'added) - (setq state-echo "Locally added file") - (concat backend-name "@" rev)) - ((eq state 'conflict) - (setq state-echo "File contains conflicts after the last merge") - (concat backend-name "!" rev)) - ((eq state 'removed) - (setq state-echo "File removed from the VC system") - (concat backend-name "!" rev)) - ((eq state 'missing) - (setq state-echo "File tracked by the VC system, but missing from the file system") - (concat backend-name "?" rev)) - (t - ;; Not just for the 'edited state, but also a fallback - ;; for all other states. Think about different symbols - ;; for 'needs-update and 'needs-merge. - (setq state-echo "Locally modified file") - (concat backend-name ":" rev))) - 'help-echo (concat state-echo " under the " backend-name - " version control system")))) - -(defun vc-follow-link () - "If current buffer visits a symbolic link, visit the real file. -If the real file is already visited in another buffer, make that buffer -current, and kill the buffer that visits the link." - (let* ((true-buffer (find-buffer-visiting buffer-file-truename)) - (this-buffer (current-buffer))) - (if (eq true-buffer this-buffer) - (let ((truename buffer-file-truename)) - (kill-buffer this-buffer) - ;; In principle, we could do something like set-visited-file-name. - ;; However, it can't be exactly the same as set-visited-file-name. - ;; I'm not going to work out the details right now. -- rms. - (set-buffer (find-file-noselect truename))) - (set-buffer true-buffer) - (kill-buffer this-buffer)))) - -(defun vc-default-find-file-hook (backend) - nil) - -(defun vc-find-file-hook () - "Function for `find-file-hook' activating VC mode if appropriate." - ;; Recompute whether file is version controlled, - ;; if user has killed the buffer and revisited. - (when vc-mode - (setq vc-mode nil)) - (when buffer-file-name - (vc-file-clearprops buffer-file-name) - ;; FIXME: Why use a hook? Why pass it buffer-file-name? - (add-hook 'vc-mode-line-hook 'vc-mode-line nil t) - (let (backend) - (cond - ((setq backend (with-demoted-errors (vc-backend buffer-file-name))) - ;; Compute the state and put it in the modeline. - (vc-mode-line buffer-file-name backend) - (unless vc-make-backup-files - ;; Use this variable, not make-backup-files, - ;; because this is for things that depend on the file name. - (set (make-local-variable 'backup-inhibited) t)) - ;; Let the backend setup any buffer-local things he needs. - (vc-call-backend backend 'find-file-hook)) - ((let ((link-type (and (not (equal buffer-file-name buffer-file-truename)) - (vc-backend buffer-file-truename)))) - (cond ((not link-type) nil) ;Nothing to do. - ((eq vc-follow-symlinks nil) - (message - "Warning: symbolic link to %s-controlled source file" link-type)) - ((or (not (eq vc-follow-symlinks 'ask)) - ;; If we already visited this file by following - ;; the link, don't ask again if we try to visit - ;; it again. GUD does that, and repeated questions - ;; are painful. - (get-file-buffer - (abbreviate-file-name - (file-chase-links buffer-file-name)))) - - (vc-follow-link) - (message "Followed link to %s" buffer-file-name) - (vc-find-file-hook)) - (t - (if (yes-or-no-p (format - "Symbolic link to %s-controlled source file; follow link? " link-type)) - (progn (vc-follow-link) - (message "Followed link to %s" buffer-file-name) - (vc-find-file-hook)) - (message - "Warning: editing through the link bypasses version control") - ))))))))) - -(add-hook 'find-file-hook 'vc-find-file-hook) - -(defun vc-kill-buffer-hook () - "Discard VC info about a file when we kill its buffer." - (when buffer-file-name (vc-file-clearprops buffer-file-name))) - -(add-hook 'kill-buffer-hook 'vc-kill-buffer-hook) - -;; Now arrange for (autoloaded) bindings of the main package. -;; Bindings for this have to go in the global map, as we'll often -;; want to call them from random buffers. - -;; Autoloading works fine, but it prevents shortcuts from appearing -;; in the menu because they don't exist yet when the menu is built. -;; (autoload 'vc-prefix-map "vc" nil nil 'keymap) -(defvar vc-prefix-map - (let ((map (make-sparse-keymap))) - (define-key map "a" 'vc-update-change-log) - (define-key map "b" 'vc-switch-backend) - (define-key map "c" 'vc-rollback) - (define-key map "d" 'vc-dir) - (define-key map "g" 'vc-annotate) - (define-key map "h" 'vc-insert-headers) - (define-key map "i" 'vc-register) - (define-key map "l" 'vc-print-log) - (define-key map "L" 'vc-print-root-log) - (define-key map "I" 'vc-log-incoming) - (define-key map "O" 'vc-log-outgoing) - (define-key map "m" 'vc-merge) - (define-key map "r" 'vc-retrieve-tag) - (define-key map "s" 'vc-create-tag) - (define-key map "u" 'vc-revert) - (define-key map "v" 'vc-next-action) - (define-key map "+" 'vc-update) - (define-key map "=" 'vc-diff) - (define-key map "D" 'vc-root-diff) - (define-key map "~" 'vc-revision-other-window) - map)) -(fset 'vc-prefix-map vc-prefix-map) -(define-key global-map "\C-xv" 'vc-prefix-map) - -(defvar vc-menu-map - (let ((map (make-sparse-keymap "Version Control"))) - ;;(define-key map [show-files] - ;; '("Show Files under VC" . (vc-directory t))) - (define-key map [vc-retrieve-tag] - `(menu-item ,(purecopy "Retrieve Tag") vc-retrieve-tag - :help ,(purecopy "Retrieve tagged version or branch"))) - (define-key map [vc-create-tag] - `(menu-item ,(purecopy "Create Tag") vc-create-tag - :help ,(purecopy "Create version tag"))) - (define-key map [separator1] menu-bar-separator) - (define-key map [vc-annotate] - `(menu-item ,(purecopy "Annotate") vc-annotate - :help ,(purecopy "Display the edit history of the current file using colors"))) - (define-key map [vc-rename-file] - `(menu-item ,(purecopy "Rename File") vc-rename-file - :help ,(purecopy "Rename file"))) - (define-key map [vc-revision-other-window] - `(menu-item ,(purecopy "Show Other Version") vc-revision-other-window - :help ,(purecopy "Visit another version of the current file in another window"))) - (define-key map [vc-diff] - `(menu-item ,(purecopy "Compare with Base Version") vc-diff - :help ,(purecopy "Compare file set with the base version"))) - (define-key map [vc-root-diff] - `(menu-item ,(purecopy "Compare Tree with Base Version") vc-root-diff - :help ,(purecopy "Compare current tree with the base version"))) - (define-key map [vc-update-change-log] - `(menu-item ,(purecopy "Update ChangeLog") vc-update-change-log - :help ,(purecopy "Find change log file and add entries from recent version control logs"))) - (define-key map [vc-log-out] - `(menu-item ,(purecopy "Show Outgoing Log") vc-log-outgoing - :help ,(purecopy "Show a log of changes that will be sent with a push operation"))) - (define-key map [vc-log-in] - `(menu-item ,(purecopy "Show Incoming Log") vc-log-incoming - :help ,(purecopy "Show a log of changes that will be received with a pull operation"))) - (define-key map [vc-print-log] - `(menu-item ,(purecopy "Show History") vc-print-log - :help ,(purecopy "List the change log of the current file set in a window"))) - (define-key map [vc-print-root-log] - `(menu-item ,(purecopy "Show Top of the Tree History ") vc-print-root-log - :help ,(purecopy "List the change log for the current tree in a window"))) - (define-key map [separator2] menu-bar-separator) - (define-key map [vc-insert-header] - `(menu-item ,(purecopy "Insert Header") vc-insert-headers - :help ,(purecopy "Insert headers into a file for use with a version control system. -"))) - (define-key map [undo] - `(menu-item ,(purecopy "Undo Last Check-In") vc-rollback - :help ,(purecopy "Remove the most recent changeset committed to the repository"))) - (define-key map [vc-revert] - `(menu-item ,(purecopy "Revert to Base Version") vc-revert - :help ,(purecopy "Revert working copies of the selected file set to their repository contents"))) - (define-key map [vc-update] - `(menu-item ,(purecopy "Update to Latest Version") vc-update - :help ,(purecopy "Update the current fileset's files to their tip revisions"))) - (define-key map [vc-next-action] - `(menu-item ,(purecopy "Check In/Out") vc-next-action - :help ,(purecopy "Do the next logical version control operation on the current fileset"))) - (define-key map [vc-register] - `(menu-item ,(purecopy "Register") vc-register - :help ,(purecopy "Register file set into a version control system"))) - (define-key map [vc-dir] - `(menu-item ,(purecopy "VC Dir") vc-dir - :help ,(purecopy "Show the VC status of files in a directory"))) - map)) - -(defalias 'vc-menu-map vc-menu-map) - -(declare-function vc-responsible-backend "vc" (file)) - -(defun vc-menu-map-filter (orig-binding) - (if (and (symbolp orig-binding) (fboundp orig-binding)) - (setq orig-binding (indirect-function orig-binding))) - (let ((ext-binding - (when vc-mode - (vc-call-backend - (if buffer-file-name - (vc-backend buffer-file-name) - (vc-responsible-backend default-directory)) - 'extra-menu)))) - ;; Give the VC backend a chance to add menu entries - ;; specific for that backend. - (if (null ext-binding) - orig-binding - (append orig-binding - '((ext-menu-separator "--")) - ext-binding)))) - -(defun vc-default-extra-menu (backend) - nil) - -(provide 'vc-hooks) - -;; arch-tag: 2e5a6fa7-1d30-48e2-8bd0-e3d335f04f32 -;;; vc-hooks.el ends here diff -r 681cd08dc0f7 -r e93288477c43 lisp/vc-mtn.el --- a/lisp/vc-mtn.el Thu Jun 10 22:43:47 2010 +0000 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,344 +0,0 @@ -;;; vc-mtn.el --- VC backend for Monotone - -;; Copyright (C) 2007, 2008, 2009, 2010 Free Software Foundation, Inc. - -;; Author: Stefan Monnier -;; Keywords: - -;; 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 3 of the License, 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. If not, see . - -;;; Commentary: - -;; - -;;; TODO: - -;; - The `previous-version' VC method needs to be supported, 'D' in -;; log-view-mode uses it. - -;;; Code: - -(eval-when-compile (require 'cl) (require 'vc)) - -(defcustom vc-mtn-diff-switches t - "String or list of strings specifying switches for monotone diff under VC. -If nil, use the value of `vc-diff-switches'. If t, use no switches." - :type '(choice (const :tag "Unspecified" nil) - (const :tag "None" t) - (string :tag "Argument String") - (repeat :tag "Argument List" :value ("") string)) - :version "23.1" - :group 'vc) - -(define-obsolete-variable-alias 'vc-mtn-command 'vc-mtn-program "23.1") -(defcustom vc-mtn-program "mtn" - "Name of the monotone executable." - :type 'string - :group 'vc) - -;; Clear up the cache to force vc-call to check again and discover -;; new functions when we reload this file. -(put 'Mtn 'vc-functions nil) - -(unless (executable-find vc-mtn-program) - ;; vc-mtn.el is 100% non-functional without the `mtn' executable. - (setq vc-handled-backends (delq 'Mtn vc-handled-backends))) - -;;;###autoload -(defconst vc-mtn-admin-dir "_MTN") -;;;###autoload -(defconst vc-mtn-admin-format (concat vc-mtn-admin-dir "/format")) - -;;;###autoload (defun vc-mtn-registered (file) -;;;###autoload (if (vc-find-root file vc-mtn-admin-format) -;;;###autoload (progn -;;;###autoload (load "vc-mtn") -;;;###autoload (vc-mtn-registered file)))) - -(defun vc-mtn-revision-granularity () 'repository) -(defun vc-mtn-checkout-model (files) 'implicit) - -(defun vc-mtn-root (file) - (setq file (if (file-directory-p file) - (file-name-as-directory file) - (file-name-directory file))) - (or (vc-file-getprop file 'vc-mtn-root) - (vc-file-setprop file 'vc-mtn-root - (vc-find-root file vc-mtn-admin-format)))) - - -(defun vc-mtn-registered (file) - (let ((root (vc-mtn-root file))) - (when root - (vc-mtn-state file)))) - -(defun vc-mtn-command (buffer okstatus files &rest flags) - "A wrapper around `vc-do-command' for use in vc-mtn.el." - (let ((process-environment - ;; Avoid localization of messages so we can parse the output. - (cons "LC_MESSAGES=C" process-environment))) - (apply 'vc-do-command (or buffer "*vc*") okstatus vc-mtn-program - files flags))) - -(defun vc-mtn-state (file) - ;; If `mtn' fails or returns status>0, or if the search files, just - ;; return nil. - (ignore-errors - (with-temp-buffer - (vc-mtn-command t 0 file "status") - (goto-char (point-min)) - (re-search-forward - "^ \\(?:\\(patched\\)\\|\\(added\\) \\(?:.*\\)\\)\\|no changes$") - (cond ((match-end 1) 'edited) - ((match-end 2) 'added) - (t 'up-to-date))))) - -(defun vc-mtn-after-dir-status (update-function) - (let (result) - (goto-char (point-min)) - (re-search-forward "Current branch: \\(.*\\)\nChanges against parent \\(.*\\)" nil t) - (while (re-search-forward - "^ \\(?:\\(patched \\)\\|\\(added \\)\\)\\(.*\\)$" nil t) - (cond ((match-end 1) (push (list (match-string 3) 'edited) result)) - ((match-end 2) (push (list (match-string 3) 'added) result)))) - (funcall update-function result))) - -(defun vc-mtn-dir-status (dir update-function) - (vc-mtn-command (current-buffer) 'async dir "status") - (vc-exec-after - `(vc-mtn-after-dir-status (quote ,update-function)))) - -(defun vc-mtn-working-revision (file) - ;; If `mtn' fails or returns status>0, or if the search fails, just - ;; return nil. - (ignore-errors - (with-temp-buffer - (vc-mtn-command t 0 file "status") - (goto-char (point-min)) - (re-search-forward "Current branch: \\(.*\\)\nChanges against parent \\(.*\\)") - (match-string 2)))) - -(defun vc-mtn-workfile-branch (file) - ;; If `mtn' fails or returns status>0, or if the search files, just - ;; return nil. - (ignore-errors - (with-temp-buffer - (vc-mtn-command t 0 file "status") - (goto-char (point-min)) - (re-search-forward "Current branch: \\(.*\\)\nChanges against parent \\(.*\\)") - (match-string 1)))) - -(defun vc-mtn-workfile-unchanged-p (file) - (not (eq (vc-mtn-state file) 'edited))) - -;; Mode-line rewrite code copied from vc-arch.el. - -(defcustom vc-mtn-mode-line-rewrite - '(("\\`[^:/#]*[:/#]" . "")) ;Drop the host part. - "Rewrite rules to shorten Mtn's revision names on the mode-line." - :type '(repeat (cons regexp string)) - :version "22.2" - :group 'vc) - -(defun vc-mtn-mode-line-string (file) - "Return string for placement in modeline by `vc-mode-line' for FILE." - (let ((branch (vc-mtn-workfile-branch file))) - (dolist (rule vc-mtn-mode-line-rewrite) - (if (string-match (car rule) branch) - (setq branch (replace-match (cdr rule) t nil branch)))) - (format "Mtn%c%s" - (case (vc-state file) - ((up-to-date needs-update) ?-) - (added ?@) - (t ?:)) - branch))) - -(defun vc-mtn-register (files &optional rev comment) - (vc-mtn-command nil 0 files "add")) - -(defun vc-mtn-responsible-p (file) (vc-mtn-root file)) -(defun vc-mtn-could-register (file) (vc-mtn-root file)) - -(declare-function log-edit-extract-headers "log-edit" (headers string)) - -(defun vc-mtn-checkin (files rev comment &optional extra-args-ignored) - (apply 'vc-mtn-command nil 0 files - (nconc (list "commit" "-m") - (log-edit-extract-headers '(("Author" . "--author") - ("Date" . "--date")) - comment)))) - -(defun vc-mtn-find-revision (file rev buffer) - (vc-mtn-command buffer 0 file "cat" "-r" rev)) - -;; (defun vc-mtn-checkout (file &optional editable rev) -;; ) - -(defun vc-mtn-revert (file &optional contents-done) - (unless contents-done - (vc-mtn-command nil 0 file "revert"))) - -;; (defun vc-mtn-roolback (files) -;; ) - -(defun vc-mtn-print-log (files buffer &optional shortlog start-revision limit) - (apply 'vc-mtn-command buffer 0 files "log" - (append - (when start-revision (list "--from" (format "%s" start-revision))) - (when limit (list "--last" (format "%s" limit)))))) - -(defvar log-view-message-re) -(defvar log-view-file-re) -(defvar log-view-font-lock-keywords) -(defvar log-view-per-file-logs) - -(define-derived-mode vc-mtn-log-view-mode log-view-mode "Mtn-Log-View" - ;; Don't match anything. - (set (make-local-variable 'log-view-file-re) "\\`a\\`") - (set (make-local-variable 'log-view-per-file-logs) nil) - ;; TODO: Use a more precise regexp than "[ |/]+" to avoid false positives - ;; in the ChangeLog text. - (set (make-local-variable 'log-view-message-re) - "^[ |/]+Revision: \\([0-9a-f]+\\)") - (require 'add-log) ;For change-log faces. - (set (make-local-variable 'log-view-font-lock-keywords) - (append log-view-font-lock-keywords - '(("^[ |]+Author: \\(.*\\)" (1 'change-log-email)) - ("^[ |]+Date: \\(.*\\)" (1 'change-log-date-face)))))) - -;; (defun vc-mtn-show-log-entry (revision) -;; ) - -(defun vc-mtn-diff (files &optional rev1 rev2 buffer) - "Get a difference report using monotone between two revisions of FILES." - (apply 'vc-mtn-command (or buffer "*vc-diff*") 1 files "diff" - (append - (vc-switches 'mtn 'diff) - (if rev1 (list "-r" rev1)) (if rev2 (list "-r" rev2))))) - -(defun vc-mtn-annotate-command (file buf &optional rev) - (apply 'vc-mtn-command buf 'async file "annotate" - (if rev (list "-r" rev)))) - -(declare-function vc-annotate-convert-time "vc-annotate" (time)) - -(defconst vc-mtn-annotate-full-re - "^ *\\([0-9a-f]+\\)\\.* by [^ ]+ \\([0-9]+\\)-\\([0-9]+\\)-\\([0-9]+\\): ") -(defconst vc-mtn-annotate-any-re - (concat "^\\(?: +: \\|" vc-mtn-annotate-full-re "\\)")) - -(defun vc-mtn-annotate-time () - (when (looking-at vc-mtn-annotate-any-re) - (goto-char (match-end 0)) - (let ((year (match-string 2))) - (if (not year) - ;; Look for the date on a previous line. - (save-excursion - (get-text-property (1- (previous-single-property-change - (point) 'vc-mtn-time nil (point-min))) - 'vc-mtn-time)) - (let ((time (vc-annotate-convert-time - (encode-time 0 0 0 - (string-to-number (match-string 4)) - (string-to-number (match-string 3)) - (string-to-number year) - t)))) - (let ((inhibit-read-only t) - (inhibit-modification-hooks t)) - (put-text-property (match-beginning 0) (match-end 0) - 'vc-mtn-time time)) - time))))) - -(defun vc-mtn-annotate-extract-revision-at-line () - (save-excursion - (when (or (looking-at vc-mtn-annotate-full-re) - (re-search-backward vc-mtn-annotate-full-re nil t)) - (match-string 1)))) - -;;; Revision completion. - -(defun vc-mtn-list-tags () - (with-temp-buffer - (vc-mtn-command t 0 nil "list" "tags") - (goto-char (point-min)) - (let ((tags ())) - (while (re-search-forward "^[^ ]+" nil t) - (push (match-string 0) tags)) - tags))) - -(defun vc-mtn-list-branches () - (with-temp-buffer - (vc-mtn-command t 0 nil "list" "branches") - (goto-char (point-min)) - (let ((branches ())) - (while (re-search-forward "^.+" nil t) - (push (match-string 0) branches)) - branches))) - -(defun vc-mtn-list-revision-ids (prefix) - (with-temp-buffer - (vc-mtn-command t 0 nil "complete" "revision" prefix) - (goto-char (point-min)) - (let ((ids ())) - (while (re-search-forward "^.+" nil t) - (push (match-string 0) ids)) - ids))) - -(defun vc-mtn-revision-completion-table (files) - ;; TODO: Implement completion for for selectors - ;; TODO: Implement completion for composite selectors. - (lexical-let ((files files)) - ;; What about using `files'?!? --Stef - (lambda (string pred action) - (cond - ;; "Tag" selectors. - ((string-match "\\`t:" string) - (complete-with-action action - (mapcar (lambda (tag) (concat "t:" tag)) - (vc-mtn-list-tags)) - string pred)) - ;; "Branch" selectors. - ((string-match "\\`b:" string) - (complete-with-action action - (mapcar (lambda (tag) (concat "b:" tag)) - (vc-mtn-list-branches)) - string pred)) - ;; "Head" selectors. Not sure how they differ from "branch" selectors. - ((string-match "\\`h:" string) - (complete-with-action action - (mapcar (lambda (tag) (concat "h:" tag)) - (vc-mtn-list-branches)) - string pred)) - ;; "ID" selectors. - ((string-match "\\`i:" string) - (complete-with-action action - (mapcar (lambda (tag) (concat "i:" tag)) - (vc-mtn-list-revision-ids - (substring string (match-end 0)))) - string pred)) - (t - (complete-with-action action - '("t:" "b:" "h:" "i:" - ;; Completion not implemented for these. - "a:" "c:" "d:" "e:" "l:") - string pred)))))) - - - -(provide 'vc-mtn) - -;; arch-tag: 2b89ffbc-cbb8-405a-9080-2eafd4becb70 -;;; vc-mtn.el ends here diff -r 681cd08dc0f7 -r e93288477c43 lisp/vc-rcs.el --- a/lisp/vc-rcs.el Thu Jun 10 22:43:47 2010 +0000 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,1470 +0,0 @@ -;;; vc-rcs.el --- support for RCS version-control - -;; Copyright (C) 1992, 1993, 1994, 1995, 1996, 1997, 1998, 1999, 2000, -;; 2001, 2002, 2003, 2004, 2005, 2006, 2007, 2008, 2009, 2010 -;; Free Software Foundation, Inc. - -;; Author: FSF (see vc.el for full credits) -;; Maintainer: Andre Spiegel - -;; 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 3 of the License, 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. If not, see . - -;;; Commentary: - -;; See vc.el - -;; Some features will not work with old RCS versions. Where -;; appropriate, VC finds out which version you have, and allows or -;; disallows those features (stealing locks, for example, works only -;; from 5.6.2 onwards). -;; Even initial checkins will fail if your RCS version is so old that ci -;; doesn't understand -t-; this has been known to happen to people running -;; NExTSTEP 3.0. -;; -;; You can support the RCS -x option by customizing vc-rcs-master-templates. - -;;; Code: - -;;; -;;; Customization options -;;; - -(eval-when-compile - (require 'cl) - (require 'vc)) - -(defcustom vc-rcs-release nil - "The release number of your RCS installation, as a string. -If nil, VC itself computes this value when it is first needed." - :type '(choice (const :tag "Auto" nil) - (string :tag "Specified") - (const :tag "Unknown" unknown)) - :group 'vc) - -(defcustom vc-rcs-register-switches nil - "Switches for registering a file in RCS. -A string or list of strings passed to the checkin program by -\\[vc-register]. If nil, use the value of `vc-register-switches'. -If t, use no switches." - :type '(choice (const :tag "Unspecified" nil) - (const :tag "None" t) - (string :tag "Argument String") - (repeat :tag "Argument List" :value ("") string)) - :version "21.1" - :group 'vc) - -(defcustom vc-rcs-diff-switches nil - "String or list of strings specifying switches for RCS diff under VC. -If nil, use the value of `vc-diff-switches'. If t, use no switches." - :type '(choice (const :tag "Unspecified" nil) - (const :tag "None" t) - (string :tag "Argument String") - (repeat :tag "Argument List" :value ("") string)) - :version "21.1" - :group 'vc) - -(defcustom vc-rcs-header (or (cdr (assoc 'RCS vc-header-alist)) '("\$Id\$")) - "Header keywords to be inserted by `vc-insert-headers'." - :type '(repeat string) - :version "21.1" - :group 'vc) - -(defcustom vc-rcsdiff-knows-brief nil - "Indicates whether rcsdiff understands the --brief option. -The value is either `yes', `no', or nil. If it is nil, VC tries -to use --brief and sets this variable to remember whether it worked." - :type '(choice (const :tag "Work out" nil) (const yes) (const no)) - :group 'vc) - -;;;###autoload -(defcustom vc-rcs-master-templates - (purecopy '("%sRCS/%s,v" "%s%s,v" "%sRCS/%s")) - "Where to look for RCS master files. -For a description of possible values, see `vc-check-master-templates'." - :type '(choice (const :tag "Use standard RCS file names" - '("%sRCS/%s,v" "%s%s,v" "%sRCS/%s")) - (repeat :tag "User-specified" - (choice string - function))) - :version "21.1" - :group 'vc) - - -;;; Properties of the backend - -(defun vc-rcs-revision-granularity () 'file) - -(defun vc-rcs-checkout-model (files) - "RCS-specific version of `vc-checkout-model'." - (let ((file (if (consp files) (car files) files)) - result) - (when vc-consult-headers - (vc-file-setprop file 'vc-checkout-model nil) - (vc-rcs-consult-headers file) - (setq result (vc-file-getprop file 'vc-checkout-model))) - (or result - (progn (vc-rcs-fetch-master-state file) - (vc-file-getprop file 'vc-checkout-model))))) - -;;; -;;; State-querying functions -;;; - -;; The autoload cookie below places vc-rcs-registered directly into -;; loaddefs.el, so that vc-rcs.el does not need to be loaded for -;; every file that is visited. -;;;###autoload -(progn -(defun vc-rcs-registered (f) (vc-default-registered 'RCS f))) - -(defun vc-rcs-state (file) - "Implementation of `vc-state' for RCS." - (if (not (vc-rcs-registered file)) - 'unregistered - (or (boundp 'vc-rcs-headers-result) - (and vc-consult-headers - (vc-rcs-consult-headers file))) - (let ((state - ;; vc-working-revision might not be known; in that case the - ;; property is nil. vc-rcs-fetch-master-state knows how to - ;; handle that. - (vc-rcs-fetch-master-state file - (vc-file-getprop file - 'vc-working-revision)))) - (if (not (eq state 'up-to-date)) - state - (if (vc-workfile-unchanged-p file) - 'up-to-date - (if (eq (vc-rcs-checkout-model (list file)) 'locking) - 'unlocked-changes - 'edited)))))) - -(defun vc-rcs-state-heuristic (file) - "State heuristic for RCS." - (let (vc-rcs-headers-result) - (if (and vc-consult-headers - (setq vc-rcs-headers-result - (vc-rcs-consult-headers file)) - (eq vc-rcs-headers-result 'rev-and-lock)) - (let ((state (vc-file-getprop file 'vc-state))) - ;; If the headers say that the file is not locked, the - ;; permissions can tell us whether locking is used for - ;; the file or not. - (if (and (eq state 'up-to-date) - (not (vc-mistrust-permissions file)) - (file-exists-p file)) - (cond - ((string-match ".rw..-..-." (nth 8 (file-attributes file))) - (vc-file-setprop file 'vc-checkout-model 'implicit) - (setq state - (if (vc-rcs-workfile-is-newer file) - 'edited - 'up-to-date))) - ((string-match ".r-..-..-." (nth 8 (file-attributes file))) - (vc-file-setprop file 'vc-checkout-model 'locking)))) - state) - (if (not (vc-mistrust-permissions file)) - (let* ((attributes (file-attributes file 'string)) - (owner-name (nth 2 attributes)) - (permissions (nth 8 attributes))) - (cond ((and permissions (string-match ".r-..-..-." permissions)) - (vc-file-setprop file 'vc-checkout-model 'locking) - 'up-to-date) - ((and permissions (string-match ".rw..-..-." permissions)) - (if (eq (vc-rcs-checkout-model file) 'locking) - (if (file-ownership-preserved-p file) - 'edited - owner-name) - (if (vc-rcs-workfile-is-newer file) - 'edited - 'up-to-date))) - (t - ;; Strange permissions. Fall through to - ;; expensive state computation. - (vc-rcs-state file)))) - (vc-rcs-state file))))) - -(defun vc-rcs-dir-status (dir update-function) - ;; FIXME: this function should be rewritten or `vc-expand-dirs' - ;; should be changed to take a backend parameter. Using - ;; `vc-expand-dirs' is not TRTD because it returns files from - ;; multiple backends. It should also return 'unregistered files. - - ;; Doing individual vc-state calls is painful but there - ;; is no better way in RCS-land. - (let ((flist (vc-expand-dirs (list dir))) - (result nil)) - (dolist (file flist) - (let ((state (vc-state file)) - (frel (file-relative-name file))) - (when (and (eq (vc-backend file) 'RCS) - (not (eq state 'up-to-date))) - (push (list frel state) result)))) - (funcall update-function result))) - -(defun vc-rcs-working-revision (file) - "RCS-specific version of `vc-working-revision'." - (or (and vc-consult-headers - (vc-rcs-consult-headers file) - (vc-file-getprop file 'vc-working-revision)) - (progn - (vc-rcs-fetch-master-state file) - (vc-file-getprop file 'vc-working-revision)))) - -(defun vc-rcs-latest-on-branch-p (file &optional version) - "Return non-nil if workfile version of FILE is the latest on its branch. -When VERSION is given, perform check for that version." - (unless version (setq version (vc-working-revision file))) - (with-temp-buffer - (string= version - (if (vc-rcs-trunk-p version) - (progn - ;; Compare VERSION to the head version number. - (vc-insert-file (vc-name file) "^[0-9]") - (vc-parse-buffer "^head[ \t\n]+\\([^;]+\\);" 1)) - ;; If we are not on the trunk, we need to examine the - ;; whole current branch. - (vc-insert-file (vc-name file) "^desc") - (vc-rcs-find-most-recent-rev (vc-branch-part version)))))) - -(defun vc-rcs-workfile-unchanged-p (file) - "RCS-specific implementation of `vc-workfile-unchanged-p'." - ;; Try to use rcsdiff --brief. If rcsdiff does not understand that, - ;; do a double take and remember the fact for the future - (let* ((version (concat "-r" (vc-working-revision file))) - (status (if (eq vc-rcsdiff-knows-brief 'no) - (vc-do-command "*vc*" 1 "rcsdiff" file version) - (vc-do-command "*vc*" 2 "rcsdiff" file "--brief" version)))) - (if (eq status 2) - (if (not vc-rcsdiff-knows-brief) - (setq vc-rcsdiff-knows-brief 'no - status (vc-do-command "*vc*" 1 "rcsdiff" file version)) - (error "rcsdiff failed")) - (if (not vc-rcsdiff-knows-brief) (setq vc-rcsdiff-knows-brief 'yes))) - ;; The workfile is unchanged if rcsdiff found no differences. - (zerop status))) - - -;;; -;;; State-changing functions -;;; - -(defun vc-rcs-create-repo () - "Create a new RCS repository." - ;; RCS is totally file-oriented, so all we have to do is make the directory. - (make-directory "RCS")) - -(defun vc-rcs-register (files &optional rev comment) - "Register FILES into the RCS version-control system. -REV is the optional revision number for the files. COMMENT can be used -to provide an initial description for each FILES. -Passes either `vc-rcs-register-switches' or `vc-register-switches' -to the RCS command. - -Automatically retrieve a read-only version of the file with keywords -expanded if `vc-keep-workfiles' is non-nil, otherwise, delete the workfile." - (let (subdir name) - ;; When REV is specified, we need to force using "-t-". - (when rev (unless comment (setq comment ""))) - (dolist (file files) - (and (not (file-exists-p - (setq subdir (expand-file-name "RCS" - (file-name-directory file))))) - (not (directory-files (file-name-directory file) - nil ".*,v$" t)) - (yes-or-no-p "Create RCS subdirectory? ") - (make-directory subdir)) - (apply 'vc-do-command "*vc*" 0 "ci" file - ;; if available, use the secure registering option - (and (vc-rcs-release-p "5.6.4") "-i") - (concat (if vc-keep-workfiles "-u" "-r") rev) - (and comment (concat "-t-" comment)) - (vc-switches 'RCS 'register)) - ;; parse output to find master file name and workfile version - (with-current-buffer "*vc*" - (goto-char (point-min)) - (if (not (setq name - (if (looking-at (concat "^\\(.*\\) <-- " - (file-name-nondirectory file))) - (match-string 1)))) - ;; if we couldn't find the master name, - ;; run vc-rcs-registered to get it - ;; (will be stored into the vc-name property) - (vc-rcs-registered file) - (vc-file-setprop file 'vc-name - (if (file-name-absolute-p name) - name - (expand-file-name - name - (file-name-directory file)))))) - (vc-file-setprop file 'vc-working-revision - (if (re-search-forward - "^initial revision: \\([0-9.]+\\).*\n" - nil t) - (match-string 1)))))) - -(defun vc-rcs-responsible-p (file) - "Return non-nil if RCS thinks it would be responsible for registering FILE." - ;; TODO: check for all the patterns in vc-rcs-master-templates - (file-directory-p (expand-file-name "RCS" (file-name-directory file)))) - -(defun vc-rcs-receive-file (file rev) - "Implementation of receive-file for RCS." - (let ((checkout-model (vc-rcs-checkout-model (list file)))) - (vc-rcs-register file rev "") - (when (eq checkout-model 'implicit) - (vc-rcs-set-non-strict-locking file)) - (vc-rcs-set-default-branch file (concat rev ".1")))) - -(defun vc-rcs-unregister (file) - "Unregister FILE from RCS. -If this leaves the RCS subdirectory empty, ask the user -whether to remove it." - (let* ((master (vc-name file)) - (dir (file-name-directory master)) - (backup-info (find-backup-file-name master))) - (if (not backup-info) - (delete-file master) - (rename-file master (car backup-info) 'ok-if-already-exists) - (dolist (f (cdr backup-info)) (ignore-errors (delete-file f)))) - (and (string= (file-name-nondirectory (directory-file-name dir)) "RCS") - ;; check whether RCS dir is empty, i.e. it does not - ;; contain any files except "." and ".." - (not (directory-files dir nil - "^\\([^.]\\|\\.[^.]\\|\\.\\.[^.]\\).*")) - (yes-or-no-p (format "Directory %s is empty; remove it? " dir)) - (delete-directory dir)))) - -(defun vc-rcs-checkin (files rev comment &optional extra-args-ignored) - "RCS-specific version of `vc-backend-checkin'." - (let ((switches (vc-switches 'RCS 'checkin))) - ;; Now operate on the files - (dolist (file (vc-expand-dirs files)) - (let ((old-version (vc-working-revision file)) new-version - (default-branch (vc-file-getprop file 'vc-rcs-default-branch))) - ;; Force branch creation if an appropriate - ;; default branch has been set. - (and (not rev) - default-branch - (string-match (concat "^" (regexp-quote old-version) "\\.") - default-branch) - (setq rev default-branch) - (setq switches (cons "-f" switches))) - (if (and (not rev) old-version) - (setq rev (vc-branch-part old-version))) - (apply 'vc-do-command "*vc*" 0 "ci" (vc-name file) - ;; if available, use the secure check-in option - (and (vc-rcs-release-p "5.6.4") "-j") - (concat (if vc-keep-workfiles "-u" "-r") rev) - (concat "-m" comment) - switches) - (vc-file-setprop file 'vc-working-revision nil) - - ;; determine the new workfile version - (set-buffer "*vc*") - (goto-char (point-min)) - (when (or (re-search-forward - "new revision: \\([0-9.]+\\);" nil t) - (re-search-forward - "reverting to previous revision \\([0-9.]+\\)" nil t)) - (setq new-version (match-string 1)) - (vc-file-setprop file 'vc-working-revision new-version)) - - ;; if we got to a different branch, adjust the default - ;; branch accordingly - (cond - ((and old-version new-version - (not (string= (vc-branch-part old-version) - (vc-branch-part new-version)))) - (vc-rcs-set-default-branch file - (if (vc-rcs-trunk-p new-version) nil - (vc-branch-part new-version))) - ;; If this is an old RCS release, we might have - ;; to remove a remaining lock. - (if (not (vc-rcs-release-p "5.6.2")) - ;; exit status of 1 is also accepted. - ;; It means that the lock was removed before. - (vc-do-command "*vc*" 1 "rcs" (vc-name file) - (concat "-u" old-version))))))))) - -(defun vc-rcs-find-revision (file rev buffer) - (apply 'vc-do-command - (or buffer "*vc*") 0 "co" (vc-name file) - "-q" ;; suppress diagnostic output - (concat "-p" rev) - (vc-switches 'RCS 'checkout))) - -(defun vc-rcs-checkout (file &optional editable rev) - "Retrieve a copy of a saved version of FILE. If FILE is a directory, -attempt the checkout for all registered files beneath it." - (if (file-directory-p file) - (mapc 'vc-rcs-checkout (vc-expand-dirs (list file))) - (let ((file-buffer (get-file-buffer file)) - switches) - (message "Checking out %s..." file) - (save-excursion - ;; Change buffers to get local value of vc-checkout-switches. - (if file-buffer (set-buffer file-buffer)) - (setq switches (vc-switches 'RCS 'checkout)) - ;; Save this buffer's default-directory - ;; and use save-excursion to make sure it is restored - ;; in the same buffer it was saved in. - (let ((default-directory default-directory)) - (save-excursion - ;; Adjust the default-directory so that the check-out creates - ;; the file in the right place. - (setq default-directory (file-name-directory file)) - (let (new-version) - ;; if we should go to the head of the trunk, - ;; clear the default branch first - (and rev (string= rev "") - (vc-rcs-set-default-branch file nil)) - ;; now do the checkout - (apply 'vc-do-command - "*vc*" 0 "co" (vc-name file) - ;; If locking is not strict, force to overwrite - ;; the writable workfile. - (if (eq (vc-rcs-checkout-model (list file)) 'implicit) "-f") - (if editable "-l") - (if (stringp rev) - ;; a literal revision was specified - (concat "-r" rev) - (let ((workrev (vc-working-revision file))) - (if workrev - (concat "-r" - (if (not rev) - ;; no revision specified: - ;; use current workfile version - workrev - ;; REV is t ... - (if (not (vc-rcs-trunk-p workrev)) - ;; ... go to head of current branch - (vc-branch-part workrev) - ;; ... go to head of trunk - (vc-rcs-set-default-branch file - nil) - "")))))) - switches) - ;; determine the new workfile version - (with-current-buffer "*vc*" - (setq new-version - (vc-parse-buffer "^revision \\([0-9.]+\\).*\n" 1))) - (vc-file-setprop file 'vc-working-revision new-version) - ;; if necessary, adjust the default branch - (and rev (not (string= rev "")) - (vc-rcs-set-default-branch - file - (if (vc-rcs-latest-on-branch-p file new-version) - (if (vc-rcs-trunk-p new-version) nil - (vc-branch-part new-version)) - new-version))))) - (message "Checking out %s...done" file)))))) - -(defun vc-rcs-rollback (files) - "Roll back, undoing the most recent checkins of FILES. Directories are -expanded to all registered subfiles in them." - (if (not files) - (error "RCS backend doesn't support directory-level rollback")) - (dolist (file (vc-expand-dirs files)) - (let* ((discard (vc-working-revision file)) - (previous (if (vc-rcs-trunk-p discard) "" (vc-branch-part discard))) - (config (current-window-configuration)) - (done nil)) - (if (null (yes-or-no-p (format "Remove version %s from %s history? " - discard file))) - (error "Aborted")) - (message "Removing revision %s from %s." discard file) - (vc-do-command "*vc*" 0 "rcs" (vc-name file) (concat "-o" discard)) - ;; Check out the most recent remaining version. If it - ;; fails, because the whole branch got deleted, do a - ;; double-take and check out the version where the branch - ;; started. - (while (not done) - (condition-case err - (progn - (vc-do-command "*vc*" 0 "co" (vc-name file) "-f" - (concat "-u" previous)) - (setq done t)) - (error (set-buffer "*vc*") - (goto-char (point-min)) - (if (search-forward "no side branches present for" nil t) - (progn (setq previous (vc-branch-part previous)) - (vc-rcs-set-default-branch file previous) - ;; vc-do-command popped up a window with - ;; the error message. Get rid of it, by - ;; restoring the old window configuration. - (set-window-configuration config)) - ;; No, it was some other error: re-signal it. - (signal (car err) (cdr err))))))))) - -(defun vc-rcs-revert (file &optional contents-done) - "Revert FILE to the version it was based on. If FILE is a directory, -revert all registered files beneath it." - (if (file-directory-p file) - (mapc 'vc-rcs-revert (vc-expand-dirs (list file))) - (vc-do-command "*vc*" 0 "co" (vc-name file) "-f" - (concat (if (eq (vc-state file) 'edited) "-u" "-r") - (vc-working-revision file))))) - -(defun vc-rcs-merge (file first-version &optional second-version) - "Merge changes into current working copy of FILE. -The changes are between FIRST-VERSION and SECOND-VERSION." - (vc-do-command "*vc*" 1 "rcsmerge" (vc-name file) - "-kk" ; ignore keyword conflicts - (concat "-r" first-version) - (if second-version (concat "-r" second-version)))) - -(defun vc-rcs-steal-lock (file &optional rev) - "Steal the lock on the current workfile for FILE and revision REV. -If FILE is a directory, steal the lock on all registered files beneath it. -Needs RCS 5.6.2 or later for -M." - (if (file-directory-p file) - (mapc 'vc-rcs-steal-lock (vc-expand-dirs (list file))) - (vc-do-command "*vc*" 0 "rcs" (vc-name file) "-M" (concat "-u" rev)) - ;; Do a real checkout after stealing the lock, so that we see - ;; expanded headers. - (vc-do-command "*vc*" 0 "co" (vc-name file) "-f" (concat "-l" rev)))) - -(defun vc-rcs-modify-change-comment (files rev comment) - "Modify the change comments change on FILES on a specified REV. If FILE is a -directory the operation is applied to all registered files beneath it." - (dolist (file (vc-expand-dirs files)) - (vc-do-command "*vc*" 0 "rcs" (vc-name file) - (concat "-m" rev ":" comment)))) - - -;;; -;;; History functions -;;; - -(defun vc-rcs-print-log-cleanup () - (let ((inhibit-read-only t)) - (goto-char (point-max)) - (forward-line -1) - (while (looking-at "=*\n") - (delete-char (- (match-end 0) (match-beginning 0))) - (forward-line -1)) - (goto-char (point-min)) - (when (looking-at "[\b\t\n\v\f\r ]+") - (delete-char (- (match-end 0) (match-beginning 0)))))) - -(defun vc-rcs-print-log (files buffer &optional shortlog start-revision-ignored limit) - "Get change log associated with FILE. If FILE is a -directory the operation is applied to all registered files beneath it." - (vc-do-command (or buffer "*vc*") 0 "rlog" (mapcar 'vc-name (vc-expand-dirs files))) - (with-current-buffer (or buffer "*vc*") - (vc-rcs-print-log-cleanup)) - (when limit 'limit-unsupported)) - -(defun vc-rcs-diff (files &optional oldvers newvers buffer) - "Get a difference report using RCS between two sets of files." - (apply 'vc-do-command (or buffer "*vc-diff*") - 1 ;; Always go synchronous, the repo is local - "rcsdiff" (vc-expand-dirs files) - (append (list "-q" - (and oldvers (concat "-r" oldvers)) - (and newvers (concat "-r" newvers))) - (vc-switches 'RCS 'diff)))) - -(defun vc-rcs-comment-history (file) - "Return a string with all log entries stored in BACKEND for FILE." - (with-current-buffer "*vc*" - ;; Has to be written this way, this function is used by the CVS backend too - (vc-call-backend (vc-backend file) 'print-log (list file)) - ;; Remove cruft - (let ((separator (concat "^-+\nrevision [0-9.]+\ndate: .*\n" - "\\(branches: .*;\n\\)?" - "\\(\\*\\*\\* empty log message \\*\\*\\*\n\\)?"))) - (goto-char (point-max)) (forward-line -1) - (while (looking-at "=*\n") - (delete-char (- (match-end 0) (match-beginning 0))) - (forward-line -1)) - (goto-char (point-min)) - (if (looking-at "[\b\t\n\v\f\r ]+") - (delete-char (- (match-end 0) (match-beginning 0)))) - (goto-char (point-min)) - (re-search-forward separator nil t) - (delete-region (point-min) (point)) - (while (re-search-forward separator nil t) - (delete-region (match-beginning 0) (match-end 0)))) - ;; Return the de-crufted comment list - (buffer-string))) - -(defun vc-rcs-annotate-command (file buffer &optional revision) - "Annotate FILE, inserting the results in BUFFER. -Optional arg REVISION is a revision to annotate from." - (vc-setup-buffer buffer) - ;; Aside from the "head revision on the trunk", the instructions for - ;; each revision on the trunk are an ordered list of kill and insert - ;; commands necessary to go from the chronologically-following - ;; revision to this one. That is, associated with revision N are - ;; edits that applied to revision N+1 would result in revision N. - ;; - ;; On a branch, however, (some) things are inverted: the commands - ;; listed are those necessary to go from the chronologically-preceding - ;; revision to this one. That is, associated with revision N are - ;; edits that applied to revision N-1 would result in revision N. - ;; - ;; So, to get per-line history info, we apply reverse-chronological - ;; edits, starting with the head revision on the trunk, all the way - ;; back through the initial revision (typically "1.1" or similar), - ;; then apply forward-chronological edits -- keeping track of which - ;; revision is associated with each inserted line -- until we reach - ;; the desired revision for display (which may be either on the trunk - ;; or on a branch). - (let* ((tree (with-temp-buffer - (insert-file-contents (vc-rcs-registered file)) - (vc-rcs-parse))) - (revisions (cdr (assq 'revisions tree))) - ;; The revision N whose instructions we currently are processing. - (cur (cdr (assq 'head (cdr (assq 'headers tree))))) - ;; Alist from the parse tree for N. - (meta (cdr (assoc cur revisions))) - ;; Point and temporary string, respectively. - p s - ;; "Next-branch list". Nil means the desired revision to - ;; display lives on the trunk. Non-nil means it lives on a - ;; branch, in which case the value is a list of revision pairs - ;; (PARENT . CHILD), the first PARENT being on the trunk, that - ;; links each series of revisions in the path from the initial - ;; revision to the desired revision to display. - nbls - ;; "Path-accumulate-predicate plus revision/date/author". - ;; Until set, forward-chronological edits are not accumulated. - ;; Once set, its value (updated every revision) is used for - ;; the text property `:vc-rcs-r/d/a' for inserts during - ;; processing of forward-chronological instructions for N. - ;; See internal func `r/d/a'. - prda - ;; List of forward-chronological instructions, each of the - ;; form: (POS . ACTION), where POS is a buffer position. If - ;; ACTION is a string, it is inserted, otherwise it is taken as - ;; the number of characters to be deleted. - path - ;; N+1. When `cur' is "", this is the initial revision. - pre) - (unless revision - (setq revision cur)) - (unless (assoc revision revisions) - (error "No such revision: %s" revision)) - ;; Find which branches (if any) must be included in the edits. - (let ((par revision) - bpt kids) - (while (setq bpt (vc-branch-part par) - par (vc-branch-part bpt)) - (setq kids (cdr (assq 'branches (cdr (assoc par revisions))))) - ;; A branchpoint may have multiple children. Find the right one. - (while (not (string= bpt (vc-branch-part (car kids)))) - (setq kids (cdr kids))) - (push (cons par (car kids)) nbls))) - ;; Start with the full text. - (set-buffer buffer) - (insert (cdr (assq 'text meta))) - ;; Apply reverse-chronological edits on the trunk, computing and - ;; accumulating forward-chronological edits after some point, for - ;; later. - (flet ((r/d/a () (vector pre - (cdr (assq 'date meta)) - (cdr (assq 'author meta))))) - (while (when (setq pre cur cur (cdr (assq 'next meta))) - (not (string= "" cur))) - (setq - ;; Start accumulating the forward-chronological edits when N+1 - ;; on the trunk is either the desired revision to display, or - ;; the appropriate branchpoint for it. Do this before - ;; updating `meta' since `r/d/a' uses N+1's `meta' value. - prda (when (or prda (string= (if nbls (caar nbls) revision) pre)) - (r/d/a)) - meta (cdr (assoc cur revisions))) - ;; Edits in the parse tree specify a line number (in the buffer - ;; *BEFORE* editing occurs) to start from, but line numbers - ;; change as a result of edits. To DTRT, we apply edits in - ;; order of descending buffer position so that edits further - ;; down in the buffer occur first w/o corrupting specified - ;; buffer positions of edits occurring towards the beginning of - ;; the buffer. In this way we avoid using markers. A pleasant - ;; property of this approach is ability to push instructions - ;; onto `path' directly, w/o need to maintain rev boundaries. - (dolist (insn (cdr (assq :insn meta))) - (goto-char (point-min)) - (forward-line (1- (pop insn))) - (setq p (point)) - (case (pop insn) - (k (setq s (buffer-substring-no-properties - p (progn (forward-line (car insn)) - (point)))) - (when prda - (push `(,p . ,(propertize s :vc-rcs-r/d/a prda)) path)) - (delete-region p (point))) - (i (setq s (car insn)) - (when prda - (push `(,p . ,(length s)) path)) - (insert s))))) - ;; For the initial revision, setting `:vc-rcs-r/d/a' directly is - ;; equivalent to pushing an insert instruction (of the entire buffer - ;; contents) onto `path' then erasing the buffer, but less wasteful. - (put-text-property (point-min) (point-max) :vc-rcs-r/d/a (r/d/a)) - ;; Now apply the forward-chronological edits for the trunk. - (dolist (insn path) - (goto-char (pop insn)) - (if (stringp insn) - (insert insn) - (delete-char insn))) - ;; Now apply the forward-chronological edits (directly from the - ;; parse-tree) for the branch(es), if necessary. We re-use vars - ;; `pre' and `meta' for the sake of internal func `r/d/a'. - (while nbls - (setq pre (cdr (pop nbls))) - (while (progn - (setq meta (cdr (assoc pre revisions)) - prda nil) - (dolist (insn (cdr (assq :insn meta))) - (goto-char (point-min)) - (forward-line (1- (pop insn))) - (case (pop insn) - (k (delete-region - (point) (progn (forward-line (car insn)) - (point)))) - (i (insert (propertize - (car insn) - :vc-rcs-r/d/a - (or prda (setq prda (r/d/a)))))))) - (prog1 (not (string= (if nbls (caar nbls) revision) pre)) - (setq pre (cdr (assq 'next meta))))))))) - ;; Lastly, for each line, insert at bol nicely-formatted history info. - ;; We do two passes to collect summary information used to minimize - ;; the annotation's usage of screen real-estate: (1) Consider rendered - ;; width of revision plus author together as a unit; and (2) Omit - ;; author entirely if all authors are the same as the user. - (let ((ht (make-hash-table :test 'eq)) - (me (user-login-name)) - (maxw 0) - (all-me t) - rda w a) - (goto-char (point-max)) - (while (not (bobp)) - (forward-line -1) - (setq rda (get-text-property (point) :vc-rcs-r/d/a)) - (unless (gethash rda ht) - (setq a (aref rda 2) - all-me (and all-me (string= a me))) - (puthash rda (setq w (+ (length (aref rda 0)) - (length a))) - ht) - (setq maxw (max w maxw)))) - (let ((padding (make-string maxw 32))) - (flet ((pad (w) (substring-no-properties padding w)) - (render (rda &rest ls) - (propertize - (apply 'concat - (format-time-string "%Y-%m-%d" (aref rda 1)) - " " - (aref rda 0) - ls) - :vc-annotate-prefix t - :vc-rcs-r/d/a rda))) - (maphash - (if all-me - (lambda (rda w) - (puthash rda (render rda (pad w) ": ") ht)) - (lambda (rda w) - (puthash rda (render rda " " (pad w) " " (aref rda 2) ": ") ht))) - ht))) - (while (not (eobp)) - (insert (gethash (get-text-property (point) :vc-rcs-r/d/a) ht)) - (forward-line 1)))) - -(declare-function vc-annotate-convert-time "vc-annotate" (time)) - -(defun vc-rcs-annotate-current-time () - "Return the current time, based at midnight of the current day, and -encoded as fractional days." - (vc-annotate-convert-time - (apply 'encode-time 0 0 0 (nthcdr 3 (decode-time (current-time)))))) - -(defun vc-rcs-annotate-time () - "Return the time of the next annotation (as fraction of days) -systime, or nil if there is none. Also, reposition point." - (unless (eobp) - (prog1 (vc-annotate-convert-time - (aref (get-text-property (point) :vc-rcs-r/d/a) 1)) - (goto-char (next-single-property-change (point) :vc-annotate-prefix))))) - -(defun vc-rcs-annotate-extract-revision-at-line () - (aref (get-text-property (point) :vc-rcs-r/d/a) 0)) - - -;;; -;;; Tag system -;;; - -(defun vc-rcs-create-tag (backend dir name branchp) - (when branchp - (error "RCS backend %s does not support module branches" backend)) - (let ((result (vc-tag-precondition dir))) - (if (stringp result) - (error "File %s is not up-to-date" result) - (vc-file-tree-walk - dir - (lambda (f) - (vc-do-command "*vc*" 0 "rcs" (vc-name f) (concat "-n" name ":"))))))) - - -;;; -;;; Miscellaneous -;;; - -(defun vc-rcs-trunk-p (rev) - "Return t if REV is a revision on the trunk." - (not (eq nil (string-match "\\`[0-9]+\\.[0-9]+\\'" rev)))) - -(defun vc-rcs-minor-part (rev) - "Return the minor revision number of a revision number REV." - (string-match "[0-9]+\\'" rev) - (substring rev (match-beginning 0) (match-end 0))) - -(defun vc-rcs-previous-revision (file rev) - "Return the revision number immediately preceding REV for FILE, -or nil if there is no previous revision. This default -implementation works for MAJOR.MINOR-style revision numbers as -used by RCS and CVS." - (let ((branch (vc-branch-part rev)) - (minor-num (string-to-number (vc-rcs-minor-part rev)))) - (when branch - (if (> minor-num 1) - ;; revision does probably not start a branch or release - (concat branch "." (number-to-string (1- minor-num))) - (if (vc-rcs-trunk-p rev) - ;; we are at the beginning of the trunk -- - ;; don't know anything to return here - nil - ;; we are at the beginning of a branch -- - ;; return revision of starting point - (vc-branch-part branch)))))) - -(defun vc-rcs-next-revision (file rev) - "Return the revision number immediately following REV for FILE, -or nil if there is no next revision. This default implementation -works for MAJOR.MINOR-style revision numbers as used by RCS -and CVS." - (when (not (string= rev (vc-working-revision file))) - (let ((branch (vc-branch-part rev)) - (minor-num (string-to-number (vc-rcs-minor-part rev)))) - (concat branch "." (number-to-string (1+ minor-num)))))) - -(defun vc-rcs-update-changelog (files) - "Default implementation of update-changelog. -Uses `rcs2log' which only works for RCS and CVS." - ;; FIXME: We (c|sh)ould add support for cvs2cl - (let ((odefault default-directory) - (changelog (find-change-log)) - ;; Presumably not portable to non-Unixy systems, along with rcs2log: - (tempfile (make-temp-file - (expand-file-name "vc" - (or small-temporary-file-directory - temporary-file-directory)))) - (login-name (or user-login-name - (format "uid%d" (number-to-string (user-uid))))) - (full-name (or add-log-full-name - (user-full-name) - (user-login-name) - (format "uid%d" (number-to-string (user-uid))))) - (mailing-address (or add-log-mailing-address - user-mail-address))) - (find-file-other-window changelog) - (barf-if-buffer-read-only) - (vc-buffer-sync) - (undo-boundary) - (goto-char (point-min)) - (push-mark) - (message "Computing change log entries...") - (message "Computing change log entries... %s" - (unwind-protect - (progn - (setq default-directory odefault) - (if (eq 0 (apply 'call-process - (expand-file-name "rcs2log" - exec-directory) - nil (list t tempfile) nil - "-c" changelog - "-u" (concat login-name - "\t" full-name - "\t" mailing-address) - (mapcar - (lambda (f) - (file-relative-name - (expand-file-name f odefault))) - files))) - "done" - (pop-to-buffer (get-buffer-create "*vc*")) - (erase-buffer) - (insert-file-contents tempfile) - "failed")) - (setq default-directory (file-name-directory changelog)) - (delete-file tempfile))))) - -(defun vc-rcs-check-headers () - "Check if the current file has any headers in it." - (save-excursion - (goto-char (point-min)) - (re-search-forward "\\$[A-Za-z\300-\326\330-\366\370-\377]+\ -\\(: [\t -#%-\176\240-\377]*\\)?\\$" nil t))) - -(defun vc-rcs-clear-headers () - "Implementation of vc-clear-headers for RCS." - (let ((case-fold-search nil)) - (goto-char (point-min)) - (while (re-search-forward - (concat "\\$\\(Author\\|Date\\|Header\\|Id\\|Locker\\|Name\\|" - "RCSfile\\|Revision\\|Source\\|State\\): [^$\n]+\\$") - nil t) - (replace-match "$\\1$")))) - -(defun vc-rcs-rename-file (old new) - ;; Just move the master file (using vc-rcs-master-templates). - (vc-rename-master (vc-name old) new vc-rcs-master-templates)) - -(defun vc-rcs-find-file-hook () - ;; If the file is locked by some other user, make - ;; the buffer read-only. Like this, even root - ;; cannot modify a file that someone else has locked. - (and (stringp (vc-state buffer-file-name 'RCS)) - (setq buffer-read-only t))) - - -;;; -;;; Internal functions -;;; - -(defun vc-rcs-workfile-is-newer (file) - "Return non-nil if FILE is newer than its RCS master. -This likely means that FILE has been changed with respect -to its master version." - (let ((file-time (nth 5 (file-attributes file))) - (master-time (nth 5 (file-attributes (vc-name file))))) - (or (> (nth 0 file-time) (nth 0 master-time)) - (and (= (nth 0 file-time) (nth 0 master-time)) - (> (nth 1 file-time) (nth 1 master-time)))))) - -(defun vc-rcs-find-most-recent-rev (branch) - "Find most recent revision on BRANCH." - (goto-char (point-min)) - (let ((latest-rev -1) value) - (while (re-search-forward (concat "^\\(" (regexp-quote branch) - "\\.\\([0-9]+\\)\\)\ndate[ \t]+[0-9.]+;") - nil t) - (let ((rev (string-to-number (match-string 2)))) - (when (< latest-rev rev) - (setq latest-rev rev) - (setq value (match-string 1))))) - (or value - (vc-branch-part branch)))) - -(defun vc-rcs-fetch-master-state (file &optional working-revision) - "Compute the master file's idea of the state of FILE. -If a WORKING-REVISION is given, compute the state of that version, -otherwise determine the workfile version based on the master file. -This function sets the properties `vc-working-revision' and -`vc-checkout-model' to their correct values, based on the master -file." - (with-temp-buffer - (if (or (not (vc-insert-file (vc-name file) "^[0-9]")) - (progn (goto-char (point-min)) - (not (looking-at "^head[ \t\n]+[^;]+;$")))) - (error "File %s is not an RCS master file" (vc-name file))) - (let ((workfile-is-latest nil) - (default-branch (vc-parse-buffer "^branch[ \t\n]+\\([^;]*\\);" 1))) - (vc-file-setprop file 'vc-rcs-default-branch default-branch) - (unless working-revision - ;; Workfile version not known yet. Determine that first. It - ;; is either the head of the trunk, the head of the default - ;; branch, or the "default branch" itself, if that is a full - ;; revision number. - (cond - ;; no default branch - ((or (not default-branch) (string= "" default-branch)) - (setq working-revision - (vc-parse-buffer "^head[ \t\n]+\\([^;]+\\);" 1)) - (setq workfile-is-latest t)) - ;; default branch is actually a revision - ((string-match "^[0-9]+\\.[0-9]+\\(\\.[0-9]+\\.[0-9]+\\)*$" - default-branch) - (setq working-revision default-branch)) - ;; else, search for the head of the default branch - (t (vc-insert-file (vc-name file) "^desc") - (setq working-revision - (vc-rcs-find-most-recent-rev default-branch)) - (setq workfile-is-latest t))) - (vc-file-setprop file 'vc-working-revision working-revision)) - ;; Check strict locking - (goto-char (point-min)) - (vc-file-setprop file 'vc-checkout-model - (if (re-search-forward ";[ \t\n]*strict;" nil t) - 'locking 'implicit)) - ;; Compute state of workfile version - (goto-char (point-min)) - (let ((locking-user - (vc-parse-buffer (concat "^locks[ \t\n]+[^;]*[ \t\n]+\\([^:]+\\):" - (regexp-quote working-revision) - "[^0-9.]") - 1))) - (cond - ;; not locked - ((not locking-user) - (if (or workfile-is-latest - (vc-rcs-latest-on-branch-p file working-revision)) - ;; workfile version is latest on branch - 'up-to-date - ;; workfile version is not latest on branch - 'needs-update)) - ;; locked by the calling user - ((and (stringp locking-user) - (string= locking-user (vc-user-login-name file))) - ;; Don't call `vc-rcs-checkout-model' to avoid inf-looping. - (if (or (eq (vc-file-getprop file 'vc-checkout-model) 'locking) - workfile-is-latest - (vc-rcs-latest-on-branch-p file working-revision)) - 'edited - ;; Locking is not used for the file, but the owner does - ;; have a lock, and there is a higher version on the current - ;; branch. Not sure if this can occur, and if it is right - ;; to use `needs-merge' in this case. - 'needs-merge)) - ;; locked by somebody else - ((stringp locking-user) - locking-user) - (t - (error "Error getting state of RCS file"))))))) - -(defun vc-rcs-consult-headers (file) - "Search for RCS headers in FILE, and set properties accordingly. - -Returns: nil if no headers were found - 'rev if a workfile revision was found - 'rev-and-lock if revision and lock info was found" - (cond - ((not (get-file-buffer file)) nil) - ((let (status version locking-user) - (with-current-buffer (get-file-buffer file) - (save-excursion - (goto-char (point-min)) - (cond - ;; search for $Id or $Header - ;; ------------------------- - ;; The `\ 's below avoid an RCS 5.7 bug when checking in this file. - ((or (and (search-forward "$Id\ : " nil t) - (looking-at "[^ ]+ \\([0-9.]+\\) ")) - (and (progn (goto-char (point-min)) - (search-forward "$Header\ : " nil t)) - (looking-at "[^ ]+ \\([0-9.]+\\) "))) - (goto-char (match-end 0)) - ;; if found, store the revision number ... - (setq version (match-string-no-properties 1)) - ;; ... and check for the locking state - (cond - ((looking-at - (concat "[0-9]+[/-][01][0-9][/-][0-3][0-9] " ; date - "[0-2][0-9]:[0-5][0-9]+:[0-6][0-9]+\\([+-][0-9:]+\\)? " ; time - "[^ ]+ [^ ]+ ")) ; author & state - (goto-char (match-end 0)) ; [0-6] in regexp handles leap seconds - (cond - ;; unlocked revision - ((looking-at "\\$") - (setq locking-user 'none) - (setq status 'rev-and-lock)) - ;; revision is locked by some user - ((looking-at "\\([^ ]+\\) \\$") - (setq locking-user (match-string-no-properties 1)) - (setq status 'rev-and-lock)) - ;; everything else: false - (nil))) - ;; unexpected information in - ;; keyword string --> quit - (nil))) - ;; search for $Revision - ;; -------------------- - ((re-search-forward (concat "\\$" - "Revision: \\([0-9.]+\\) \\$") - nil t) - ;; if found, store the revision number ... - (setq version (match-string-no-properties 1)) - ;; and see if there's any lock information - (goto-char (point-min)) - (if (re-search-forward (concat "\\$" "Locker:") nil t) - (cond ((looking-at " \\([^ ]+\\) \\$") - (setq locking-user (match-string-no-properties 1)) - (setq status 'rev-and-lock)) - ((looking-at " *\\$") - (setq locking-user 'none) - (setq status 'rev-and-lock)) - (t - (setq locking-user 'none) - (setq status 'rev-and-lock))) - (setq status 'rev))) - ;; else: nothing found - ;; ------------------- - (t nil)))) - (if status (vc-file-setprop file 'vc-working-revision version)) - (and (eq status 'rev-and-lock) - (vc-file-setprop file 'vc-state - (cond - ((eq locking-user 'none) 'up-to-date) - ((string= locking-user (vc-user-login-name file)) - 'edited) - (t locking-user))) - ;; If the file has headers, we don't want to query the - ;; master file, because that would eliminate all the - ;; performance gain the headers brought us. We therefore - ;; use a heuristic now to find out whether locking is used - ;; for this file. If we trust the file permissions, and the - ;; file is not locked, then if the file is read-only we - ;; assume that locking is used for the file, otherwise - ;; locking is not used. - (not (vc-mistrust-permissions file)) - (vc-up-to-date-p file) - (if (string-match ".r-..-..-." (nth 8 (file-attributes file))) - (vc-file-setprop file 'vc-checkout-model 'locking) - (vc-file-setprop file 'vc-checkout-model 'implicit))) - status)))) - -(defun vc-release-greater-or-equal (r1 r2) - "Compare release numbers, represented as strings. -Release components are assumed cardinal numbers, not decimal fractions -\(5.10 is a higher release than 5.9\). Omitted fields are considered -lower \(5.6.7 is earlier than 5.6.7.1\). Comparison runs till the end -of the string is found, or a non-numeric component shows up \(5.6.7 is -earlier than \"5.6.7 beta\", which is probably not what you want in -some cases\). This code is suitable for existing RCS release numbers. -CVS releases are handled reasonably, too \(1.3 < 1.4* < 1.5\)." - (let (v1 v2 i1 i2) - (catch 'done - (or (and (string-match "^\\.?\\([0-9]+\\)" r1) - (setq i1 (match-end 0)) - (setq v1 (string-to-number (match-string 1 r1))) - (or (and (string-match "^\\.?\\([0-9]+\\)" r2) - (setq i2 (match-end 0)) - (setq v2 (string-to-number (match-string 1 r2))) - (if (> v1 v2) (throw 'done t) - (if (< v1 v2) (throw 'done nil) - (throw 'done - (vc-release-greater-or-equal - (substring r1 i1) - (substring r2 i2))))))) - (throw 'done t))) - (or (and (string-match "^\\.?\\([0-9]+\\)" r2) - (throw 'done nil)) - (throw 'done t))))) - -(defun vc-rcs-release-p (release) - "Return t if we have RELEASE or better." - (let ((installation (vc-rcs-system-release))) - (if (and installation - (not (eq installation 'unknown))) - (vc-release-greater-or-equal installation release)))) - -(defun vc-rcs-system-release () - "Return the RCS release installed on this system, as a string. -Return symbol `unknown' if the release cannot be deducted. The user can -override this using variable `vc-rcs-release'. - -If the user has not set variable `vc-rcs-release' and it is nil, -variable `vc-rcs-release' is set to the returned value." - (or vc-rcs-release - (setq vc-rcs-release - (or (and (zerop (vc-do-command "*vc*" nil "rcs" nil "-V")) - (with-current-buffer (get-buffer "*vc*") - (vc-parse-buffer "^RCS version \\([0-9.]+ *.*\\)" 1))) - 'unknown)))) - -(defun vc-rcs-set-non-strict-locking (file) - (vc-do-command "*vc*" 0 "rcs" file "-U") - (vc-file-setprop file 'vc-checkout-model 'implicit) - (set-file-modes file (logior (file-modes file) 128))) - -(defun vc-rcs-set-default-branch (file branch) - (vc-do-command "*vc*" 0 "rcs" (vc-name file) (concat "-b" branch)) - (vc-file-setprop file 'vc-rcs-default-branch branch)) - -(defun vc-rcs-parse (&optional buffer) - "Parse current buffer, presumed to be in RCS-style masterfile format. -Optional arg BUFFER specifies another buffer to parse. Return an alist -of two elements, w/ keys `headers' and `revisions' and values in turn -sub-alists. For `headers', the values unless otherwise specified are -strings and the keys are: - - desc -- description - head -- latest revision - branch -- the branch the \"head revision\" lies on; - absent if the head revision lies on the trunk - access -- ??? - symbols -- sub-alist of (SYMBOL . REVISION) elements - locks -- if file is checked out, something like \"ttn:1.7\" - strict -- t if \"strict locking\" is in effect, otherwise nil - comment -- may be absent; typically something like \"# \" or \"; \" - expand -- may be absent; ??? - -For `revisions', the car is REVISION (string), the cdr a sub-alist, -with string values (unless otherwise specified) and keys: - - date -- a time value (like that returned by `encode-time'); as a - special case, a year value less than 100 is augmented by 1900 - author -- username - state -- typically \"Exp\" or \"Rel\" - branches -- list of revisions that begin branches from this revision - next -- on the trunk: the chronologically-preceding revision, or \"\"; - on a branch: the chronologically-following revision, or \"\" - log -- change log entry - text -- for the head revision on the trunk, the body of the file; - other revisions have `:insn' instead - :insn -- for non-head revisions, a list of parsed instructions - in one of two forms, in both cases START meaning \"first - go to line START\": - - `(START k COUNT)' -- kill COUNT lines - - `(START i TEXT)' -- insert TEXT (a string) - The list is in descending order by START. - -The `:insn' key is a keyword to distinguish it as a vc-rcs.el extension." - (setq buffer (get-buffer (or buffer (current-buffer)))) - (set-buffer buffer) - ;; An RCS masterfile can be viewed as containing four regular (for the - ;; most part) sections: (a) the "headers", (b) the "rev headers", (c) - ;; the "description" and (d) the "rev bodies", in that order. In the - ;; returned alist (see docstring), elements from (b) and (d) are - ;; combined pairwise to form the "revisions", while those from (a) and - ;; (c) are simply combined to form the "headers". - ;; - ;; Loosely speaking, each section contains a series of alternating - ;; "tags" and "printed representations". In the (b) and (d), many - ;; such series can appear, and a revision number on a line by itself - ;; precedes the series of tags and printed representations associated - ;; with it. - ;; - ;; In (a) and (b), the printed representations (with the exception of - ;; the `comment' tag in the headers) terminate with a semicolon, which - ;; is NOT part of the "value" finally associated with the tag. All - ;; other printed representations are in "@@-format"; there is an "@", - ;; the middle part (to be translated into the value), another "@" and - ;; a newline. Each "@@" in the middle part indicates the position of - ;; a single "@" (and consequently the requirement of an additional - ;; initial step when translating to the value). - ;; - ;; Parser state includes vars that collect parts of the return value... - (let ((desc nil) (headers nil) (revs nil) - ;; ... as well as vars that support a single-pass, tag-assisted, - ;; minimal-data-copying scan. Basically -- skirting around the - ;; grouping by revision required in (b) and (d) -- we repeatedly - ;; and context-sensitively read a tag (that MUST be present), - ;; determine the bounds of the printed representation, translate - ;; it into a value, and push the tag plus value onto one of the - ;; collection vars. Finally, we return the parse tree - ;; incorporating the values of the collection vars (see "rv"). - ;; - ;; A symbol or string to keep track of context (for error messages). - context - ;; A symbol, the current tag. - tok - ;; Region (begin and end buffer positions) of the printed - ;; representation for the current tag. - b e - ;; A list of buffer positions where "@@" can be found within the - ;; printed representation region. For each location, we push two - ;; elements onto the list, 1+ and 2+ the location, respectively, - ;; with the 2+ appearing at the head. In this way, the expression - ;; `(,e ,@@-holes ,b) - ;; describes regions that can be concatenated (in reverse order) - ;; to "de-@@-format" the printed representation as the first step - ;; to translating it into some value. See internal func `gather'. - @-holes) - (flet ((sw () (skip-chars-forward " \t\n")) ; i.e., `[:space:]' - (at (tag) (save-excursion (eq tag (read buffer)))) - (to-eol () (buffer-substring-no-properties - (point) (progn (forward-line 1) - (1- (point))))) - (to-semi () (setq b (point) - e (progn (search-forward ";") - (1- (point))))) - (to-one@ () (setq @-holes nil - b (progn (search-forward "@") (point)) - e (progn (while (and (search-forward "@") - (= ?@ (char-after)) - (progn - (push (point) @-holes) - (forward-char 1) - (push (point) @-holes)))) - (1- (point))))) - (tok+val (set-b+e name &optional proc) - (unless (eq name (setq tok (read buffer))) - (error "Missing `%s' while parsing %s" name context)) - (sw) - (funcall set-b+e) - (cons tok (if proc - (funcall proc) - (buffer-substring-no-properties b e)))) - (k-semi (name &optional proc) (tok+val 'to-semi name proc)) - (gather () (let ((pairs `(,e ,@@-holes ,b)) - acc) - (while pairs - (push (buffer-substring-no-properties - (cadr pairs) (car pairs)) - acc) - (setq pairs (cddr pairs))) - (apply 'concat acc))) - (k-one@ (name &optional later) (tok+val 'to-one@ name - (if later - (lambda () t) - 'gather)))) - (save-excursion - (goto-char (point-min)) - ;; headers - (setq context 'headers) - (flet ((hpush (name &optional proc) - (push (k-semi name proc) headers))) - (hpush 'head) - (when (at 'branch) - (hpush 'branch)) - (hpush 'access) - (hpush 'symbols - (lambda () - (mapcar (lambda (together) - (let ((two (split-string together ":"))) - (setcar two (intern (car two))) - (setcdr two (cadr two)) - two)) - (split-string - (buffer-substring-no-properties b e))))) - (hpush 'locks)) - (push `(strict . ,(when (at 'strict) - (search-forward ";") - t)) - headers) - (when (at 'comment) - (push (k-one@ 'comment) headers) - (search-forward ";")) - (when (at 'expand) - (push (k-one@ 'expand) headers) - (search-forward ";")) - (setq headers (nreverse headers)) - ;; rev headers - (sw) (setq context 'rev-headers) - (while (looking-at "[0-9]") - (push `(,(to-eol) - ,(k-semi 'date - (lambda () - (let ((ls (mapcar 'string-to-number - (split-string - (buffer-substring-no-properties - b e) - "\\.")))) - ;; Hack the year -- verified to be the - ;; same algorithm used in RCS 5.7. - (when (< (car ls) 100) - (setcar ls (+ 1900 (car ls)))) - (apply 'encode-time (nreverse ls))))) - ,@(mapcar 'k-semi '(author state)) - ,(k-semi 'branches - (lambda () - (split-string - (buffer-substring-no-properties b e)))) - ,(k-semi 'next)) - revs) - (sw)) - (setq revs (nreverse revs)) - ;; desc - (sw) (setq context 'desc - desc (k-one@ 'desc)) - ;; rev bodies - (let (acc - ;; Element of `revs' that initially holds only header info. - ;; "Pairwise combination" occurs when we add body info. - rev - ;; Components of the editing commands (aside from the actual - ;; text) that comprise the `text' printed representations - ;; (not including the "head" revision). - cmd start act - ;; Ascending (reversed) `@-holes' which the internal func - ;; `incg' pops to effect incremental gathering. - asc - ;; Function to extract text (for the `a' command), either - ;; `incg' or `buffer-substring-no-properties'. (This is - ;; for speed; strictly speaking, it is sufficient to use - ;; only the former since it behaves identically to the - ;; latter in the absense of "@@".) - sub) - (flet ((incg (beg end) (let ((b beg) (e end) @-holes) - (while (and asc (< (car asc) e)) - (push (pop asc) @-holes)) - ;; Self-deprecate when work is done. - ;; Folding many dimensions into one. - ;; Thanks B.Mandelbrot, for complex sum. - ;; O beauteous math! --the Unvexed Bum - (unless asc - (setq sub 'buffer-substring-no-properties)) - (gather)))) - (while (and (sw) - (not (eobp)) - (setq context (to-eol) - rev (or (assoc context revs) - (error "Rev `%s' has body but no head" - context)))) - (push (k-one@ 'log) (cdr rev)) - ;; For rev body `text' tags, delay translation slightly... - (push (k-one@ 'text t) (cdr rev)) - ;; ... until we decide which tag and value is appropriate to - ;; collect. For the "head" revision, compute the value of the - ;; `text' printed representation by simple `gather'. For all - ;; other revisions, replace the `text' tag+value with `:insn' - ;; plus value, always scanning in-place. - (if (string= context (cdr (assq 'head headers))) - (setcdr (cadr rev) (gather)) - (if @-holes - (setq asc (nreverse @-holes) - sub 'incg) - (setq sub 'buffer-substring-no-properties)) - (goto-char b) - (setq acc nil) - (while (< (point) e) - (forward-char 1) - (setq cmd (char-before) - start (read (current-buffer)) - act (read (current-buffer))) - (forward-char 1) - (push (case cmd - (?d - ;; `d' means "delete lines". - ;; For Emacs spirit, we use `k' for "kill". - `(,start k ,act)) - (?a - ;; `a' means "append after this line" but - ;; internally we normalize it so that START - ;; specifies the actual line for insert, thus - ;; requiring less hair in the realization algs. - ;; For Emacs spirit, we use `i' for "insert". - `(,(1+ start) i - ,(funcall sub (point) (progn (forward-line act) - (point))))) - (t (error "Bad command `%c' in `text' for rev `%s'" - cmd context))) - acc)) - (goto-char (1+ e)) - (setcar (cdr rev) (cons :insn acc))))))) - ;; rv - `((headers ,desc ,@headers) - (revisions ,@revs))))) - -(provide 'vc-rcs) - -;; arch-tag: 759b4916-5b0d-431d-b647-b185b8c652cf -;;; vc-rcs.el ends here diff -r 681cd08dc0f7 -r e93288477c43 lisp/vc-sccs.el --- a/lisp/vc-sccs.el Thu Jun 10 22:43:47 2010 +0000 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,485 +0,0 @@ -;;; vc-sccs.el --- support for SCCS version-control - -;; Copyright (C) 1992, 1993, 1994, 1995, 1996, 1997, 1998, 1999, 2000, -;; 2001, 2002, 2003, 2004, 2005, 2006, 2007, 2008, 2009, 2010 -;; Free Software Foundation, Inc. - -;; Author: FSF (see vc.el for full credits) -;; Maintainer: Andre Spiegel - -;; 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 3 of the License, 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. If not, see . - -;;; Commentary: - -;; Proper function of the SCCS diff commands requires the shellscript vcdiff -;; to be installed somewhere on Emacs's path for executables. -;; - -;;; Code: - -(eval-when-compile - (require 'vc)) - -;;; -;;; Customization options -;;; - -;; ;; Maybe a better solution is to not use "get" but "sccs get". -;; (defcustom vc-sccs-path -;; (let ((path ())) -;; (dolist (dir '("/usr/sccs" "/usr/lib/sccs" "/usr/libexec/sccs")) -;; (if (file-directory-p dir) -;; (push dir path))) -;; path) -;; "List of extra directories to search for SCCS commands." -;; :type '(repeat directory) -;; :group 'vc) - -(defcustom vc-sccs-register-switches nil - "Switches for registering a file in SCCS. -A string or list of strings passed to the checkin program by -\\[vc-register]. If nil, use the value of `vc-register-switches'. -If t, use no switches." - :type '(choice (const :tag "Unspecified" nil) - (const :tag "None" t) - (string :tag "Argument String") - (repeat :tag "Argument List" :value ("") string)) - :version "21.1" - :group 'vc) - -(defcustom vc-sccs-diff-switches nil - "String or list of strings specifying switches for SCCS diff under VC. -If nil, use the value of `vc-diff-switches'. If t, use no switches." - :type '(choice (const :tag "Unspecified" nil) - (const :tag "None" t) - (string :tag "Argument String") - (repeat :tag "Argument List" :value ("") string)) - :version "21.1" - :group 'vc) - -(defcustom vc-sccs-header (or (cdr (assoc 'SCCS vc-header-alist)) '("%W%")) - "Header keywords to be inserted by `vc-insert-headers'." - :type '(repeat string) - :group 'vc) - -;;;###autoload -(defcustom vc-sccs-master-templates - (purecopy '("%sSCCS/s.%s" "%ss.%s" vc-sccs-search-project-dir)) - "Where to look for SCCS master files. -For a description of possible values, see `vc-check-master-templates'." - :type '(choice (const :tag "Use standard SCCS file names" - ("%sSCCS/s.%s" "%ss.%s" vc-sccs-search-project-dir)) - (repeat :tag "User-specified" - (choice string - function))) - :version "21.1" - :group 'vc) - - -;;; -;;; Internal variables -;;; - -(defconst vc-sccs-name-assoc-file "VC-names") - - -;;; Properties of the backend - -(defun vc-sccs-revision-granularity () 'file) -(defun vc-sccs-checkout-model (files) 'locking) - -;;; -;;; State-querying functions -;;; - -;; The autoload cookie below places vc-sccs-registered directly into -;; loaddefs.el, so that vc-sccs.el does not need to be loaded for -;; every file that is visited. The definition is repeated below -;; so that Help and etags can find it. - -;;;###autoload (defun vc-sccs-registered(f) (vc-default-registered 'SCCS f)) -(defun vc-sccs-registered (f) (vc-default-registered 'SCCS f)) - -(defun vc-sccs-state (file) - "SCCS-specific function to compute the version control state." - (if (not (vc-sccs-registered file)) - 'unregistered - (with-temp-buffer - (if (vc-insert-file (vc-sccs-lock-file file)) - (let* ((locks (vc-sccs-parse-locks)) - (working-revision (vc-working-revision file)) - (locking-user (cdr (assoc working-revision locks)))) - (if (not locking-user) - (if (vc-workfile-unchanged-p file) - 'up-to-date - 'unlocked-changes) - (if (string= locking-user (vc-user-login-name file)) - 'edited - locking-user))) - 'up-to-date)))) - -(defun vc-sccs-state-heuristic (file) - "SCCS-specific state heuristic." - (if (not (vc-mistrust-permissions file)) - ;; This implementation assumes that any file which is under version - ;; control and has -rw-r--r-- is locked by its owner. This is true - ;; for both RCS and SCCS, which keep unlocked files at -r--r--r--. - ;; We have to be careful not to exclude files with execute bits on; - ;; scripts can be under version control too. Also, we must ignore the - ;; group-read and other-read bits, since paranoid users turn them off. - (let* ((attributes (file-attributes file 'string)) - (owner-name (nth 2 attributes)) - (permissions (nth 8 attributes))) - (if (string-match ".r-..-..-." permissions) - 'up-to-date - (if (string-match ".rw..-..-." permissions) - (if (file-ownership-preserved-p file) - 'edited - owner-name) - ;; Strange permissions. - ;; Fall through to real state computation. - (vc-sccs-state file)))) - (vc-sccs-state file))) - -(defun vc-sccs-dir-status (dir update-function) - ;; FIXME: this function should be rewritten, using `vc-expand-dirs' - ;; is not TRTD because it returns files from multiple backends. - ;; It should also return 'unregistered files. - - ;; Doing lots of individual VC-state calls is painful, but - ;; there is no better option in SCCS-land. - (let ((flist (vc-expand-dirs (list dir))) - (result nil)) - (dolist (file flist) - (let ((state (vc-state file)) - (frel (file-relative-name file))) - (when (and (eq (vc-backend file) 'SCCS) - (not (eq state 'up-to-date))) - (push (list frel state) result)))) - (funcall update-function result))) - -(defun vc-sccs-working-revision (file) - "SCCS-specific version of `vc-working-revision'." - (with-temp-buffer - ;; The working revision is always the latest revision number. - ;; To find this number, search the entire delta table, - ;; rather than just the first entry, because the - ;; first entry might be a deleted ("R") revision. - (vc-insert-file (vc-name file) "^\001e\n\001[^s]") - (vc-parse-buffer "^\001d D \\([^ ]+\\)" 1))) - -(defun vc-sccs-workfile-unchanged-p (file) - "SCCS-specific implementation of `vc-workfile-unchanged-p'." - (zerop (apply 'vc-do-command "*vc*" 1 "vcdiff" (vc-name file) - (list "--brief" "-q" - (concat "-r" (vc-working-revision file)))))) - - -;;; -;;; State-changing functions -;;; - -(defun vc-sccs-do-command (buffer okstatus command file-or-list &rest flags) - ;; (let ((load-path (append vc-sccs-path load-path))) - ;; (apply 'vc-do-command buffer okstatus command file-or-list flags)) - (apply 'vc-do-command (or buffer "*vc*") okstatus "sccs" file-or-list command flags)) - -(defun vc-sccs-create-repo () - "Create a new SCCS repository." - ;; SCCS is totally file-oriented, so all we have to do is make the directory - (make-directory "SCCS")) - -(defun vc-sccs-register (files &optional rev comment) - "Register FILES into the SCCS version-control system. -REV is the optional revision number for the file. COMMENT can be used -to provide an initial description of FILES. -Passes either `vc-sccs-register-switches' or `vc-register-switches' -to the SCCS command. - -Automatically retrieve a read-only version of the files with keywords -expanded if `vc-keep-workfiles' is non-nil, otherwise, delete the workfile." - (dolist (file files) - (let* ((dirname (or (file-name-directory file) "")) - (basename (file-name-nondirectory file)) - (project-file (vc-sccs-search-project-dir dirname basename))) - (let ((vc-name - (or project-file - (format (car vc-sccs-master-templates) dirname basename)))) - (apply 'vc-sccs-do-command nil 0 "admin" vc-name - (and rev (not (string= rev "")) (concat "-r" rev)) - "-fb" - (concat "-i" (file-relative-name file)) - (and comment (concat "-y" comment)) - (vc-switches 'SCCS 'register))) - (delete-file file) - (if vc-keep-workfiles - (vc-sccs-do-command nil 0 "get" (vc-name file)))))) - -(defun vc-sccs-responsible-p (file) - "Return non-nil if SCCS thinks it would be responsible for registering FILE." - ;; TODO: check for all the patterns in vc-sccs-master-templates - (or (file-directory-p (expand-file-name "SCCS" (file-name-directory file))) - (stringp (vc-sccs-search-project-dir (or (file-name-directory file) "") - (file-name-nondirectory file))))) - -(defun vc-sccs-checkin (files rev comment &optional extra-args-ignored) - "SCCS-specific version of `vc-backend-checkin'." - (dolist (file (vc-expand-dirs files)) - (apply 'vc-sccs-do-command nil 0 "delta" (vc-name file) - (if rev (concat "-r" rev)) - (concat "-y" comment) - (vc-switches 'SCCS 'checkin)) - (if vc-keep-workfiles - (vc-sccs-do-command nil 0 "get" (vc-name file))))) - -(defun vc-sccs-find-revision (file rev buffer) - (apply 'vc-sccs-do-command - buffer 0 "get" (vc-name file) - "-s" ;; suppress diagnostic output - "-p" - (and rev - (concat "-r" - (vc-sccs-lookup-triple file rev))) - (vc-switches 'SCCS 'checkout))) - -(defun vc-sccs-checkout (file &optional editable rev) - "Retrieve a copy of a saved revision of SCCS controlled FILE. -If FILE is a directory, all version-controlled files beneath are checked out. -EDITABLE non-nil means that the file should be writable and -locked. REV is the revision to check out." - (if (file-directory-p file) - (mapc 'vc-sccs-checkout (vc-expand-dirs (list file))) - (let ((file-buffer (get-file-buffer file)) - switches) - (message "Checking out %s..." file) - (save-excursion - ;; Change buffers to get local value of vc-checkout-switches. - (if file-buffer (set-buffer file-buffer)) - (setq switches (vc-switches 'SCCS 'checkout)) - ;; Save this buffer's default-directory - ;; and use save-excursion to make sure it is restored - ;; in the same buffer it was saved in. - (let ((default-directory default-directory)) - (save-excursion - ;; Adjust the default-directory so that the check-out creates - ;; the file in the right place. - (setq default-directory (file-name-directory file)) - - (and rev (or (string= rev "") - (not (stringp rev))) - (setq rev nil)) - (apply 'vc-sccs-do-command nil 0 "get" (vc-name file) - (if editable "-e") - (and rev (concat "-r" (vc-sccs-lookup-triple file rev))) - switches)))) - (message "Checking out %s...done" file)))) - -(defun vc-sccs-rollback (files) - "Roll back, undoing the most recent checkins of FILES. Directories -are expanded to all version-controlled subfiles." - (setq files (vc-expand-dirs files)) - (if (not files) - (error "SCCS backend doesn't support directory-level rollback")) - (dolist (file files) - (let ((discard (vc-working-revision file))) - (if (null (yes-or-no-p (format "Remove version %s from %s history? " - discard file))) - (error "Aborted")) - (message "Removing revision %s from %s..." discard file) - (vc-sccs-do-command nil 0 "rmdel" - (vc-name file) (concat "-r" discard)) - (vc-sccs-do-command nil 0 "get" (vc-name file) nil)))) - -(defun vc-sccs-revert (file &optional contents-done) - "Revert FILE to the version it was based on. If FILE is a directory, -revert all subfiles." - (if (file-directory-p file) - (mapc 'vc-sccs-revert (vc-expand-dirs (list file))) - (vc-sccs-do-command nil 0 "unget" (vc-name file)) - (vc-sccs-do-command nil 0 "get" (vc-name file)) - ;; Checking out explicit revisions is not supported under SCCS, yet. - ;; We always "revert" to the latest revision; therefore - ;; vc-working-revision is cleared here so that it gets recomputed. - (vc-file-setprop file 'vc-working-revision nil))) - -(defun vc-sccs-steal-lock (file &optional rev) - "Steal the lock on the current workfile for FILE and revision REV." - (if (file-directory-p file) - (mapc 'vc-sccs-steal-lock (vc-expand-dirs (list file))) - (vc-sccs-do-command nil 0 "unget" - (vc-name file) "-n" (if rev (concat "-r" rev))) - (vc-sccs-do-command nil 0 "get" - (vc-name file) "-g" (if rev (concat "-r" rev))))) - -(defun vc-sccs-modify-change-comment (files rev comment) - "Modify (actually, append to) the change comments for FILES on a specified REV." - (dolist (file (vc-expand-dirs files)) - (vc-sccs-do-command nil 0 "cdc" (vc-name file) - (concat "-y" comment) (concat "-r" rev)))) - - -;;; -;;; History functions -;;; - -(defun vc-sccs-print-log (files buffer &optional shortlog start-revision-ignored limit) - "Get change log associated with FILES." - (setq files (vc-expand-dirs files)) - (vc-sccs-do-command buffer 0 "prs" (mapcar 'vc-name files)) - (when limit 'limit-unsupported)) - -(defun vc-sccs-diff (files &optional oldvers newvers buffer) - "Get a difference report using SCCS between two filesets." - (setq files (vc-expand-dirs files)) - (setq oldvers (vc-sccs-lookup-triple (car files) oldvers)) - (setq newvers (vc-sccs-lookup-triple (car files) newvers)) - (apply 'vc-do-command (or buffer "*vc-diff*") - 1 "vcdiff" (mapcar 'vc-name (vc-expand-dirs files)) - (append (list "-q" - (and oldvers (concat "-r" oldvers)) - (and newvers (concat "-r" newvers))) - (vc-switches 'SCCS 'diff)))) - - -;;; -;;; Tag system. SCCS doesn't have tags, so we simulate them by maintaining -;;; our own set of name-to-revision mappings. -;;; - -(defun vc-sccs-create-tag (backend dir name branchp) - (when branchp - (error "SCCS backend %s does not support module branches" backend)) - (let ((result (vc-tag-precondition dir))) - (if (stringp result) - (error "File %s is not up-to-date" result) - (vc-file-tree-walk - dir - (lambda (f) - (vc-sccs-add-triple name f (vc-working-revision f))))))) - - -;;; -;;; Miscellaneous -;;; - -(defun vc-sccs-previous-revision (file rev) - (vc-call-backend 'RCS 'previous-revision file rev)) - -(defun vc-sccs-next-revision (file rev) - (vc-call-backend 'RCS 'next-revision file rev)) - -(defun vc-sccs-check-headers () - "Check if the current file has any headers in it." - (save-excursion - (goto-char (point-min)) - (re-search-forward "%[A-Z]%" nil t))) - -(defun vc-sccs-rename-file (old new) - ;; Move the master file (using vc-rcs-master-templates). - (vc-rename-master (vc-name old) new vc-sccs-master-templates) - ;; Update the tag file. - (with-current-buffer - (find-file-noselect - (expand-file-name vc-sccs-name-assoc-file - (file-name-directory (vc-name old)))) - (goto-char (point-min)) - ;; (replace-regexp (concat ":" (regexp-quote old) "$") (concat ":" new)) - (while (re-search-forward (concat ":" (regexp-quote old) "$") nil t) - (replace-match (concat ":" new) nil nil)) - (basic-save-buffer) - (kill-buffer (current-buffer)))) - -(defun vc-sccs-find-file-hook () - ;; If the file is locked by some other user, make - ;; the buffer read-only. Like this, even root - ;; cannot modify a file that someone else has locked. - (and (stringp (vc-state buffer-file-name 'SCCS)) - (setq buffer-read-only t))) - - -;;; -;;; Internal functions -;;; - -;; This function is wrapped with `progn' so that the autoload cookie -;; copies the whole function itself into loaddefs.el rather than just placing -;; a (autoload 'vc-sccs-search-project-dir "vc-sccs") which would not -;; help us avoid loading vc-sccs. -;;;###autoload -(progn (defun vc-sccs-search-project-dir (dirname basename) - "Return the name of a master file in the SCCS project directory. -Does not check whether the file exists but returns nil if it does not -find any project directory." - (let ((project-dir (getenv "PROJECTDIR")) dirs dir) - (when project-dir - (if (file-name-absolute-p project-dir) - (setq dirs '("SCCS" "")) - (setq dirs '("src/SCCS" "src" "source/SCCS" "source")) - (setq project-dir (expand-file-name (concat "~" project-dir)))) - (while (and (not dir) dirs) - (setq dir (expand-file-name (car dirs) project-dir)) - (unless (file-directory-p dir) - (setq dir nil) - (setq dirs (cdr dirs)))) - (and dir (expand-file-name (concat "s." basename) dir)))))) - -(defun vc-sccs-lock-file (file) - "Generate lock file name corresponding to FILE." - (let ((master (vc-name file))) - (and - master - (string-match "\\(.*/\\)\\(s\\.\\)\\(.*\\)" master) - (replace-match "p." t t master 2)))) - -(defun vc-sccs-parse-locks () - "Parse SCCS locks in current buffer. -The result is a list of the form ((REVISION . USER) (REVISION . USER) ...)." - (let (master-locks) - (goto-char (point-min)) - (while (re-search-forward "^\\([0-9.]+\\) [0-9.]+ \\([^ ]+\\) .*\n?" - nil t) - (setq master-locks - (cons (cons (match-string 1) (match-string 2)) master-locks))) - ;; FIXME: is it really necessary to reverse ? - (nreverse master-locks))) - -(defun vc-sccs-add-triple (name file rev) - (with-current-buffer - (find-file-noselect - (expand-file-name vc-sccs-name-assoc-file - (file-name-directory (vc-name file)))) - (goto-char (point-max)) - (insert name "\t:\t" file "\t" rev "\n") - (basic-save-buffer) - (kill-buffer (current-buffer)))) - -(defun vc-sccs-lookup-triple (file name) - "Return the numeric revision corresponding to a named tag of FILE. -If NAME is nil or a revision number string it's just passed through." - (if (or (null name) - (let ((firstchar (aref name 0))) - (and (>= firstchar ?0) (<= firstchar ?9)))) - name - (with-temp-buffer - (vc-insert-file - (expand-file-name vc-sccs-name-assoc-file - (file-name-directory (vc-name file)))) - (vc-parse-buffer (concat name "\t:\t" file "\t\\(.+\\)") 1)))) - -(provide 'vc-sccs) - -;; arch-tag: d751dee3-d7b3-47e1-95e3-7ae98c052041 -;;; vc-sccs.el ends here diff -r 681cd08dc0f7 -r e93288477c43 lisp/vc-svn.el --- a/lisp/vc-svn.el Thu Jun 10 22:43:47 2010 +0000 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,747 +0,0 @@ -;;; vc-svn.el --- non-resident support for Subversion version-control - -;; Copyright (C) 2003, 2004, 2005, 2006, 2007, 2008, 2009, 2010 -;; Free Software Foundation, Inc. - -;; Author: FSF (see vc.el for full credits) -;; Maintainer: Stefan Monnier - -;; 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 3 of the License, 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. If not, see . - -;;; Commentary: - -;; Sync'd with Subversion's vc-svn.el as of revision 5801. but this version -;; has been extensively modified since to handle filesets. - -;;; Code: - -(eval-when-compile - (require 'vc)) - -;; Clear up the cache to force vc-call to check again and discover -;; new functions when we reload this file. -(put 'SVN 'vc-functions nil) - -;;; -;;; Customization options -;;; - -;; FIXME there is also svnadmin. -(defcustom vc-svn-program "svn" - "Name of the SVN executable." - :type 'string - :group 'vc) - -(defcustom vc-svn-global-switches nil - "Global switches to pass to any SVN command." - :type '(choice (const :tag "None" nil) - (string :tag "Argument String") - (repeat :tag "Argument List" - :value ("") - string)) - :version "22.1" - :group 'vc) - -(defcustom vc-svn-register-switches nil - "Switches for registering a file into SVN. -A string or list of strings passed to the checkin program by -\\[vc-register]. If nil, use the value of `vc-register-switches'. -If t, use no switches." - :type '(choice (const :tag "Unspecified" nil) - (const :tag "None" t) - (string :tag "Argument String") - (repeat :tag "Argument List" :value ("") string)) - :version "22.1" - :group 'vc) - -(defcustom vc-svn-diff-switches - t ;`svn' doesn't support common args like -c or -b. - "String or list of strings specifying extra switches for svn diff under VC. -If nil, use the value of `vc-diff-switches' (or `diff-switches'), -together with \"-x --diff-cmd=diff\" (since svn diff does not -support the default \"-c\" value of `diff-switches'). If you -want to force an empty list of arguments, use t." - :type '(choice (const :tag "Unspecified" nil) - (const :tag "None" t) - (string :tag "Argument String") - (repeat :tag "Argument List" - :value ("") - string)) - :version "22.1" - :group 'vc) - -(defcustom vc-svn-header (or (cdr (assoc 'SVN vc-header-alist)) '("\$Id\$")) - "Header keywords to be inserted by `vc-insert-headers'." - :version "22.1" - :type '(repeat string) - :group 'vc) - -;; We want to autoload it for use by the autoloaded version of -;; vc-svn-registered, but we want the value to be compiled at startup, not -;; at dump time. -;; ;;;###autoload -(defconst vc-svn-admin-directory - (cond ((and (memq system-type '(cygwin windows-nt ms-dos)) - (getenv "SVN_ASP_DOT_NET_HACK")) - "_svn") - (t ".svn")) - "The name of the \".svn\" subdirectory or its equivalent.") - -;;; Properties of the backend - -(defun vc-svn-revision-granularity () 'repository) -(defun vc-svn-checkout-model (files) 'implicit) - -;;; -;;; State-querying functions -;;; - -;;; vc-svn-admin-directory is generally not defined when the -;;; autoloaded function is called. - -;;;###autoload (defun vc-svn-registered (f) -;;;###autoload (let ((admin-dir (cond ((and (eq system-type 'windows-nt) -;;;###autoload (getenv "SVN_ASP_DOT_NET_HACK")) -;;;###autoload "_svn") -;;;###autoload (t ".svn")))) -;;;###autoload (when (file-readable-p (expand-file-name -;;;###autoload (concat admin-dir "/entries") -;;;###autoload (file-name-directory f))) -;;;###autoload (load "vc-svn") -;;;###autoload (vc-svn-registered f)))) - -(defun vc-svn-registered (file) - "Check if FILE is SVN registered." - (when (file-readable-p (expand-file-name (concat vc-svn-admin-directory - "/entries") - (file-name-directory file))) - (with-temp-buffer - (cd (file-name-directory file)) - (let* (process-file-side-effects - (status - (condition-case nil - ;; Ignore all errors. - (vc-svn-command t t file "status" "-v") - ;; Some problem happened. E.g. We can't find an `svn' - ;; executable. We used to only catch `file-error' but when - ;; the process is run on a remote host via Tramp, the error - ;; is only reported via the exit status which is turned into - ;; an `error' by vc-do-command. - (error nil)))) - (when (eq 0 status) - (let ((parsed (vc-svn-parse-status file))) - (and parsed (not (memq parsed '(ignored unregistered)))))))))) - -(defun vc-svn-state (file &optional localp) - "SVN-specific version of `vc-state'." - (let (process-file-side-effects) - (setq localp (or localp (vc-stay-local-p file 'SVN))) - (with-temp-buffer - (cd (file-name-directory file)) - (vc-svn-command t 0 file "status" (if localp "-v" "-u")) - (vc-svn-parse-status file)))) - -(defun vc-svn-state-heuristic (file) - "SVN-specific state heuristic." - (vc-svn-state file 'local)) - -;; FIXME it would be better not to have the "remote" argument, -;; but to distinguish the two output formats based on content. -(defun vc-svn-after-dir-status (callback &optional remote) - (let ((state-map '((?A . added) - (?C . conflict) - (?I . ignored) - (?M . edited) - (?D . removed) - (?R . removed) - (?? . unregistered) - ;; This is what vc-svn-parse-status does. - (?~ . edited))) - (re (if remote "^\\(.\\)...... \\([ *]\\) +\\(?:[-0-9]+\\)? \\(.*\\)$" - ;; Subexp 2 is a dummy in this case, so the numbers match. - "^\\(.\\)....\\(.\\) \\(.*\\)$")) - result) - (goto-char (point-min)) - (while (re-search-forward re nil t) - (let ((state (cdr (assq (aref (match-string 1) 0) state-map))) - (filename (match-string 3))) - (and remote (string-equal (match-string 2) "*") - ;; FIXME are there other possible combinations? - (cond ((eq state 'edited) (setq state 'needs-merge)) - ((not state) (setq state 'needs-update)))) - (when (and state (not (string= "." filename))) - (setq result (cons (list filename state) result))))) - (funcall callback result))) - -(defun vc-svn-dir-status (dir callback) - "Run 'svn status' for DIR and update BUFFER via CALLBACK. -CALLBACK is called as (CALLBACK RESULT BUFFER), where -RESULT is a list of conses (FILE . STATE) for directory DIR." - ;; FIXME should this rather be all the files in dir? - ;; FIXME: the vc-stay-local-p logic below is disabled, it ends up - ;; calling synchronously (vc-svn-registered DIR) => calling svn status -v DIR - ;; which is VERY SLOW for big trees and it makes emacs - ;; completely unresponsive during that time. - (let* ((local (and nil (vc-stay-local-p dir 'SVN))) - (remote (or t (not local) (eq local 'only-file)))) - (vc-svn-command (current-buffer) 'async nil "status" - (if remote "-u")) - (vc-exec-after - `(vc-svn-after-dir-status (quote ,callback) ,remote)))) - -(defun vc-svn-dir-status-files (dir files default-state callback) - (apply 'vc-svn-command (current-buffer) 'async nil "status" files) - (vc-exec-after - `(vc-svn-after-dir-status (quote ,callback)))) - -(defun vc-svn-dir-extra-headers (dir) - "Generate extra status headers for a Subversion working copy." - (let (process-file-side-effects) - (vc-svn-command "*vc*" 0 nil "info")) - (let ((repo - (save-excursion - (and (progn - (set-buffer "*vc*") - (goto-char (point-min)) - (re-search-forward "Repository Root: *\\(.*\\)" nil t)) - (match-string 1))))) - (concat - (cond (repo - (concat - (propertize "Repository : " 'face 'font-lock-type-face) - (propertize repo 'face 'font-lock-variable-name-face))) - (t ""))))) - -(defun vc-svn-working-revision (file) - "SVN-specific version of `vc-working-revision'." - ;; There is no need to consult RCS headers under SVN, because we - ;; get the workfile version for free when we recognize that a file - ;; is registered in SVN. - (vc-svn-registered file) - (vc-file-getprop file 'vc-working-revision)) - -;; vc-svn-mode-line-string doesn't exist because the default implementation -;; works just fine. - -(defun vc-svn-previous-revision (file rev) - (let ((newrev (1- (string-to-number rev)))) - (when (< 0 newrev) - (number-to-string newrev)))) - -(defun vc-svn-next-revision (file rev) - (let ((newrev (1+ (string-to-number rev)))) - ;; The "working revision" is an uneasy conceptual fit under Subversion; - ;; we use it as the upper bound until a better idea comes along. If the - ;; workfile version W coincides with the tree's latest revision R, then - ;; this check prevents a "no such revision: R+1" error. Otherwise, it - ;; inhibits showing of W+1 through R, which could be considered anywhere - ;; from gracious to impolite. - (unless (< (string-to-number (vc-file-getprop file 'vc-working-revision)) - newrev) - (number-to-string newrev)))) - - -;;; -;;; State-changing functions -;;; - -(defun vc-svn-create-repo () - "Create a new SVN repository." - (vc-do-command "*vc*" 0 "svnadmin" '("create" "SVN")) - (vc-do-command "*vc*" 0 vc-svn-program '(".") - "checkout" (concat "file://" default-directory "SVN"))) - -(defun vc-svn-register (files &optional rev comment) - "Register FILES into the SVN version-control system. -The COMMENT argument is ignored This does an add but not a commit. -Passes either `vc-svn-register-switches' or `vc-register-switches' -to the SVN command." - (apply 'vc-svn-command nil 0 files "add" (vc-switches 'SVN 'register))) - -(defun vc-svn-responsible-p (file) - "Return non-nil if SVN thinks it is responsible for FILE." - (file-directory-p (expand-file-name vc-svn-admin-directory - (if (file-directory-p file) - file - (file-name-directory file))))) - -(defalias 'vc-svn-could-register 'vc-svn-responsible-p - "Return non-nil if FILE could be registered in SVN. -This is only possible if SVN is responsible for FILE's directory.") - -(defun vc-svn-checkin (files rev comment &optional extra-args-ignored) - "SVN-specific version of `vc-backend-checkin'." - (if rev (error "Committing to a specific revision is unsupported in SVN")) - (let ((status (apply - 'vc-svn-command nil 1 files "ci" - (nconc (list "-m" comment) (vc-switches 'SVN 'checkin))))) - (set-buffer "*vc*") - (goto-char (point-min)) - (unless (equal status 0) - ;; Check checkin problem. - (cond - ((search-forward "Transaction is out of date" nil t) - (mapc (lambda (file) (vc-file-setprop file 'vc-state 'needs-merge)) - files) - (error (substitute-command-keys - (concat "Up-to-date check failed: " - "type \\[vc-next-action] to merge in changes")))) - (t - (pop-to-buffer (current-buffer)) - (goto-char (point-min)) - (shrink-window-if-larger-than-buffer) - (error "Check-in failed")))) - ;; Update file properties - ;; (vc-file-setprop - ;; file 'vc-working-revision - ;; (vc-parse-buffer "^\\(new\\|initial\\) revision: \\([0-9.]+\\)" 2)) - )) - -(defun vc-svn-find-revision (file rev buffer) - "SVN-specific retrieval of a specified version into a buffer." - (let (process-file-side-effects) - (apply 'vc-svn-command - buffer 0 file - "cat" - (and rev (not (string= rev "")) - (concat "-r" rev)) - (vc-switches 'SVN 'checkout)))) - -(defun vc-svn-checkout (file &optional editable rev) - (message "Checking out %s..." file) - (with-current-buffer (or (get-file-buffer file) (current-buffer)) - (vc-svn-update file editable rev (vc-switches 'SVN 'checkout))) - (vc-mode-line file 'SVN) - (message "Checking out %s...done" file)) - -(defun vc-svn-update (file editable rev switches) - (if (and (file-exists-p file) (not rev)) - ;; If no revision was specified, there's nothing to do. - nil - ;; Check out a particular version (or recreate the file). - (vc-file-setprop file 'vc-working-revision nil) - (apply 'vc-svn-command nil 0 file - "--non-interactive" ; bug#4280 - "update" - (cond - ((null rev) "-rBASE") - ((or (eq rev t) (equal rev "")) nil) - (t (concat "-r" rev))) - switches))) - -(defun vc-svn-delete-file (file) - (vc-svn-command nil 0 file "remove")) - -(defun vc-svn-rename-file (old new) - (vc-svn-command nil 0 new "move" (file-relative-name old))) - -(defun vc-svn-revert (file &optional contents-done) - "Revert FILE to the version it was based on." - (unless contents-done - (vc-svn-command nil 0 file "revert"))) - -(defun vc-svn-merge (file first-version &optional second-version) - "Merge changes into current working copy of FILE. -The changes are between FIRST-VERSION and SECOND-VERSION." - (vc-svn-command nil 0 file - "merge" - "-r" (if second-version - (concat first-version ":" second-version) - first-version)) - (vc-file-setprop file 'vc-state 'edited) - (with-current-buffer (get-buffer "*vc*") - (goto-char (point-min)) - (if (looking-at "C ") - 1 ; signal conflict - 0))) ; signal success - -(defun vc-svn-merge-news (file) - "Merge in any new changes made to FILE." - (message "Merging changes into %s..." file) - ;; (vc-file-setprop file 'vc-working-revision nil) - (vc-file-setprop file 'vc-checkout-time 0) - (vc-svn-command nil 0 file "update") - ;; Analyze the merge result reported by SVN, and set - ;; file properties accordingly. - (with-current-buffer (get-buffer "*vc*") - (goto-char (point-min)) - ;; get new working revision - (if (re-search-forward - "^\\(Updated to\\|At\\) revision \\([0-9]+\\)" nil t) - (vc-file-setprop file 'vc-working-revision (match-string 2)) - (vc-file-setprop file 'vc-working-revision nil)) - ;; get file status - (goto-char (point-min)) - (prog1 - (if (looking-at "At revision") - 0 ;; there were no news; indicate success - (if (re-search-forward - ;; Newer SVN clients have 3 columns of chars (one for the - ;; file's contents, then second for its properties, and the - ;; third for lock-grabbing info), before the 2 spaces. - ;; We also used to match the filename in column 0 without any - ;; meta-info before it, but I believe this can never happen. - (concat "^\\(\\([ACGDU]\\)\\(.[B ]\\)? \\)" - (regexp-quote (file-name-nondirectory file))) - nil t) - (cond - ;; Merge successful, we are in sync with repository now - ((string= (match-string 2) "U") - (vc-file-setprop file 'vc-state 'up-to-date) - (vc-file-setprop file 'vc-checkout-time - (nth 5 (file-attributes file))) - 0);; indicate success to the caller - ;; Merge successful, but our own changes are still in the file - ((string= (match-string 2) "G") - (vc-file-setprop file 'vc-state 'edited) - 0);; indicate success to the caller - ;; Conflicts detected! - (t - (vc-file-setprop file 'vc-state 'edited) - 1);; signal the error to the caller - ) - (pop-to-buffer "*vc*") - (error "Couldn't analyze svn update result"))) - (message "Merging changes into %s...done" file)))) - -(defun vc-svn-modify-change-comment (files rev comment) - "Modify the change comments for a specified REV. -You must have ssh access to the repository host, and the directory Emacs -uses locally for temp files must also be writable by you on that host. -This is only supported if the repository access method is either file:// -or svn+ssh://." - (let (tempfile host remotefile directory fileurl-p) - (with-temp-buffer - (vc-do-command (current-buffer) 0 vc-svn-program nil "info") - (goto-char (point-min)) - (unless (re-search-forward "Repository Root: \\(file://\\(/.*\\)\\)\\|\\(svn\\+ssh://\\([^/]+\\)\\(/.*\\)\\)" nil t) - (error "Repository information is unavailable")) - (if (match-string 1) - (progn - (setq fileurl-p t) - (setq directory (match-string 2))) - (setq host (match-string 4)) - (setq directory (match-string 5)) - (setq remotefile (concat host ":" tempfile)))) - (with-temp-file (setq tempfile (make-temp-file user-mail-address)) - (insert comment)) - (if fileurl-p - ;; Repository Root is a local file. - (progn - (unless (vc-do-command - "*vc*" 0 "svnadmin" nil - "setlog" "--bypass-hooks" directory - "-r" rev (format "%s" tempfile)) - (error "Log edit failed")) - (delete-file tempfile)) - - ;; Remote repository, using svn+ssh. - (unless (vc-do-command "*vc*" 0 "scp" nil "-q" tempfile remotefile) - (error "Copy of comment to %s failed" remotefile)) - (unless (vc-do-command - "*vc*" 0 "ssh" nil "-q" host - (format "svnadmin setlog --bypass-hooks %s -r %s %s; rm %s" - directory rev tempfile tempfile)) - (error "Log edit failed"))))) - -;;; -;;; History functions -;;; - -(defvar log-view-per-file-logs) - -(define-derived-mode vc-svn-log-view-mode log-view-mode "SVN-Log-View" - (require 'add-log) - (set (make-local-variable 'log-view-per-file-logs) nil)) - -(defun vc-svn-print-log (files buffer &optional shortlog start-revision limit) - "Get change log(s) associated with FILES." - (save-current-buffer - (vc-setup-buffer buffer) - (let ((inhibit-read-only t)) - (goto-char (point-min)) - (if files - (dolist (file files) - (insert "Working file: " file "\n") - (apply - 'vc-svn-command - buffer - 'async - ;; (if (and (= (length files) 1) (vc-stay-local-p file 'SVN)) 'async 0) - (list file) - "log" - (append - (list - (if start-revision - (format "-r%s" start-revision) - ;; By default Subversion only shows the log up to the - ;; working revision, whereas we also want the log of the - ;; subsequent commits. At least that's what the - ;; vc-cvs.el code does. - "-rHEAD:0")) - (when limit (list "--limit" (format "%s" limit)))))) - ;; Dump log for the entire directory. - (apply 'vc-svn-command buffer 0 nil "log" - (append - (list - (if start-revision (format "-r%s" start-revision) "-rHEAD:0")) - (when limit (list "--limit" (format "%s" limit))))))))) - -(defun vc-svn-diff (files &optional oldvers newvers buffer) - "Get a difference report using SVN between two revisions of fileset FILES." - (and oldvers - (not newvers) - files - (catch 'no - (dolist (f files) - (or (equal oldvers (vc-working-revision f)) - (throw 'no nil))) - t) - ;; Use nil rather than the current revision because svn handles - ;; it better (i.e. locally). Note that if _any_ of the files - ;; has a different revision, we fetch the lot, which is - ;; obviously sub-optimal. - (setq oldvers nil)) - (let* ((switches - (if vc-svn-diff-switches - (vc-switches 'SVN 'diff) - (list "--diff-cmd=diff" "-x" - (mapconcat 'identity (vc-switches nil 'diff) " ")))) - (async (and (not vc-disable-async-diff) - (vc-stay-local-p files 'SVN) - (or oldvers newvers)))) ; Svn diffs those locally. - (apply 'vc-svn-command buffer - (if async 'async 0) - files "diff" - (append - switches - (when oldvers - (list "-r" (if newvers (concat oldvers ":" newvers) - oldvers))))) - (if async 1 ; async diff => pessimistic assumption - ;; For some reason `svn diff' does not return a useful - ;; status w.r.t whether the diff was empty or not. - (buffer-size (get-buffer buffer))))) - -;;; -;;; Tag system -;;; - -(defun vc-svn-create-tag (dir name branchp) - "Assign to DIR's current revision a given NAME. -If BRANCHP is non-nil, the name is created as a branch (and the current -workspace is immediately moved to that new branch). -NAME is assumed to be a URL." - (vc-svn-command nil 0 dir "copy" name) - (when branchp (vc-svn-retrieve-tag dir name nil))) - -(defun vc-svn-retrieve-tag (dir name update) - "Retrieve a tag at and below DIR. -NAME is the name of the tag; if it is empty, do a `svn update'. -If UPDATE is non-nil, then update (resynch) any affected buffers. -NAME is assumed to be a URL." - (vc-svn-command nil 0 dir "switch" name) - ;; FIXME: parse the output and obey `update'. - ) - -;;; -;;; Miscellaneous -;;; - -;; Subversion makes backups for us, so don't bother. -;; (defun vc-svn-make-version-backups-p (file) -;; "Return non-nil if version backups should be made for FILE." -;; (vc-stay-local-p file 'SVN)) - -(defun vc-svn-check-headers () - "Check if the current file has any headers in it." - (save-excursion - (goto-char (point-min)) - (re-search-forward "\\$[A-Za-z\300-\326\330-\366\370-\377]+\ -\\(: [\t -#%-\176\240-\377]*\\)?\\$" nil t))) - - -;;; -;;; Internal functions -;;; - -(defun vc-svn-command (buffer okstatus file-or-list &rest flags) - "A wrapper around `vc-do-command' for use in vc-svn.el. -The difference to vc-do-command is that this function always invokes `svn', -and that it passes `vc-svn-global-switches' to it before FLAGS." - (apply 'vc-do-command (or buffer "*vc*") okstatus vc-svn-program file-or-list - (if (stringp vc-svn-global-switches) - (cons vc-svn-global-switches flags) - (append vc-svn-global-switches - flags)))) - -(defun vc-svn-repository-hostname (dirname) - (with-temp-buffer - (let ((coding-system-for-read - (or file-name-coding-system - default-file-name-coding-system))) - (vc-insert-file (expand-file-name (concat vc-svn-admin-directory - "/entries") - dirname))) - (goto-char (point-min)) - (when (re-search-forward - ;; Old `svn' used name="svn:this_dir", newer use just name="". - (concat "name=\"\\(?:svn:this_dir\\)?\"[\n\t ]*" - "\\(?:[-a-z]+=\"[^\"]*\"[\n\t ]*\\)*?" - "url=\"\\(?1:[^\"]+\\)\"" - ;; Yet newer ones don't use XML any more. - "\\|^\ndir\n[0-9]+\n\\(?1:.*\\)") nil t) - ;; This is not a hostname but a URL. This may actually be considered - ;; as a feature since it allows vc-svn-stay-local to specify different - ;; behavior for different modules on the same server. - (match-string 1)))) - -(defun vc-svn-resolve-when-done () - "Call \"svn resolved\" if the conflict markers have been removed." - (save-excursion - (goto-char (point-min)) - (unless (re-search-forward "^<<<<<<< " nil t) - (vc-svn-command nil 0 buffer-file-name "resolved") - ;; Remove the hook so that it is not called multiple times. - (remove-hook 'after-save-hook 'vc-svn-resolve-when-done t)))) - -;; Inspired by vc-arch-find-file-hook. -(defun vc-svn-find-file-hook () - (when (eq ?C (vc-file-getprop buffer-file-name 'vc-svn-status)) - ;; If the file is marked as "conflicted", then we should try and call - ;; "svn resolved" when applicable. - (if (save-excursion - (goto-char (point-min)) - (re-search-forward "^<<<<<<< " nil t)) - ;; There are conflict markers. - (progn - (smerge-start-session) - (add-hook 'after-save-hook 'vc-svn-resolve-when-done nil t)) - ;; There are no conflict markers. This is problematic: maybe it means - ;; the conflict has been resolved and we should immediately call "svn - ;; resolved", or it means that the file's type does not allow Svn to - ;; use conflict markers in which case we don't really know what to do. - ;; So let's just punt for now. - nil) - (message "There are unresolved conflicts in this file"))) - -(defun vc-svn-parse-status (&optional filename) - "Parse output of \"svn status\" command in the current buffer. -Set file properties accordingly. Unless FILENAME is non-nil, parse only -information about FILENAME and return its status." - (let (file status) - (goto-char (point-min)) - (while (re-search-forward - ;; Ignore the files with status X. - "^\\(?:\\?\\|[ ACDGIMR!~][ MC][ L][ +][ S]..\\([ *]\\) +\\([-0-9]+\\) +\\([0-9?]+\\) +\\([^ ]+\\)\\) +" nil t) - ;; If the username contains spaces, the output format is ambiguous, - ;; so don't trust the output's filename unless we have to. - (setq file (or filename - (expand-file-name - (buffer-substring (point) (line-end-position))))) - (setq status (char-after (line-beginning-position))) - (if (eq status ??) - (vc-file-setprop file 'vc-state 'unregistered) - ;; Use the last-modified revision, so that searching in vc-print-log - ;; output works. - (vc-file-setprop file 'vc-working-revision (match-string 3)) - ;; Remember Svn's own status. - (vc-file-setprop file 'vc-svn-status status) - (vc-file-setprop - file 'vc-state - (cond - ((eq status ?\ ) - (if (eq (char-after (match-beginning 1)) ?*) - 'needs-update - (vc-file-setprop file 'vc-checkout-time - (nth 5 (file-attributes file))) - 'up-to-date)) - ((eq status ?A) - ;; If the file was actually copied, (match-string 2) is "-". - (vc-file-setprop file 'vc-working-revision "0") - (vc-file-setprop file 'vc-checkout-time 0) - 'added) - ((eq status ?C) - (vc-file-setprop file 'vc-state 'conflict)) - ((eq status '?M) - (if (eq (char-after (match-beginning 1)) ?*) - 'needs-merge - 'edited)) - ((eq status ?I) - (vc-file-setprop file 'vc-state 'ignored)) - ((memq status '(?D ?R)) - (vc-file-setprop file 'vc-state 'removed)) - (t 'edited))))) - (when filename (vc-file-getprop filename 'vc-state)))) - -(defun vc-svn-valid-symbolic-tag-name-p (tag) - "Return non-nil if TAG is a valid symbolic tag name." - ;; According to the SVN manual, a valid symbolic tag must start with - ;; an uppercase or lowercase letter and can contain uppercase and - ;; lowercase letters, digits, `-', and `_'. - (and (string-match "^[a-zA-Z]" tag) - (not (string-match "[^a-z0-9A-Z-_]" tag)))) - -(defun vc-svn-valid-revision-number-p (tag) - "Return non-nil if TAG is a valid revision number." - (and (string-match "^[0-9]" tag) - (not (string-match "[^0-9]" tag)))) - -;; Support for `svn annotate' - -(defun vc-svn-annotate-command (file buf &optional rev) - (vc-svn-command buf 'async file "annotate" (if rev (concat "-r" rev)))) - -(defun vc-svn-annotate-time-of-rev (rev) - ;; Arbitrarily assume 10 commmits per day. - (/ (string-to-number rev) 10.0)) - -(defvar vc-annotate-parent-rev) - -(defun vc-svn-annotate-current-time () - (vc-svn-annotate-time-of-rev vc-annotate-parent-rev)) - -(defconst vc-svn-annotate-re "[ \t]*\\([0-9]+\\)[ \t]+[^\t ]+ ") - -(defun vc-svn-annotate-time () - (when (looking-at vc-svn-annotate-re) - (goto-char (match-end 0)) - (vc-svn-annotate-time-of-rev (match-string 1)))) - -(defun vc-svn-annotate-extract-revision-at-line () - (save-excursion - (beginning-of-line) - (if (looking-at vc-svn-annotate-re) (match-string 1)))) - -(defun vc-svn-revision-table (files) - (let ((vc-svn-revisions '())) - (with-current-buffer "*vc*" - (vc-svn-command nil 0 files "log" "-q") - (goto-char (point-min)) - (forward-line) - (let ((start (point-min)) - (loglines (buffer-substring-no-properties (point-min) - (point-max)))) - (while (string-match "^r\\([0-9]+\\) " loglines) - (push (match-string 1 loglines) vc-svn-revisions) - (setq start (+ start (match-end 0))) - (setq loglines (buffer-substring-no-properties start (point-max))))) - vc-svn-revisions))) - -(provide 'vc-svn) - -;; arch-tag: 02f10c68-2b4d-453a-90fc-1eee6cfb268d -;;; vc-svn.el ends here diff -r 681cd08dc0f7 -r e93288477c43 lisp/vc.el --- a/lisp/vc.el Thu Jun 10 22:43:47 2010 +0000 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,2702 +0,0 @@ -;;; vc.el --- drive a version-control system from within Emacs - -;; Copyright (C) 1992, 1993, 1994, 1995, 1996, 1997, 1998, 2000, -;; 2001, 2002, 2003, 2004, 2005, 2006, 2007, 2008, 2009, 2010 -;; Free Software Foundation, Inc. - -;; Author: FSF (see below for full credits) -;; Maintainer: Andre Spiegel -;; Keywords: tools - -;; 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 3 of the License, 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. If not, see . - -;;; Credits: - -;; VC was initially designed and implemented by Eric S. Raymond -;; in 1992. Over the years, many other people have -;; contributed substantial amounts of work to VC. These include: -;; -;; Per Cederqvist -;; Paul Eggert -;; Sebastian Kremer -;; Martin Lorentzson -;; Dave Love -;; Stefan Monnier -;; Thien-Thi Nguyen -;; Dan Nicolaescu -;; J.D. Smith -;; Andre Spiegel -;; Richard Stallman -;; -;; In July 2007 ESR returned and redesigned the mode to cope better -;; with modern version-control systems that do commits by fileset -;; rather than per individual file. -;; -;; If you maintain a client of the mode or customize it in your .emacs, -;; note that some backend functions which formerly took single file arguments -;; now take a list of files. These include: register, checkin, print-log, -;; rollback, and diff. - -;;; Commentary: - -;; This mode is fully documented in the Emacs user's manual. -;; -;; Supported version-control systems presently include CVS, RCS, GNU -;; Arch, Subversion, Bzr, Git, Mercurial, Monotone and SCCS -;; (or its free replacement, CSSC). -;; -;; If your site uses the ChangeLog convention supported by Emacs, the -;; function `log-edit-comment-to-change-log' could prove a useful checkin hook, -;; although you might prefer to use C-c C-a (i.e. `log-edit-insert-changelog') -;; from the commit buffer instead or to set `log-edit-setup-invert'. -;; -;; When using SCCS, RCS, CVS: be careful not to do repo surgery, or -;; operations like registrations and deletions and renames, outside VC -;; while VC is running. The support for these systems was designed -;; when disks were much slower, and the code maintains a lot of -;; internal state in order to reduce expensive operations to a -;; minimum. Thus, if you mess with the repo while VC's back is turned, -;; VC may get seriously confused. -;; -;; When using Subversion or a later system, anything you do outside VC -;; *through the VCS tools* should safely interlock with VC -;; operations. Under these VC does little state caching, because local -;; operations are assumed to be fast. The dividing line is -;; -;; ADDING SUPPORT FOR OTHER BACKENDS -;; -;; VC can use arbitrary version control systems as a backend. To add -;; support for a new backend named SYS, write a library vc-sys.el that -;; contains functions of the form `vc-sys-...' (note that SYS is in lower -;; case for the function and library names). VC will use that library if -;; you put the symbol SYS somewhere into the list of -;; `vc-handled-backends'. Then, for example, if `vc-sys-registered' -;; returns non-nil for a file, all SYS-specific versions of VC commands -;; will be available for that file. -;; -;; VC keeps some per-file information in the form of properties (see -;; vc-file-set/getprop in vc-hooks.el). The backend-specific functions -;; do not generally need to be aware of these properties. For example, -;; `vc-sys-working-revision' should compute the working revision and -;; return it; it should not look it up in the property, and it needn't -;; store it there either. However, if a backend-specific function does -;; store a value in a property, that value takes precedence over any -;; value that the generic code might want to set (check for uses of -;; the macro `with-vc-properties' in vc.el). -;; -;; In the list of functions below, each identifier needs to be prepended -;; with `vc-sys-'. Some of the functions are mandatory (marked with a -;; `*'), others are optional (`-'). -;; -;; BACKEND PROPERTIES -;; -;; * revision-granularity -;; -;; Takes no arguments. Returns either 'file or 'repository. Backends -;; that return 'file have per-file revision numbering; backends -;; that return 'repository have per-repository revision numbering, -;; so a revision level implicitly identifies a changeset -;; -;; STATE-QUERYING FUNCTIONS -;; -;; * registered (file) -;; -;; Return non-nil if FILE is registered in this backend. Both this -;; function as well as `state' should be careful to fail gracefully -;; in the event that the backend executable is absent. It is -;; preferable that this function's body is autoloaded, that way only -;; calling vc-registered does not cause the backend to be loaded -;; (all the vc-FOO-registered functions are called to try to find -;; the controlling backend for FILE. -;; -;; * state (file) -;; -;; Return the current version control state of FILE. For a list of -;; possible values, see `vc-state'. This function should do a full and -;; reliable state computation; it is usually called immediately after -;; C-x v v. If you want to use a faster heuristic when visiting a -;; file, put that into `state-heuristic' below. Note that under most -;; VCSes this won't be called at all, dir-status is used instead. -;; -;; - state-heuristic (file) -;; -;; If provided, this function is used to estimate the version control -;; state of FILE at visiting time. It should be considerably faster -;; than the implementation of `state'. For a list of possible values, -;; see the doc string of `vc-state'. -;; -;; - dir-status (dir update-function) -;; -;; Produce RESULT: a list of lists of the form (FILE VC-STATE EXTRA) -;; for the files in DIR. -;; EXTRA can be used for backend specific information about FILE. -;; If a command needs to be run to compute this list, it should be -;; run asynchronously using (current-buffer) as the buffer for the -;; command. When RESULT is computed, it should be passed back by -;; doing: (funcall UPDATE-FUNCTION RESULT nil). -;; If the backend uses a process filter, hence it produces partial results, -;; they can be passed back by doing: -;; (funcall UPDATE-FUNCTION RESULT t) -;; and then do a (funcall UPDATE-FUNCTION RESULT nil) -;; when all the results have been computed. -;; To provide more backend specific functionality for `vc-dir' -;; the following functions might be needed: `dir-extra-headers', -;; `dir-printer', `extra-dir-menu' and `dir-status-files'. -;; -;; - dir-status-files (dir files default-state update-function) -;; -;; This function is identical to dir-status except that it should -;; only report status for the specified FILES. Also it needs to -;; report on all requested files, including up-to-date or ignored -;; files. If not provided, the default is to consider that the files -;; are in DEFAULT-STATE. -;; -;; - dir-extra-headers (dir) -;; -;; Return a string that will be added to the *vc-dir* buffer header. -;; -;; - dir-printer (fileinfo) -;; -;; Pretty print the `vc-dir-fileinfo' FILEINFO. -;; If a backend needs to show more information than the default FILE -;; and STATE in the vc-dir listing, it can store that extra -;; information in `vc-dir-fileinfo->extra'. This function can be -;; used to display that extra information in the *vc-dir* buffer. -;; -;; - status-fileinfo-extra (file) -;; -;; Compute `vc-dir-fileinfo->extra' for FILE. -;; -;; * working-revision (file) -;; -;; Return the working revision of FILE. This is the revision fetched -;; by the last checkout or upate, not necessarily the same thing as the -;; head or tip revision. Should return "0" for a file added but not yet -;; committed. -;; -;; - latest-on-branch-p (file) -;; -;; Return non-nil if the working revision of FILE is the latest revision -;; on its branch (many VCSes call this the 'tip' or 'head' revision). -;; The default implementation always returns t, which means that -;; working with non-current revisions is not supported by default. -;; -;; * checkout-model (files) -;; -;; Indicate whether FILES need to be "checked out" before they can be -;; edited. See `vc-checkout-model' for a list of possible values. -;; -;; - workfile-unchanged-p (file) -;; -;; Return non-nil if FILE is unchanged from the working revision. -;; This function should do a brief comparison of FILE's contents -;; with those of the repository copy of the working revision. If -;; the backend does not have such a brief-comparison feature, the -;; default implementation of this function can be used, which -;; delegates to a full vc-BACKEND-diff. (Note that vc-BACKEND-diff -;; must not run asynchronously in this case, see variable -;; `vc-disable-async-diff'.) -;; -;; - mode-line-string (file) -;; -;; If provided, this function should return the VC-specific mode -;; line string for FILE. The returned string should have a -;; `help-echo' property which is the text to be displayed as a -;; tooltip when the mouse hovers over the VC entry on the mode-line. -;; The default implementation deals well with all states that -;; `vc-state' can return. -;; -;; STATE-CHANGING FUNCTIONS -;; -;; * create-repo (backend) -;; -;; Create an empty repository in the current directory and initialize -;; it so VC mode can add files to it. For file-oriented systems, this -;; need do no more than create a subdirectory with the right name. -;; -;; * register (files &optional rev comment) -;; -;; Register FILES in this backend. Optionally, an initial revision REV -;; and an initial description of the file, COMMENT, may be specified, -;; but it is not guaranteed that the backend will do anything with this. -;; The implementation should pass the value of vc-register-switches -;; to the backend command. (Note: in older versions of VC, this -;; command took a single file argument and not a list.) -;; -;; - init-revision (file) -;; -;; The initial revision to use when registering FILE if one is not -;; specified by the user. If not provided, the variable -;; vc-default-init-revision is used instead. -;; -;; - responsible-p (file) -;; -;; Return non-nil if this backend considers itself "responsible" for -;; FILE, which can also be a directory. This function is used to find -;; out what backend to use for registration of new files and for things -;; like change log generation. The default implementation always -;; returns nil. -;; -;; - could-register (file) -;; -;; Return non-nil if FILE could be registered under this backend. The -;; default implementation always returns t. -;; -;; - receive-file (file rev) -;; -;; Let this backend "receive" a file that is already registered under -;; another backend. The default implementation simply calls `register' -;; for FILE, but it can be overridden to do something more specific, -;; e.g. keep revision numbers consistent or choose editing modes for -;; FILE that resemble those of the other backend. -;; -;; - unregister (file) -;; -;; Unregister FILE from this backend. This is only needed if this -;; backend may be used as a "more local" backend for temporary editing. -;; -;; * checkin (files rev comment) -;; -;; Commit changes in FILES to this backend. REV is a historical artifact -;; and should be ignored. COMMENT is used as a check-in comment. -;; The implementation should pass the value of vc-checkin-switches to -;; the backend command. -;; -;; * find-revision (file rev buffer) -;; -;; Fetch revision REV of file FILE and put it into BUFFER. -;; If REV is the empty string, fetch the head of the trunk. -;; The implementation should pass the value of vc-checkout-switches -;; to the backend command. -;; -;; * checkout (file &optional editable rev) -;; -;; Check out revision REV of FILE into the working area. If EDITABLE -;; is non-nil, FILE should be writable by the user and if locking is -;; used for FILE, a lock should also be set. If REV is non-nil, that -;; is the revision to check out (default is the working revision). -;; If REV is t, that means to check out the head of the current branch; -;; if it is the empty string, check out the head of the trunk. -;; The implementation should pass the value of vc-checkout-switches -;; to the backend command. -;; -;; * revert (file &optional contents-done) -;; -;; Revert FILE back to the working revision. If optional -;; arg CONTENTS-DONE is non-nil, then the contents of FILE have -;; already been reverted from a version backup, and this function -;; only needs to update the status of FILE within the backend. -;; If FILE is in the `added' state it should be returned to the -;; `unregistered' state. -;; -;; - rollback (files) -;; -;; Remove the tip revision of each of FILES from the repository. If -;; this function is not provided, trying to cancel a revision is -;; caught as an error. (Most backends don't provide it.) (Also -;; note that older versions of this backend command were called -;; 'cancel-version' and took a single file arg, not a list of -;; files.) -;; -;; - merge (file rev1 rev2) -;; -;; Merge the changes between REV1 and REV2 into the current working file. -;; -;; - merge-news (file) -;; -;; Merge recent changes from the current branch into FILE. -;; -;; - steal-lock (file &optional revision) -;; -;; Steal any lock on the working revision of FILE, or on REVISION if -;; that is provided. This function is only needed if locking is -;; used for files under this backend, and if files can indeed be -;; locked by other users. -;; -;; - modify-change-comment (files rev comment) -;; -;; Modify the change comments associated with the files at the -;; given revision. This is optional, many backends do not support it. -;; -;; - mark-resolved (files) -;; -;; Mark conflicts as resolved. Some VC systems need to run a -;; command to mark conflicts as resolved. -;; -;; HISTORY FUNCTIONS -;; -;; * print-log (files buffer &optional shortlog start-revision limit) -;; -;; Insert the revision log for FILES into BUFFER. -;; If SHORTLOG is true insert a short version of the log. -;; If LIMIT is true insert only insert LIMIT log entries. If the -;; backend does not support limiting the number of entries to show -;; it should return `limit-unsupported'. -;; If START-REVISION is given, then show the log starting from the -;; revision. At this point START-REVISION is only required to work -;; in conjunction with LIMIT = 1. -;; -;; * log-outgoing (backend remote-location) -;; -;; Insert in BUFFER the revision log for the changes that will be -;; sent when performing a push operation to REMOTE-LOCATION. -;; -;; * log-incoming (backend remote-location) -;; -;; Insert in BUFFER the revision log for the changes that will be -;; received when performing a pull operation from REMOTE-LOCATION. -;; -;; - log-view-mode () -;; -;; Mode to use for the output of print-log. This defaults to -;; `log-view-mode' and is expected to be changed (if at all) to a derived -;; mode of `log-view-mode'. -;; -;; - show-log-entry (revision) -;; -;; If provided, search the log entry for REVISION in the current buffer, -;; and make sure it is displayed in the buffer's window. The default -;; implementation of this function works for RCS-style logs. -;; -;; - comment-history (file) -;; -;; Return a string containing all log entries that were made for FILE. -;; This is used for transferring a file from one backend to another, -;; retaining comment information. -;; -;; - update-changelog (files) -;; -;; Using recent log entries, create ChangeLog entries for FILES, or for -;; all files at or below the default-directory if FILES is nil. The -;; default implementation runs rcs2log, which handles RCS- and -;; CVS-style logs. -;; -;; * diff (files &optional rev1 rev2 buffer) -;; -;; Insert the diff for FILE into BUFFER, or the *vc-diff* buffer if -;; BUFFER is nil. If REV1 and REV2 are non-nil, report differences -;; from REV1 to REV2. If REV1 is nil, use the working revision (as -;; found in the repository) as the older revision; if REV2 is nil, -;; use the current working-copy contents as the newer revision. This -;; function should pass the value of (vc-switches BACKEND 'diff) to -;; the backend command. It should return a status of either 0 (no -;; differences found), or 1 (either non-empty diff or the diff is -;; run asynchronously). -;; -;; - revision-completion-table (files) -;; -;; Return a completion table for existing revisions of FILES. -;; The default is to not use any completion table. -;; -;; - annotate-command (file buf &optional rev) -;; -;; If this function is provided, it should produce an annotated display -;; of FILE in BUF, relative to revision REV. Annotation means each line -;; of FILE displayed is prefixed with version information associated with -;; its addition (deleted lines leave no history) and that the text of the -;; file is fontified according to age. -;; -;; - annotate-time () -;; -;; Only required if `annotate-command' is defined for the backend. -;; Return the time of the next line of annotation at or after point, -;; as a floating point fractional number of days. The helper -;; function `vc-annotate-convert-time' may be useful for converting -;; multi-part times as returned by `current-time' and `encode-time' -;; to this format. Return nil if no more lines of annotation appear -;; in the buffer. You can safely assume that point is placed at the -;; beginning of each line, starting at `point-min'. The buffer that -;; point is placed in is the Annotate output, as defined by the -;; relevant backend. This function also affects how much of the line -;; is fontified; where it leaves point is where fontification begins. -;; -;; - annotate-current-time () -;; -;; Only required if `annotate-command' is defined for the backend, -;; AND you'd like the current time considered to be anything besides -;; (vc-annotate-convert-time (current-time)) -- i.e. the current -;; time with hours, minutes, and seconds included. Probably safe to -;; ignore. Return the current-time, in units of fractional days. -;; -;; - annotate-extract-revision-at-line () -;; -;; Only required if `annotate-command' is defined for the backend. -;; Invoked from a buffer in vc-annotate-mode, return the revision -;; corresponding to the current line, or nil if there is no revision -;; corresponding to the current line. -;; If the backend supports annotating through copies and renames, -;; and displays a file name and a revision, then return a cons -;; (REVISION . FILENAME). -;; -;; TAG SYSTEM -;; -;; - create-tag (dir name branchp) -;; -;; Attach the tag NAME to the state of the working copy. This -;; should make sure that files are up-to-date before proceeding with -;; the action. DIR can also be a file and if BRANCHP is specified, -;; NAME should be created as a branch and DIR should be checked out -;; under this new branch. The default implementation does not -;; support branches but does a sanity check, a tree traversal and -;; assigns the tag to each file. -;; -;; - retrieve-tag (dir name update) -;; -;; Retrieve the version tagged by NAME of all registered files at or below DIR. -;; If UPDATE is non-nil, then update buffers of any files in the -;; tag that are currently visited. The default implementation -;; does a sanity check whether there aren't any uncommitted changes at -;; or below DIR, and then performs a tree walk, using the `checkout' -;; function to retrieve the corresponding revisions. -;; -;; MISCELLANEOUS -;; -;; - make-version-backups-p (file) -;; -;; Return non-nil if unmodified repository revisions of FILE should be -;; backed up locally. If this is done, VC can perform `diff' and -;; `revert' operations itself, without calling the backend system. The -;; default implementation always returns nil. -;; -;; - root (file) -;; Return the root of the VC controlled hierarchy for file. -;; -;; - repository-hostname (dirname) -;; -;; Return the hostname that the backend will have to contact -;; in order to operate on a file in DIRNAME. If the return value -;; is nil, it means that the repository is local. -;; This function is used in `vc-stay-local-p' which backends can use -;; for their convenience. -;; -;; - previous-revision (file rev) -;; -;; Return the revision number that precedes REV for FILE, or nil if no such -;; revision exists. -;; -;; - next-revision (file rev) -;; -;; Return the revision number that follows REV for FILE, or nil if no such -;; revision exists. -;; -;; - log-edit-mode () -;; -;; Turn on the mode used for editing the check in log. This -;; defaults to `log-edit-mode'. If changed, it should use a mode -;; derived from`log-edit-mode'. -;; -;; - check-headers () -;; -;; Return non-nil if the current buffer contains any version headers. -;; -;; - clear-headers () -;; -;; In the current buffer, reset all version headers to their unexpanded -;; form. This function should be provided if the state-querying code -;; for this backend uses the version headers to determine the state of -;; a file. This function will then be called whenever VC changes the -;; version control state in such a way that the headers would give -;; wrong information. -;; -;; - delete-file (file) -;; -;; Delete FILE and mark it as deleted in the repository. If this -;; function is not provided, the command `vc-delete-file' will -;; signal an error. -;; -;; - rename-file (old new) -;; -;; Rename file OLD to NEW, both in the working area and in the -;; repository. If this function is not provided, the renaming -;; will be done by (vc-delete-file old) and (vc-register new). -;; -;; - find-file-hook () -;; -;; Operation called in current buffer when opening a file. This can -;; be used by the backend to setup some local variables it might need. -;; -;; - extra-menu () -;; -;; Return a menu keymap, the items in the keymap will appear at the -;; end of the Version Control menu. The goal is to allow backends -;; to specify extra menu items that appear in the VC menu. This way -;; you can provide menu entries for functionality that is specific -;; to your backend and which does not map to any of the VC generic -;; concepts. -;; -;; - extra-dir-menu () -;; -;; Return a menu keymap, the items in the keymap will appear at the -;; end of the VC Status menu. The goal is to allow backends to -;; specify extra menu items that appear in the VC Status menu. This -;; makes it possible to provide menu entries for functionality that -;; is specific to a backend and which does not map to any of the VC -;; generic concepts. -;; -;; - conflicted-files (dir) -;; -;; Return the list of files where conflict resolution is needed in -;; the project that contains DIR. -;; FIXME: what should it do with non-text conflicts? - -;;; Todo: - -;; - Get rid of the "master file" terminology. - -;; - Add key-binding for vc-delete-file. - -;;;; New Primitives: -;; -;; - deal with push/pull operations. -;; -;; - add a mechanism for editing the underlying VCS's list of files -;; to be ignored, when that's possible. -;; -;;;; Primitives that need changing: -;; -;; - vc-update/vc-merge should deal with VC systems that don't -;; update/merge on a file basis, but on a whole repository basis. -;; vc-update and vc-merge assume the arguments are always files, -;; they don't deal with directories. Make sure the *vc-dir* buffer -;; is updated after these operations. -;; At least bzr, git and hg should benefit from this. -;; -;;;; Improved branch and tag handling: -;; -;; - add a generic mechanism for remembering the current branch names, -;; display the branch name in the mode-line. Replace -;; vc-cvs-sticky-tag with that. -;; -;;;; Internal cleanups: -;; -;; - backends that care about vc-stay-local should try to take it into -;; account for vc-dir. Is this likely to be useful??? YES! -;; -;; - vc-expand-dirs should take a backend parameter and only look for -;; files managed by that backend. -;; -;; - Another important thing: merge all the status-like backend operations. -;; We should remove dir-status, state, and dir-status-files, and -;; replace them with just `status' which takes a fileset and a continuation -;; (like dir-status) and returns a buffer in which the process(es) are run -;; (or nil if it worked synchronously). Hopefully we can define the old -;; 4 operations in term of this one. -;; -;;;; Other -;; -;; - when a file is in `conflict' state, turn on smerge-mode. -;; -;; - figure out what to do with conflicts that are not caused by the -;; file contents, but by metadata or other causes. Example: File A -;; gets renamed to B in one branch and to C in another and you merge -;; the two branches. Or you locally add file FOO and then pull a -;; change that also adds a new file FOO, ... -;; -;; - make it easier to write logs. Maybe C-x 4 a should add to the log -;; buffer, if one is present, instead of adding to the ChangeLog. -;; -;; - When vc-next-action calls vc-checkin it could pre-fill the -;; *VC-log* buffer with some obvious items: the list of files that -;; were added, the list of files that were removed. If the diff is -;; available, maybe it could even call something like -;; `diff-add-change-log-entries-other-window' to create a detailed -;; skeleton for the log... -;; -;; - most vc-dir backends need more work. They might need to -;; provide custom headers, use the `extra' field and deal with all -;; possible VC states. -;; -;; - add a function that calls vc-dir to `find-directory-functions'. -;; -;; - vc-diff, vc-annotate, etc. need to deal better with unregistered -;; files. Now that unregistered and ignored files are shown in -;; vc-dir, it is possible that these commands are called -;; for unregistered/ignored files. -;; -;; - vc-next-action needs work in order to work with multiple -;; backends: `vc-state' returns the state for the default backend, -;; not for the backend in the current *vc-dir* buffer. -;; -;; - vc-dir-kill-dir-status-process should not be specific to dir-status, -;; it should work for other async commands done through vc-do-command -;; as well, -;; -;; - vc-dir toolbar needs more icons. -;; -;; - The backends should avoid using `vc-file-setprop' and `vc-file-getprop'. -;; -;;; Code: - -(require 'vc-hooks) -(require 'vc-dispatcher) - -(eval-when-compile - (require 'cl) - (require 'dired)) - -(unless (assoc 'vc-parent-buffer minor-mode-alist) - (setq minor-mode-alist - (cons '(vc-parent-buffer vc-parent-buffer-name) - minor-mode-alist))) - -;; General customization - -(defgroup vc nil - "Version-control system in Emacs." - :group 'tools) - -(defcustom vc-initial-comment nil - "If non-nil, prompt for initial comment when a file is registered." - :type 'boolean - :group 'vc) - -(defcustom vc-default-init-revision "1.1" - "A string used as the default revision number when a new file is registered. -This can be overridden by giving a prefix argument to \\[vc-register]. This -can also be overridden by a particular VC backend." - :type 'string - :group 'vc - :version "20.3") - -(defcustom vc-checkin-switches nil - "A string or list of strings specifying extra switches for checkin. -These are passed to the checkin program by \\[vc-checkin]." - :type '(choice (const :tag "None" nil) - (string :tag "Argument String") - (repeat :tag "Argument List" - :value ("") - string)) - :group 'vc) - -(defcustom vc-checkout-switches nil - "A string or list of strings specifying extra switches for checkout. -These are passed to the checkout program by \\[vc-checkout]." - :type '(choice (const :tag "None" nil) - (string :tag "Argument String") - (repeat :tag "Argument List" - :value ("") - string)) - :group 'vc) - -(defcustom vc-register-switches nil - "A string or list of strings; extra switches for registering a file. -These are passed to the checkin program by \\[vc-register]." - :type '(choice (const :tag "None" nil) - (string :tag "Argument String") - (repeat :tag "Argument List" - :value ("") - string)) - :group 'vc) - -(defcustom vc-diff-switches nil - "A string or list of strings specifying switches for diff under VC. -When running diff under a given BACKEND, VC uses the first -non-nil value of `vc-BACKEND-diff-switches', `vc-diff-switches', -and `diff-switches', in that order. Since nil means to check the -next variable in the sequence, either of the first two may use -the value t to mean no switches at all. `vc-diff-switches' -should contain switches that are specific to version control, but -not specific to any particular backend." - :type '(choice (const :tag "Unspecified" nil) - (const :tag "None" t) - (string :tag "Argument String") - (repeat :tag "Argument List" :value ("") string)) - :group 'vc - :version "21.1") - -(defcustom vc-diff-knows-L nil - "Indicates whether diff understands the -L option. -The value is either `yes', `no', or nil. If it is nil, VC tries -to use -L and sets this variable to remember whether it worked." - :type '(choice (const :tag "Work out" nil) (const yes) (const no)) - :group 'vc) - -(defcustom vc-log-show-limit 2000 - "Limit the number of items shown by the VC log commands. -Zero means unlimited. -Not all VC backends are able to support this feature." - :type 'integer - :group 'vc) - -(defcustom vc-allow-async-revert nil - "Specifies whether the diff during \\[vc-revert] may be asynchronous. -Enabling this option means that you can confirm a revert operation even -if the local changes in the file have not been found and displayed yet." - :type '(choice (const :tag "No" nil) - (const :tag "Yes" t)) - :group 'vc - :version "22.1") - -;;;###autoload -(defcustom vc-checkout-hook nil - "Normal hook (list of functions) run after checking out a file. -See `run-hooks'." - :type 'hook - :group 'vc - :version "21.1") - -;;;###autoload -(defcustom vc-checkin-hook nil - "Normal hook (list of functions) run after commit or file checkin. -See also `log-edit-done-hook'." - :type 'hook - :options '(log-edit-comment-to-change-log) - :group 'vc) - -;;;###autoload -(defcustom vc-before-checkin-hook nil - "Normal hook (list of functions) run before a commit or a file checkin. -See `run-hooks'." - :type 'hook - :group 'vc) - -;; Header-insertion hair - -(defcustom vc-static-header-alist - '(("\\.c\\'" . - "\n#ifndef lint\nstatic char vcid[] = \"\%s\";\n#endif /* lint */\n")) - "Associate static header string templates with file types. -A \%s in the template is replaced with the first string associated with -the file's version control type in `vc-header-alist'." - :type '(repeat (cons :format "%v" - (regexp :tag "File Type") - (string :tag "Header String"))) - :group 'vc) - -(defcustom vc-comment-alist - '((nroff-mode ".\\\"" "")) - "Special comment delimiters for generating VC headers. -Add an entry in this list if you need to override the normal `comment-start' -and `comment-end' variables. This will only be necessary if the mode language -is sensitive to blank lines." - :type '(repeat (list :format "%v" - (symbol :tag "Mode") - (string :tag "Comment Start") - (string :tag "Comment End"))) - :group 'vc) - -(defcustom vc-checkout-carefully (= (user-uid) 0) - "Non-nil means be extra-careful in checkout. -Verify that the file really is not locked -and that its contents match what the repository version says." - :type 'boolean - :group 'vc) -(make-obsolete-variable 'vc-checkout-carefully - "the corresponding checks are always done now." - "21.1") - - -;; Variables users don't need to see - -(defvar vc-disable-async-diff nil - "VC sets this to t locally to disable some async diff operations. -Backends that offer asynchronous diffs should respect this variable -in their implementation of vc-BACKEND-diff.") - -;; File property caching - -(defun vc-clear-context () - "Clear all cached file properties." - (interactive) - (fillarray vc-file-prop-obarray 0)) - -(defmacro with-vc-properties (files form settings) - "Execute FORM, then maybe set per-file properties for FILES. -If any of FILES is actually a directory, then do the same for all -buffers for files in that directory. -SETTINGS is an association list of property/value pairs. After -executing FORM, set those properties from SETTINGS that have not yet -been updated to their corresponding values." - (declare (debug t)) - `(let ((vc-touched-properties (list t)) - (flist nil)) - (dolist (file ,files) - (if (file-directory-p file) - (dolist (buffer (buffer-list)) - (let ((fname (buffer-file-name buffer))) - (when (and fname (vc-string-prefix-p file fname)) - (push fname flist)))) - (push file flist))) - ,form - (dolist (file flist) - (dolist (setting ,settings) - (let ((property (car setting))) - (unless (memq property vc-touched-properties) - (put (intern file vc-file-prop-obarray) - property (cdr setting)))))))) - -;;; Code for deducing what fileset and backend to assume - -(defun vc-backend-for-registration (file) - "Return a backend that can be used for registering FILE. - -If no backend declares itself responsible for FILE, then FILE -must not be in a version controlled directory, so try to create a -repository, prompting for the directory and the VC backend to -use." - (catch 'found - ;; First try: find a responsible backend, it must be a backend - ;; under which FILE is not yet registered. - (dolist (backend vc-handled-backends) - (and (not (vc-call-backend backend 'registered file)) - (vc-call-backend backend 'responsible-p file) - (throw 'found backend))) - ;; no responsible backend - (let* ((possible-backends - (let (pos) - (dolist (crt vc-handled-backends) - (when (vc-find-backend-function crt 'create-repo) - (push crt pos))) - pos)) - (bk - (intern - ;; Read the VC backend from the user, only - ;; complete with the backends that have the - ;; 'create-repo method. - (completing-read - (format "%s is not in a version controlled directory.\nUse VC backend: " file) - (mapcar 'symbol-name possible-backends) nil t))) - (repo-dir - (let ((def-dir (file-name-directory file))) - ;; read the directory where to create the - ;; repository, make sure it's a parent of - ;; file. - (read-file-name - (format "create %s repository in: " bk) - default-directory def-dir t nil - (lambda (arg) - (message "arg %s" arg) - (and (file-directory-p arg) - (vc-string-prefix-p (expand-file-name arg) def-dir))))))) - (let ((default-directory repo-dir)) - (vc-call-backend bk 'create-repo)) - (throw 'found bk)))) - -(defun vc-responsible-backend (file) - "Return the name of a backend system that is responsible for FILE. - -If FILE is already registered, return the -backend of FILE. If FILE is not registered, then the -first backend in `vc-handled-backends' that declares itself -responsible for FILE is returned." - (or (and (not (file-directory-p file)) (vc-backend file)) - (catch 'found - ;; First try: find a responsible backend. If this is for registration, - ;; it must be a backend under which FILE is not yet registered. - (dolist (backend vc-handled-backends) - (and (vc-call-backend backend 'responsible-p file) - (throw 'found backend)))) - (error "No VC backend is responsible for %s" file))) - -(defun vc-expand-dirs (file-or-dir-list) - "Expands directories in a file list specification. -Within directories, only files already under version control are noticed." - (let ((flattened '())) - (dolist (node file-or-dir-list) - (when (file-directory-p node) - (vc-file-tree-walk - node (lambda (f) (when (vc-backend f) (push f flattened))))) - (unless (file-directory-p node) (push node flattened))) - (nreverse flattened))) - -(defvar vc-dir-backend) - -(declare-function vc-dir-current-file "vc-dir" ()) -(declare-function vc-dir-deduce-fileset "vc-dir" (&optional state-model-only-files)) - -(defun vc-deduce-fileset (&optional observer allow-unregistered - state-model-only-files) - "Deduce a set of files and a backend to which to apply an operation. - -Return (BACKEND FILESET FILESET-ONLY-FILES STATE CHECKOUT-MODEL). -If we're in VC-dir mode, the fileset is the list of marked files. -Otherwise, if we're looking at a buffer visiting a version-controlled file, -the fileset is a singleton containing this file. -If none of these conditions is met, but ALLOW_UNREGISTERED is on and the -visited file is not registered, return a singleton fileset containing it. -Otherwise, throw an error. - -STATE-MODEL-ONLY-FILES if non-nil, means that the caller needs -the FILESET-ONLY-FILES STATE and MODEL info. Otherwise, that -part may be skipped. -BEWARE: this function may change the -current buffer." - ;; FIXME: OBSERVER is unused. The name is not intuitive and is not - ;; documented. It's set to t when called from diff and print-log. - (let (backend) - (cond - ((derived-mode-p 'vc-dir-mode) - (vc-dir-deduce-fileset state-model-only-files)) - ((derived-mode-p 'dired-mode) - (if observer - (vc-dired-deduce-fileset) - (error "State changing VC operations not supported in `dired-mode'"))) - ((setq backend (vc-backend buffer-file-name)) - (if state-model-only-files - (list backend (list buffer-file-name) - (list buffer-file-name) - (vc-state buffer-file-name) - (vc-checkout-model backend buffer-file-name)) - (list backend (list buffer-file-name)))) - ((and (buffer-live-p vc-parent-buffer) - ;; FIXME: Why this test? --Stef - (or (buffer-file-name vc-parent-buffer) - (with-current-buffer vc-parent-buffer - (derived-mode-p 'vc-dir-mode)))) - (progn ;FIXME: Why not `with-current-buffer'? --Stef. - (set-buffer vc-parent-buffer) - (vc-deduce-fileset observer allow-unregistered state-model-only-files))) - ((not buffer-file-name) - (error "Buffer %s is not associated with a file" (buffer-name))) - ((and allow-unregistered (not (vc-registered buffer-file-name))) - (if state-model-only-files - (list (vc-backend-for-registration (buffer-file-name)) - (list buffer-file-name) - (list buffer-file-name) - (when state-model-only-files 'unregistered) - nil) - (list (vc-backend-for-registration (buffer-file-name)) - (list buffer-file-name)))) - (t (error "No fileset is available here"))))) - -(defun vc-dired-deduce-fileset () - (let ((backend (vc-responsible-backend default-directory))) - (unless backend (error "Directory not under VC")) - (list backend - (dired-map-over-marks (dired-get-filename nil t) nil)))) - -(defun vc-ensure-vc-buffer () - "Make sure that the current buffer visits a version-controlled file." - (cond - ((derived-mode-p 'vc-dir-mode) - (set-buffer (find-file-noselect (vc-dir-current-file)))) - (t - (while (and vc-parent-buffer - (buffer-live-p vc-parent-buffer) - ;; Avoid infinite looping when vc-parent-buffer and - ;; current buffer are the same buffer. - (not (eq vc-parent-buffer (current-buffer)))) - (set-buffer vc-parent-buffer)) - (if (not buffer-file-name) - (error "Buffer %s is not associated with a file" (buffer-name)) - (unless (vc-backend buffer-file-name) - (error "File %s is not under version control" buffer-file-name)))))) - -;;; Support for the C-x v v command. -;; This is where all the single-file-oriented code from before the fileset -;; rewrite lives. - -(defsubst vc-editable-p (file) - "Return non-nil if FILE can be edited." - (let ((backend (vc-backend file))) - (and backend - (or (eq (vc-checkout-model backend (list file)) 'implicit) - (memq (vc-state file) '(edited needs-merge conflict)))))) - -(defun vc-compatible-state (p q) - "Controls which states can be in the same commit." - (or - (eq p q) - (and (member p '(edited added removed)) (member q '(edited added removed))))) - -;; Here's the major entry point. - -;;;###autoload -(defun vc-next-action (verbose) - "Do the next logical version control operation on the current fileset. -This requires that all files in the fileset be in the same state. - -For locking systems: - If every file is not already registered, this registers each for version -control. - If every file is registered and not locked by anyone, this checks out -a writable and locked file of each ready for editing. - If every file is checked out and locked by the calling user, this -first checks to see if each file has changed since checkout. If not, -it performs a revert on that file. - If every file has been changed, this pops up a buffer for entry -of a log message; when the message has been entered, it checks in the -resulting changes along with the log message as change commentary. If -the variable `vc-keep-workfiles' is non-nil (which is its default), a -read-only copy of each changed file is left in place afterwards. - If the affected file is registered and locked by someone else, you are -given the option to steal the lock(s). - -For merging systems: - If every file is not already registered, this registers each one for version -control. This does an add, but not a commit. - If every file is added but not committed, each one is committed. - If every working file is changed, but the corresponding repository file is -unchanged, this pops up a buffer for entry of a log message; when the -message has been entered, it checks in the resulting changes along -with the logmessage as change commentary. A writable file is retained. - If the repository file is changed, you are asked if you want to -merge in the changes into your working copy." - (interactive "P") - (let* ((vc-fileset (vc-deduce-fileset nil t 'state-model-only-files)) - (backend (car vc-fileset)) - (files (nth 1 vc-fileset)) - (fileset-only-files (nth 2 vc-fileset)) - ;; FIXME: We used to call `vc-recompute-state' here. - (state (nth 3 vc-fileset)) - ;; The backend should check that the checkout-model is consistent - ;; among all the `files'. - (model (nth 4 vc-fileset))) - - ;; Do the right thing - (cond - ((eq state 'missing) - (error "Fileset files are missing, so cannot be operated on")) - ((eq state 'ignored) - (error "Fileset files are ignored by the version-control system")) - ((or (null state) (eq state 'unregistered)) - (vc-register nil vc-fileset)) - ;; Files are up-to-date, or need a merge and user specified a revision - ((or (eq state 'up-to-date) (and verbose (eq state 'needs-update))) - (cond - (verbose - ;; go to a different revision - (let* ((revision - (read-string "Branch, revision, or backend to move to: ")) - (revision-downcase (downcase revision))) - (if (member - revision-downcase - (mapcar (lambda (arg) (downcase (symbol-name arg))) - vc-handled-backends)) - (let ((vsym (intern-soft revision-downcase))) - (dolist (file files) (vc-transfer-file file vsym))) - (dolist (file files) - (vc-checkout file (eq model 'implicit) revision))))) - ((not (eq model 'implicit)) - ;; check the files out - (dolist (file files) (vc-checkout file t))) - (t - ;; do nothing - (message "Fileset is up-to-date")))) - ;; Files have local changes - ((vc-compatible-state state 'edited) - (let ((ready-for-commit files)) - ;; If files are edited but read-only, give user a chance to correct - (dolist (file files) - (unless (file-writable-p file) - ;; Make the file+buffer read-write. - (unless (y-or-n-p (format "%s is edited but read-only; make it writable and continue?" file)) - (error "Aborted")) - (set-file-modes file (logior (file-modes file) 128)) - (let ((visited (get-file-buffer file))) - (when visited - (with-current-buffer visited - (toggle-read-only -1)))))) - ;; Allow user to revert files with no changes - (save-excursion - (dolist (file files) - (let ((visited (get-file-buffer file))) - ;; For files with locking, if the file does not contain - ;; any changes, just let go of the lock, i.e. revert. - (when (and (not (eq model 'implicit)) - (vc-workfile-unchanged-p file) - ;; If buffer is modified, that means the user just - ;; said no to saving it; in that case, don't revert, - ;; because the user might intend to save after - ;; finishing the log entry and committing. - (not (and visited (buffer-modified-p)))) - (vc-revert-file file) - (setq ready-for-commit (delete file ready-for-commit)))))) - ;; Remaining files need to be committed - (if (not ready-for-commit) - (message "No files remain to be committed") - (if (not verbose) - (vc-checkin ready-for-commit backend) - (let* ((revision (read-string "New revision or backend: ")) - (revision-downcase (downcase revision))) - (if (member - revision-downcase - (mapcar (lambda (arg) (downcase (symbol-name arg))) - vc-handled-backends)) - (let ((vsym (intern revision-downcase))) - (dolist (file files) (vc-transfer-file file vsym))) - (vc-checkin ready-for-commit backend revision))))))) - ;; locked by somebody else (locking VCSes only) - ((stringp state) - ;; In the old days, we computed the revision once and used it on - ;; the single file. Then, for the 2007-2008 fileset rewrite, we - ;; computed the revision once (incorrectly, using a free var) and - ;; used it on all files. To fix the free var bug, we can either - ;; use `(car files)' or do what we do here: distribute the - ;; revision computation among `files'. Although this may be - ;; tedious for those backends where a "revision" is a trans-file - ;; concept, it is nonetheless correct for both those and (more - ;; importantly) for those where "revision" is a per-file concept. - ;; If the intersection of the former group and "locking VCSes" is - ;; non-empty [I vaguely doubt it --ttn], we can reinstate the - ;; pre-computation approach of yore. - (dolist (file files) - (vc-steal-lock - file (if verbose - (read-string (format "%s revision to steal: " file)) - (vc-working-revision file)) - state))) - ;; conflict - ((eq state 'conflict) - ;; FIXME: Is it really the UI we want to provide? - ;; In my experience, the conflicted files should be marked as resolved - ;; one-by-one when saving the file after resolving the conflicts. - ;; I.e. stating explicitly that the conflicts are resolved is done - ;; very rarely. - (vc-mark-resolved backend files)) - ;; needs-update - ((eq state 'needs-update) - (dolist (file files) - (if (yes-or-no-p (format - "%s is not up-to-date. Get latest revision? " - (file-name-nondirectory file))) - (vc-checkout file (eq model 'implicit) t) - (when (and (not (eq model 'implicit)) - (yes-or-no-p "Lock this revision? ")) - (vc-checkout file t))))) - ;; needs-merge - ((eq state 'needs-merge) - (dolist (file files) - (when (yes-or-no-p (format - "%s is not up-to-date. Merge in changes now? " - (file-name-nondirectory file))) - (vc-maybe-resolve-conflicts - file (vc-call-backend backend 'merge-news file))))) - - ;; unlocked-changes - ((eq state 'unlocked-changes) - (dolist (file files) - (when (not (equal buffer-file-name file)) - (find-file-other-window file)) - (if (save-window-excursion - (vc-diff-internal nil - (cons (car vc-fileset) (cons (cadr vc-fileset) (list file))) - (vc-working-revision file) nil) - (goto-char (point-min)) - (let ((inhibit-read-only t)) - (insert - (format "Changes to %s since last lock:\n\n" file))) - (not (beep)) - (yes-or-no-p (concat "File has unlocked changes. " - "Claim lock retaining changes? "))) - (progn (vc-call-backend backend 'steal-lock file) - (clear-visited-file-modtime) - ;; Must clear any headers here because they wouldn't - ;; show that the file is locked now. - (vc-clear-headers file) - (write-file buffer-file-name) - (vc-mode-line file backend)) - (if (not (yes-or-no-p - "Revert to checked-in revision, instead? ")) - (error "Checkout aborted") - (vc-revert-buffer-internal t t) - (vc-checkout file t))))) - ;; Unknown fileset state - (t - (error "Fileset is in an unknown state %s" state))))) - -(defun vc-create-repo (backend) - "Create an empty repository in the current directory." - (interactive - (list - (intern - (upcase - (completing-read - "Create repository for: " - (mapcar (lambda (b) (list (downcase (symbol-name b)))) vc-handled-backends) - nil t))))) - (vc-call-backend backend 'create-repo)) - -(declare-function vc-dir-move-to-goal-column "vc-dir" ()) - -;;;###autoload -(defun vc-register (&optional set-revision vc-fileset comment) - "Register into a version control system. -If VC-FILESET is given, register the files in that fileset. -Otherwise register the current file. -With prefix argument SET-REVISION, allow user to specify initial revision -level. If COMMENT is present, use that as an initial comment. - -The version control system to use is found by cycling through the list -`vc-handled-backends'. The first backend in that list which declares -itself responsible for the file (usually because other files in that -directory are already registered under that backend) will be used to -register the file. If no backend declares itself responsible, the -first backend that could register the file is used." - (interactive "P") - (let* ((fileset-arg (or vc-fileset (vc-deduce-fileset nil t))) - (backend (car fileset-arg)) - (files (nth 1 fileset-arg))) - ;; We used to operate on `only-files', but VC wants to provide the - ;; possibility to register directories rather than files only, since - ;; many VCS allow that as well. - (dolist (fname files) - (let ((bname (get-file-buffer fname))) - (unless fname (setq fname buffer-file-name)) - (when (vc-backend fname) - (if (vc-registered fname) - (error "This file is already registered") - (unless (y-or-n-p "Previous master file has vanished. Make a new one? ") - (error "Aborted")))) - ;; Watch out for new buffers of size 0: the corresponding file - ;; does not exist yet, even though buffer-modified-p is nil. - (when bname - (with-current-buffer bname - (when (and (not (buffer-modified-p)) - (zerop (buffer-size)) - (not (file-exists-p buffer-file-name))) - (set-buffer-modified-p t)) - (vc-buffer-sync))))) - (message "Registering %s... " files) - (mapc 'vc-file-clearprops files) - (vc-call-backend backend 'register files - (if set-revision - (read-string (format "Initial revision level for %s: " files)) - (vc-call-backend backend 'init-revision)) - comment) - (mapc - (lambda (file) - (vc-file-setprop file 'vc-backend backend) - ;; FIXME: This is wrong: it should set `backup-inhibited' in all - ;; the buffers visiting files affected by this `vc-register', not - ;; in the current-buffer. - ;; (unless vc-make-backup-files - ;; (make-local-variable 'backup-inhibited) - ;; (setq backup-inhibited t)) - - (vc-resynch-buffer file vc-keep-workfiles t)) - files) - (when (derived-mode-p 'vc-dir-mode) - (vc-dir-move-to-goal-column)) - (message "Registering %s... done" files))) - -(defun vc-register-with (backend) - "Register the current file with a specified back end." - (interactive "SBackend: ") - (when (not (member backend vc-handled-backends)) - (error "Unknown back end")) - (let ((vc-handled-backends (list backend))) - (call-interactively 'vc-register))) - -(defun vc-checkout (file &optional writable rev) - "Retrieve a copy of the revision REV of FILE. -If WRITABLE is non-nil, make sure the retrieved file is writable. -REV defaults to the latest revision. - -After check-out, runs the normal hook `vc-checkout-hook'." - (and writable - (not rev) - (vc-call make-version-backups-p file) - (vc-up-to-date-p file) - (vc-make-version-backup file)) - (let ((backend (vc-backend file))) - (with-vc-properties (list file) - (condition-case err - (vc-call-backend backend 'checkout file writable rev) - (file-error - ;; Maybe the backend is not installed ;-( - (when writable - (let ((buf (get-file-buffer file))) - (when buf (with-current-buffer buf (toggle-read-only -1))))) - (signal (car err) (cdr err)))) - `((vc-state . ,(if (or (eq (vc-checkout-model backend (list file)) 'implicit) - (not writable)) - (if (vc-call-backend backend 'latest-on-branch-p file) - 'up-to-date - 'needs-update) - 'edited)) - (vc-checkout-time . ,(nth 5 (file-attributes file)))))) - (vc-resynch-buffer file t t) - (run-hooks 'vc-checkout-hook)) - -(defun vc-mark-resolved (backend files) - (prog1 (with-vc-properties - files - (vc-call-backend backend 'mark-resolved files) - ;; FIXME: Is this TRTD? Might not be. - `((vc-state . edited))) - (message - (substitute-command-keys - "Conflicts have been resolved in %s. \ -Type \\[vc-next-action] to check in changes.") - (if (> (length files) 1) - (format "%d files" (length files)) - "this file")))) - -(defun vc-steal-lock (file rev owner) - "Steal the lock on FILE." - (let (file-description) - (if rev - (setq file-description (format "%s:%s" file rev)) - (setq file-description file)) - (when (not (yes-or-no-p (format "Steal the lock on %s from %s? " - file-description owner))) - (error "Steal canceled")) - (message "Stealing lock on %s..." file) - (with-vc-properties - (list file) - (vc-call steal-lock file rev) - `((vc-state . edited))) - (vc-resynch-buffer file t t) - (message "Stealing lock on %s...done" file) - ;; Write mail after actually stealing, because if the stealing - ;; goes wrong, we don't want to send any mail. - (compose-mail owner (format "Stolen lock on %s" file-description)) - (setq default-directory (expand-file-name "~/")) - (goto-char (point-max)) - (insert - (format "I stole the lock on %s, " file-description) - (current-time-string) - ".\n") - (message "Please explain why you stole the lock. Type C-c C-c when done."))) - -(defun vc-checkin (files backend &optional rev comment initial-contents) - "Check in FILES. -The optional argument REV may be a string specifying the new revision -level (strongly deprecated). COMMENT is a comment -string; if omitted, a buffer is popped up to accept a comment. If -INITIAL-CONTENTS is non-nil, then COMMENT is used as the initial contents -of the log entry buffer. - -If `vc-keep-workfiles' is nil, FILE is deleted afterwards, provided -that the version control system supports this mode of operation. - -Runs the normal hooks `vc-before-checkin-hook' and `vc-checkin-hook'." - (when vc-before-checkin-hook - (run-hooks 'vc-before-checkin-hook)) - (lexical-let - ((backend backend)) - (vc-start-logentry - files comment initial-contents - "Enter a change comment." - "*VC-log*" - (lambda () - (vc-call-backend backend 'log-edit-mode)) - (lexical-let ((rev rev)) - (lambda (files comment) - (message "Checking in %s..." (vc-delistify files)) - ;; "This log message intentionally left almost blank". - ;; RCS 5.7 gripes about white-space-only comments too. - (or (and comment (string-match "[^\t\n ]" comment)) - (setq comment "*** empty log message ***")) - (with-vc-properties - files - ;; We used to change buffers to get local value of - ;; vc-checkin-switches, but 'the' local buffer is - ;; not a well-defined concept for filesets. - (progn - (vc-call-backend backend 'checkin files rev comment) - (mapc 'vc-delete-automatic-version-backups files)) - `((vc-state . up-to-date) - (vc-checkout-time . ,(nth 5 (file-attributes file))) - (vc-working-revision . nil))) - (message "Checking in %s...done" (vc-delistify files)))) - 'vc-checkin-hook))) - -;;; Additional entry points for examining version histories - -;; (defun vc-default-diff-tree (backend dir rev1 rev2) -;; "List differences for all registered files at and below DIR. -;; The meaning of REV1 and REV2 is the same as for `vc-revision-diff'." -;; ;; This implementation does an explicit tree walk, and calls -;; ;; vc-BACKEND-diff directly for each file. An optimization -;; ;; would be to use `vc-diff-internal', so that diffs can be local, -;; ;; and to call it only for files that are actually changed. -;; ;; However, this is expensive for some backends, and so it is left -;; ;; to backend-specific implementations. -;; (setq default-directory dir) -;; (vc-file-tree-walk -;; default-directory -;; (lambda (f) -;; (vc-exec-after -;; `(let ((coding-system-for-read (vc-coding-system-for-diff ',f))) -;; (message "Looking at %s" ',f) -;; (vc-call-backend ',(vc-backend f) -;; 'diff (list ',f) ',rev1 ',rev2)))))) - -(defun vc-coding-system-for-diff (file) - "Return the coding system for reading diff output for FILE." - (or coding-system-for-read - ;; if we already have this file open, - ;; use the buffer's coding system - (let ((buf (find-buffer-visiting file))) - (when buf (with-current-buffer buf - buffer-file-coding-system))) - ;; otherwise, try to find one based on the file name - (car (find-operation-coding-system 'insert-file-contents file)) - ;; and a final fallback - 'undecided)) - -(defun vc-switches (backend op) - "Return a list of vc-BACKEND switches for operation OP. -BACKEND is a symbol such as `CVS', which will be downcased. -OP is a symbol such as `diff'. - -In decreasing order of preference, return the value of: -vc-BACKEND-OP-switches (e.g. `vc-cvs-diff-switches'); -vc-OP-switches (e.g. `vc-diff-switches'); or, in the case of -diff only, `diff-switches'. - -If the chosen value is not a string or a list, return nil. -This is so that you may set, e.g. `vc-svn-diff-switches' to t in order -to override the value of `vc-diff-switches' and `diff-switches'." - (let ((switches - (or (when backend - (let ((sym (vc-make-backend-sym - backend (intern (concat (symbol-name op) - "-switches"))))) - (when (boundp sym) (symbol-value sym)))) - (let ((sym (intern (format "vc-%s-switches" (symbol-name op))))) - (when (boundp sym) (symbol-value sym))) - (cond - ((eq op 'diff) diff-switches))))) - (if (stringp switches) (list switches) - ;; If not a list, return nil. - ;; This is so we can set vc-diff-switches to t to override - ;; any switches in diff-switches. - (when (listp switches) switches)))) - -;; Old def for compatibility with Emacs-21.[123]. -(defmacro vc-diff-switches-list (backend) `(vc-switches ',backend 'diff)) -(make-obsolete 'vc-diff-switches-list 'vc-switches "22.1") - -(defun vc-diff-finish (buffer messages) - ;; The empty sync output case has already been handled, so the only - ;; possibility of an empty output is for an async process. - (when (buffer-live-p buffer) - (let ((window (get-buffer-window buffer t)) - (emptyp (zerop (buffer-size buffer)))) - (with-current-buffer buffer - (and messages emptyp - (let ((inhibit-read-only t)) - (insert (cdr messages) ".\n") - (message "%s" (cdr messages)))) - (goto-char (point-min)) - (when window - (shrink-window-if-larger-than-buffer window))) - (when (and messages (not emptyp)) - (message "%sdone" (car messages)))))) - -(defvar vc-diff-added-files nil - "If non-nil, diff added files by comparing them to /dev/null.") - -(defun vc-diff-internal (async vc-fileset rev1 rev2 &optional verbose) - "Report diffs between two revisions of a fileset. -Diff output goes to the *vc-diff* buffer. The function -returns t if the buffer had changes, nil otherwise." - (let* ((files (cadr vc-fileset)) - (messages (cons (format "Finding changes in %s..." - (vc-delistify files)) - (format "No changes between %s and %s" - (or rev1 "working revision") - (or rev2 "workfile")))) - ;; Set coding system based on the first file. It's a kluge, - ;; but the only way to set it for each file included would - ;; be to call the back end separately for each file. - (coding-system-for-read - (if files (vc-coding-system-for-diff (car files)) 'undecided))) - (vc-setup-buffer "*vc-diff*") - (message "%s" (car messages)) - ;; Many backends don't handle well the case of a file that has been - ;; added but not yet committed to the repo (notably CVS and Subversion). - ;; Do that work here so the backends don't have to futz with it. --ESR - ;; - ;; Actually most backends (including CVS) have options to control the - ;; behavior since which one is better depends on the user and on the - ;; situation). Worse yet: this code does not handle the case where - ;; `file' is a directory which contains added files. - ;; I made it conditional on vc-diff-added-files but it should probably - ;; just be removed (or copied/moved to specific backends). --Stef. - (when vc-diff-added-files - (let ((filtered '()) - process-file-side-effects) - (dolist (file files) - (if (or (file-directory-p file) - (not (string= (vc-working-revision file) "0"))) - (push file filtered) - ;; This file is added but not yet committed; - ;; there is no repository version to diff against. - (if (or rev1 rev2) - (error "No revisions of %s exist" file) - ;; We regard this as "changed". - ;; Diff it against /dev/null. - (apply 'vc-do-command "*vc-diff*" - 1 "diff" file - (append (vc-switches nil 'diff) '("/dev/null")))))) - (setq files (nreverse filtered)))) - (let ((vc-disable-async-diff (not async))) - (vc-call-backend (car vc-fileset) 'diff files rev1 rev2 "*vc-diff*")) - (set-buffer "*vc-diff*") - (if (and (zerop (buffer-size)) - (not (get-buffer-process (current-buffer)))) - ;; Treat this case specially so as not to pop the buffer. - (progn - (message "%s" (cdr messages)) - nil) - (diff-mode) - ;; Make the *vc-diff* buffer read only, the diff-mode key - ;; bindings are nicer for read only buffers. pcl-cvs does the - ;; same thing. - (setq buffer-read-only t) - (vc-exec-after `(vc-diff-finish ,(current-buffer) ',(when verbose - messages))) - ;; Display the buffer, but at the end because it can change point. - (pop-to-buffer (current-buffer)) - ;; In the async case, we return t even if there are no differences - ;; because we don't know that yet. - t))) - -(defun vc-read-revision (prompt &optional files backend default initial-input) - (cond - ((null files) - (let ((vc-fileset (vc-deduce-fileset t))) ;FIXME: why t? --Stef - (setq files (cadr vc-fileset)) - (setq backend (car vc-fileset)))) - ((null backend) (setq backend (vc-backend (car files))))) - (let ((completion-table - (vc-call-backend backend 'revision-completion-table files))) - (if completion-table - (completing-read prompt completion-table - nil nil initial-input nil default) - (read-string prompt initial-input nil default)))) - -;;;###autoload -(defun vc-version-diff (files rev1 rev2) - "Report diffs between revisions of the fileset in the repository history." - (interactive - (let* ((vc-fileset (vc-deduce-fileset t)) ;FIXME: why t? --Stef - (files (cadr vc-fileset)) - (backend (car vc-fileset)) - (first (car files)) - (rev1-default nil) - (rev2-default nil)) - (cond - ;; someday we may be able to do revision completion on non-singleton - ;; filesets, but not yet. - ((/= (length files) 1) - nil) - ;; if it's a directory, don't supply any revision default - ((file-directory-p first) - nil) - ;; if the file is not up-to-date, use working revision as older revision - ((not (vc-up-to-date-p first)) - (setq rev1-default (vc-working-revision first))) - ;; if the file is not locked, use last and previous revisions as defaults - (t - (setq rev1-default (vc-call-backend backend 'previous-revision first - (vc-working-revision first))) - (when (string= rev1-default "") (setq rev1-default nil)) - (setq rev2-default (vc-working-revision first)))) - ;; construct argument list - (let* ((rev1-prompt (if rev1-default - (concat "Older revision (default " - rev1-default "): ") - "Older revision: ")) - (rev2-prompt (concat "Newer revision (default " - (or rev2-default "current source") "): ")) - (rev1 (vc-read-revision rev1-prompt files backend rev1-default)) - (rev2 (vc-read-revision rev2-prompt files backend rev2-default))) - (when (string= rev1 "") (setq rev1 nil)) - (when (string= rev2 "") (setq rev2 nil)) - (list files rev1 rev2)))) - ;; All that was just so we could do argument completion! - (when (and (not rev1) rev2) - (error "Not a valid revision range")) - ;; Yes, it's painful to call (vc-deduce-fileset) again. Alas, the - ;; placement rules for (interactive) don't actually leave us a choice. - (vc-diff-internal t (vc-deduce-fileset t) rev1 rev2 - (called-interactively-p 'interactive))) - -;;;###autoload -(defun vc-diff (historic &optional not-urgent) - "Display diffs between file revisions. -Normally this compares the currently selected fileset with their -working revisions. With a prefix argument HISTORIC, it reads two revision -designators specifying which revisions to compare. - -The optional argument NOT-URGENT non-nil means it is ok to say no to -saving the buffer." - (interactive (list current-prefix-arg t)) - (if historic - (call-interactively 'vc-version-diff) - (when buffer-file-name (vc-buffer-sync not-urgent)) - (vc-diff-internal t (vc-deduce-fileset t) nil nil - (called-interactively-p 'interactive)))) - -;;;###autoload -(defun vc-root-diff (historic &optional not-urgent) - "Display diffs between VC-controlled whole tree revisions. -Normally, this compares the tree corresponding to the current -fileset with the working revision. -With a prefix argument HISTORIC, prompt for two revision -designators specifying which revisions to compare. - -The optional argument NOT-URGENT non-nil means it is ok to say no to -saving the buffer." - (interactive (list current-prefix-arg t)) - (if historic - ;; FIXME: this does not work right, `vc-version-diff' ends up - ;; calling `vc-deduce-fileset' to find the files to diff, and - ;; that's not what we want here, we want the diff for the VC root dir. - (call-interactively 'vc-version-diff) - (when buffer-file-name (vc-buffer-sync not-urgent)) - (let ((backend - (cond ((derived-mode-p 'vc-dir-mode) vc-dir-backend) - ((derived-mode-p 'dired-mode) (vc-responsible-backend default-directory)) - (vc-mode (vc-backend buffer-file-name)))) - rootdir working-revision) - (unless backend - (error "Buffer is not version controlled")) - (setq rootdir (vc-call-backend backend 'root default-directory)) - (setq working-revision (vc-working-revision rootdir)) - ;; VC diff for the root directory produces output that is - ;; relative to it. Bind default-directory to the root directory - ;; here, this way the *vc-diff* buffer is setup correctly, so - ;; relative file names work. - (let ((default-directory rootdir)) - (vc-diff-internal - t (list backend (list rootdir) working-revision) nil nil - (called-interactively-p 'interactive)))))) - -;;;###autoload -(defun vc-revision-other-window (rev) - "Visit revision REV of the current file in another window. -If the current file is named `F', the revision is named `F.~REV~'. -If `F.~REV~' already exists, use it instead of checking it out again." - (interactive - (save-current-buffer - (vc-ensure-vc-buffer) - (list - (vc-read-revision "Revision to visit (default is working revision): " - (list buffer-file-name))))) - (vc-ensure-vc-buffer) - (let* ((file buffer-file-name) - (revision (if (string-equal rev "") - (vc-working-revision file) - rev))) - (switch-to-buffer-other-window (vc-find-revision file revision)))) - -(defun vc-find-revision (file revision) - "Read REVISION of FILE into a buffer and return the buffer." - (let ((automatic-backup (vc-version-backup-file-name file revision)) - (filebuf (or (get-file-buffer file) (current-buffer))) - (filename (vc-version-backup-file-name file revision 'manual))) - (unless (file-exists-p filename) - (if (file-exists-p automatic-backup) - (rename-file automatic-backup filename nil) - (message "Checking out %s..." filename) - (with-current-buffer filebuf - (let ((failed t)) - (unwind-protect - (let ((coding-system-for-read 'no-conversion) - (coding-system-for-write 'no-conversion)) - (with-temp-file filename - (let ((outbuf (current-buffer))) - ;; Change buffer to get local value of - ;; vc-checkout-switches. - (with-current-buffer filebuf - (vc-call find-revision file revision outbuf)))) - (setq failed nil)) - (when (and failed (file-exists-p filename)) - (delete-file filename)))) - (vc-mode-line file)) - (message "Checking out %s...done" filename))) - (let ((result-buf (find-file-noselect filename))) - (with-current-buffer result-buf - ;; Set the parent buffer so that things like - ;; C-x v g, C-x v l, ... etc work. - (set (make-local-variable 'vc-parent-buffer) filebuf)) - result-buf))) - -;; Header-insertion code - -;;;###autoload -(defun vc-insert-headers () - "Insert headers into a file for use with a version control system. -Headers desired are inserted at point, and are pulled from -the variable `vc-BACKEND-header'." - (interactive) - (vc-ensure-vc-buffer) - (save-excursion - (save-restriction - (widen) - (when (or (not (vc-check-headers)) - (y-or-n-p "Version headers already exist. Insert another set? ")) - (let* ((delims (cdr (assq major-mode vc-comment-alist))) - (comment-start-vc (or (car delims) comment-start "#")) - (comment-end-vc (or (car (cdr delims)) comment-end "")) - (hdsym (vc-make-backend-sym (vc-backend buffer-file-name) - 'header)) - (hdstrings (and (boundp hdsym) (symbol-value hdsym)))) - (dolist (s hdstrings) - (insert comment-start-vc "\t" s "\t" - comment-end-vc "\n")) - (when vc-static-header-alist - (dolist (f vc-static-header-alist) - (when (string-match (car f) buffer-file-name) - (insert (format (cdr f) (car hdstrings))))))))))) - -(defun vc-clear-headers (&optional file) - "Clear all version headers in the current buffer (or FILE). -The headers are reset to their non-expanded form." - (let* ((filename (or file buffer-file-name)) - (visited (find-buffer-visiting filename)) - (backend (vc-backend filename))) - (when (vc-find-backend-function backend 'clear-headers) - (if visited - (let ((context (vc-buffer-context))) - ;; save-excursion may be able to relocate point and mark - ;; properly. If it fails, vc-restore-buffer-context - ;; will give it a second try. - (save-excursion - (vc-call-backend backend 'clear-headers)) - (vc-restore-buffer-context context)) - (set-buffer (find-file-noselect filename)) - (vc-call-backend backend 'clear-headers) - (kill-buffer filename))))) - -(defun vc-modify-change-comment (files rev oldcomment) - "Edit the comment associated with the given files and revision." - ;; Less of a kluge than it looks like; log-view mode only passes - ;; this function a singleton list. Arguments left in this form in - ;; case the more general operation ever becomes meaningful. - (let ((backend (vc-responsible-backend (car files)))) - (vc-start-logentry - files oldcomment t - "Enter a replacement change comment." - "*VC-log*" - (lambda () (vc-call-backend backend 'log-edit-mode)) - (lexical-let ((rev rev)) - (lambda (files comment) - (vc-call-backend backend - 'modify-change-comment files rev comment)))))) - -;;;###autoload -(defun vc-merge () - "Merge changes between two revisions into the current buffer's file. -This asks for two revisions to merge from in the minibuffer. If the -first revision is a branch number, then merge all changes from that -branch. If the first revision is empty, merge news, i.e. recent changes -from the current branch. - -See Info node `Merging'." - (interactive) - (vc-ensure-vc-buffer) - (vc-buffer-sync) - (let* ((file buffer-file-name) - (backend (vc-backend file)) - (state (vc-state file)) - first-revision second-revision status) - (cond - ((stringp state) ;; Locking VCses only - (error "File is locked by %s" state)) - ((not (vc-editable-p file)) - (if (y-or-n-p - "File must be checked out for merging. Check out now? ") - (vc-checkout file t) - (error "Merge aborted")))) - (setq first-revision - (vc-read-revision - (concat "Branch or revision to merge from " - "(default news on current branch): ") - (list file) - backend)) - (if (string= first-revision "") - (setq status (vc-call-backend backend 'merge-news file)) - (if (not (vc-find-backend-function backend 'merge)) - (error "Sorry, merging is not implemented for %s" backend) - (if (not (vc-branch-p first-revision)) - (setq second-revision - (vc-read-revision - "Second revision: " - (list file) backend nil - ;; FIXME: This is CVS/RCS/SCCS specific. - (concat (vc-branch-part first-revision) "."))) - ;; We want to merge an entire branch. Set revisions - ;; accordingly, so that vc-BACKEND-merge understands us. - (setq second-revision first-revision) - ;; first-revision must be the starting point of the branch - (setq first-revision (vc-branch-part first-revision))) - (setq status (vc-call-backend backend 'merge file - first-revision second-revision)))) - (vc-maybe-resolve-conflicts file status "WORKFILE" "MERGE SOURCE"))) - -(defun vc-maybe-resolve-conflicts (file status &optional name-A name-B) - (vc-resynch-buffer file t (not (buffer-modified-p))) - (if (zerop status) (message "Merge successful") - (smerge-mode 1) - (message "File contains conflicts."))) - -;;;###autoload -(defalias 'vc-resolve-conflicts 'smerge-ediff) - -;; TODO: This is OK but maybe we could integrate it better. -;; E.g. it could be run semi-automatically (via a prompt?) when saving a file -;; that was conflicted (i.e. upon mark-resolved). -;; FIXME: should we add an "other-window" version? Or maybe we should -;; hook it inside find-file so it automatically works for -;; find-file-other-window as well. E.g. find-file could use a new -;; `default-next-file' variable for its default file (M-n), and -;; we could then set it upon mark-resolve, so C-x C-s C-x C-f M-n would -;; automatically offer the next conflicted file. -(defun vc-find-conflicted-file () - "Visit the next conflicted file in the current project." - (interactive) - (let* ((backend (or (if buffer-file-name (vc-backend buffer-file-name)) - (vc-responsible-backend default-directory) - (error "No VC backend"))) - (files (vc-call-backend backend - 'conflicted-files default-directory))) - ;; Don't try and visit the current file. - (if (equal (car files) buffer-file-name) (pop files)) - (if (null files) - (message "No more conflicted files") - (find-file (pop files)) - (message "%s more conflicted files after this one" - (if files (length files) "No"))))) - -;; Named-configuration entry points - -(defun vc-tag-precondition (dir) - "Scan the tree below DIR, looking for files not up-to-date. -If any file is not up-to-date, return the name of the first such file. -\(This means, neither tag creation nor retrieval is allowed.\) -If one or more of the files are currently visited, return `visited'. -Otherwise, return nil." - (let ((status nil)) - (catch 'vc-locked-example - (vc-file-tree-walk - dir - (lambda (f) - (if (not (vc-up-to-date-p f)) (throw 'vc-locked-example f) - (when (get-file-buffer f) (setq status 'visited))))) - status))) - -;;;###autoload -(defun vc-create-tag (dir name branchp) - "Descending recursively from DIR, make a tag called NAME. -For each registered file, the working revision becomes part of -the named configuration. If the prefix argument BRANCHP is -given, the tag is made as a new branch and the files are -checked out in that new branch." - (interactive - (let ((granularity - (vc-call-backend (vc-responsible-backend default-directory) - 'revision-granularity))) - (list - (if (eq granularity 'repository) - ;; For VC's that do not work at file level, it's pointless - ;; to ask for a directory, branches are created at repository level. - default-directory - (read-file-name "Directory: " default-directory default-directory t)) - (read-string (if current-prefix-arg "New branch name: " "New tag name: ")) - current-prefix-arg))) - (message "Making %s... " (if branchp "branch" "tag")) - (when (file-directory-p dir) (setq dir (file-name-as-directory dir))) - (vc-call-backend (vc-responsible-backend dir) - 'create-tag dir name branchp) - (vc-resynch-buffer dir t t t) - (message "Making %s... done" (if branchp "branch" "tag"))) - -;;;###autoload -(defun vc-retrieve-tag (dir name) - "Descending recursively from DIR, retrieve the tag called NAME. -If NAME is empty, it refers to the latest revisions. -If locking is used for the files in DIR, then there must not be any -locked files at or below DIR (but if NAME is empty, locked files are -allowed and simply skipped)." - (interactive - (let ((granularity - (vc-call-backend (vc-responsible-backend default-directory) - 'revision-granularity))) - (list - (if (eq granularity 'repository) - ;; For VC's that do not work at file level, it's pointless - ;; to ask for a directory, branches are created at repository level. - default-directory - (read-file-name "Directory: " default-directory default-directory t)) - (read-string "Tag name to retrieve (default latest revisions): ")))) - (let ((update (yes-or-no-p "Update any affected buffers? ")) - (msg (if (or (not name) (string= name "")) - (format "Updating %s... " (abbreviate-file-name dir)) - (format "Retrieving tag into %s... " - (abbreviate-file-name dir))))) - (message "%s" msg) - (vc-call-backend (vc-responsible-backend dir) - 'retrieve-tag dir name update) - (vc-resynch-buffer dir t t t) - (message "%s" (concat msg "done")))) - - -;; Miscellaneous other entry points - -;; FIXME: this should be a defcustom -;; FIXME: maybe add another choice: -;; `root-directory' (or somesuch), which would mean show a short log -;; for the root directory. -(defvar vc-log-short-style '(directory) - "Whether or not to show a short log. -If it contains `directory' then if the fileset contains a directory show a short log. -If it contains `file' then show short logs for files. -Not all VC backends support short logs!") - -(defvar log-view-vc-backend) -(defvar log-view-vc-fileset) - -(defun vc-print-log-setup-buttons (working-revision is-start-revision limit pl-return) - (when (and limit (not (eq 'limit-unsupported pl-return)) - (not is-start-revision)) - (goto-char (point-max)) - (lexical-let ((working-revision working-revision) - (limit limit)) - (widget-create 'push-button - :notify (lambda (&rest ignore) - (vc-print-log-internal - log-view-vc-backend log-view-vc-fileset - working-revision nil (* 2 limit))) - :help-echo "Show the log again, and double the number of log entries shown" - "Show 2X entries") - (widget-insert " ") - (widget-create 'push-button - :notify (lambda (&rest ignore) - (vc-print-log-internal - log-view-vc-backend log-view-vc-fileset - working-revision nil nil)) - :help-echo "Show the log again, showing all entries" - "Show unlimited entries")) - (widget-setup))) - -(defun vc-print-log-internal (backend files working-revision - &optional is-start-revision limit) - ;; Don't switch to the output buffer before running the command, - ;; so that any buffer-local settings in the vc-controlled - ;; buffer can be accessed by the command. - (let ((dir-present nil) - (vc-short-log nil) - (buffer-name "*vc-change-log*") - type - pl-return) - (dolist (file files) - (when (file-directory-p file) - (setq dir-present t))) - (setq vc-short-log - (not (null (if dir-present - (memq 'directory vc-log-short-style) - (memq 'file vc-log-short-style))))) - (setq type (if vc-short-log 'short 'long)) - (lexical-let - ((working-revision working-revision) - (limit limit) - (shortlog vc-short-log) - (is-start-revision is-start-revision)) - (vc-log-internal-common - backend buffer-name files type - (lambda (bk buf type-arg files-arg) - (vc-call-backend bk 'print-log files-arg buf - shortlog (when is-start-revision working-revision) limit)) - (lambda (bk files-arg ret) - (vc-print-log-setup-buttons working-revision - is-start-revision limit ret)) - (lambda (bk) - (vc-call-backend bk 'show-log-entry working-revision)))))) - -(defvar vc-log-view-type nil - "Set this to differentiate the different types of logs.") -(put 'vc-log-view-type 'permanent-local t) - -(defun vc-log-internal-common (backend - buffer-name - files - type - backend-func - setup-buttons-func - goto-location-func) - (let (retval) - (with-current-buffer (get-buffer-create buffer-name) - (set (make-local-variable 'vc-log-view-type) type)) - (setq retval (funcall backend-func backend buffer-name type files)) - (pop-to-buffer buffer-name) - (let ((inhibit-read-only t)) - ;; log-view-mode used to be called with inhibit-read-only bound - ;; to t, so let's keep doing it, just in case. - (vc-call-backend backend 'log-view-mode) - (set (make-local-variable 'log-view-vc-backend) backend) - (set (make-local-variable 'log-view-vc-fileset) files)) - (vc-exec-after - `(let ((inhibit-read-only t)) - (funcall ',setup-buttons-func ',backend ',files ',retval) - (shrink-window-if-larger-than-buffer) - (funcall ',goto-location-func ',backend) - (setq vc-sentinel-movepoint (point)) - (set-buffer-modified-p nil))))) - -(defun vc-incoming-outgoing-internal (backend remote-location buffer-name type) - (vc-log-internal-common - backend buffer-name nil type - (lexical-let - ((remote-location remote-location)) - (lambda (bk buf type-arg files) - (vc-call-backend bk type-arg buf remote-location))) - (lambda (bk files-arg ret)) - (lambda (bk) - (goto-char (point-min))))) - -;;;###autoload -(defun vc-print-log (&optional working-revision limit) - "List the change log of the current fileset in a window. -If WORKING-REVISION is non-nil, leave point at that revision. -If LIMIT is non-nil, it should be a number specifying the maximum -number of revisions to show; the default is `vc-log-show-limit'. - -When called interactively with a prefix argument, prompt for -WORKING-REVISION and LIMIT." - (interactive - (cond - (current-prefix-arg - (let ((rev (read-from-minibuffer "Log from revision (default: last revision): " nil - nil nil nil)) - (lim (string-to-number - (read-from-minibuffer - "Limit display (unlimited: 0): " - (format "%s" vc-log-show-limit) - nil nil nil)))) - (when (string= rev "") (setq rev nil)) - (when (<= lim 0) (setq lim nil)) - (list rev lim))) - (t - (list nil (when (> vc-log-show-limit 0) vc-log-show-limit))))) - (let* ((vc-fileset (vc-deduce-fileset t)) ;FIXME: Why t? --Stef - (backend (car vc-fileset)) - (files (cadr vc-fileset)) - (working-revision (or working-revision (vc-working-revision (car files))))) - (vc-print-log-internal backend files working-revision nil limit))) - -;;;###autoload -(defun vc-print-root-log (&optional limit) - "List the change log for the current VC controlled tree in a window. -If LIMIT is non-nil, it should be a number specifying the maximum -number of revisions to show; the default is `vc-log-show-limit'. -When called interactively with a prefix argument, prompt for LIMIT." - (interactive - (cond - (current-prefix-arg - (let ((lim (string-to-number - (read-from-minibuffer - "Limit display (unlimited: 0): " - (format "%s" vc-log-show-limit) - nil nil nil)))) - (when (<= lim 0) (setq lim nil)) - (list lim))) - (t - (list (when (> vc-log-show-limit 0) vc-log-show-limit))))) - (let ((backend - (cond ((derived-mode-p 'vc-dir-mode) vc-dir-backend) - ((derived-mode-p 'dired-mode) (vc-responsible-backend default-directory)) - (vc-mode (vc-backend buffer-file-name)))) - rootdir working-revision) - (unless backend - (error "Buffer is not version controlled")) - (setq rootdir (vc-call-backend backend 'root default-directory)) - (setq working-revision (vc-working-revision rootdir)) - (vc-print-log-internal backend (list rootdir) working-revision nil limit))) - -;;;###autoload -(defun vc-log-incoming (&optional remote-location) - "Show a log of changes that will be received with a pull operation from REMOTE-LOCATION." - (interactive "sRemote location (empty for default): ") - (let ((backend - (cond ((derived-mode-p 'vc-dir-mode) vc-dir-backend) - ((derived-mode-p 'dired-mode) (vc-responsible-backend default-directory)) - (vc-mode (vc-backend buffer-file-name)))) - rootdir working-revision) - (unless backend - (error "Buffer is not version controlled")) - (vc-incoming-outgoing-internal backend remote-location "*vc-incoming*" 'log-incoming))) - -;;;###autoload -(defun vc-log-outgoing (&optional remote-location) - "Show a log of changes that will be sent with a push operation to REMOTE-LOCATION." - (interactive "sRemote location (empty for default): ") - (let ((backend - (cond ((derived-mode-p 'vc-dir-mode) vc-dir-backend) - ((derived-mode-p 'dired-mode) (vc-responsible-backend default-directory)) - (vc-mode (vc-backend buffer-file-name)))) - rootdir working-revision) - (unless backend - (error "Buffer is not version controlled")) - (vc-incoming-outgoing-internal backend remote-location "*vc-outgoing*" 'log-outgoing))) - -;;;###autoload -(defun vc-revert () - "Revert working copies of the selected fileset to their repository contents. -This asks for confirmation if the buffer contents are not identical -to the working revision (except for keyword expansion)." - (interactive) - (let* ((vc-fileset (vc-deduce-fileset)) - (files (cadr vc-fileset))) - ;; If any of the files is visited by the current buffer, make - ;; sure buffer is saved. If the user says `no', abort since - ;; we cannot show the changes and ask for confirmation to - ;; discard them. - (when (or (not files) (memq (buffer-file-name) files)) - (vc-buffer-sync nil)) - (dolist (file files) - (let ((buf (get-file-buffer file))) - (when (and buf (buffer-modified-p buf)) - (error "Please kill or save all modified buffers before reverting"))) - (when (vc-up-to-date-p file) - (unless (yes-or-no-p (format "%s seems up-to-date. Revert anyway? " file)) - (error "Revert canceled")))) - (when (vc-diff-internal vc-allow-async-revert vc-fileset nil nil) - (unless (yes-or-no-p - (format "Discard changes in %s? " - (let ((str (vc-delistify files)) - (nfiles (length files))) - (if (< (length str) 50) - str - (format "%d file%s" nfiles - (if (= nfiles 1) "" "s")))))) - (error "Revert canceled")) - (delete-windows-on "*vc-diff*") - (kill-buffer "*vc-diff*")) - (dolist (file files) - (message "Reverting %s..." (vc-delistify files)) - (vc-revert-file file) - (message "Reverting %s...done" (vc-delistify files))))) - -;;;###autoload -(defun vc-rollback () - "Roll back (remove) the most recent changeset committed to the repository. -This may be either a file-level or a repository-level operation, -depending on the underlying version-control system." - (interactive) - (let* ((vc-fileset (vc-deduce-fileset)) - (backend (car vc-fileset)) - (files (cadr vc-fileset)) - (granularity (vc-call-backend backend 'revision-granularity))) - (unless (vc-find-backend-function backend 'rollback) - (error "Rollback is not supported in %s" backend)) - (when (and (not (eq granularity 'repository)) (/= (length files) 1)) - (error "Rollback requires a singleton fileset or repository versioning")) - ;; FIXME: latest-on-branch-p should take the fileset. - (when (not (vc-call-backend backend 'latest-on-branch-p (car files))) - (error "Rollback is only possible at the tip revision")) - ;; If any of the files is visited by the current buffer, make - ;; sure buffer is saved. If the user says `no', abort since - ;; we cannot show the changes and ask for confirmation to - ;; discard them. - (when (or (not files) (memq (buffer-file-name) files)) - (vc-buffer-sync nil)) - (dolist (file files) - (when (buffer-modified-p (get-file-buffer file)) - (error "Please kill or save all modified buffers before rollback")) - (when (not (vc-up-to-date-p file)) - (error "Please revert all modified workfiles before rollback"))) - ;; Accumulate changes associated with the fileset - (vc-setup-buffer "*vc-diff*") - (not-modified) - (message "Finding changes...") - (let* ((tip (vc-working-revision (car files))) - ;; FIXME: `previous-revision' should take the fileset. - (previous (vc-call-backend backend 'previous-revision - (car files) tip))) - (vc-diff-internal nil vc-fileset previous tip)) - ;; Display changes - (unless (yes-or-no-p "Discard these revisions? ") - (error "Rollback canceled")) - (delete-windows-on "*vc-diff*") - (kill-buffer"*vc-diff*") - ;; Do the actual reversions - (message "Rolling back %s..." (vc-delistify files)) - (with-vc-properties - files - (vc-call-backend backend 'rollback files) - `((vc-state . ,'up-to-date) - (vc-checkout-time . , (nth 5 (file-attributes file))) - (vc-working-revision . nil))) - (dolist (f files) (vc-resynch-buffer f t t)) - (message "Rolling back %s...done" (vc-delistify files)))) - -;;;###autoload -(define-obsolete-function-alias 'vc-revert-buffer 'vc-revert "23.1") - -;;;###autoload -(defun vc-update () - "Update the current fileset's files to their tip revisions. -For each one that contains no changes, and is not locked, then this simply -replaces the work file with the latest revision on its branch. If the file -contains changes, and the backend supports merging news, then any recent -changes from the current branch are merged into the working file." - (interactive) - (let* ((vc-fileset (vc-deduce-fileset)) - (backend (car vc-fileset)) - (files (cadr vc-fileset))) - (save-some-buffers ; save buffers visiting files - nil (lambda () - (and (buffer-modified-p) - (let ((file (buffer-file-name))) - (and file (member file files)))))) - (dolist (file files) - (if (vc-up-to-date-p file) - (vc-checkout file nil t) - (if (eq (vc-checkout-model backend (list file)) 'locking) - (if (eq (vc-state file) 'edited) - (error "%s" - (substitute-command-keys - "File is locked--type \\[vc-revert] to discard changes")) - (error "Unexpected file state (%s) -- type %s" - (vc-state file) - (substitute-command-keys - "\\[vc-next-action] to correct"))) - (vc-maybe-resolve-conflicts - file (vc-call-backend backend 'merge-news file))))))) - -(defun vc-version-backup-file (file &optional rev) - "Return name of backup file for revision REV of FILE. -If version backups should be used for FILE, and there exists -such a backup for REV or the working revision of file, return -its name; otherwise return nil." - (when (vc-call make-version-backups-p file) - (let ((backup-file (vc-version-backup-file-name file rev))) - (if (file-exists-p backup-file) - backup-file - ;; there is no automatic backup, but maybe the user made one manually - (setq backup-file (vc-version-backup-file-name file rev 'manual)) - (when (file-exists-p backup-file) - backup-file))))) - -(defun vc-revert-file (file) - "Revert FILE back to the repository working revision it was based on." - (with-vc-properties - (list file) - (let ((backup-file (vc-version-backup-file file))) - (when backup-file - (copy-file backup-file file 'ok-if-already-exists 'keep-date) - (vc-delete-automatic-version-backups file)) - (vc-call revert file backup-file)) - `((vc-state . up-to-date) - (vc-checkout-time . ,(nth 5 (file-attributes file))))) - (vc-resynch-buffer file t t)) - -;;;###autoload -(defun vc-switch-backend (file backend) - "Make BACKEND the current version control system for FILE. -FILE must already be registered in BACKEND. The change is not -permanent, only for the current session. This function only changes -VC's perspective on FILE, it does not register or unregister it. -By default, this command cycles through the registered backends. -To get a prompt, use a prefix argument." - (interactive - (list - (or buffer-file-name - (error "There is no version-controlled file in this buffer")) - (let ((crt-bk (vc-backend buffer-file-name)) - (backends nil)) - (unless crt-bk - (error "File %s is not under version control" buffer-file-name)) - ;; Find the registered backends. - (dolist (crt vc-handled-backends) - (when (and (vc-call-backend crt 'registered buffer-file-name) - (not (eq crt-bk crt))) - (push crt backends))) - ;; Find the next backend. - (let ((def (car backends)) - (others backends)) - (cond - ((null others) (error "No other backend to switch to")) - (current-prefix-arg - (intern - (upcase - (completing-read - (format "Switch to backend [%s]: " def) - (mapcar (lambda (b) (list (downcase (symbol-name b)))) backends) - nil t nil nil (downcase (symbol-name def)))))) - (t def)))))) - (unless (eq backend (vc-backend file)) - (vc-file-clearprops file) - (vc-file-setprop file 'vc-backend backend) - ;; Force recomputation of the state - (unless (vc-call-backend backend 'registered file) - (vc-file-clearprops file) - (error "%s is not registered in %s" file backend)) - (vc-mode-line file))) - -;;;###autoload -(defun vc-transfer-file (file new-backend) - "Transfer FILE to another version control system NEW-BACKEND. -If NEW-BACKEND has a higher precedence than FILE's current backend -\(i.e. it comes earlier in `vc-handled-backends'), then register FILE in -NEW-BACKEND, using the revision number from the current backend as the -base level. If NEW-BACKEND has a lower precedence than the current -backend, then commit all changes that were made under the current -backend to NEW-BACKEND, and unregister FILE from the current backend. -\(If FILE is not yet registered under NEW-BACKEND, register it.)" - (let* ((old-backend (vc-backend file)) - (edited (memq (vc-state file) '(edited needs-merge))) - (registered (vc-call-backend new-backend 'registered file)) - (move - (and registered ; Never move if not registered in new-backend yet. - ;; move if new-backend comes later in vc-handled-backends - (or (memq new-backend (memq old-backend vc-handled-backends)) - (y-or-n-p "Final transfer? ")))) - (comment nil)) - (when (eq old-backend new-backend) - (error "%s is the current backend of %s" new-backend file)) - (if registered - (set-file-modes file (logior (file-modes file) 128)) - ;; `registered' might have switched under us. - (vc-switch-backend file old-backend) - (let* ((rev (vc-working-revision file)) - (modified-file (and edited (make-temp-file file))) - (unmodified-file (and modified-file (vc-version-backup-file file)))) - ;; Go back to the base unmodified file. - (unwind-protect - (progn - (when modified-file - (copy-file file modified-file 'ok-if-already-exists) - ;; If we have a local copy of the unmodified file, handle that - ;; here and not in vc-revert-file because we don't want to - ;; delete that copy -- it is still useful for OLD-BACKEND. - (if unmodified-file - (copy-file unmodified-file file - 'ok-if-already-exists 'keep-date) - (when (y-or-n-p "Get base revision from repository? ") - (vc-revert-file file)))) - (vc-call-backend new-backend 'receive-file file rev)) - (when modified-file - (vc-switch-backend file new-backend) - (unless (eq (vc-checkout-model new-backend (list file)) 'implicit) - (vc-checkout file t nil)) - (rename-file modified-file file 'ok-if-already-exists) - (vc-file-setprop file 'vc-checkout-time nil))))) - (when move - (vc-switch-backend file old-backend) - (setq comment (vc-call-backend old-backend 'comment-history file)) - (vc-call-backend old-backend 'unregister file)) - (vc-switch-backend file new-backend) - (when (or move edited) - (vc-file-setprop file 'vc-state 'edited) - (vc-mode-line file new-backend) - (vc-checkin file new-backend nil comment (stringp comment))))) - -(defun vc-rename-master (oldmaster newfile templates) - "Rename OLDMASTER to be the master file for NEWFILE based on TEMPLATES." - (let* ((dir (file-name-directory (expand-file-name oldmaster))) - (newdir (or (file-name-directory newfile) "")) - (newbase (file-name-nondirectory newfile)) - (masters - ;; List of potential master files for `newfile' - (mapcar - (lambda (s) (vc-possible-master s newdir newbase)) - templates))) - (when (or (file-symlink-p oldmaster) - (file-symlink-p (file-name-directory oldmaster))) - (error "This is unsafe in the presence of symbolic links")) - (rename-file - oldmaster - (catch 'found - ;; If possible, keep the master file in the same directory. - (dolist (f masters) - (when (and f (string= (file-name-directory (expand-file-name f)) dir)) - (throw 'found f))) - ;; If not, just use the first possible place. - (dolist (f masters) - (and f (or (not (setq dir (file-name-directory f))) - (file-directory-p dir)) - (throw 'found f))) - (error "New file lacks a version control directory"))))) - -;;;###autoload -(defun vc-delete-file (file) - "Delete file and mark it as such in the version control system." - (interactive "fVC delete file: ") - (setq file (expand-file-name file)) - (let ((buf (get-file-buffer file)) - (backend (vc-backend file))) - (unless backend - (error "File %s is not under version control" - (file-name-nondirectory file))) - (unless (vc-find-backend-function backend 'delete-file) - (error "Deleting files under %s is not supported in VC" backend)) - (when (and buf (buffer-modified-p buf)) - (error "Please save or undo your changes before deleting %s" file)) - (let ((state (vc-state file))) - (when (eq state 'edited) - (error "Please commit or undo your changes before deleting %s" file)) - (when (eq state 'conflict) - (error "Please resolve the conflicts before deleting %s" file))) - (unless (y-or-n-p (format "Really want to delete %s? " - (file-name-nondirectory file))) - (error "Abort!")) - (unless (or (file-directory-p file) (null make-backup-files) - (not (file-exists-p file))) - (with-current-buffer (or buf (find-file-noselect file)) - (let ((backup-inhibited nil)) - (backup-buffer)))) - ;; Bind `default-directory' so that the command that the backend - ;; runs to remove the file is invoked in the correct context. - (let ((default-directory (file-name-directory file))) - (vc-call-backend backend 'delete-file file)) - ;; If the backend hasn't deleted the file itself, let's do it for him. - (when (file-exists-p file) (delete-file file)) - ;; Forget what VC knew about the file. - (vc-file-clearprops file) - ;; Make sure the buffer is deleted and the *vc-dir* buffers are - ;; updated after this. - (vc-resynch-buffer file nil t))) - -;;;###autoload -(defun vc-rename-file (old new) - "Rename file OLD to NEW in both work area and repository." - (interactive "fVC rename file: \nFRename to: ") - ;; in CL I would have said (setq new (merge-pathnames new old)) - (let ((old-base (file-name-nondirectory old))) - (when (and (not (string= "" old-base)) - (string= "" (file-name-nondirectory new))) - (setq new (concat new old-base)))) - (let ((oldbuf (get-file-buffer old))) - (when (and oldbuf (buffer-modified-p oldbuf)) - (error "Please save files before moving them")) - (when (get-file-buffer new) - (error "Already editing new file name")) - (when (file-exists-p new) - (error "New file already exists")) - (let ((state (vc-state old))) - (unless (memq state '(up-to-date edited)) - (error "Please %s files before moving them" - (if (stringp state) "check in" "update")))) - (vc-call rename-file old new) - (vc-file-clearprops old) - ;; Move the actual file (unless the backend did it already) - (when (file-exists-p old) (rename-file old new)) - ;; ?? Renaming a file might change its contents due to keyword expansion. - ;; We should really check out a new copy if the old copy was precisely equal - ;; to some checked-in revision. However, testing for this is tricky.... - (when oldbuf - (with-current-buffer oldbuf - (let ((buffer-read-only buffer-read-only)) - (set-visited-file-name new)) - (vc-mode-line new (vc-backend new)) - (set-buffer-modified-p nil))))) - -;;;###autoload -(defun vc-update-change-log (&rest args) - "Find change log file and add entries from recent version control logs. -Normally, find log entries for all registered files in the default -directory. - -With prefix arg of \\[universal-argument], only find log entries for the current buffer's file. - -With any numeric prefix arg, find log entries for all currently visited -files that are under version control. This puts all the entries in the -log for the default directory, which may not be appropriate. - -From a program, any ARGS are assumed to be filenames for which -log entries should be gathered." - (interactive - (cond ((consp current-prefix-arg) ;C-u - (list buffer-file-name)) - (current-prefix-arg ;Numeric argument. - (let ((files nil) - (buffers (buffer-list)) - file) - (while buffers - (setq file (buffer-file-name (car buffers))) - (and file (vc-backend file) - (setq files (cons file files))) - (setq buffers (cdr buffers))) - files)) - (t - ;; Don't supply any filenames to backend; this means - ;; it should find all relevant files relative to - ;; the default-directory. - nil))) - (vc-call-backend (vc-responsible-backend default-directory) - 'update-changelog args)) - -;; functions that operate on RCS revision numbers. This code should -;; also be moved into the backends. It stays for now, however, since -;; it is used in code below. -(defun vc-branch-p (rev) - "Return t if REV is a branch revision." - (not (eq nil (string-match "\\`[0-9]+\\(\\.[0-9]+\\.[0-9]+\\)*\\'" rev)))) - -;;;###autoload -(defun vc-branch-part (rev) - "Return the branch part of a revision number REV." - (let ((index (string-match "\\.[0-9]+\\'" rev))) - (when index - (substring rev 0 index)))) - -(define-obsolete-function-alias - 'vc-default-previous-version 'vc-default-previous-revision "23.1") - -(defun vc-default-responsible-p (backend file) - "Indicate whether BACKEND is reponsible for FILE. -The default is to return nil always." - nil) - -(defun vc-default-could-register (backend file) - "Return non-nil if BACKEND could be used to register FILE. -The default implementation returns t for all files." - t) - -(defun vc-default-latest-on-branch-p (backend file) - "Return non-nil if FILE is the latest on its branch. -This default implementation always returns non-nil, which means that -editing non-current revisions is not supported by default." - t) - -(defun vc-default-init-revision (backend) vc-default-init-revision) - -(defun vc-default-find-revision (backend file rev buffer) - "Provide the new `find-revision' op based on the old `checkout' op. -This is only for compatibility with old backends. They should be updated -to provide the `find-revision' operation instead." - (let ((tmpfile (make-temp-file (expand-file-name file)))) - (unwind-protect - (progn - (vc-call-backend backend 'checkout file nil rev tmpfile) - (with-current-buffer buffer - (insert-file-contents-literally tmpfile))) - (delete-file tmpfile)))) - -(defun vc-default-rename-file (backend old new) - (condition-case nil - (add-name-to-file old new) - (error (rename-file old new))) - (vc-delete-file old) - (with-current-buffer (find-file-noselect new) - (vc-register))) - -(defalias 'vc-default-check-headers 'ignore) - -(declare-function log-edit-mode "log-edit" ()) - -(defun vc-default-log-edit-mode (backend) (log-edit-mode)) - -(defun vc-default-log-view-mode (backend) (log-view-mode)) - -(defun vc-default-show-log-entry (backend rev) - (with-no-warnings - (log-view-goto-rev rev))) - -(defun vc-default-comment-history (backend file) - "Return a string with all log entries stored in BACKEND for FILE." - (when (vc-find-backend-function backend 'print-log) - (with-current-buffer "*vc*" - (vc-call-backend backend 'print-log (list file)) - (buffer-string)))) - -(defun vc-default-receive-file (backend file rev) - "Let BACKEND receive FILE from another version control system." - (vc-call-backend backend 'register (list file) rev "")) - -(defun vc-default-retrieve-tag (backend dir name update) - (if (string= name "") - (progn - (vc-file-tree-walk - dir - (lambda (f) (and - (vc-up-to-date-p f) - (vc-error-occurred - (vc-call-backend backend 'checkout f nil "") - (when update (vc-resynch-buffer f t t))))))) - (let ((result (vc-tag-precondition dir))) - (if (stringp result) - (error "File %s is locked" result) - (setq update (and (eq result 'visited) update)) - (vc-file-tree-walk - dir - (lambda (f) (vc-error-occurred - (vc-call-backend backend 'checkout f nil name) - (when update (vc-resynch-buffer f t t))))))))) - -(defun vc-default-revert (backend file contents-done) - (unless contents-done - (let ((rev (vc-working-revision file)) - (file-buffer (or (get-file-buffer file) (current-buffer)))) - (message "Checking out %s..." file) - (let ((failed t) - (backup-name (car (find-backup-file-name file)))) - (when backup-name - (copy-file file backup-name 'ok-if-already-exists 'keep-date) - (unless (file-writable-p file) - (set-file-modes file (logior (file-modes file) 128)))) - (unwind-protect - (let ((coding-system-for-read 'no-conversion) - (coding-system-for-write 'no-conversion)) - (with-temp-file file - (let ((outbuf (current-buffer))) - ;; Change buffer to get local value of vc-checkout-switches. - (with-current-buffer file-buffer - (let ((default-directory (file-name-directory file))) - (vc-call-backend backend 'find-revision - file rev outbuf))))) - (setq failed nil)) - (when backup-name - (if failed - (rename-file backup-name file 'ok-if-already-exists) - (and (not vc-make-backup-files) (delete-file backup-name)))))) - (message "Checking out %s...done" file)))) - -(defalias 'vc-default-revision-completion-table 'ignore) -(defalias 'vc-default-mark-resolved 'ignore) - -(defun vc-default-dir-status-files (backend dir files default-state update-function) - (funcall update-function - (mapcar (lambda (file) (list file default-state)) files))) - -(defun vc-check-headers () - "Check if the current file has any headers in it." - (interactive) - (vc-call-backend (vc-backend buffer-file-name) 'check-headers)) - - - -;; These things should probably be generally available - -(defun vc-string-prefix-p (prefix string) - (let ((lpref (length prefix))) - (and (>= (length string) lpref) - (eq t (compare-strings prefix nil nil string nil lpref))))) - -(defun vc-file-tree-walk (dirname func &rest args) - "Walk recursively through DIRNAME. -Invoke FUNC f ARGS on each VC-managed file f underneath it." - (vc-file-tree-walk-internal (expand-file-name dirname) func args) - (message "Traversing directory %s...done" dirname)) - -(defun vc-file-tree-walk-internal (file func args) - (if (not (file-directory-p file)) - (when (vc-backend file) (apply func file args)) - (message "Traversing directory %s..." (abbreviate-file-name file)) - (let ((dir (file-name-as-directory file))) - (mapcar - (lambda (f) (or - (string-equal f ".") - (string-equal f "..") - (member f vc-directory-exclusion-list) - (let ((dirf (expand-file-name f dir))) - (or - (file-symlink-p dirf) ;; Avoid possible loops. - (vc-file-tree-walk-internal dirf func args))))) - (directory-files dir))))) - -(provide 'vc) - -;; arch-tag: ca82c1de-3091-4e26-af92-460abc6213a6 -;;; vc.el ends here diff -r 681cd08dc0f7 -r e93288477c43 lisp/vc/add-log.el --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/lisp/vc/add-log.el Sun Jun 13 22:57:55 2010 +0000 @@ -0,0 +1,1365 @@ +;;; add-log.el --- change log maintenance commands for Emacs + +;; Copyright (C) 1985, 1986, 1988, 1993, 1994, 1997, 1998, 2000, 2001, +;; 2002, 2003, 2004, 2005, 2006, 2007, 2008, 2009, 2010 +;; Free Software Foundation, Inc. + +;; Maintainer: FSF +;; Keywords: vc tools + +;; 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 3 of the License, 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. If not, see . + +;;; Commentary: + +;; This facility is documented in the Emacs Manual. + +;; Todo: + +;; - Find/use/create _MTN/log if there's a _MTN directory. +;; - Find/use/create ++log.* if there's an {arch} directory. +;; - Use an open *VC-Log* or *cvs-commit* buffer if it's related to the +;; source file. +;; - Don't add TAB indents (and username?) if inserting entries in those +;; special places. + +;;; Code: + +(eval-when-compile + (require 'timezone)) + +(defgroup change-log nil + "Change log maintenance." + :group 'tools + :link '(custom-manual "(emacs)Change Log") + :prefix "change-log-" + :prefix "add-log-") + + +(defcustom change-log-default-name nil + "Name of a change log file for \\[add-change-log-entry]." + :type '(choice (const :tag "default" nil) + string) + :group 'change-log) +;;;###autoload +(put 'change-log-default-name 'safe-local-variable 'string-or-null-p) + +(defcustom change-log-mode-hook nil + "Normal hook run by `change-log-mode'." + :type 'hook + :group 'change-log) + +;; Many modes set this variable, so avoid warnings. +;;;###autoload +(defcustom add-log-current-defun-function nil + "If non-nil, function to guess name of surrounding function. +It is used by `add-log-current-defun' in preference to built-in rules. +Returns function's name as a string, or nil if outside a function." + :type '(choice (const nil) function) + :group 'change-log) + +;;;###autoload +(defcustom add-log-full-name nil + "Full name of user, for inclusion in ChangeLog daily headers. +This defaults to the value returned by the function `user-full-name'." + :type '(choice (const :tag "Default" nil) + string) + :group 'change-log) + +;;;###autoload +(defcustom add-log-mailing-address nil + "Email addresses of user, for inclusion in ChangeLog headers. +This defaults to the value of `user-mail-address'. In addition to +being a simple string, this value can also be a list. All elements +will be recognized as referring to the same user; when creating a new +ChangeLog entry, one element will be chosen at random." + :type '(choice (const :tag "Default" nil) + (string :tag "String") + (repeat :tag "List of Strings" string)) + :group 'change-log) + +(defcustom add-log-time-format 'add-log-iso8601-time-string + "Function that defines the time format. +For example, `add-log-iso8601-time-string', which gives the +date in international ISO 8601 format, +and `current-time-string' are two valid values." + :type '(radio (const :tag "International ISO 8601 format" + add-log-iso8601-time-string) + (const :tag "Old format, as returned by `current-time-string'" + current-time-string) + (function :tag "Other")) + :group 'change-log) + +(defcustom add-log-keep-changes-together nil + "If non-nil, normally keep day's log entries for one file together. + +Log entries for a given file made with \\[add-change-log-entry] or +\\[add-change-log-entry-other-window] will only be added to others \ +for that file made +today if this variable is non-nil or that file comes first in today's +entries. Otherwise another entry for that file will be started. An +original log: + + * foo (...): ... + * bar (...): change 1 + +in the latter case, \\[add-change-log-entry-other-window] in a \ +buffer visiting `bar', yields: + + * bar (...): -!- + * foo (...): ... + * bar (...): change 1 + +and in the former: + + * foo (...): ... + * bar (...): change 1 + (...): -!- + +The NEW-ENTRY arg to `add-change-log-entry' can override the effect of +this variable." + :version "20.3" + :type 'boolean + :group 'change-log) + +(defcustom add-log-always-start-new-record nil + "If non-nil, `add-change-log-entry' will always start a new record." + :version "22.1" + :type 'boolean + :group 'change-log) + +(defcustom add-log-buffer-file-name-function nil + "If non-nil, function to call to identify the full filename of a buffer. +This function is called with no argument. If this is nil, the default is to +use `buffer-file-name'." + :type '(choice (const nil) function) + :group 'change-log) + +(defcustom add-log-file-name-function nil + "If non-nil, function to call to identify the filename for a ChangeLog entry. +This function is called with one argument, the value of variable +`buffer-file-name' in that buffer. If this is nil, the default is to +use the file's name relative to the directory of the change log file." + :type '(choice (const nil) function) + :group 'change-log) + + +(defcustom change-log-version-info-enabled nil + "If non-nil, enable recording version numbers with the changes." + :version "21.1" + :type 'boolean + :group 'change-log) + +(defcustom change-log-version-number-regexp-list + (let ((re "\\([0-9]+\.[0-9.]+\\)")) + (list + ;; (defconst ad-version "2.15" + (concat "^(def[^ \t\n]+[ \t]+[^ \t\n][ \t]\"" re) + ;; Revision: pcl-cvs.el,v 1.72 1999/09/05 20:21:54 monnier Exp + (concat "^;+ *Revision: +[^ \t\n]+[ \t]+" re))) + "List of regexps to search for version number. +The version number must be in group 1. +Note: The search is conducted only within 10%, at the beginning of the file." + :version "21.1" + :type '(repeat regexp) + :group 'change-log) + +(defface change-log-date + '((t (:inherit font-lock-string-face))) + "Face used to highlight dates in date lines." + :version "21.1" + :group 'change-log) +(define-obsolete-face-alias 'change-log-date-face 'change-log-date "22.1") + +(defface change-log-name + '((t (:inherit font-lock-constant-face))) + "Face for highlighting author names." + :version "21.1" + :group 'change-log) +(define-obsolete-face-alias 'change-log-name-face 'change-log-name "22.1") + +(defface change-log-email + '((t (:inherit font-lock-variable-name-face))) + "Face for highlighting author email addresses." + :version "21.1" + :group 'change-log) +(define-obsolete-face-alias 'change-log-email-face 'change-log-email "22.1") + +(defface change-log-file + '((t (:inherit font-lock-function-name-face))) + "Face for highlighting file names." + :version "21.1" + :group 'change-log) +(define-obsolete-face-alias 'change-log-file-face 'change-log-file "22.1") + +(defface change-log-list + '((t (:inherit font-lock-keyword-face))) + "Face for highlighting parenthesized lists of functions or variables." + :version "21.1" + :group 'change-log) +(define-obsolete-face-alias 'change-log-list-face 'change-log-list "22.1") + +(defface change-log-conditionals + '((t (:inherit font-lock-variable-name-face))) + "Face for highlighting conditionals of the form `[...]'." + :version "21.1" + :group 'change-log) +(define-obsolete-face-alias 'change-log-conditionals-face + 'change-log-conditionals "22.1") + +(defface change-log-function + '((t (:inherit font-lock-variable-name-face))) + "Face for highlighting items of the form `<....>'." + :version "21.1" + :group 'change-log) +(define-obsolete-face-alias 'change-log-function-face + 'change-log-function "22.1") + +(defface change-log-acknowledgement + '((t (:inherit font-lock-comment-face))) + "Face for highlighting acknowledgments." + :version "21.1" + :group 'change-log) +(define-obsolete-face-alias 'change-log-acknowledgement-face + 'change-log-acknowledgement "22.1") + +(defconst change-log-file-names-re "^\\( +\\|\t\\)\\* \\([^ ,:([\n]+\\)") +(defconst change-log-start-entry-re "^\\sw.........[0-9:+ ]*") + +(defvar change-log-font-lock-keywords + `(;; + ;; Date lines, new (2000-01-01) and old (Sat Jan 1 00:00:00 2000) styles. + ;; Fixme: this regepx is just an approximate one and may match + ;; wrongly with a non-date line existing as a random note. In + ;; addition, using any kind of fixed setting like this doesn't + ;; work if a user customizes add-log-time-format. + ("^[0-9-]+ +\\|^ \\{11,\\}\\|^\\(Sun\\|Mon\\|Tue\\|Wed\\|Thu\\|Fri\\|Sat\\) [A-z][a-z][a-z] [0-9:+ ]+" + (0 'change-log-date-face) + ;; Name and e-mail; some people put e-mail in parens, not angles. + ("\\([^<(]+?\\)[ \t]*[(<]\\([A-Za-z0-9_.+-]+@[A-Za-z0-9_.-]+\\)[>)]" nil nil + (1 'change-log-name) + (2 'change-log-email))) + ;; + ;; File names. + (,change-log-file-names-re + (2 'change-log-file) + ;; Possibly further names in a list: + ("\\=, \\([^ ,:([\n]+\\)" nil nil (1 'change-log-file)) + ;; Possibly a parenthesized list of names: + ("\\= (\\([^(),\n]+\\|(\\(setf\\|SETF\\) [^() ,\n]+)\\)" + nil nil (1 'change-log-list)) + ("\\=, *\\([^(),\n]+\\|(\\(setf\\|SETF\\) [^() ,\n]+)\\)" + nil nil (1 'change-log-list))) + ;; + ;; Function or variable names. + ("^\\( +\\|\t\\)(\\([^(),\n]+\\|(\\(setf\\|SETF\\) [^() ,\n]+)\\)" + (2 'change-log-list) + ("\\=, *\\([^(),\n]+\\|(\\(setf\\|SETF\\) [^() ,\n]+)\\)" nil nil + (1 'change-log-list))) + ;; + ;; Conditionals. + ("\\[!?\\([^]\n]+\\)\\]\\(:\\| (\\)" (1 'change-log-conditionals)) + ;; + ;; Function of change. + ("<\\([^>\n]+\\)>\\(:\\| (\\)" (1 'change-log-function)) + ;; + ;; Acknowledgements. + ;; Don't include plain "From" because that is vague; + ;; we want to encourage people to say something more specific. + ;; Note that the FSF does not use "Patches by"; our convention + ;; is to put the name of the author of the changes at the top + ;; of the change log entry. + ("\\(^\\( +\\|\t\\)\\| \\)\\(Patch\\(es\\)? by\\|Report\\(ed by\\| from\\)\\|Suggest\\(ed by\\|ion from\\)\\)" + 3 'change-log-acknowledgement)) + "Additional expressions to highlight in Change Log mode.") + +(defun change-log-search-file-name (where) + "Return the file-name for the change under point." + (save-excursion + (goto-char where) + (beginning-of-line 1) + (if (looking-at change-log-start-entry-re) + ;; We are at the start of an entry, search forward for a file + ;; name. + (progn + (re-search-forward change-log-file-names-re nil t) + (match-string-no-properties 2)) + (if (looking-at change-log-file-names-re) + ;; We found a file name. + (match-string-no-properties 2) + ;; Look backwards for either a file name or the log entry start. + (if (re-search-backward + (concat "\\(" change-log-start-entry-re + "\\)\\|\\(" + change-log-file-names-re "\\)") nil t) + (if (match-beginning 1) + ;; We got the start of the entry, look forward for a + ;; file name. + (progn + (re-search-forward change-log-file-names-re nil t) + (match-string-no-properties 2)) + (match-string-no-properties 4)) + ;; We must be before any file name, look forward. + (re-search-forward change-log-file-names-re nil t) + (match-string-no-properties 2)))))) + +(defun change-log-find-file () + "Visit the file for the change under point." + (interactive) + (let ((file (change-log-search-file-name (point)))) + (if (and file (file-exists-p file)) + (find-file file) + (message "No such file or directory: %s" file)))) + +(defun change-log-search-tag-name-1 (&optional from) + "Search for a tag name within subexpression 1 of last match. +Optional argument FROM specifies a buffer position where the tag +name should be located. Return value is a cons whose car is the +string representing the tag and whose cdr is the position where +the tag was found." + (save-restriction + (narrow-to-region (match-beginning 1) (match-end 1)) + (when from (goto-char from)) + ;; The regexp below skips any symbol near `point' (FROM) followed by + ;; whitespace and another symbol. This should skip, for example, + ;; "struct" in a specification like "(struct buffer)" and move to + ;; "buffer". A leading paren is ignored. + (when (looking-at + "[(]?\\(?:\\(?:\\sw\\|\\s_\\)+\\(?:[ \t]+\\(\\sw\\|\\s_\\)+\\)\\)") + (goto-char (match-beginning 1))) + (cons (find-tag-default) (point)))) + +(defconst change-log-tag-re + "(\\(\\(?:\\sw\\|\\s_\\)+\\(?:[, \t]+\\(?:\\sw\\|\\s_\\)+\\)*\\))" + "Regexp matching a tag name in change log entries.") + +(defun change-log-search-tag-name (&optional at) + "Search for a tag name near `point'. +Optional argument AT non-nil means search near buffer position AT. +Return value is a cons whose car is the string representing +the tag and whose cdr is the position where the tag was found." + (save-excursion + (goto-char (setq at (or at (point)))) + (save-restriction + (widen) + (or (condition-case nil + ;; Within parenthesized list? + (save-excursion + (backward-up-list) + (when (looking-at change-log-tag-re) + (change-log-search-tag-name-1 at))) + (error nil)) + (condition-case nil + ;; Before parenthesized list on same line? + (save-excursion + (when (and (skip-chars-forward " \t") + (looking-at change-log-tag-re)) + (change-log-search-tag-name-1))) + (error nil)) + (condition-case nil + ;; Near file name? + (save-excursion + (when (and (progn + (beginning-of-line) + (looking-at change-log-file-names-re)) + (goto-char (match-end 0)) + (skip-syntax-forward " ") + (looking-at change-log-tag-re)) + (change-log-search-tag-name-1))) + (error nil)) + (condition-case nil + ;; Anywhere else within current entry? + (let ((from + (save-excursion + (end-of-line) + (if (re-search-backward change-log-start-entry-re nil t) + (match-beginning 0) + (point-min)))) + (to + (save-excursion + (end-of-line) + (if (re-search-forward change-log-start-entry-re nil t) + (match-beginning 0) + (point-max))))) + (when (and (< from to) (<= from at) (<= at to)) + (save-restriction + ;; Narrow to current change log entry. + (narrow-to-region from to) + (cond + ((re-search-backward change-log-tag-re nil t) + (narrow-to-region (match-beginning 1) (match-end 1)) + (goto-char (point-max)) + (cons (find-tag-default) (point-max))) + ((re-search-forward change-log-tag-re nil t) + (narrow-to-region (match-beginning 1) (match-end 1)) + (goto-char (point-min)) + (cons (find-tag-default) (point-min))))))) + (error nil)))))) + +(defvar change-log-find-head nil) +(defvar change-log-find-tail nil) +(defvar change-log-find-window nil) + +(defun change-log-goto-source-1 (tag regexp file buffer + &optional window first last) + "Search for tag TAG in buffer BUFFER visiting file FILE. +REGEXP is a regular expression for TAG. The remaining arguments +are optional: WINDOW denotes the window to display the results of +the search. FIRST is a position in BUFFER denoting the first +match from previous searches for TAG. LAST is the position in +BUFFER denoting the last match for TAG in the last search." + (with-current-buffer buffer + (save-excursion + (save-restriction + (widen) + (if last + (progn + ;; When LAST is set make sure we continue from the next + ;; line end to not find the same tag again. + (goto-char last) + (end-of-line) + (condition-case nil + ;; Try to go to the end of the current defun to avoid + ;; false positives within the current defun's body + ;; since these would match `add-log-current-defun'. + (end-of-defun) + ;; Don't fall behind when `end-of-defun' fails. + (error (progn (goto-char last) (end-of-line)))) + (setq last nil)) + ;; When LAST was not set start at beginning of BUFFER. + (goto-char (point-min))) + (let (current-defun) + (while (and (not last) (re-search-forward regexp nil t)) + ;; Verify that `add-log-current-defun' invoked at the end + ;; of the match returns TAG. This heuristic works well + ;; whenever the name of the defun occurs within the first + ;; line of the defun. + (setq current-defun (add-log-current-defun)) + (when (and current-defun (string-equal current-defun tag)) + ;; Record this as last match. + (setq last (line-beginning-position)) + ;; Record this as first match when there's none. + (unless first (setq first last))))))) + (if (or last first) + (with-selected-window + (setq change-log-find-window (or window (display-buffer buffer))) + (if last + (progn + (when (or (< last (point-min)) (> last (point-max))) + ;; Widen to show TAG. + (widen)) + (push-mark) + (goto-char last)) + ;; When there are no more matches go (back) to FIRST. + (message "No more matches for tag `%s' in file `%s'" tag file) + (setq last first) + (goto-char first)) + ;; Return new "tail". + (list (selected-window) first last)) + (message "Source location of tag `%s' not found in file `%s'" tag file) + nil))) + +(defun change-log-goto-source () + "Go to source location of \"change log tag\" near `point'. +A change log tag is a symbol within a parenthesized, +comma-separated list. If no suitable tag can be found nearby, +try to visit the file for the change under `point' instead." + (interactive) + (if (and (eq last-command 'change-log-goto-source) + change-log-find-tail) + (setq change-log-find-tail + (condition-case nil + (apply 'change-log-goto-source-1 + (append change-log-find-head change-log-find-tail)) + (error + (format "Cannot find more matches for tag `%s' in file `%s'" + (car change-log-find-head) + (nth 2 change-log-find-head))))) + (save-excursion + (let* ((at (point)) + (tag-at (change-log-search-tag-name)) + (tag (car tag-at)) + (file (when tag-at (change-log-search-file-name (cdr tag-at)))) + (file-at (when file (match-beginning 2))) + ;; `file-2' is the file `change-log-search-file-name' finds + ;; at `point'. We use `file-2' as a fallback when `tag' or + ;; `file' are not suitable for some reason. + (file-2 (change-log-search-file-name at)) + (file-2-at (when file-2 (match-beginning 2)))) + (cond + ((and (or (not tag) (not file) (not (file-exists-p file))) + (or (not file-2) (not (file-exists-p file-2)))) + (error "Cannot find tag or file near `point'")) + ((and file-2 (file-exists-p file-2) + (or (not tag) (not file) (not (file-exists-p file)) + (and (or (and (< file-at file-2-at) (<= file-2-at at)) + (and (<= at file-2-at) (< file-2-at file-at)))))) + ;; We either have not found a suitable file name or `file-2' + ;; provides a "better" file name wrt `point'. Go to the + ;; buffer of `file-2' instead. + (setq change-log-find-window + (display-buffer (find-file-noselect file-2)))) + (t + (setq change-log-find-head + (list tag (concat "\\_<" (regexp-quote tag) "\\_>") + file (find-file-noselect file))) + (condition-case nil + (setq change-log-find-tail + (apply 'change-log-goto-source-1 change-log-find-head)) + (error + (format "Cannot find matches for tag `%s' in file `%s'" + tag file))))))))) + +(defun change-log-next-error (&optional argp reset) + "Move to the Nth (default 1) next match in a ChangeLog buffer. +Compatibility function for \\[next-error] invocations." + (interactive "p") + (let* ((argp (or argp 0)) + (count (abs argp)) ; how many cycles + (down (< argp 0)) ; are we going down? (is argp negative?) + (up (not down)) + (search-function (if up 're-search-forward 're-search-backward))) + + ;; set the starting position + (goto-char (cond (reset (point-min)) + (down (line-beginning-position)) + (up (line-end-position)) + ((point)))) + + (funcall search-function change-log-file-names-re nil t count)) + + (beginning-of-line) + ;; if we found a place to visit... + (when (looking-at change-log-file-names-re) + (let (change-log-find-window) + (change-log-goto-source) + (when change-log-find-window + ;; Select window displaying source file. + (select-window change-log-find-window))))) + +(defvar change-log-mode-map + (let ((map (make-sparse-keymap)) + (menu-map (make-sparse-keymap))) + (define-key map [?\C-c ?\C-p] 'add-log-edit-prev-comment) + (define-key map [?\C-c ?\C-n] 'add-log-edit-next-comment) + (define-key map [?\C-c ?\C-f] 'change-log-find-file) + (define-key map [?\C-c ?\C-c] 'change-log-goto-source) + (define-key map [menu-bar changelog] (cons "ChangeLog" menu-map)) + (define-key menu-map [gs] + '(menu-item "Go To Source" change-log-goto-source + :help "Go to source location of ChangeLog tag near point")) + (define-key menu-map [ff] + '(menu-item "Find File" change-log-find-file + :help "Visit the file for the change under point")) + (define-key menu-map [sep] '("--")) + (define-key menu-map [nx] + '(menu-item "Next Log-Edit Comment" add-log-edit-next-comment + :help "Cycle forward through Log-Edit mode comment history")) + (define-key menu-map [pr] + '(menu-item "Previous Log-Edit Comment" add-log-edit-prev-comment + :help "Cycle backward through Log-Edit mode comment history")) + map) + "Keymap for Change Log major mode.") + +;; It used to be called change-log-time-zone-rule but really should be +;; called add-log-time-zone-rule since it's only used from add-log-* code. +(defvaralias 'change-log-time-zone-rule 'add-log-time-zone-rule) +(defvar add-log-time-zone-rule nil + "Time zone used for calculating change log time stamps. +It takes the same format as the TZ argument of `set-time-zone-rule'. +If nil, use local time. +If t, use universal time.") +(put 'add-log-time-zone-rule 'safe-local-variable + '(lambda (x) (or (booleanp x) (stringp x)))) + +(defun add-log-iso8601-time-zone (&optional time) + (let* ((utc-offset (or (car (current-time-zone time)) 0)) + (sign (if (< utc-offset 0) ?- ?+)) + (sec (abs utc-offset)) + (ss (% sec 60)) + (min (/ sec 60)) + (mm (% min 60)) + (hh (/ min 60))) + (format (cond ((not (zerop ss)) "%c%02d:%02d:%02d") + ((not (zerop mm)) "%c%02d:%02d") + (t "%c%02d")) + sign hh mm ss))) + +(defvar add-log-iso8601-with-time-zone nil) + +(defun add-log-iso8601-time-string () + (let ((time (format-time-string "%Y-%m-%d" + nil (eq t add-log-time-zone-rule)))) + (if add-log-iso8601-with-time-zone + (concat time " " (add-log-iso8601-time-zone)) + time))) + +(defun change-log-name () + "Return (system-dependent) default name for a change log file." + (or change-log-default-name + "ChangeLog")) + +(defun add-log-edit-prev-comment (arg) + "Cycle backward through Log-Edit mode comment history. +With a numeric prefix ARG, go back ARG comments." + (interactive "*p") + (save-restriction + (narrow-to-region (point) + (if (memq last-command '(add-log-edit-prev-comment + add-log-edit-next-comment)) + (mark) (point))) + (when (fboundp 'log-edit-previous-comment) + (log-edit-previous-comment arg) + (indent-region (point-min) (point-max)) + (goto-char (point-min)) + (unless (save-restriction (widen) (bolp)) + (delete-region (point) (progn (skip-chars-forward " \t\n") (point)))) + (set-mark (point-min)) + (goto-char (point-max)) + (delete-region (point) (progn (skip-chars-backward " \t\n") (point)))))) + +(defun add-log-edit-next-comment (arg) + "Cycle forward through Log-Edit mode comment history. +With a numeric prefix ARG, go back ARG comments." + (interactive "*p") + (add-log-edit-prev-comment (- arg))) + +;;;###autoload +(defun prompt-for-change-log-name () + "Prompt for a change log name." + (let* ((default (change-log-name)) + (name (expand-file-name + (read-file-name (format "Log file (default %s): " default) + nil default)))) + ;; Handle something that is syntactically a directory name. + ;; Look for ChangeLog or whatever in that directory. + (if (string= (file-name-nondirectory name) "") + (expand-file-name (file-name-nondirectory default) + name) + ;; Handle specifying a file that is a directory. + (if (file-directory-p name) + (expand-file-name (file-name-nondirectory default) + (file-name-as-directory name)) + name)))) + +(defun change-log-version-number-search () + "Return version number of current buffer's file. +This is the value returned by `vc-working-revision' or, if that is +nil, by matching `change-log-version-number-regexp-list'." + (let* ((size (buffer-size)) + (limit + ;; The version number can be anywhere in the file, but + ;; restrict search to the file beginning: 10% should be + ;; enough to prevent some mishits. + ;; + ;; Apply percentage only if buffer size is bigger than + ;; approx 100 lines. + (if (> size (* 100 80)) (+ (point) (/ size 10))))) + (or (and buffer-file-name (vc-working-revision buffer-file-name)) + (save-restriction + (widen) + (let ((regexps change-log-version-number-regexp-list) + version) + (while regexps + (save-excursion + (goto-char (point-min)) + (when (re-search-forward (pop regexps) limit t) + (setq version (match-string 1) + regexps nil)))) + version))))) + +(declare-function diff-find-source-location "diff-mode" + (&optional other-file reverse noprompt)) + +;;;###autoload +(defun find-change-log (&optional file-name buffer-file) + "Find a change log file for \\[add-change-log-entry] and return the name. + +Optional arg FILE-NAME specifies the file to use. +If FILE-NAME is nil, use the value of `change-log-default-name'. +If `change-log-default-name' is nil, behave as though it were 'ChangeLog' +\(or whatever we use on this operating system). + +If `change-log-default-name' contains a leading directory component, then +simply find it in the current directory. Otherwise, search in the current +directory and its successive parents for a file so named. + +Once a file is found, `change-log-default-name' is set locally in the +current buffer to the complete file name. +Optional arg BUFFER-FILE overrides `buffer-file-name'." + ;; If we are called from a diff, first switch to the source buffer; + ;; in order to respect buffer-local settings of change-log-default-name, etc. + (with-current-buffer (let ((buff (if (eq major-mode 'diff-mode) + (car (ignore-errors + (diff-find-source-location)))))) + (if (buffer-live-p buff) buff + (current-buffer))) + ;; If user specified a file name or if this buffer knows which one to use, + ;; just use that. + (or file-name + (setq file-name (and change-log-default-name + (file-name-directory change-log-default-name) + change-log-default-name)) + (progn + ;; Chase links in the source file + ;; and use the change log in the dir where it points. + (setq file-name (or (and (or buffer-file buffer-file-name) + (file-name-directory + (file-chase-links + (or buffer-file buffer-file-name)))) + default-directory)) + (if (file-directory-p file-name) + (setq file-name (expand-file-name (change-log-name) file-name))) + ;; Chase links before visiting the file. + ;; This makes it easier to use a single change log file + ;; for several related directories. + (setq file-name (file-chase-links file-name)) + (setq file-name (expand-file-name file-name)) + ;; Move up in the dir hierarchy till we find a change log file. + (let ((file1 file-name) + parent-dir) + (while (and (not (or (get-file-buffer file1) (file-exists-p file1))) + (progn (setq parent-dir + (file-name-directory + (directory-file-name + (file-name-directory file1)))) + ;; Give up if we are already at the root dir. + (not (string= (file-name-directory file1) + parent-dir)))) + ;; Move up to the parent dir and try again. + (setq file1 (expand-file-name + (file-name-nondirectory (change-log-name)) + parent-dir))) + ;; If we found a change log in a parent, use that. + (if (or (get-file-buffer file1) (file-exists-p file1)) + (setq file-name file1))))) + ;; Make a local variable in this buffer so we needn't search again. + (set (make-local-variable 'change-log-default-name) file-name)) + file-name) + +(defun add-log-file-name (buffer-file log-file) + ;; Never want to add a change log entry for the ChangeLog file itself. + (unless (or (null buffer-file) (string= buffer-file log-file)) + (if add-log-file-name-function + (funcall add-log-file-name-function buffer-file) + (setq buffer-file + (file-relative-name buffer-file (file-name-directory log-file))) + ;; If we have a backup file, it's presumably because we're + ;; comparing old and new versions (e.g. for deleted + ;; functions) and we'll want to use the original name. + (if (backup-file-name-p buffer-file) + (file-name-sans-versions buffer-file) + buffer-file)))) + +;;;###autoload +(defun add-change-log-entry (&optional whoami file-name other-window new-entry + put-new-entry-on-new-line) + "Find change log file, and add an entry for today and an item for this file. +Optional arg WHOAMI (interactive prefix) non-nil means prompt for user +name and email (stored in `add-log-full-name' and `add-log-mailing-address'). + +Second arg FILE-NAME is file name of the change log. +If nil, use the value of `change-log-default-name'. + +Third arg OTHER-WINDOW non-nil means visit in other window. + +Fourth arg NEW-ENTRY non-nil means always create a new entry at the front; +never append to an existing entry. Option `add-log-keep-changes-together' +otherwise affects whether a new entry is created. + +Fifth arg PUT-NEW-ENTRY-ON-NEW-LINE non-nil means that if a new +entry is created, put it on a new line by itself, do not put it +after a comma on an existing line. + +Option `add-log-always-start-new-record' non-nil means always create a +new record, even when the last record was made on the same date and by +the same person. + +The change log file can start with a copyright notice and a copying +permission notice. The first blank line indicates the end of these +notices. + +Today's date is calculated according to `add-log-time-zone-rule' if +non-nil, otherwise in local time." + (interactive (list current-prefix-arg + (prompt-for-change-log-name))) + (let* ((defun (add-log-current-defun)) + (version (and change-log-version-info-enabled + (change-log-version-number-search))) + (buf-file-name (if add-log-buffer-file-name-function + (funcall add-log-buffer-file-name-function) + buffer-file-name)) + (buffer-file (if buf-file-name (expand-file-name buf-file-name))) + (file-name (expand-file-name (find-change-log file-name buffer-file))) + ;; Set ITEM to the file name to use in the new item. + (item (add-log-file-name buffer-file file-name))) + + (unless (equal file-name buffer-file-name) + (cond + ((equal file-name (buffer-file-name (window-buffer (selected-window)))) + ;; If the selected window already shows the desired buffer don't show + ;; it again (particularly important if other-window is true). + ;; This is important for diff-add-change-log-entries-other-window. + (set-buffer (window-buffer (selected-window)))) + ((or other-window (window-dedicated-p (selected-window))) + (find-file-other-window file-name)) + (t (find-file file-name)))) + (or (derived-mode-p 'change-log-mode) + (change-log-mode)) + (undo-boundary) + (goto-char (point-min)) + + (let ((full-name (or add-log-full-name (user-full-name))) + (mailing-address (or add-log-mailing-address user-mail-address))) + + (when whoami + (setq full-name (read-string "Full name: " full-name)) + ;; Note that some sites have room and phone number fields in + ;; full name which look silly when inserted. Rather than do + ;; anything about that here, let user give prefix argument so that + ;; s/he can edit the full name field in prompter if s/he wants. + (setq mailing-address + (read-string "Mailing address: " mailing-address))) + + ;; If file starts with a copyright and permission notice, skip them. + ;; Assume they end at first blank line. + (when (looking-at "Copyright") + (search-forward "\n\n") + (skip-chars-forward "\n")) + + ;; Advance into first entry if it is usable; else make new one. + (let ((new-entries + (mapcar (lambda (addr) + (concat + (if (stringp add-log-time-zone-rule) + (let ((tz (getenv "TZ"))) + (unwind-protect + (progn + (set-time-zone-rule add-log-time-zone-rule) + (funcall add-log-time-format)) + (set-time-zone-rule tz))) + (funcall add-log-time-format)) + " " full-name + " <" addr ">")) + (if (consp mailing-address) + mailing-address + (list mailing-address))))) + (if (and (not add-log-always-start-new-record) + (let ((hit nil)) + (dolist (entry new-entries hit) + (when (looking-at (regexp-quote entry)) + (setq hit t))))) + (forward-line 1) + (insert (nth (random (length new-entries)) + new-entries) + (if use-hard-newlines hard-newline "\n") + (if use-hard-newlines hard-newline "\n")) + (forward-line -1)))) + + ;; Determine where we should stop searching for a usable + ;; item to add to, within this entry. + (let ((bound + (save-excursion + (if (looking-at "\n*[^\n* \t]") + (skip-chars-forward "\n") + (if add-log-keep-changes-together + (forward-page) ; page delimits entries for date + (forward-paragraph))) ; paragraph delimits entries for file + (point)))) + + ;; Now insert the new line for this item. + (cond ((re-search-forward "^\\s *\\*\\s *$" bound t) + ;; Put this file name into the existing empty item. + (if item + (insert item))) + ((and (not new-entry) + (let (case-fold-search) + (re-search-forward + (concat (regexp-quote (concat "* " item)) + ;; Don't accept `foo.bar' when + ;; looking for `foo': + "\\(\\s \\|[(),:]\\)") + bound t))) + ;; Add to the existing item for the same file. + (re-search-forward "^\\s *$\\|^\\s \\*") + (goto-char (match-beginning 0)) + ;; Delete excess empty lines; make just 2. + (while (and (not (eobp)) (looking-at "^\\s *$")) + (delete-region (point) (line-beginning-position 2))) + (insert (if use-hard-newlines hard-newline "\n") + (if use-hard-newlines hard-newline "\n")) + (forward-line -2) + (indent-relative-maybe)) + (t + ;; Make a new item. + (while (looking-at "\\sW") + (forward-line 1)) + (while (and (not (eobp)) (looking-at "^\\s *$")) + (delete-region (point) (line-beginning-position 2))) + (insert (if use-hard-newlines hard-newline "\n") + (if use-hard-newlines hard-newline "\n") + (if use-hard-newlines hard-newline "\n")) + (forward-line -2) + (indent-to left-margin) + (insert "* ") + (if item (insert item))))) + ;; Now insert the function name, if we have one. + ;; Point is at the item for this file, + ;; either at the end of the line or at the first blank line. + (if (not defun) + ;; No function name, so put in a colon unless we have just a star. + (unless (save-excursion + (beginning-of-line 1) + (looking-at "\\s *\\(\\*\\s *\\)?$")) + (insert ": ") + (if version (insert version ?\s))) + ;; Make it easy to get rid of the function name. + (undo-boundary) + (unless (save-excursion + (beginning-of-line 1) + (looking-at "\\s *$")) + (insert ?\s)) + ;; See if the prev function name has a message yet or not. + ;; If not, merge the two items. + (let ((pos (point-marker))) + (skip-syntax-backward " ") + (skip-chars-backward "):") + (if (and (not put-new-entry-on-new-line) + (looking-at "):") + (let ((pos (save-excursion (backward-sexp 1) (point)))) + (when (equal (buffer-substring pos (point)) defun) + (delete-region pos (point))) + (> fill-column (+ (current-column) (length defun) 4)))) + (progn (skip-chars-backward ", ") + (delete-region (point) pos) + (unless (memq (char-before) '(?\()) (insert ", "))) + (when (and (not put-new-entry-on-new-line) (looking-at "):")) + (delete-region (+ 1 (point)) (line-end-position))) + (goto-char pos) + (insert "(")) + (set-marker pos nil)) + (insert defun "): ") + (if version (insert version ?\s))))) + +;;;###autoload +(defun add-change-log-entry-other-window (&optional whoami file-name) + "Find change log file in other window and add entry and item. +This is just like `add-change-log-entry' except that it displays +the change log file in another window." + (interactive (if current-prefix-arg + (list current-prefix-arg + (prompt-for-change-log-name)))) + (add-change-log-entry whoami file-name t)) + + +(defvar change-log-indent-text 0) + +(defun change-log-fill-parenthesized-list () + ;; Fill parenthesized lists of names according to GNU standards. + ;; * file-name.ext (very-long-foo, very-long-bar, very-long-foobar): + ;; should be filled as + ;; * file-name.ext (very-long-foo, very-long-bar) + ;; (very-long-foobar): + (save-excursion + (end-of-line 0) + (skip-chars-backward " \t") + (when (and (equal (char-before) ?\,) + (> (point) (1+ (point-min)))) + (condition-case nil + (when (save-excursion + (and (prog2 + (up-list -1) + (equal (char-after) ?\() + (skip-chars-backward " \t")) + (or (bolp) + ;; Skip everything but a whitespace or asterisk. + (and (not (zerop (skip-chars-backward "^ \t\n*"))) + (skip-chars-backward " \t") + ;; We want one asterisk here. + (= (skip-chars-backward "*") -1) + (skip-chars-backward " \t") + (bolp))))) + ;; Delete the comma. + (delete-char -1) + ;; Close list on previous line. + (insert ")") + (skip-chars-forward " \t\n") + ;; Start list on new line. + (insert-before-markers "(")) + (error nil))))) + +(defun change-log-indent () + (change-log-fill-parenthesized-list) + (let* ((indent + (save-excursion + (beginning-of-line) + (skip-chars-forward " \t") + (cond + ((and (looking-at "\\(.*\\) [^ \n].*[^ \n] <.*>\\(?: +(.*)\\)? *$") + ;; Matching the output of add-log-time-format is difficult, + ;; but I'll get it has at least two adjacent digits. + (string-match "[[:digit:]][[:digit:]]" (match-string 1))) + 0) + ((looking-at "[^*(]") + (+ (current-left-margin) change-log-indent-text)) + (t (current-left-margin))))) + (pos (save-excursion (indent-line-to indent) (point)))) + (if (> pos (point)) (goto-char pos)))) + + +(defvar smerge-resolve-function) +(defvar copyright-at-end-flag) + +;;;###autoload +(define-derived-mode change-log-mode text-mode "Change Log" + "Major mode for editing change logs; like Indented Text mode. +Prevents numeric backups and sets `left-margin' to 8 and `fill-column' to 74. +New log entries are usually made with \\[add-change-log-entry] or \\[add-change-log-entry-other-window]. +Each entry behaves as a paragraph, and the entries for one day as a page. +Runs `change-log-mode-hook'. +\n\\{change-log-mode-map}" + (setq left-margin 8 + fill-column 74 + indent-tabs-mode t + tab-width 8 + show-trailing-whitespace t) + (set (make-local-variable 'fill-forward-paragraph-function) + 'change-log-fill-forward-paragraph) + ;; Make sure we call `change-log-indent' when filling. + (set (make-local-variable 'fill-indent-according-to-mode) t) + ;; Avoid that filling leaves behind a single "*" on a line. + (add-hook 'fill-nobreak-predicate + '(lambda () + (looking-back "^\\s *\\*\\s *" (line-beginning-position))) + nil t) + (set (make-local-variable 'indent-line-function) 'change-log-indent) + (set (make-local-variable 'tab-always-indent) nil) + (set (make-local-variable 'copyright-at-end-flag) t) + ;; We really do want "^" in paragraph-start below: it is only the + ;; lines that begin at column 0 (despite the left-margin of 8) that + ;; we are looking for. Adding `* ' allows eliding the blank line + ;; between entries for different files. + (set (make-local-variable 'paragraph-start) "\\s *$\\|\f\\|^\\<") + (set (make-local-variable 'paragraph-separate) paragraph-start) + ;; Match null string on the date-line so that the date-line + ;; is grouped with what follows. + (set (make-local-variable 'page-delimiter) "^\\<\\|^\f") + (set (make-local-variable 'version-control) 'never) + (set (make-local-variable 'smerge-resolve-function) + 'change-log-resolve-conflict) + (set (make-local-variable 'adaptive-fill-regexp) "\\s *") + (set (make-local-variable 'font-lock-defaults) + '(change-log-font-lock-keywords t nil nil backward-paragraph)) + (set (make-local-variable 'multi-isearch-next-buffer-function) + 'change-log-next-buffer) + (set (make-local-variable 'beginning-of-defun-function) + 'change-log-beginning-of-defun) + (set (make-local-variable 'end-of-defun-function) + 'change-log-end-of-defun) + ;; next-error function glue + (setq next-error-function 'change-log-next-error) + (setq next-error-last-buffer (current-buffer))) + +(defun change-log-next-buffer (&optional buffer wrap) + "Return the next buffer in the series of ChangeLog file buffers. +This function is used for multiple buffers isearch. +A sequence of buffers is formed by ChangeLog files with decreasing +numeric file name suffixes in the directory of the initial ChangeLog +file were isearch was started." + (let* ((name (change-log-name)) + (files (cons name (sort (file-expand-wildcards + (concat name "[-.][0-9]*")) + (lambda (a b) + ;; The file's extension may not have a valid + ;; version form (e.g. VC backup revisions). + (ignore-errors + (version< (substring b (length name)) + (substring a (length name)))))))) + (files (if isearch-forward files (reverse files)))) + (find-file-noselect + (if wrap + (car files) + (cadr (member (file-name-nondirectory (buffer-file-name buffer)) + files)))))) + +(defun change-log-fill-forward-paragraph (n) + "Cut paragraphs so filling preserves open parentheses at beginning of lines." + (let (;; Add lines starting with whitespace followed by a left paren or an + ;; asterisk. + (paragraph-start (concat paragraph-start "\\|\\s *\\(?:\\s(\\|\\*\\)"))) + (forward-paragraph n))) + +(defcustom add-log-current-defun-header-regexp + "^\\([[:upper:]][[:upper:]_ ]*[[:upper:]_]\\|[-_[:alpha:]]+\\)[ \t]*[:=]" + "Heuristic regexp used by `add-log-current-defun' for unknown major modes. +The regexp's first submatch is placed in the ChangeLog entry, in +parentheses." + :type 'regexp + :group 'change-log) + +;;;###autoload +(defvar add-log-lisp-like-modes + '(emacs-lisp-mode lisp-mode scheme-mode dsssl-mode lisp-interaction-mode) + "*Modes that look like Lisp to `add-log-current-defun'.") + +;;;###autoload +(defvar add-log-c-like-modes + '(c-mode c++-mode c++-c-mode objc-mode) + "*Modes that look like C to `add-log-current-defun'.") + +;;;###autoload +(defvar add-log-tex-like-modes + '(TeX-mode plain-TeX-mode LaTeX-mode tex-mode) + "*Modes that look like TeX to `add-log-current-defun'.") + +(declare-function c-cpp-define-name "cc-cmds" ()) +(declare-function c-defun-name "cc-cmds" ()) + +;;;###autoload +(defun add-log-current-defun () + "Return name of function definition point is in, or nil. + +Understands C, Lisp, LaTeX (\"functions\" are chapters, sections, ...), +Texinfo (@node titles) and Perl. + +Other modes are handled by a heuristic that looks in the 10K before +point for uppercase headings starting in the first column or +identifiers followed by `:' or `='. See variables +`add-log-current-defun-header-regexp' and +`add-log-current-defun-function'. + +Has a preference of looking backwards." + (condition-case nil + (save-excursion + (let ((location (point))) + (cond (add-log-current-defun-function + (funcall add-log-current-defun-function)) + ((apply 'derived-mode-p add-log-lisp-like-modes) + ;; If we are now precisely at the beginning of a defun, + ;; make sure beginning-of-defun finds that one + ;; rather than the previous one. + (or (eobp) (forward-char 1)) + (beginning-of-defun) + ;; Make sure we are really inside the defun found, + ;; not after it. + (when (and (looking-at "\\s(") + (progn (end-of-defun) + (< location (point))) + (progn (forward-sexp -1) + (>= location (point)))) + (if (looking-at "\\s(") + (forward-char 1)) + ;; Skip the defining construct name, typically "defun" + ;; or "defvar". + (forward-sexp 1) + ;; The second element is usually a symbol being defined. + ;; If it is not, use the first symbol in it. + (skip-chars-forward " \t\n'(") + (buffer-substring-no-properties (point) + (progn (forward-sexp 1) + (point))))) + ((apply 'derived-mode-p add-log-c-like-modes) + (or (c-cpp-define-name) + (c-defun-name))) + ((memq major-mode add-log-tex-like-modes) + (if (re-search-backward + "\\\\\\(sub\\)*\\(section\\|paragraph\\|chapter\\)" + nil t) + (progn + (goto-char (match-beginning 0)) + (buffer-substring-no-properties + (1+ (point)) ; without initial backslash + (line-end-position))))) + ((derived-mode-p 'texinfo-mode) + (if (re-search-backward "^@node[ \t]+\\([^,\n]+\\)" nil t) + (match-string-no-properties 1))) + ((derived-mode-p 'perl-mode 'cperl-mode) + (if (re-search-backward "^sub[ \t]+\\([^({ \t\n]+\\)" nil t) + (match-string-no-properties 1))) + ;; Emacs's autoconf-mode installs its own + ;; `add-log-current-defun-function'. This applies to + ;; a different mode apparently for editing .m4 + ;; autoconf source. + ((derived-mode-p 'autoconf-mode) + (if (re-search-backward + "^\\(\\(m4_\\)?define\\|A._DEFUN\\)(\\[?\\([A-Za-z0-9_]+\\)" nil t) + (match-string-no-properties 3))) + (t + ;; If all else fails, try heuristics + (let (case-fold-search + result) + (end-of-line) + (when (re-search-backward + add-log-current-defun-header-regexp + (- (point) 10000) + t) + (setq result (or (match-string-no-properties 1) + (match-string-no-properties 0))) + ;; Strip whitespace away + (when (string-match "\\([^ \t\n\r\f].*[^ \t\n\r\f]\\)" + result) + (setq result (match-string-no-properties 1 result))) + result)))))) + (error nil))) + +(defvar change-log-get-method-definition-md) + +;; Subroutine used within change-log-get-method-definition. +;; Add the last match in the buffer to the end of `md', +;; followed by the string END; move to the end of that match. +(defun change-log-get-method-definition-1 (end) + (setq change-log-get-method-definition-md + (concat change-log-get-method-definition-md + (match-string 1) + end)) + (goto-char (match-end 0))) + +(defun change-log-get-method-definition () +"For Objective C, return the method name if we are in a method." + (let ((change-log-get-method-definition-md "[")) + (save-excursion + (if (re-search-backward "^@implementation\\s-*\\([A-Za-z_]*\\)" nil t) + (change-log-get-method-definition-1 " "))) + (save-excursion + (cond + ((re-search-forward "^\\([-+]\\)[ \t\n\f\r]*\\(([^)]*)\\)?\\s-*" nil t) + (change-log-get-method-definition-1 "") + (while (not (looking-at "[{;]")) + (looking-at + "\\([A-Za-z_]*:?\\)\\s-*\\(([^)]*)\\)?[A-Za-z_]*[ \t\n\f\r]*") + (change-log-get-method-definition-1 "")) + (concat change-log-get-method-definition-md "]")))))) + +(defun change-log-sortable-date-at () + "Return date of log entry in a consistent form for sorting. +Point is assumed to be at the start of the entry." + (require 'timezone) + (if (looking-at change-log-start-entry-re) + (let ((date (match-string-no-properties 0))) + (if date + (if (string-match "\\(....\\)-\\(..\\)-\\(..\\)\\s-+" date) + (concat (match-string 1 date) (match-string 2 date) + (match-string 3 date)) + (condition-case nil + (timezone-make-date-sortable date) + (error nil))))) + (error "Bad date"))) + +(defun change-log-resolve-conflict () + "Function to be used in `smerge-resolve-function'." + (save-excursion + (save-restriction + (narrow-to-region (match-beginning 0) (match-end 0)) + (let ((mb1 (match-beginning 1)) + (me1 (match-end 1)) + (mb3 (match-beginning 3)) + (me3 (match-end 3)) + (tmp1 (generate-new-buffer " *changelog-resolve-1*")) + (tmp2 (generate-new-buffer " *changelog-resolve-2*"))) + (unwind-protect + (let ((buf (current-buffer))) + (with-current-buffer tmp1 + (change-log-mode) + (insert-buffer-substring buf mb1 me1)) + (with-current-buffer tmp2 + (change-log-mode) + (insert-buffer-substring buf mb3 me3) + ;; Do the merge here instead of inside `buf' so as to be + ;; more robust in case change-log-merge fails. + (change-log-merge tmp1)) + (goto-char (point-max)) + (delete-region (point-min) + (prog1 (point) + (insert-buffer-substring tmp2)))) + (kill-buffer tmp1) + (kill-buffer tmp2)))))) + +;;;###autoload +(defun change-log-merge (other-log) + "Merge the contents of change log file OTHER-LOG with this buffer. +Both must be found in Change Log mode (since the merging depends on +the appropriate motion commands). OTHER-LOG can be either a file name +or a buffer. + +Entries are inserted in chronological order. Both the current and +old-style time formats for entries are supported." + (interactive "*fLog file name to merge: ") + (if (not (derived-mode-p 'change-log-mode)) + (error "Not in Change Log mode")) + (let ((other-buf (if (bufferp other-log) other-log + (find-file-noselect other-log))) + (buf (current-buffer)) + date1 start end) + (save-excursion + (goto-char (point-min)) + (set-buffer other-buf) + (goto-char (point-min)) + (if (not (derived-mode-p 'change-log-mode)) + (error "%s not found in Change Log mode" other-log)) + ;; Loop through all the entries in OTHER-LOG. + (while (not (eobp)) + (setq date1 (change-log-sortable-date-at)) + (setq start (point) + end (progn (forward-page) (point))) + ;; Look for an entry in original buffer that isn't later. + (with-current-buffer buf + (while (and (not (eobp)) + (string< date1 (change-log-sortable-date-at))) + (forward-page)) + (if (not (eobp)) + (insert-buffer-substring other-buf start end) + ;; At the end of the original buffer, insert a newline to + ;; separate entries and then the rest of the file being + ;; merged. + (unless (or (bobp) + (and (= ?\n (char-before)) + (or (<= (1- (point)) (point-min)) + (= ?\n (char-before (1- (point))))))) + (insert (if use-hard-newlines hard-newline "\n"))) + ;; Move to the end of it to terminate outer loop. + (with-current-buffer other-buf + (goto-char (point-max))) + (insert-buffer-substring other-buf start))))))) + +(defun change-log-beginning-of-defun () + (re-search-backward change-log-start-entry-re nil 'move)) + +(defun change-log-end-of-defun () + ;; Look back and if there is no entry there it means we are before + ;; the first ChangeLog entry, so go forward until finding one. + (unless (save-excursion (re-search-backward change-log-start-entry-re nil t)) + (re-search-forward change-log-start-entry-re nil t)) + + ;; In case we are at the end of log entry going forward a line will + ;; make us find the next entry when searching. If we are inside of + ;; an entry going forward a line will still keep the point inside + ;; the same entry. + (forward-line 1) + + ;; In case we are at the beginning of an entry, move past it. + (when (looking-at change-log-start-entry-re) + (goto-char (match-end 0)) + (forward-line 1)) + + ;; Search for the start of the next log entry. Go to the end of the + ;; buffer if we could not find a next entry. + (when (re-search-forward change-log-start-entry-re nil 'move) + (goto-char (match-beginning 0)) + (forward-line -1))) + +(provide 'add-log) + +;; arch-tag: 81eee6fc-088f-4372-a37f-80ad9620e762 +;;; add-log.el ends here diff -r 681cd08dc0f7 -r e93288477c43 lisp/vc/cvs-status.el --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/lisp/vc/cvs-status.el Sun Jun 13 22:57:55 2010 +0000 @@ -0,0 +1,540 @@ +;;; cvs-status.el --- major mode for browsing `cvs status' output -*- coding: utf-8 -*- + +;; Copyright (C) 1999, 2000, 2001, 2002, 2003, 2004, +;; 2005, 2006, 2007, 2008, 2009, 2010 Free Software Foundation, Inc. + +;; Author: Stefan Monnier +;; Keywords: pcl-cvs cvs status tree vc tools + +;; 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 3 of the License, 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. If not, see . + +;;; Commentary: + +;; Todo: + +;; - Somehow allow cvs-status-tree to work on-the-fly + +;;; Code: + +(eval-when-compile (require 'cl)) +(require 'pcvs-util) + +;;; + +(defgroup cvs-status nil + "Major mode for browsing `cvs status' output." + :group 'pcl-cvs + :prefix "cvs-status-") + +(easy-mmode-defmap cvs-status-mode-map + '(("n" . next-line) + ("p" . previous-line) + ("N" . cvs-status-next) + ("P" . cvs-status-prev) + ("\M-n" . cvs-status-next) + ("\M-p" . cvs-status-prev) + ("t" . cvs-status-cvstrees) + ("T" . cvs-status-trees) + (">" . cvs-mode-checkout)) + "CVS-Status' keymap." + :group 'cvs-status + :inherit 'cvs-mode-map) + +;;(easy-menu-define cvs-status-menu cvs-status-mode-map +;; "Menu for `cvs-status-mode'." +;; '("CVS-Status" +;; ["Show Tag Trees" cvs-status-tree t] +;; )) + +(defvar cvs-status-mode-hook nil + "Hook run at the end of `cvs-status-mode'.") + +(defconst cvs-status-tags-leader-re "^ Existing Tags:$") +(defconst cvs-status-entry-leader-re + "^File:\\s-+\\(?:no file \\)?\\(.*\\S-\\)\\s-+Status: \\(.+\\)$") +(defconst cvs-status-dir-re "^cvs[.ex]* [a-z]+: Examining \\(.+\\)$") +(defconst cvs-status-rev-re "[0-9][.0-9]*\\.[.0-9]*[0-9]") +(defconst cvs-status-tag-re "[ \t]\\([a-zA-Z][^ \t\n.]*\\)") + +(defconst cvs-status-font-lock-keywords + `((,cvs-status-entry-leader-re + (1 'cvs-filename) + (2 'cvs-need-action)) + (,cvs-status-tags-leader-re + (,cvs-status-rev-re + (save-excursion (re-search-forward "^\n" nil 'move) (point)) + (progn (re-search-backward cvs-status-tags-leader-re nil t) + (forward-line 1)) + (0 font-lock-comment-face)) + (,cvs-status-tag-re + (save-excursion (re-search-forward "^\n" nil 'move) (point)) + (progn (re-search-backward cvs-status-tags-leader-re nil t) + (forward-line 1)) + (1 font-lock-function-name-face))))) +(defconst cvs-status-font-lock-defaults + '(cvs-status-font-lock-keywords t nil nil nil (font-lock-multiline . t))) + +(defvar cvs-minor-wrap-function) +(put 'cvs-status-mode 'mode-class 'special) +;;;###autoload +(define-derived-mode cvs-status-mode fundamental-mode "CVS-Status" + "Mode used for cvs status output." + (set (make-local-variable 'font-lock-defaults) cvs-status-font-lock-defaults) + (set (make-local-variable 'cvs-minor-wrap-function) 'cvs-status-minor-wrap)) + +;; Define cvs-status-next and cvs-status-prev +(easy-mmode-define-navigation cvs-status cvs-status-entry-leader-re "entry") + +(defun cvs-status-current-file () + (save-excursion + (forward-line 1) + (or (re-search-backward cvs-status-entry-leader-re nil t) + (re-search-forward cvs-status-entry-leader-re)) + (let* ((file (match-string 1)) + (cvsdir (and (re-search-backward cvs-status-dir-re nil t) + (match-string 1))) + (pcldir (and (if (boundp 'cvs-pcl-cvs-dirchange-re) + (re-search-backward cvs-pcl-cvs-dirchange-re nil t)) + (match-string 1))) + (dir "")) + (let ((default-directory "")) + (when pcldir (setq dir (expand-file-name pcldir dir))) + (when cvsdir (setq dir (expand-file-name cvsdir dir))) + (expand-file-name file dir))))) + +(defun cvs-status-current-tag () + (save-excursion + (let ((pt (point)) + (col (current-column)) + (start (progn (re-search-backward cvs-status-tags-leader-re nil t) (point))) + (end (progn (re-search-forward "^$" nil t) (point)))) + (when (and (< start pt) (> end pt)) + (goto-char pt) + (end-of-line) + (let ((tag nil) (dist pt) (end (point))) + (beginning-of-line) + (while (re-search-forward cvs-status-tag-re end t) + (let* ((cole (current-column)) + (colb (save-excursion + (goto-char (match-beginning 1)) (current-column))) + (ndist (min (abs (- cole col)) (abs (- colb col))))) + (when (< ndist dist) + (setq dist ndist) + (setq tag (match-string 1))))) + tag))))) + +(defun cvs-status-minor-wrap (buf f) + (let ((data (with-current-buffer buf + (cons + (cons (cvs-status-current-file) + (cvs-status-current-tag)) + (when mark-active + (save-excursion + (goto-char (mark)) + (cons (cvs-status-current-file) + (cvs-status-current-tag)))))))) + (let ((cvs-branch-prefix (cdar data)) + (cvs-secondary-branch-prefix (and (cdar data) (cddr data))) + (cvs-minor-current-files + (cons (caar data) + (when (and (cadr data) (not (equal (caar data) (cadr data)))) + (list (cadr data))))) + ;; FIXME: I need to force because the fileinfos are UNKNOWN + (cvs-force-command "/F")) + (funcall f)))) + +;; +;; Tagelt, tag element +;; + +(defstruct (cvs-tag + (:constructor nil) + (:constructor cvs-tag-make + (vlist &optional name type)) + (:conc-name cvs-tag->)) + vlist + name + type) + +(defsubst cvs-status-vl-to-str (vl) (mapconcat 'number-to-string vl ".")) + +(defun cvs-tag->string (tag) + (if (stringp tag) tag + (let ((name (cvs-tag->name tag)) + (vl (cvs-tag->vlist tag))) + (if (null name) (cvs-status-vl-to-str vl) + (let ((rev (if vl (concat " (" (cvs-status-vl-to-str vl) ")") ""))) + (if (consp name) (mapcar (lambda (name) (concat name rev)) name) + (concat name rev))))))) + +(defun cvs-tag-compare-1 (vl1 vl2) + (cond + ((and (null vl1) (null vl2)) 'equal) + ((null vl1) 'more2) + ((null vl2) 'more1) + (t (let ((v1 (car vl1)) + (v2 (car vl2))) + (cond + ((> v1 v2) 'more1) + ((< v1 v2) 'more2) + (t (cvs-tag-compare-1 (cdr vl1) (cdr vl2)))))))) + +(defsubst cvs-tag-compare (tag1 tag2) + (cvs-tag-compare-1 (cvs-tag->vlist tag1) (cvs-tag->vlist tag2))) + +(defun cvs-tag-merge (tag1 tag2) + "Merge TAG1 and TAG2 into one." + (let ((type1 (cvs-tag->type tag1)) + (type2 (cvs-tag->type tag2)) + (name1 (cvs-tag->name tag1)) + (name2 (cvs-tag->name tag2))) + (unless (equal (cvs-tag->vlist tag1) (cvs-tag->vlist tag2)) + (setf (cvs-tag->vlist tag1) nil)) + (if type1 + (unless (or (not type2) (equal type1 type2)) + (setf (cvs-tag->type tag1) nil)) + (setf (cvs-tag->type tag1) type2)) + (if name1 + (setf (cvs-tag->name tag1) (cvs-append name1 name2)) + (setf (cvs-tag->name tag1) name2)) + tag1)) + +(defun cvs-tree-print (tags printer column) + "Print the tree of TAGS where each tag's string is given by PRINTER. +PRINTER should accept both a tag (in which case it should return a string) +or a string (in which case it should simply return its argument). +A tag cannot be a CONS. The return value can also be a list of strings, +if several nodes where merged into one. +The tree will be printed no closer than column COLUMN." + + (let* ((eol (save-excursion (end-of-line) (current-column))) + (column (max (+ eol 2) column))) + (if (null tags) column + ;;(move-to-column-force column) + (let* ((rev (cvs-car tags)) + (name (funcall printer (cvs-car rev))) + (rest (append (cvs-cdr name) (cvs-cdr tags))) + (prefix + (save-excursion + (or (= (forward-line 1) 0) (insert "\n")) + (cvs-tree-print rest printer column)))) + (assert (>= prefix column)) + (move-to-column prefix t) + (assert (eolp)) + (insert (cvs-car name)) + (dolist (br (cvs-cdr rev)) + (let* ((column (current-column)) + (brrev (funcall printer (cvs-car br))) + (brlength (length (cvs-car brrev))) + (brfill (concat (make-string (/ brlength 2) ? ) "|")) + (prefix + (save-excursion + (insert " -- ") + (cvs-tree-print (cvs-append brrev brfill (cvs-cdr br)) + printer (current-column))))) + (delete-region (save-excursion (move-to-column prefix) (point)) + (point)) + (insert " " (make-string (- prefix column 2) ?-) " ") + (end-of-line))) + prefix)))) + +(defun cvs-tree-merge (tree1 tree2) + "Merge tags trees TREE1 and TREE2 into one. +BEWARE: because of stability issues, this is not a symetric operation." + (assert (and (listp tree1) (listp tree2))) + (cond + ((null tree1) tree2) + ((null tree2) tree1) + (t + (let* ((rev1 (car tree1)) + (tag1 (cvs-car rev1)) + (vl1 (cvs-tag->vlist tag1)) + (l1 (length vl1)) + (rev2 (car tree2)) + (tag2 (cvs-car rev2)) + (vl2 (cvs-tag->vlist tag2)) + (l2 (length vl2))) + (cond + ((= l1 l2) + (case (cvs-tag-compare tag1 tag2) + (more1 (list* rev2 (cvs-tree-merge tree1 (cdr tree2)))) + (more2 (list* rev1 (cvs-tree-merge (cdr tree1) tree2))) + (equal + (cons (cons (cvs-tag-merge tag1 tag2) + (cvs-tree-merge (cvs-cdr rev1) (cvs-cdr rev2))) + (cvs-tree-merge (cdr tree1) (cdr tree2)))))) + ((> l1 l2) + (cvs-tree-merge + (list (cons (cvs-tag-make (butlast vl1)) tree1)) tree2)) + ((< l1 l2) + (cvs-tree-merge + tree1 (list (cons (cvs-tag-make (butlast vl2)) tree2))))))))) + +(defun cvs-tag-make-tag (tag) + (let ((vl (mapcar 'string-to-number (split-string (nth 2 tag) "\\.")))) + (cvs-tag-make vl (nth 0 tag) (intern (nth 1 tag))))) + +(defun cvs-tags->tree (tags) + "Make a tree out of a list of TAGS." + (let ((tags + (mapcar + (lambda (tag) + (let ((tag (cvs-tag-make-tag tag))) + (list (if (not (eq (cvs-tag->type tag) 'branch)) tag + (list (cvs-tag-make (butlast (cvs-tag->vlist tag))) + tag))))) + tags))) + (while (cdr tags) + (let (tl) + (while tags + (push (cvs-tree-merge (pop tags) (pop tags)) tl)) + (setq tags (nreverse tl)))) + (car tags))) + +(defun cvs-status-get-tags () + "Look for a list of tags, read them in and delete them. +Return nil if there was an empty list of tags and t if there wasn't +even a list. Else, return the list of tags where each element of +the list is a three-string list TAG, KIND, REV." + (let ((tags nil)) + (if (not (re-search-forward cvs-status-tags-leader-re nil t)) t + (forward-char 1) + (let ((pt (point)) + (lastrev nil) + (case-fold-search t)) + (or + (looking-at "\\s-+no\\s-+tags") + + (progn ; normal listing + (while (looking-at "^[ \t]+\\([^ \t\n]+\\)[ \t]+(\\([a-z]+\\): \\(.+\\))$") + (push (list (match-string 1) (match-string 2) (match-string 3)) tags) + (forward-line 1)) + (unless (looking-at "^$") (setq tags nil) (goto-char pt)) + tags) + + (progn ; cvstree-style listing + (while (or (looking-at "^ .+\\(.\\) \\([0-9.]+\\): \\([^\n\t .0-9][^\n\t ]*\\)?$") + (and lastrev + (looking-at "^ .+\\(\\) \\(8\\)? \\([^\n\t .0-9][^\n\t ]*\\)$"))) + (setq lastrev (or (match-string 2) lastrev)) + (push (list (match-string 3) + (if (equal (match-string 1) " ") "branch" "revision") + lastrev) tags) + (forward-line 1)) + (unless (looking-at "^$") (setq tags nil) (goto-char pt)) + (setq tags (nreverse tags))) + + (progn ; new tree style listing + (let* ((re-lead "[ \t]*\\(-+\\)?\\(|\n?[ \t]+\\)*") + (re3 (concat re-lead "\\(\\.\\)?\\(" cvs-status-rev-re "\\)")) + (re2 (concat re-lead cvs-status-tag-re "\\(\\)")) + (re1 (concat re-lead cvs-status-tag-re + " (\\(" cvs-status-rev-re "\\))"))) + (while (or (looking-at re1) (looking-at re2) (looking-at re3)) + (push (list (match-string 3) + (if (match-string 1) "branch" "revision") + (match-string 4)) tags) + (goto-char (match-end 0)) + (when (eolp) (forward-char 1)))) + (unless (looking-at "^$") (setq tags nil) (goto-char pt)) + (setq tags (nreverse tags)))) + + (delete-region pt (point))) + tags))) + +(defvar font-lock-mode) +;; (defun cvs-refontify (beg end) +;; (when (and (boundp 'font-lock-mode) +;; font-lock-mode +;; (fboundp 'font-lock-fontify-region)) +;; (font-lock-fontify-region (1- beg) (1+ end)))) + +(defun cvs-status-trees () + "Look for a lists of tags, and replace them with trees." + (interactive) + (save-excursion + (goto-char (point-min)) + (let ((inhibit-read-only t) + (tags nil)) + (while (listp (setq tags (cvs-status-get-tags))) + ;;(let ((pt (save-excursion (forward-line -1) (point)))) + (save-restriction + (narrow-to-region (point) (point)) + ;;(newline) + (combine-after-change-calls + (cvs-tree-print (cvs-tags->tree tags) 'cvs-tag->string 3))) + ;;(cvs-refontify pt (point)) + ;;(sit-for 0) + ;;) + )))) + +;;;; +;;;; CVSTree-style trees +;;;; + +(defvar cvs-tree-use-jisx0208 nil) ;Old compat var. +(defvar cvs-tree-use-charset + (cond + (cvs-tree-use-jisx0208 'jisx0208) + ((char-displayable-p ?━) 'unicode) + ((char-displayable-p (make-char 'japanese-jisx0208 40 44)) 'jisx0208)) + "*Non-nil if we should use the graphical glyphs from `japanese-jisx0208'. +Otherwise, default to ASCII chars like +, - and |.") + +(defconst cvs-tree-char-space + (case cvs-tree-use-charset + (jisx0208 (make-char 'japanese-jisx0208 33 33)) + (unicode " ") + (t " "))) +(defconst cvs-tree-char-hbar + (case cvs-tree-use-charset + (jisx0208 (make-char 'japanese-jisx0208 40 44)) + (unicode "━") + (t "--"))) +(defconst cvs-tree-char-vbar + (case cvs-tree-use-charset + (jisx0208 (make-char 'japanese-jisx0208 40 45)) + (unicode "┃") + (t "| "))) +(defconst cvs-tree-char-branch + (case cvs-tree-use-charset + (jisx0208 (make-char 'japanese-jisx0208 40 50)) + (unicode "┣") + (t "+-"))) +(defconst cvs-tree-char-eob ;end of branch + (case cvs-tree-use-charset + (jisx0208 (make-char 'japanese-jisx0208 40 49)) + (unicode "┗") + (t "`-"))) +(defconst cvs-tree-char-bob ;beginning of branch + (case cvs-tree-use-charset + (jisx0208 (make-char 'japanese-jisx0208 40 51)) + (unicode "┳") + (t "+-"))) + +(defun cvs-tag-lessp (tag1 tag2) + (eq (cvs-tag-compare tag1 tag2) 'more2)) + +(defvar cvs-tree-nomerge nil) + +(defun cvs-status-cvstrees (&optional arg) + "Look for a list of tags, and replace it with a tree. +Optional prefix ARG chooses between two representations." + (interactive "P") + (when (and cvs-tree-use-charset + (not enable-multibyte-characters)) + ;; We need to convert the buffer from unibyte to multibyte + ;; since we'll use multibyte chars for the tree. + (let ((modified (buffer-modified-p)) + (inhibit-read-only t) + (inhibit-modification-hooks t)) + (unwind-protect + (progn + (decode-coding-region (point-min) (point-max) 'undecided) + (set-buffer-multibyte t)) + (restore-buffer-modified-p modified)))) + (save-excursion + (goto-char (point-min)) + (let ((inhibit-read-only t) + (tags nil) + (cvs-tree-nomerge (if arg (not cvs-tree-nomerge) cvs-tree-nomerge))) + (while (listp (setq tags (cvs-status-get-tags))) + (let ((tags (mapcar 'cvs-tag-make-tag tags)) + ;;(pt (save-excursion (forward-line -1) (point))) + ) + (setq tags (sort tags 'cvs-tag-lessp)) + (let* ((first (car tags)) + (prev (if (cvs-tag-p first) + (list (car (cvs-tag->vlist first))) nil))) + (combine-after-change-calls + (cvs-tree-tags-insert tags prev)) + ;;(cvs-refontify pt (point)) + ;;(sit-for 0) + )))))) + +(defun cvs-tree-tags-insert (tags prev) + (when tags + (let* ((tag (car tags)) + (vlist (cvs-tag->vlist tag)) + (nprev ;"next prev" + (let* ((next (cvs-car (cadr tags))) + (nprev (if (and cvs-tree-nomerge next + (equal vlist (cvs-tag->vlist next))) + prev vlist))) + (cvs-map (lambda (v p) v) nprev prev))) + (after (save-excursion + (newline) + (cvs-tree-tags-insert (cdr tags) nprev))) + (pe t) ;"prev equal" + (nas nil)) ;"next afters" to be returned + (insert " ") + (do* ((vs vlist (cdr vs)) + (ps prev (cdr ps)) + (as after (cdr as))) + ((and (null as) (null vs) (null ps)) + (let ((revname (cvs-status-vl-to-str vlist))) + (if (cvs-every 'identity (cvs-map 'equal prev vlist)) + (insert (make-string (+ 4 (length revname)) ? ) + (or (cvs-tag->name tag) "")) + (insert " " revname ": " (or (cvs-tag->name tag) ""))))) + (let* ((eq (and pe (equal (car ps) (car vs)))) + (next-eq (equal (cadr ps) (cadr vs)))) + (let* ((na+char + (if (car as) + (if eq + (if next-eq (cons t cvs-tree-char-vbar) + (cons t cvs-tree-char-branch)) + (cons nil cvs-tree-char-bob)) + (if eq + (if next-eq (cons nil cvs-tree-char-space) + (cons t cvs-tree-char-eob)) + (cons nil (if (and (eq (cvs-tag->type tag) 'branch) + (cvs-every 'null as)) + cvs-tree-char-space + cvs-tree-char-hbar)))))) + (insert (cdr na+char)) + (push (car na+char) nas)) + (setq pe eq))) + (nreverse nas)))) + +;;;; +;;;; Merged trees from different files +;;;; + +(defun cvs-tree-fuzzy-merge-1 (trees tree prev) + ) + +(defun cvs-tree-fuzzy-merge (trees tree) + "Do the impossible: merge TREE into TREES." + ()) + +(defun cvs-tree () + "Get tags from the status output and merge tham all into a big tree." + (save-excursion + (goto-char (point-min)) + (let ((inhibit-read-only t) + (trees (make-vector 31 0)) tree) + (while (listp (setq tree (cvs-tags->tree (cvs-status-get-tags)))) + (cvs-tree-fuzzy-merge trees tree)) + (erase-buffer) + (let ((cvs-tag-print-rev nil)) + (cvs-tree-print tree 'cvs-tag->string 3))))) + + +(provide 'cvs-status) + +;; arch-tag: db8b5094-d02a-473e-a476-544e89ff5ad0 +;;; cvs-status.el ends here diff -r 681cd08dc0f7 -r e93288477c43 lisp/vc/diff-mode.el --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/lisp/vc/diff-mode.el Sun Jun 13 22:57:55 2010 +0000 @@ -0,0 +1,1935 @@ +;;; diff-mode.el --- a mode for viewing/editing context diffs + +;; Copyright (C) 1998, 1999, 2000, 2001, 2002, 2003, 2004,2005, 2006, +;; 2007, 2008, 2009, 2010 Free Software Foundation, Inc. + +;; Author: Stefan Monnier +;; Keywords: convenience patch diff vc + +;; 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 3 of the License, 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. If not, see . + +;;; Commentary: + +;; Provides support for font-lock, outline, navigation +;; commands, editing and various conversions as well as jumping +;; to the corresponding source file. + +;; Inspired by Pavel Machek's patch-mode.el () +;; Some efforts were spent to have it somewhat compatible with XEmacs' +;; diff-mode as well as with compilation-minor-mode + +;; Bugs: + +;; - Reverse doesn't work with normal diffs. + +;; Todo: + +;; - Improve `diff-add-change-log-entries-other-window', +;; it is very simplistic now. +;; +;; - Add a `delete-after-apply' so C-c C-a automatically deletes hunks. +;; Also allow C-c C-a to delete already-applied hunks. +;; +;; - Try `diff ' to try and fuzzily discover the source location +;; of a hunk. Show then the changes between and and make it +;; possible to apply them to , , or . +;; Or maybe just make it into a ".rej to diff3-markers converter". +;; Maybe just use `wiggle' (by Neil Brown) to do it for us. +;; +;; - in diff-apply-hunk, strip context in replace-match to better +;; preserve markers and spacing. +;; - Handle `diff -b' output in context->unified. + +;;; Code: +(eval-when-compile (require 'cl)) + +(defvar add-log-buffer-file-name-function) + + +(defgroup diff-mode () + "Major mode for viewing/editing diffs." + :version "21.1" + :group 'tools + :group 'diff) + +(defcustom diff-default-read-only nil + "If non-nil, `diff-mode' buffers default to being read-only." + :type 'boolean + :group 'diff-mode) + +(defcustom diff-jump-to-old-file nil + "Non-nil means `diff-goto-source' jumps to the old file. +Else, it jumps to the new file." + :type 'boolean + :group 'diff-mode) + +(defcustom diff-update-on-the-fly t + "Non-nil means hunk headers are kept up-to-date on-the-fly. +When editing a diff file, the line numbers in the hunk headers +need to be kept consistent with the actual diff. This can +either be done on the fly (but this sometimes interacts poorly with the +undo mechanism) or whenever the file is written (can be slow +when editing big diffs)." + :type 'boolean + :group 'diff-mode) + +(defcustom diff-advance-after-apply-hunk t + "Non-nil means `diff-apply-hunk' will move to the next hunk after applying." + :type 'boolean + :group 'diff-mode) + +(defcustom diff-mode-hook nil + "Run after setting up the `diff-mode' major mode." + :type 'hook + :options '(diff-delete-empty-files diff-make-unified) + :group 'diff-mode) + +(defvar diff-outline-regexp + "\\([*+][*+][*+] [^0-9]\\|@@ ...\\|\\*\\*\\* [0-9].\\|--- [0-9]..\\)") + +;;;; +;;;; keymap, menu, ... +;;;; + +(easy-mmode-defmap diff-mode-shared-map + '(;; From Pavel Machek's patch-mode. + ("n" . diff-hunk-next) + ("N" . diff-file-next) + ("p" . diff-hunk-prev) + ("P" . diff-file-prev) + ("\t" . diff-hunk-next) + ([backtab] . diff-hunk-prev) + ("k" . diff-hunk-kill) + ("K" . diff-file-kill) + ;; From compilation-minor-mode. + ("}" . diff-file-next) + ("{" . diff-file-prev) + ("\C-m" . diff-goto-source) + ([mouse-2] . diff-goto-source) + ;; From XEmacs' diff-mode. + ;; Standard M-w is useful, so don't change M-W. + ;;("W" . widen) + ;;("." . diff-goto-source) ;display-buffer + ;;("f" . diff-goto-source) ;find-file + ("o" . diff-goto-source) ;other-window + ;;("w" . diff-goto-source) ;other-frame + ;;("N" . diff-narrow) + ;;("h" . diff-show-header) + ;;("j" . diff-show-difference) ;jump to Nth diff + ;;("q" . diff-quit) + ;; Not useful if you have to metafy them. + ;;(" " . scroll-up) + ;;("\177" . scroll-down) + ;; Standard M-a is useful, so don't change M-A. + ;;("A" . diff-ediff-patch) + ;; Standard M-r is useful, so don't change M-r or M-R. + ;;("r" . diff-restrict-view) + ;;("R" . diff-reverse-direction) + ("q" . quit-window)) + "Basic keymap for `diff-mode', bound to various prefix keys.") + +(easy-mmode-defmap diff-mode-map + `(("\e" . ,diff-mode-shared-map) + ;; From compilation-minor-mode. + ("\C-c\C-c" . diff-goto-source) + ;; By analogy with the global C-x 4 a binding. + ("\C-x4A" . diff-add-change-log-entries-other-window) + ;; Misc operations. + ("\C-c\C-a" . diff-apply-hunk) + ("\C-c\C-e" . diff-ediff-patch) + ("\C-c\C-n" . diff-restrict-view) + ("\C-c\C-s" . diff-split-hunk) + ("\C-c\C-t" . diff-test-hunk) + ("\C-c\C-r" . diff-reverse-direction) + ("\C-c\C-u" . diff-context->unified) + ;; `d' because it duplicates the context :-( --Stef + ("\C-c\C-d" . diff-unified->context) + ("\C-c\C-w" . diff-ignore-whitespace-hunk) + ("\C-c\C-b" . diff-refine-hunk) ;No reason for `b' :-( + ("\C-c\C-f" . next-error-follow-minor-mode)) + "Keymap for `diff-mode'. See also `diff-mode-shared-map'.") + +(easy-menu-define diff-mode-menu diff-mode-map + "Menu for `diff-mode'." + '("Diff" + ["Jump to Source" diff-goto-source + :help "Jump to the corresponding source line"] + ["Apply hunk" diff-apply-hunk + :help "Apply the current hunk to the source file and go to the next"] + ["Test applying hunk" diff-test-hunk + :help "See whether it's possible to apply the current hunk"] + ["Apply diff with Ediff" diff-ediff-patch + :help "Call `ediff-patch-file' on the current buffer"] + ["Create Change Log entries" diff-add-change-log-entries-other-window + :help "Create ChangeLog entries for the changes in the diff buffer"] + "-----" + ["Reverse direction" diff-reverse-direction + :help "Reverse the direction of the diffs"] + ["Context -> Unified" diff-context->unified + :help "Convert context diffs to unified diffs"] + ["Unified -> Context" diff-unified->context + :help "Convert unified diffs to context diffs"] + ;;["Fixup Headers" diff-fixup-modifs (not buffer-read-only)] + ["Show trailing whitespace" whitespace-mode + :style toggle :selected (bound-and-true-p whitespace-mode) + :help "Show trailing whitespace in modified lines"] + "-----" + ["Split hunk" diff-split-hunk + :active (diff-splittable-p) + :help "Split the current (unified diff) hunk at point into two hunks"] + ["Ignore whitespace changes" diff-ignore-whitespace-hunk + :help "Re-diff the current hunk, ignoring whitespace differences"] + ["Highlight fine changes" diff-refine-hunk + :help "Highlight changes of hunk at point at a finer granularity"] + ["Kill current hunk" diff-hunk-kill + :help "Kill current hunk"] + ["Kill current file's hunks" diff-file-kill + :help "Kill all current file's hunks"] + "-----" + ["Previous Hunk" diff-hunk-prev + :help "Go to the previous count'th hunk"] + ["Next Hunk" diff-hunk-next + :help "Go to the next count'th hunk"] + ["Previous File" diff-file-prev + :help "Go to the previous count'th file"] + ["Next File" diff-file-next + :help "Go to the next count'th file"] + )) + +(defcustom diff-minor-mode-prefix "\C-c=" + "Prefix key for `diff-minor-mode' commands." + :type '(choice (string "\e") (string "C-c=") string) + :group 'diff-mode) + +(easy-mmode-defmap diff-minor-mode-map + `((,diff-minor-mode-prefix . ,diff-mode-shared-map)) + "Keymap for `diff-minor-mode'. See also `diff-mode-shared-map'.") + +(define-minor-mode diff-auto-refine-mode + "Automatically highlight changes in detail as the user visits hunks. +When transitioning from disabled to enabled, +try to refine the current hunk, as well." + :group 'diff-mode :init-value t :lighter nil ;; " Auto-Refine" + (when diff-auto-refine-mode + (condition-case-no-debug nil (diff-refine-hunk) (error nil)))) + +;;;; +;;;; font-lock support +;;;; + +(defface diff-header + '((((class color) (min-colors 88) (background light)) + :background "grey80") + (((class color) (min-colors 88) (background dark)) + :background "grey45") + (((class color) (background light)) + :foreground "blue1" :weight bold) + (((class color) (background dark)) + :foreground "green" :weight bold) + (t :weight bold)) + "`diff-mode' face inherited by hunk and index header faces." + :group 'diff-mode) +(define-obsolete-face-alias 'diff-header-face 'diff-header "22.1") +(defvar diff-header-face 'diff-header) + +(defface diff-file-header + '((((class color) (min-colors 88) (background light)) + :background "grey70" :weight bold) + (((class color) (min-colors 88) (background dark)) + :background "grey60" :weight bold) + (((class color) (background light)) + :foreground "green" :weight bold) + (((class color) (background dark)) + :foreground "cyan" :weight bold) + (t :weight bold)) ; :height 1.3 + "`diff-mode' face used to highlight file header lines." + :group 'diff-mode) +(define-obsolete-face-alias 'diff-file-header-face 'diff-file-header "22.1") +(defvar diff-file-header-face 'diff-file-header) + +(defface diff-index + '((t :inherit diff-file-header)) + "`diff-mode' face used to highlight index header lines." + :group 'diff-mode) +(define-obsolete-face-alias 'diff-index-face 'diff-index "22.1") +(defvar diff-index-face 'diff-index) + +(defface diff-hunk-header + '((t :inherit diff-header)) + "`diff-mode' face used to highlight hunk header lines." + :group 'diff-mode) +(define-obsolete-face-alias 'diff-hunk-header-face 'diff-hunk-header "22.1") +(defvar diff-hunk-header-face 'diff-hunk-header) + +(defface diff-removed + '((t :inherit diff-changed)) + "`diff-mode' face used to highlight removed lines." + :group 'diff-mode) +(define-obsolete-face-alias 'diff-removed-face 'diff-removed "22.1") +(defvar diff-removed-face 'diff-removed) + +(defface diff-added + '((t :inherit diff-changed)) + "`diff-mode' face used to highlight added lines." + :group 'diff-mode) +(define-obsolete-face-alias 'diff-added-face 'diff-added "22.1") +(defvar diff-added-face 'diff-added) + +(defface diff-changed + '((((type tty pc) (class color) (background light)) + :foreground "magenta" :weight bold :slant italic) + (((type tty pc) (class color) (background dark)) + :foreground "yellow" :weight bold :slant italic)) + "`diff-mode' face used to highlight changed lines." + :group 'diff-mode) +(define-obsolete-face-alias 'diff-changed-face 'diff-changed "22.1") +(defvar diff-changed-face 'diff-changed) + +(defface diff-indicator-removed + '((t :inherit diff-removed)) + "`diff-mode' face used to highlight indicator of removed lines (-, <)." + :group 'diff-mode + :version "22.1") +(defvar diff-indicator-removed-face 'diff-indicator-removed) + +(defface diff-indicator-added + '((t :inherit diff-added)) + "`diff-mode' face used to highlight indicator of added lines (+, >)." + :group 'diff-mode + :version "22.1") +(defvar diff-indicator-added-face 'diff-indicator-added) + +(defface diff-indicator-changed + '((t :inherit diff-changed)) + "`diff-mode' face used to highlight indicator of changed lines." + :group 'diff-mode + :version "22.1") +(defvar diff-indicator-changed-face 'diff-indicator-changed) + +(defface diff-function + '((t :inherit diff-header)) + "`diff-mode' face used to highlight function names produced by \"diff -p\"." + :group 'diff-mode) +(define-obsolete-face-alias 'diff-function-face 'diff-function "22.1") +(defvar diff-function-face 'diff-function) + +(defface diff-context + '((((class color grayscale) (min-colors 88)) :inherit shadow)) + "`diff-mode' face used to highlight context and other side-information." + :group 'diff-mode) +(define-obsolete-face-alias 'diff-context-face 'diff-context "22.1") +(defvar diff-context-face 'diff-context) + +(defface diff-nonexistent + '((t :inherit diff-file-header)) + "`diff-mode' face used to highlight nonexistent files in recursive diffs." + :group 'diff-mode) +(define-obsolete-face-alias 'diff-nonexistent-face 'diff-nonexistent "22.1") +(defvar diff-nonexistent-face 'diff-nonexistent) + +(defconst diff-yank-handler '(diff-yank-function)) +(defun diff-yank-function (text) + ;; FIXME: the yank-handler is now called separately on each piece of text + ;; with a yank-handler property, so the next-single-property-change call + ;; below will always return nil :-( --stef + (let ((mixed (next-single-property-change 0 'yank-handler text)) + (start (point))) + ;; First insert the text. + (insert text) + ;; If the text does not include any diff markers and if we're not + ;; yanking back into a diff-mode buffer, get rid of the prefixes. + (unless (or mixed (derived-mode-p 'diff-mode)) + (undo-boundary) ; Just in case the user wanted the prefixes. + (let ((re (save-excursion + (if (re-search-backward "^[>][ \t]") + "^[ <>!+-]")))) + (save-excursion + (while (re-search-backward re start t) + (replace-match "" t t))))))) + +(defconst diff-hunk-header-re-unified + "^@@ -\\([0-9]+\\)\\(?:,\\([0-9]+\\)\\)? \\+\\([0-9]+\\)\\(?:,\\([0-9]+\\)\\)? @@") +(defconst diff-context-mid-hunk-header-re + "--- \\([0-9]+\\)\\(?:,\\([0-9]+\\)\\)? ----$") + +(defvar diff-font-lock-keywords + `((,(concat "\\(" diff-hunk-header-re-unified "\\)\\(.*\\)$") + (1 diff-hunk-header-face) (6 diff-function-face)) + ("^\\(\\*\\{15\\}\\)\\(.*\\)$" ;context + (1 diff-hunk-header-face) (2 diff-function-face)) + ("^\\*\\*\\* .+ \\*\\*\\*\\*". diff-hunk-header-face) ;context + (,diff-context-mid-hunk-header-re . diff-hunk-header-face) ;context + ("^[0-9,]+[acd][0-9,]+$" . diff-hunk-header-face) ;normal + ("^---$" . diff-hunk-header-face) ;normal + ;; For file headers, accept files with spaces, but be careful to rule + ;; out false-positives when matching hunk headers. + ("^\\(---\\|\\+\\+\\+\\|\\*\\*\\*\\) \\([^\t\n]+?\\)\\(?:\t.*\\| \\(\\*\\*\\*\\*\\|----\\)\\)?\n" + (0 diff-header-face) + (2 (if (not (match-end 3)) diff-file-header-face) prepend)) + ("^\\([-<]\\)\\(.*\n\\)" + (1 diff-indicator-removed-face) (2 diff-removed-face)) + ("^\\([+>]\\)\\(.*\n\\)" + (1 diff-indicator-added-face) (2 diff-added-face)) + ("^\\(!\\)\\(.*\n\\)" + (1 diff-indicator-changed-face) (2 diff-changed-face)) + ("^Index: \\(.+\\).*\n" + (0 diff-header-face) (1 diff-index-face prepend)) + ("^Only in .*\n" . diff-nonexistent-face) + ("^\\(#\\)\\(.*\\)" + (1 font-lock-comment-delimiter-face) + (2 font-lock-comment-face)) + ("^[^-=+*!<>#].*\n" (0 diff-context-face)))) + +(defconst diff-font-lock-defaults + '(diff-font-lock-keywords t nil nil nil (font-lock-multiline . nil))) + +(defvar diff-imenu-generic-expression + ;; Prefer second name as first is most likely to be a backup or + ;; version-control name. The [\t\n] at the end of the unidiff pattern + ;; catches Debian source diff files (which lack the trailing date). + '((nil "\\+\\+\\+\\ \\([^\t\n]+\\)[\t\n]" 1) ; unidiffs + (nil "^--- \\([^\t\n]+\\)\t.*\n\\*" 1))) ; context diffs + +;;;; +;;;; Movement +;;;; + +(defvar diff-valid-unified-empty-line t + "If non-nil, empty lines are valid in unified diffs. +Some versions of diff replace all-blank context lines in unified format with +empty lines. This makes the format less robust, but is tolerated. +See http://lists.gnu.org/archive/html/emacs-devel/2007-11/msg01990.html") + +(defconst diff-hunk-header-re + (concat "^\\(?:" diff-hunk-header-re-unified ".*\\|\\*\\{15\\}.*\n\\*\\*\\* .+ \\*\\*\\*\\*\\|[0-9]+\\(,[0-9]+\\)?[acd][0-9]+\\(,[0-9]+\\)?\\)$")) +(defconst diff-file-header-re (concat "^\\(--- .+\n\\+\\+\\+ \\|\\*\\*\\* .+\n--- \\|[^-+!<>0-9@* \n]\\).+\n" (substring diff-hunk-header-re 1))) +(defvar diff-narrowed-to nil) + +(defun diff-hunk-style (&optional style) + (when (looking-at diff-hunk-header-re) + (setq style (cdr (assq (char-after) '((?@ . unified) (?* . context))))) + (goto-char (match-end 0))) + style) + +(defun diff-end-of-hunk (&optional style donttrustheader) + (let (end) + (when (looking-at diff-hunk-header-re) + ;; Especially important for unified (because headers are ambiguous). + (setq style (diff-hunk-style style)) + (goto-char (match-end 0)) + (when (and (not donttrustheader) (match-end 2)) + (let* ((nold (string-to-number (or (match-string 2) "1"))) + (nnew (string-to-number (or (match-string 4) "1"))) + (endold + (save-excursion + (re-search-forward (if diff-valid-unified-empty-line + "^[- \n]" "^[- ]") + nil t nold) + (line-beginning-position 2))) + (endnew + ;; The hunk may end with a bunch of "+" lines, so the `end' is + ;; then further than computed above. + (save-excursion + (re-search-forward (if diff-valid-unified-empty-line + "^[+ \n]" "^[+ ]") + nil t nnew) + (line-beginning-position 2)))) + (setq end (max endold endnew))))) + ;; We may have a first evaluation of `end' thanks to the hunk header. + (unless end + (setq end (and (re-search-forward + (case style + (unified (concat (if diff-valid-unified-empty-line + "^[^-+# \\\n]\\|" "^[^-+# \\]\\|") + ;; A `unified' header is ambiguous. + diff-file-header-re)) + (context "^[^-+#! \\]") + (normal "^[^<>#\\]") + (t "^[^-+#!<> \\]")) + nil t) + (match-beginning 0))) + (when diff-valid-unified-empty-line + ;; While empty lines may be valid inside hunks, they are also likely + ;; to be unrelated to the hunk. + (goto-char (or end (point-max))) + (while (eq ?\n (char-before (1- (point)))) + (forward-char -1) + (setq end (point))))) + ;; The return value is used by easy-mmode-define-navigation. + (goto-char (or end (point-max))))) + +(defun diff-beginning-of-hunk (&optional try-harder) + "Move back to beginning of hunk. +If TRY-HARDER is non-nil, try to cater to the case where we're not in a hunk +but in the file header instead, in which case move forward to the first hunk." + (beginning-of-line) + (unless (looking-at diff-hunk-header-re) + (forward-line 1) + (condition-case () + (re-search-backward diff-hunk-header-re) + (error + (if (not try-harder) + (error "Can't find the beginning of the hunk") + (diff-beginning-of-file-and-junk) + (diff-hunk-next)))))) + +(defun diff-unified-hunk-p () + (save-excursion + (ignore-errors + (diff-beginning-of-hunk) + (looking-at "^@@")))) + +(defun diff-beginning-of-file () + (beginning-of-line) + (unless (looking-at diff-file-header-re) + (let ((start (point)) + res) + ;; diff-file-header-re may need to match up to 4 lines, so in case + ;; we're inside the header, we need to move up to 3 lines forward. + (forward-line 3) + (if (and (setq res (re-search-backward diff-file-header-re nil t)) + ;; Maybe the 3 lines forward were too much and we matched + ;; a file header after our starting point :-( + (or (<= (point) start) + (setq res (re-search-backward diff-file-header-re nil t)))) + res + (goto-char start) + (error "Can't find the beginning of the file"))))) + + +(defun diff-end-of-file () + (re-search-forward "^[-+#!<>0-9@* \\]" nil t) + (re-search-forward (concat "^[^-+#!<>0-9@* \\]\\|" diff-file-header-re) + nil 'move) + (if (match-beginning 1) + (goto-char (match-beginning 1)) + (beginning-of-line))) + +;; Define diff-{hunk,file}-{prev,next} +(easy-mmode-define-navigation + diff-hunk diff-hunk-header-re "hunk" diff-end-of-hunk diff-restrict-view + (if diff-auto-refine-mode + (condition-case-no-debug nil (diff-refine-hunk) (error nil)))) + +(easy-mmode-define-navigation + diff-file diff-file-header-re "file" diff-end-of-hunk) + +(defun diff-restrict-view (&optional arg) + "Restrict the view to the current hunk. +If the prefix ARG is given, restrict the view to the current file instead." + (interactive "P") + (save-excursion + (if arg (diff-beginning-of-file) (diff-beginning-of-hunk 'try-harder)) + (narrow-to-region (point) + (progn (if arg (diff-end-of-file) (diff-end-of-hunk)) + (point))) + (set (make-local-variable 'diff-narrowed-to) (if arg 'file 'hunk)))) + + +(defun diff-hunk-kill () + "Kill current hunk." + (interactive) + (diff-beginning-of-hunk) + (let* ((start (point)) + ;; Search the second match, since we're looking at the first. + (nexthunk (when (re-search-forward diff-hunk-header-re nil t 2) + (match-beginning 0))) + (firsthunk (ignore-errors + (goto-char start) + (diff-beginning-of-file) (diff-hunk-next) (point))) + (nextfile (ignore-errors (diff-file-next) (point))) + (inhibit-read-only t)) + (goto-char start) + (if (and firsthunk (= firsthunk start) + (or (null nexthunk) + (and nextfile (> nexthunk nextfile)))) + ;; It's the only hunk for this file, so kill the file. + (diff-file-kill) + (diff-end-of-hunk) + (kill-region start (point))))) + +;; "index ", "old mode", "new mode", "new file mode" and +;; "deleted file mode" are output by git-diff. +(defconst diff-file-junk-re + "diff \\|index \\|\\(?:deleted file\\|new\\(?: file\\)?\\|old\\) mode") + +(defun diff-beginning-of-file-and-junk () + "Go to the beginning of file-related diff-info. +This is like `diff-beginning-of-file' except it tries to skip back over leading +data such as \"Index: ...\" and such." + (let* ((orig (point)) + ;; Skip forward over what might be "leading junk" so as to get + ;; closer to the actual diff. + (_ (progn (beginning-of-line) + (while (looking-at diff-file-junk-re) + (forward-line 1)))) + (start (point)) + (prevfile (condition-case err + (save-excursion (diff-beginning-of-file) (point)) + (error err))) + (err (if (consp prevfile) prevfile)) + (nextfile (ignore-errors + (save-excursion + (goto-char start) (diff-file-next) (point)))) + ;; prevhunk is one of the limits. + (prevhunk (save-excursion + (ignore-errors + (if (numberp prevfile) (goto-char prevfile)) + (diff-hunk-prev) (point)))) + (previndex (save-excursion + (forward-line 1) ;In case we're looking at "Index:". + (re-search-backward "^Index: " prevhunk t)))) + ;; If we're in the junk, we should use nextfile instead of prevfile. + (if (and (numberp nextfile) + (or (not (numberp prevfile)) + (and previndex (> previndex prevfile)))) + (setq prevfile nextfile)) + (if (and previndex (numberp prevfile) (< previndex prevfile)) + (setq prevfile previndex)) + (if (and (numberp prevfile) (<= prevfile start)) + (progn + (goto-char prevfile) + ;; Now skip backward over the leading junk we may have before the + ;; diff itself. + (while (save-excursion + (and (zerop (forward-line -1)) + (looking-at diff-file-junk-re))) + (forward-line -1))) + ;; File starts *after* the starting point: we really weren't in + ;; a file diff but elsewhere. + (goto-char orig) + (signal (car err) (cdr err))))) + +(defun diff-file-kill () + "Kill current file's hunks." + (interactive) + (let ((orig (point)) + (start (progn (diff-beginning-of-file-and-junk) (point))) + (inhibit-read-only t)) + (diff-end-of-file) + (if (looking-at "^\n") (forward-char 1)) ;`tla' generates such diffs. + (if (> orig (point)) (error "Not inside a file diff")) + (kill-region start (point)))) + +(defun diff-kill-junk () + "Kill spurious empty diffs." + (interactive) + (save-excursion + (let ((inhibit-read-only t)) + (goto-char (point-min)) + (while (re-search-forward (concat "^\\(Index: .*\n\\)" + "\\([^-+!* <>].*\n\\)*?" + "\\(\\(Index:\\) \\|" + diff-file-header-re "\\)") + nil t) + (delete-region (if (match-end 4) (match-beginning 0) (match-end 1)) + (match-beginning 3)) + (beginning-of-line))))) + +(defun diff-count-matches (re start end) + (save-excursion + (let ((n 0)) + (goto-char start) + (while (re-search-forward re end t) (incf n)) + n))) + +(defun diff-splittable-p () + (save-excursion + (beginning-of-line) + (and (looking-at "^[-+ ]") + (progn (forward-line -1) (looking-at "^[-+ ]")) + (diff-unified-hunk-p)))) + +(defun diff-split-hunk () + "Split the current (unified diff) hunk at point into two hunks." + (interactive) + (beginning-of-line) + (let ((pos (point)) + (start (progn (diff-beginning-of-hunk) (point)))) + (unless (looking-at diff-hunk-header-re-unified) + (error "diff-split-hunk only works on unified context diffs")) + (forward-line 1) + (let* ((start1 (string-to-number (match-string 1))) + (start2 (string-to-number (match-string 3))) + (newstart1 (+ start1 (diff-count-matches "^[- \t]" (point) pos))) + (newstart2 (+ start2 (diff-count-matches "^[+ \t]" (point) pos))) + (inhibit-read-only t)) + (goto-char pos) + ;; Hopefully the after-change-function will not screw us over. + (insert "@@ -" (number-to-string newstart1) ",1 +" + (number-to-string newstart2) ",1 @@\n") + ;; Fix the original hunk-header. + (diff-fixup-modifs start pos)))) + + +;;;; +;;;; jump to other buffers +;;;; + +(defvar diff-remembered-files-alist nil) +(defvar diff-remembered-defdir nil) + +(defun diff-filename-drop-dir (file) + (when (string-match "/" file) (substring file (match-end 0)))) + +(defun diff-merge-strings (ancestor from to) + "Merge the diff between ANCESTOR and FROM into TO. +Returns the merged string if successful or nil otherwise. +The strings are assumed not to contain any \"\\n\" (i.e. end of line). +If ANCESTOR = FROM, returns TO. +If ANCESTOR = TO, returns FROM. +The heuristic is simplistic and only really works for cases +like \(diff-merge-strings \"b/foo\" \"b/bar\" \"/a/c/foo\")." + ;; Ideally, we want: + ;; AMB ANB CMD -> CND + ;; but that's ambiguous if `foo' or `bar' is empty: + ;; a/foo a/foo1 b/foo.c -> b/foo1.c but not 1b/foo.c or b/foo.c1 + (let ((str (concat ancestor "\n" from "\n" to))) + (when (and (string-match (concat + "\\`\\(.*?\\)\\(.*\\)\\(.*\\)\n" + "\\1\\(.*\\)\\3\n" + "\\(.*\\(\\2\\).*\\)\\'") str) + (equal to (match-string 5 str))) + (concat (substring str (match-beginning 5) (match-beginning 6)) + (match-string 4 str) + (substring str (match-end 6) (match-end 5)))))) + +(defun diff-tell-file-name (old name) + "Tell Emacs where the find the source file of the current hunk. +If the OLD prefix arg is passed, tell the file NAME of the old file." + (interactive + (let* ((old current-prefix-arg) + (fs (diff-hunk-file-names current-prefix-arg))) + (unless fs (error "No file name to look for")) + (list old (read-file-name (format "File for %s: " (car fs)) + nil (diff-find-file-name old 'noprompt) t)))) + (let ((fs (diff-hunk-file-names old))) + (unless fs (error "No file name to look for")) + (push (cons fs name) diff-remembered-files-alist))) + +(defun diff-hunk-file-names (&optional old) + "Give the list of file names textually mentioned for the current hunk." + (save-excursion + (unless (looking-at diff-file-header-re) + (or (ignore-errors (diff-beginning-of-file)) + (re-search-forward diff-file-header-re nil t))) + (let ((limit (save-excursion + (condition-case () + (progn (diff-hunk-prev) (point)) + (error (point-min))))) + (header-files + (if (looking-at "[-*][-*][-*] \\(\\S-+\\)\\(\\s-.*\\)?\n[-+][-+][-+] \\(\\S-+\\)") + (list (if old (match-string 1) (match-string 3)) + (if old (match-string 3) (match-string 1))) + (forward-line 1) nil))) + (delq nil + (append + (when (and (not old) + (save-excursion + (re-search-backward "^Index: \\(.+\\)" limit t))) + (list (match-string 1))) + header-files + (when (re-search-backward + "^diff \\(-\\S-+ +\\)*\\(\\S-+\\)\\( +\\(\\S-+\\)\\)?" + nil t) + (list (if old (match-string 2) (match-string 4)) + (if old (match-string 4) (match-string 2))))))))) + +(defun diff-find-file-name (&optional old noprompt prefix) + "Return the file corresponding to the current patch. +Non-nil OLD means that we want the old file. +Non-nil NOPROMPT means to prefer returning nil than to prompt the user. +PREFIX is only used internally: don't use it." + (unless (equal diff-remembered-defdir default-directory) + ;; Flush diff-remembered-files-alist if the default-directory is changed. + (set (make-local-variable 'diff-remembered-defdir) default-directory) + (set (make-local-variable 'diff-remembered-files-alist) nil)) + (save-excursion + (unless (looking-at diff-file-header-re) + (or (ignore-errors (diff-beginning-of-file)) + (re-search-forward diff-file-header-re nil t))) + (let ((fs (diff-hunk-file-names old))) + (if prefix (setq fs (mapcar (lambda (f) (concat prefix f)) fs))) + (or + ;; use any previously used preference + (cdr (assoc fs diff-remembered-files-alist)) + ;; try to be clever and use previous choices as an inspiration + (dolist (rf diff-remembered-files-alist) + (let ((newfile (diff-merge-strings (caar rf) (car fs) (cdr rf)))) + (if (and newfile (file-exists-p newfile)) (return newfile)))) + ;; look for each file in turn. If none found, try again but + ;; ignoring the first level of directory, ... + (do* ((files fs (delq nil (mapcar 'diff-filename-drop-dir files))) + (file nil nil)) + ((or (null files) + (setq file (do* ((files files (cdr files)) + (file (car files) (car files))) + ;; Use file-regular-p to avoid + ;; /dev/null, directories, etc. + ((or (null file) (file-regular-p file)) + file)))) + file)) + ;; .rej patches implicitly apply to + (and (string-match "\\.rej\\'" (or buffer-file-name "")) + (let ((file (substring buffer-file-name 0 (match-beginning 0)))) + (when (file-exists-p file) file))) + ;; If we haven't found the file, maybe it's because we haven't paid + ;; attention to the PCL-CVS hint. + (and (not prefix) + (boundp 'cvs-pcl-cvs-dirchange-re) + (save-excursion + (re-search-backward cvs-pcl-cvs-dirchange-re nil t)) + (diff-find-file-name old noprompt (match-string 1))) + ;; if all else fails, ask the user + (unless noprompt + (let ((file (read-file-name (format "Use file %s: " + (or (first fs) "")) + nil (first fs) t (first fs)))) + (set (make-local-variable 'diff-remembered-files-alist) + (cons (cons fs file) diff-remembered-files-alist)) + file)))))) + + +(defun diff-ediff-patch () + "Call `ediff-patch-file' on the current buffer." + (interactive) + (condition-case err + (ediff-patch-file nil (current-buffer)) + (wrong-number-of-arguments (ediff-patch-file)))) + +;;;; +;;;; Conversion functions +;;;; + +;;(defvar diff-inhibit-after-change nil +;; "Non-nil means inhibit `diff-mode's after-change functions.") + +(defun diff-unified->context (start end) + "Convert unified diffs to context diffs. +START and END are either taken from the region (if a prefix arg is given) or +else cover the whole buffer." + (interactive (if (or current-prefix-arg (and transient-mark-mode mark-active)) + (list (region-beginning) (region-end)) + (list (point-min) (point-max)))) + (unless (markerp end) (setq end (copy-marker end t))) + (let (;;(diff-inhibit-after-change t) + (inhibit-read-only t)) + (save-excursion + (goto-char start) + (while (and (re-search-forward + (concat "^\\(\\(---\\) .+\n\\(\\+\\+\\+\\) .+\\|" + diff-hunk-header-re-unified ".*\\)$") + nil t) + (< (point) end)) + (combine-after-change-calls + (if (match-beginning 2) + ;; we matched a file header + (progn + ;; use reverse order to make sure the indices are kept valid + (replace-match "---" t t nil 3) + (replace-match "***" t t nil 2)) + ;; we matched a hunk header + (let ((line1 (match-string 4)) + (lines1 (or (match-string 5) "1")) + (line2 (match-string 6)) + (lines2 (or (match-string 7) "1")) + ;; Variables to use the special undo function. + (old-undo buffer-undo-list) + (old-end (marker-position end)) + (start (match-beginning 0)) + (reversible t)) + (replace-match + (concat "***************\n*** " line1 "," + (number-to-string (+ (string-to-number line1) + (string-to-number lines1) + -1)) + " ****")) + (save-restriction + (narrow-to-region (line-beginning-position 2) + ;; Call diff-end-of-hunk from just before + ;; the hunk header so it can use the hunk + ;; header info. + (progn (diff-end-of-hunk 'unified) (point))) + (let ((hunk (buffer-string))) + (goto-char (point-min)) + (if (not (save-excursion (re-search-forward "^-" nil t))) + (delete-region (point) (point-max)) + (goto-char (point-max)) + (let ((modif nil) last-pt) + (while (progn (setq last-pt (point)) + (= (forward-line -1) 0)) + (case (char-after) + (?\s (insert " ") (setq modif nil) (backward-char 1)) + (?+ (delete-region (point) last-pt) (setq modif t)) + (?- (if (not modif) + (progn (forward-char 1) + (insert " ")) + (delete-char 1) + (insert "! ")) + (backward-char 2)) + (?\\ (when (save-excursion (forward-line -1) + (= (char-after) ?+)) + (delete-region (point) last-pt) (setq modif t))) + ;; diff-valid-unified-empty-line. + (?\n (insert " ") (setq modif nil) (backward-char 2)) + (t (setq modif nil)))))) + (goto-char (point-max)) + (save-excursion + (insert "--- " line2 "," + (number-to-string (+ (string-to-number line2) + (string-to-number lines2) + -1)) + " ----\n" hunk)) + ;;(goto-char (point-min)) + (forward-line 1) + (if (not (save-excursion (re-search-forward "^+" nil t))) + (delete-region (point) (point-max)) + (let ((modif nil) (delete nil)) + (if (save-excursion (re-search-forward "^\\+.*\n-" nil t)) + ;; Normally, lines in a substitution come with + ;; first the removals and then the additions, and + ;; the context->unified function follows this + ;; convention, of course. Yet, other alternatives + ;; are valid as well, but they preclude the use of + ;; context->unified as an undo command. + (setq reversible nil)) + (while (not (eobp)) + (case (char-after) + (?\s (insert " ") (setq modif nil) (backward-char 1)) + (?- (setq delete t) (setq modif t)) + (?+ (if (not modif) + (progn (forward-char 1) + (insert " ")) + (delete-char 1) + (insert "! ")) + (backward-char 2)) + (?\\ (when (save-excursion (forward-line 1) + (not (eobp))) + (setq delete t) (setq modif t))) + ;; diff-valid-unified-empty-line. + (?\n (insert " ") (setq modif nil) (backward-char 2) + (setq reversible nil)) + (t (setq modif nil))) + (let ((last-pt (point))) + (forward-line 1) + (when delete + (delete-region last-pt (point)) + (setq delete nil))))))) + (unless (or (not reversible) (eq buffer-undo-list t)) + ;; Drop the many undo entries and replace them with + ;; a single entry that uses diff-context->unified to do + ;; the work. + (setq buffer-undo-list + (cons (list 'apply (- old-end end) start (point-max) + 'diff-context->unified start (point-max)) + old-undo))))))))))) + +(defun diff-context->unified (start end &optional to-context) + "Convert context diffs to unified diffs. +START and END are either taken from the region +\(when it is highlighted) or else cover the whole buffer. +With a prefix argument, convert unified format to context format." + (interactive (if (and transient-mark-mode mark-active) + (list (region-beginning) (region-end) current-prefix-arg) + (list (point-min) (point-max) current-prefix-arg))) + (if to-context + (diff-unified->context start end) + (unless (markerp end) (setq end (copy-marker end t))) + (let ( ;;(diff-inhibit-after-change t) + (inhibit-read-only t)) + (save-excursion + (goto-char start) + (while (and (re-search-forward "^\\(\\(\\*\\*\\*\\) .+\n\\(---\\) .+\\|\\*\\{15\\}.*\n\\*\\*\\* \\([0-9]+\\),\\(-?[0-9]+\\) \\*\\*\\*\\*\\)$" nil t) + (< (point) end)) + (combine-after-change-calls + (if (match-beginning 2) + ;; we matched a file header + (progn + ;; use reverse order to make sure the indices are kept valid + (replace-match "+++" t t nil 3) + (replace-match "---" t t nil 2)) + ;; we matched a hunk header + (let ((line1s (match-string 4)) + (line1e (match-string 5)) + (pt1 (match-beginning 0)) + ;; Variables to use the special undo function. + (old-undo buffer-undo-list) + (old-end (marker-position end)) + (reversible t)) + (replace-match "") + (unless (re-search-forward + diff-context-mid-hunk-header-re nil t) + (error "Can't find matching `--- n1,n2 ----' line")) + (let ((line2s (match-string 1)) + (line2e (match-string 2)) + (pt2 (progn + (delete-region (progn (beginning-of-line) (point)) + (progn (forward-line 1) (point))) + (point-marker)))) + (goto-char pt1) + (forward-line 1) + (while (< (point) pt2) + (case (char-after) + (?! (delete-char 2) (insert "-") (forward-line 1)) + (?- (forward-char 1) (delete-char 1) (forward-line 1)) + (?\s ;merge with the other half of the chunk + (let* ((endline2 + (save-excursion + (goto-char pt2) (forward-line 1) (point)))) + (case (char-after pt2) + ((?! ?+) + (insert "+" + (prog1 (buffer-substring (+ pt2 2) endline2) + (delete-region pt2 endline2)))) + (?\s + (unless (= (- endline2 pt2) + (- (line-beginning-position 2) (point))) + ;; If the two lines we're merging don't have the + ;; same length (can happen with "diff -b"), then + ;; diff-unified->context will not properly undo + ;; this operation. + (setq reversible nil)) + (delete-region pt2 endline2) + (delete-char 1) + (forward-line 1)) + (?\\ (forward-line 1)) + (t (setq reversible nil) + (delete-char 1) (forward-line 1))))) + (t (setq reversible nil) (forward-line 1)))) + (while (looking-at "[+! ] ") + (if (/= (char-after) ?!) (forward-char 1) + (delete-char 1) (insert "+")) + (delete-char 1) (forward-line 1)) + (save-excursion + (goto-char pt1) + (insert "@@ -" line1s "," + (number-to-string (- (string-to-number line1e) + (string-to-number line1s) + -1)) + " +" line2s "," + (number-to-string (- (string-to-number line2e) + (string-to-number line2s) + -1)) " @@")) + (set-marker pt2 nil) + ;; The whole procedure succeeded, let's replace the myriad + ;; of undo elements with just a single special one. + (unless (or (not reversible) (eq buffer-undo-list t)) + (setq buffer-undo-list + (cons (list 'apply (- old-end end) pt1 (point) + 'diff-unified->context pt1 (point)) + old-undo))) + ))))))))) + +(defun diff-reverse-direction (start end) + "Reverse the direction of the diffs. +START and END are either taken from the region (if a prefix arg is given) or +else cover the whole buffer." + (interactive (if (or current-prefix-arg (and transient-mark-mode mark-active)) + (list (region-beginning) (region-end)) + (list (point-min) (point-max)))) + (unless (markerp end) (setq end (copy-marker end t))) + (let (;;(diff-inhibit-after-change t) + (inhibit-read-only t)) + (save-excursion + (goto-char start) + (while (and (re-search-forward "^\\(\\([-*][-*][-*] \\)\\(.+\\)\n\\([-+][-+][-+] \\)\\(.+\\)\\|\\*\\{15\\}.*\n\\*\\*\\* \\(.+\\) \\*\\*\\*\\*\\|@@ -\\([0-9,]+\\) \\+\\([0-9,]+\\) @@.*\\)$" nil t) + (< (point) end)) + (combine-after-change-calls + (cond + ;; a file header + ((match-beginning 2) (replace-match "\\2\\5\n\\4\\3" nil)) + ;; a context-diff hunk header + ((match-beginning 6) + (let ((pt-lines1 (match-beginning 6)) + (lines1 (match-string 6))) + (replace-match "" nil nil nil 6) + (forward-line 1) + (let ((half1s (point))) + (while (looking-at "[-! \\][ \t]\\|#") + (when (= (char-after) ?-) (delete-char 1) (insert "+")) + (forward-line 1)) + (let ((half1 (delete-and-extract-region half1s (point)))) + (unless (looking-at diff-context-mid-hunk-header-re) + (insert half1) + (error "Can't find matching `--- n1,n2 ----' line")) + (let* ((str1end (or (match-end 2) (match-end 1))) + (str1 (buffer-substring (match-beginning 1) str1end))) + (goto-char str1end) + (insert lines1) + (delete-region (match-beginning 1) str1end) + (forward-line 1) + (let ((half2s (point))) + (while (looking-at "[!+ \\][ \t]\\|#") + (when (= (char-after) ?+) (delete-char 1) (insert "-")) + (forward-line 1)) + (let ((half2 (delete-and-extract-region half2s (point)))) + (insert (or half1 "")) + (goto-char half1s) + (insert (or half2 "")))) + (goto-char pt-lines1) + (insert str1)))))) + ;; a unified-diff hunk header + ((match-beginning 7) + (replace-match "@@ -\\8 +\\7 @@" nil) + (forward-line 1) + (let ((c (char-after)) first last) + (while (case (setq c (char-after)) + (?- (setq first (or first (point))) + (delete-char 1) (insert "+") t) + (?+ (setq last (or last (point))) + (delete-char 1) (insert "-") t) + ((?\\ ?#) t) + (t (when (and first last (< first last)) + (insert (delete-and-extract-region first last))) + (setq first nil last nil) + (memq c (if diff-valid-unified-empty-line + '(?\s ?\n) '(?\s))))) + (forward-line 1)))))))))) + +(defun diff-fixup-modifs (start end) + "Fixup the hunk headers (in case the buffer was modified). +START and END are either taken from the region (if a prefix arg is given) or +else cover the whole buffer." + (interactive (if (or current-prefix-arg (and transient-mark-mode mark-active)) + (list (region-beginning) (region-end)) + (list (point-min) (point-max)))) + (let ((inhibit-read-only t)) + (save-excursion + (goto-char end) (diff-end-of-hunk nil 'donttrustheader) + (let ((plus 0) (minus 0) (space 0) (bang 0)) + (while (and (= (forward-line -1) 0) (<= start (point))) + (if (not (looking-at + (concat diff-hunk-header-re-unified + "\\|[-*][-*][-*] [0-9,]+ [-*][-*][-*][-*]$" + "\\|--- .+\n\\+\\+\\+ "))) + (case (char-after) + (?\s (incf space)) + (?+ (incf plus)) + (?- (incf minus)) + (?! (incf bang)) + ((?\\ ?#) nil) + (t (setq space 0 plus 0 minus 0 bang 0))) + (cond + ((looking-at diff-hunk-header-re-unified) + (let* ((old1 (match-string 2)) + (old2 (match-string 4)) + (new1 (number-to-string (+ space minus))) + (new2 (number-to-string (+ space plus)))) + (if old2 + (unless (string= new2 old2) (replace-match new2 t t nil 4)) + (goto-char (match-end 4)) (insert "," new2)) + (if old1 + (unless (string= new1 old1) (replace-match new1 t t nil 2)) + (goto-char (match-end 2)) (insert "," new1)))) + ((looking-at diff-context-mid-hunk-header-re) + (when (> (+ space bang plus) 0) + (let* ((old1 (match-string 1)) + (old2 (match-string 2)) + (new (number-to-string + (+ space bang plus -1 (string-to-number old1))))) + (unless (string= new old2) (replace-match new t t nil 2))))) + ((looking-at "\\*\\*\\* \\([0-9]+\\),\\(-?[0-9]*\\) \\*\\*\\*\\*$") + (when (> (+ space bang minus) 0) + (let* ((old (match-string 1)) + (new (format + (concat "%0" (number-to-string (length old)) "d") + (+ space bang minus -1 (string-to-number old))))) + (unless (string= new old) (replace-match new t t nil 2)))))) + (setq space 0 plus 0 minus 0 bang 0))))))) + +;;;; +;;;; Hooks +;;;; + +(defun diff-write-contents-hooks () + "Fixup hunk headers if necessary." + (if (buffer-modified-p) (diff-fixup-modifs (point-min) (point-max))) + nil) + +;; It turns out that making changes in the buffer from within an +;; *-change-function is asking for trouble, whereas making them +;; from a post-command-hook doesn't pose much problems +(defvar diff-unhandled-changes nil) +(defun diff-after-change-function (beg end len) + "Remember to fixup the hunk header. +See `after-change-functions' for the meaning of BEG, END and LEN." + ;; Ignoring changes when inhibit-read-only is set is strictly speaking + ;; incorrect, but it turns out that inhibit-read-only is normally not set + ;; inside editing commands, while it tends to be set when the buffer gets + ;; updated by an async process or by a conversion function, both of which + ;; would rather not be uselessly slowed down by this hook. + (when (and (not undo-in-progress) (not inhibit-read-only)) + (if diff-unhandled-changes + (setq diff-unhandled-changes + (cons (min beg (car diff-unhandled-changes)) + (max end (cdr diff-unhandled-changes)))) + (setq diff-unhandled-changes (cons beg end))))) + +(defun diff-post-command-hook () + "Fixup hunk headers if necessary." + (when (consp diff-unhandled-changes) + (ignore-errors + (save-excursion + (goto-char (car diff-unhandled-changes)) + ;; Maybe we've cut the end of the hunk before point. + (if (and (bolp) (not (bobp))) (backward-char 1)) + ;; We used to fixup modifs on all the changes, but it turns out that + ;; it's safer not to do it on big changes, e.g. when yanking a big + ;; diff, or when the user edits the header, since we might then + ;; screw up perfectly correct values. --Stef + (diff-beginning-of-hunk) + (let* ((style (if (looking-at "\\*\\*\\*") 'context)) + (start (line-beginning-position (if (eq style 'context) 3 2))) + (mid (if (eq style 'context) + (save-excursion + (re-search-forward diff-context-mid-hunk-header-re + nil t))))) + (when (and ;; Don't try to fixup changes in the hunk header. + (> (car diff-unhandled-changes) start) + ;; Don't try to fixup changes in the mid-hunk header either. + (or (not mid) + (< (cdr diff-unhandled-changes) (match-beginning 0)) + (> (car diff-unhandled-changes) (match-end 0))) + (save-excursion + (diff-end-of-hunk nil 'donttrustheader) + ;; Don't try to fixup changes past the end of the hunk. + (>= (point) (cdr diff-unhandled-changes)))) + (diff-fixup-modifs (point) (cdr diff-unhandled-changes))))) + (setq diff-unhandled-changes nil)))) + +(defun diff-next-error (arg reset) + ;; Select a window that displays the current buffer so that point + ;; movements are reflected in that window. Otherwise, the user might + ;; never see the hunk corresponding to the source she's jumping to. + (pop-to-buffer (current-buffer)) + (if reset (goto-char (point-min))) + (diff-hunk-next arg) + (diff-goto-source)) + +(defvar whitespace-style) +(defvar whitespace-trailing-regexp) + +;;;###autoload +(define-derived-mode diff-mode fundamental-mode "Diff" + "Major mode for viewing/editing context diffs. +Supports unified and context diffs as well as (to a lesser extent) +normal diffs. + +When the buffer is read-only, the ESC prefix is not necessary. +If you edit the buffer manually, diff-mode will try to update the hunk +headers for you on-the-fly. + +You can also switch between context diff and unified diff with \\[diff-context->unified], +or vice versa with \\[diff-unified->context] and you can also reverse the direction of +a diff with \\[diff-reverse-direction]. + + \\{diff-mode-map}" + + (set (make-local-variable 'font-lock-defaults) diff-font-lock-defaults) + (set (make-local-variable 'outline-regexp) diff-outline-regexp) + (set (make-local-variable 'imenu-generic-expression) + diff-imenu-generic-expression) + ;; These are not perfect. They would be better done separately for + ;; context diffs and unidiffs. + ;; (set (make-local-variable 'paragraph-start) + ;; (concat "@@ " ; unidiff hunk + ;; "\\|\\*\\*\\* " ; context diff hunk or file start + ;; "\\|--- [^\t]+\t")) ; context or unidiff file + ;; ; start (first or second line) + ;; (set (make-local-variable 'paragraph-separate) paragraph-start) + ;; (set (make-local-variable 'page-delimiter) "--- [^\t]+\t") + ;; compile support + (set (make-local-variable 'next-error-function) 'diff-next-error) + + (set (make-local-variable 'beginning-of-defun-function) + 'diff-beginning-of-file-and-junk) + (set (make-local-variable 'end-of-defun-function) + 'diff-end-of-file) + + ;; Set up `whitespace-mode' so that turning it on will show trailing + ;; whitespace problems on the modified lines of the diff. + (set (make-local-variable 'whitespace-style) '(trailing)) + (set (make-local-variable 'whitespace-trailing-regexp) + "^[-\+!<>].*?\\([\t ]+\\)$") + + (setq buffer-read-only diff-default-read-only) + ;; setup change hooks + (if (not diff-update-on-the-fly) + (add-hook 'write-contents-functions 'diff-write-contents-hooks nil t) + (make-local-variable 'diff-unhandled-changes) + (add-hook 'after-change-functions 'diff-after-change-function nil t) + (add-hook 'post-command-hook 'diff-post-command-hook nil t)) + ;; Neat trick from Dave Love to add more bindings in read-only mode: + (lexical-let ((ro-bind (cons 'buffer-read-only diff-mode-shared-map))) + (add-to-list 'minor-mode-overriding-map-alist ro-bind) + ;; Turn off this little trick in case the buffer is put in view-mode. + (add-hook 'view-mode-hook + (lambda () + (setq minor-mode-overriding-map-alist + (delq ro-bind minor-mode-overriding-map-alist))) + nil t)) + ;; add-log support + (set (make-local-variable 'add-log-current-defun-function) + 'diff-current-defun) + (set (make-local-variable 'add-log-buffer-file-name-function) + (lambda () (diff-find-file-name nil 'noprompt))) + (unless (buffer-file-name) + (hack-dir-local-variables-non-file-buffer))) + +;;;###autoload +(define-minor-mode diff-minor-mode + "Minor mode for viewing/editing context diffs. +\\{diff-minor-mode-map}" + :group 'diff-mode :lighter " Diff" + ;; FIXME: setup font-lock + ;; setup change hooks + (if (not diff-update-on-the-fly) + (add-hook 'write-contents-functions 'diff-write-contents-hooks nil t) + (make-local-variable 'diff-unhandled-changes) + (add-hook 'after-change-functions 'diff-after-change-function nil t) + (add-hook 'post-command-hook 'diff-post-command-hook nil t))) + +;;; Handy hook functions ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +(defun diff-delete-if-empty () + ;; An empty diff file means there's no more diffs to integrate, so we + ;; can just remove the file altogether. Very handy for .rej files if we + ;; remove hunks as we apply them. + (when (and buffer-file-name + (eq 0 (nth 7 (file-attributes buffer-file-name)))) + (delete-file buffer-file-name))) + +(defun diff-delete-empty-files () + "Arrange for empty diff files to be removed." + (add-hook 'after-save-hook 'diff-delete-if-empty nil t)) + +(defun diff-make-unified () + "Turn context diffs into unified diffs if applicable." + (if (save-excursion + (goto-char (point-min)) + (and (looking-at diff-hunk-header-re) (eq (char-after) ?*))) + (let ((mod (buffer-modified-p))) + (unwind-protect + (diff-context->unified (point-min) (point-max)) + (restore-buffer-modified-p mod))))) + +;;; +;;; Misc operations that have proved useful at some point. +;;; + +(defun diff-next-complex-hunk () + "Jump to the next \"complex\" hunk. +\"Complex\" is approximated by \"the hunk changes the number of lines\". +Only works for unified diffs." + (interactive) + (while + (and (re-search-forward diff-hunk-header-re-unified nil t) + (equal (match-string 2) (match-string 4))))) + +(defun diff-sanity-check-context-hunk-half (lines) + (let ((count lines)) + (while + (cond + ((and (memq (char-after) '(?\s ?! ?+ ?-)) + (memq (char-after (1+ (point))) '(?\s ?\t))) + (decf count) t) + ((or (zerop count) (= count lines)) nil) + ((memq (char-after) '(?! ?+ ?-)) + (if (not (and (eq (char-after (1+ (point))) ?\n) + (y-or-n-p "Try to auto-fix whitespace loss damage? "))) + (error "End of hunk ambiguously marked") + (forward-char 1) (insert " ") (forward-line -1) t)) + ((< lines 0) + (error "End of hunk ambiguously marked")) + ((not (y-or-n-p "Try to auto-fix whitespace loss and word-wrap damage? ")) + (error "Abort!")) + ((eolp) (insert " ") (forward-line -1) t) + (t (insert " ") (delete-region (- (point) 2) (- (point) 1)) t)) + (forward-line)))) + +(defun diff-sanity-check-hunk () + (let (;; Every modification is protected by a y-or-n-p, so it's probably + ;; OK to override a read-only setting. + (inhibit-read-only t)) + (save-excursion + (cond + ((not (looking-at diff-hunk-header-re)) + (error "Not recognizable hunk header")) + + ;; A context diff. + ((eq (char-after) ?*) + (if (not (looking-at "\\*\\{15\\}\\(?: .*\\)?\n\\*\\*\\* \\([0-9]+\\)\\(?:,\\([0-9]+\\)\\)? \\*\\*\\*\\*")) + (error "Unrecognized context diff first hunk header format") + (forward-line 2) + (diff-sanity-check-context-hunk-half + (if (match-end 2) + (1+ (- (string-to-number (match-string 2)) + (string-to-number (match-string 1)))) + 1)) + (if (not (looking-at diff-context-mid-hunk-header-re)) + (error "Unrecognized context diff second hunk header format") + (forward-line) + (diff-sanity-check-context-hunk-half + (if (match-end 2) + (1+ (- (string-to-number (match-string 2)) + (string-to-number (match-string 1)))) + 1))))) + + ;; A unified diff. + ((eq (char-after) ?@) + (if (not (looking-at diff-hunk-header-re-unified)) + (error "Unrecognized unified diff hunk header format") + (let ((before (string-to-number (or (match-string 2) "1"))) + (after (string-to-number (or (match-string 4) "1")))) + (forward-line) + (while + (case (char-after) + (?\s (decf before) (decf after) t) + (?- + (if (and (looking-at diff-file-header-re) + (zerop before) (zerop after)) + ;; No need to query: this is a case where two patches + ;; are concatenated and only counting the lines will + ;; give the right result. Let's just add an empty + ;; line so that our code which doesn't count lines + ;; will not get confused. + (progn (save-excursion (insert "\n")) nil) + (decf before) t)) + (?+ (decf after) t) + (t + (cond + ((and diff-valid-unified-empty-line + ;; Not just (eolp) so we don't infloop at eob. + (eq (char-after) ?\n) + (> before 0) (> after 0)) + (decf before) (decf after) t) + ((and (zerop before) (zerop after)) nil) + ((or (< before 0) (< after 0)) + (error (if (or (zerop before) (zerop after)) + "End of hunk ambiguously marked" + "Hunk seriously messed up"))) + ((not (y-or-n-p (concat "Try to auto-fix " (if (eolp) "whitespace loss" "word-wrap damage") "? "))) + (error "Abort!")) + ((eolp) (insert " ") (forward-line -1) t) + (t (insert " ") + (delete-region (- (point) 2) (- (point) 1)) t)))) + (forward-line))))) + + ;; A plain diff. + (t + ;; TODO. + ))))) + +(defun diff-hunk-text (hunk destp char-offset) + "Return the literal source text from HUNK as (TEXT . OFFSET). +If DESTP is nil, TEXT is the source, otherwise the destination text. +CHAR-OFFSET is a char-offset in HUNK, and OFFSET is the corresponding +char-offset in TEXT." + (with-temp-buffer + (insert hunk) + (goto-char (point-min)) + (let ((src-pos nil) + (dst-pos nil) + (divider-pos nil) + (num-pfx-chars 2)) + ;; Set the following variables: + ;; SRC-POS buffer pos of the source part of the hunk or nil if none + ;; DST-POS buffer pos of the destination part of the hunk or nil + ;; DIVIDER-POS buffer pos of any divider line separating the src & dst + ;; NUM-PFX-CHARS number of line-prefix characters used by this format" + (cond ((looking-at "^@@") + ;; unified diff + (setq num-pfx-chars 1) + (forward-line 1) + (setq src-pos (point) dst-pos (point))) + ((looking-at "^\\*\\*") + ;; context diff + (forward-line 2) + (setq src-pos (point)) + (re-search-forward diff-context-mid-hunk-header-re nil t) + (forward-line 0) + (setq divider-pos (point)) + (forward-line 1) + (setq dst-pos (point))) + ((looking-at "^[0-9]+a[0-9,]+$") + ;; normal diff, insert + (forward-line 1) + (setq dst-pos (point))) + ((looking-at "^[0-9,]+d[0-9]+$") + ;; normal diff, delete + (forward-line 1) + (setq src-pos (point))) + ((looking-at "^[0-9,]+c[0-9,]+$") + ;; normal diff, change + (forward-line 1) + (setq src-pos (point)) + (re-search-forward "^---$" nil t) + (forward-line 0) + (setq divider-pos (point)) + (forward-line 1) + (setq dst-pos (point))) + (t + (error "Unknown diff hunk type"))) + + (if (if destp (null dst-pos) (null src-pos)) + ;; Implied empty text + (if char-offset '("" . 0) "") + + ;; For context diffs, either side can be empty, (if there's only + ;; added or only removed text). We should then use the other side. + (cond ((equal src-pos divider-pos) (setq src-pos dst-pos)) + ((equal dst-pos (point-max)) (setq dst-pos src-pos))) + + (when char-offset (goto-char (+ (point-min) char-offset))) + + ;; Get rid of anything except the desired text. + (save-excursion + ;; Delete unused text region + (let ((keep (if destp dst-pos src-pos))) + (when (and divider-pos (> divider-pos keep)) + (delete-region divider-pos (point-max))) + (delete-region (point-min) keep)) + ;; Remove line-prefix characters, and unneeded lines (unified diffs). + (let ((kill-char (if destp ?- ?+))) + (goto-char (point-min)) + (while (not (eobp)) + (if (eq (char-after) kill-char) + (delete-region (point) (progn (forward-line 1) (point))) + (delete-char num-pfx-chars) + (forward-line 1))))) + + (let ((text (buffer-substring-no-properties (point-min) (point-max)))) + (if char-offset (cons text (- (point) (point-min))) text)))))) + + +(defun diff-find-text (text) + "Return the buffer position (BEG . END) of the nearest occurrence of TEXT. +If TEXT isn't found, nil is returned." + (let* ((orig (point)) + (forw (and (search-forward text nil t) + (cons (match-beginning 0) (match-end 0)))) + (back (and (goto-char (+ orig (length text))) + (search-backward text nil t) + (cons (match-beginning 0) (match-end 0))))) + ;; Choose the closest match. + (if (and forw back) + (if (> (- (car forw) orig) (- orig (car back))) back forw) + (or back forw)))) + +(defun diff-find-approx-text (text) + "Return the buffer position (BEG . END) of the nearest occurrence of TEXT. +Whitespace differences are ignored." + (let* ((orig (point)) + (re (concat "^[ \t\n ]*" + (mapconcat 'regexp-quote (split-string text) "[ \t\n ]+") + "[ \t\n ]*\n")) + (forw (and (re-search-forward re nil t) + (cons (match-beginning 0) (match-end 0)))) + (back (and (goto-char (+ orig (length text))) + (re-search-backward re nil t) + (cons (match-beginning 0) (match-end 0))))) + ;; Choose the closest match. + (if (and forw back) + (if (> (- (car forw) orig) (- orig (car back))) back forw) + (or back forw)))) + +(defsubst diff-xor (a b) (if a (if (not b) a) b)) + +(defun diff-find-source-location (&optional other-file reverse noprompt) + "Find out (BUF LINE-OFFSET POS SRC DST SWITCHED). +BUF is the buffer corresponding to the source file. +LINE-OFFSET is the offset between the expected and actual positions + of the text of the hunk or nil if the text was not found. +POS is a pair (BEG . END) indicating the position of the text in the buffer. +SRC and DST are the two variants of text as returned by `diff-hunk-text'. + SRC is the variant that was found in the buffer. +SWITCHED is non-nil if the patch is already applied. +NOPROMPT, if non-nil, means not to prompt the user." + (save-excursion + (let* ((other (diff-xor other-file diff-jump-to-old-file)) + (char-offset (- (point) (progn (diff-beginning-of-hunk 'try-harder) + (point)))) + ;; Check that the hunk is well-formed. Otherwise diff-mode and + ;; the user may disagree on what constitutes the hunk + ;; (e.g. because an empty line truncates the hunk mid-course), + ;; leading to potentially nasty surprises for the user. + ;; + ;; Suppress check when NOPROMPT is non-nil (Bug#3033). + (_ (unless noprompt (diff-sanity-check-hunk))) + (hunk (buffer-substring + (point) (save-excursion (diff-end-of-hunk) (point)))) + (old (diff-hunk-text hunk reverse char-offset)) + (new (diff-hunk-text hunk (not reverse) char-offset)) + ;; Find the location specification. + (line (if (not (looking-at "\\(?:\\*\\{15\\}.*\n\\)?[-@* ]*\\([0-9,]+\\)\\([ acd+]+\\([0-9,]+\\)\\)?")) + (error "Can't find the hunk header") + (if other (match-string 1) + (if (match-end 3) (match-string 3) + (unless (re-search-forward + diff-context-mid-hunk-header-re nil t) + (error "Can't find the hunk separator")) + (match-string 1))))) + (file (or (diff-find-file-name other noprompt) + (error "Can't find the file"))) + (buf (find-file-noselect file))) + ;; Update the user preference if he so wished. + (when (> (prefix-numeric-value other-file) 8) + (setq diff-jump-to-old-file other)) + (with-current-buffer buf + (goto-char (point-min)) (forward-line (1- (string-to-number line))) + (let* ((orig-pos (point)) + (switched nil) + ;; FIXME: Check for case where both OLD and NEW are found. + (pos (or (diff-find-text (car old)) + (progn (setq switched t) (diff-find-text (car new))) + (progn (setq switched nil) + (condition-case nil + (diff-find-approx-text (car old)) + (invalid-regexp nil))) ;Regex too big. + (progn (setq switched t) + (condition-case nil + (diff-find-approx-text (car new)) + (invalid-regexp nil))) ;Regex too big. + (progn (setq switched nil) nil)))) + (nconc + (list buf) + (if pos + (list (count-lines orig-pos (car pos)) pos) + (list nil (cons orig-pos (+ orig-pos (length (car old)))))) + (if switched (list new old t) (list old new)))))))) + + +(defun diff-hunk-status-msg (line-offset reversed dry-run) + (let ((msg (if dry-run + (if reversed "already applied" "not yet applied") + (if reversed "undone" "applied")))) + (message (cond ((null line-offset) "Hunk text not found") + ((= line-offset 0) "Hunk %s") + ((= line-offset 1) "Hunk %s at offset %d line") + (t "Hunk %s at offset %d lines")) + msg line-offset))) + +(defvar diff-apply-hunk-to-backup-file nil) + +(defun diff-apply-hunk (&optional reverse) + "Apply the current hunk to the source file and go to the next. +By default, the new source file is patched, but if the variable +`diff-jump-to-old-file' is non-nil, then the old source file is +patched instead (some commands, such as `diff-goto-source' can change +the value of this variable when given an appropriate prefix argument). + +With a prefix argument, REVERSE the hunk." + (interactive "P") + (destructuring-bind (buf line-offset pos old new &optional switched) + ;; Sometimes we'd like to have the following behavior: if REVERSE go + ;; to the new file, otherwise go to the old. But that means that by + ;; default we use the old file, which is the opposite of the default + ;; for diff-goto-source, and is thus confusing. Also when you don't + ;; know about it it's pretty surprising. + ;; TODO: make it possible to ask explicitly for this behavior. + ;; + ;; This is duplicated in diff-test-hunk. + (diff-find-source-location nil reverse) + (cond + ((null line-offset) + (error "Can't find the text to patch")) + ((with-current-buffer buf + (and buffer-file-name + (backup-file-name-p buffer-file-name) + (not diff-apply-hunk-to-backup-file) + (not (set (make-local-variable 'diff-apply-hunk-to-backup-file) + (yes-or-no-p (format "Really apply this hunk to %s? " + (file-name-nondirectory + buffer-file-name))))))) + (error "%s" + (substitute-command-keys + (format "Use %s\\[diff-apply-hunk] to apply it to the other file" + (if (not reverse) "\\[universal-argument] "))))) + ((and switched + ;; A reversed patch was detected, perhaps apply it in reverse. + (not (save-window-excursion + (pop-to-buffer buf) + (goto-char (+ (car pos) (cdr old))) + (y-or-n-p + (if reverse + "Hunk hasn't been applied yet; apply it now? " + "Hunk has already been applied; undo it? "))))) + (message "(Nothing done)")) + (t + ;; Apply the hunk + (with-current-buffer buf + (goto-char (car pos)) + (delete-region (car pos) (cdr pos)) + (insert (car new))) + ;; Display BUF in a window + (set-window-point (display-buffer buf) (+ (car pos) (cdr new))) + (diff-hunk-status-msg line-offset (diff-xor switched reverse) nil) + (when diff-advance-after-apply-hunk + (diff-hunk-next)))))) + + +(defun diff-test-hunk (&optional reverse) + "See whether it's possible to apply the current hunk. +With a prefix argument, try to REVERSE the hunk." + (interactive "P") + (destructuring-bind (buf line-offset pos src dst &optional switched) + (diff-find-source-location nil reverse) + (set-window-point (display-buffer buf) (+ (car pos) (cdr src))) + (diff-hunk-status-msg line-offset (diff-xor reverse switched) t))) + + +(defalias 'diff-mouse-goto-source 'diff-goto-source) + +(defun diff-goto-source (&optional other-file event) + "Jump to the corresponding source line. +`diff-jump-to-old-file' (or its opposite if the OTHER-FILE prefix arg +is given) determines whether to jump to the old or the new file. +If the prefix arg is bigger than 8 (for example with \\[universal-argument] \\[universal-argument]) +then `diff-jump-to-old-file' is also set, for the next invocations." + (interactive (list current-prefix-arg last-input-event)) + ;; When pointing at a removal line, we probably want to jump to + ;; the old location, and else to the new (i.e. as if reverting). + ;; This is a convenient detail when using smerge-diff. + (if event (posn-set-point (event-end event))) + (let ((rev (not (save-excursion (beginning-of-line) (looking-at "[-<]"))))) + (destructuring-bind (buf line-offset pos src dst &optional switched) + (diff-find-source-location other-file rev) + (pop-to-buffer buf) + (goto-char (+ (car pos) (cdr src))) + (diff-hunk-status-msg line-offset (diff-xor rev switched) t)))) + + +(defun diff-current-defun () + "Find the name of function at point. +For use in `add-log-current-defun-function'." + ;; Kill change-log-default-name so it gets recomputed each time, since + ;; each hunk may belong to another file which may belong to another + ;; directory and hence have a different ChangeLog file. + (kill-local-variable 'change-log-default-name) + (save-excursion + (when (looking-at diff-hunk-header-re) + (forward-line 1) + (re-search-forward "^[^ ]" nil t)) + (destructuring-bind (&optional buf line-offset pos src dst switched) + ;; Use `noprompt' since this is used in which-func-mode and such. + (ignore-errors ;Signals errors in place of prompting. + (diff-find-source-location nil nil 'noprompt)) + (when buf + (beginning-of-line) + (or (when (memq (char-after) '(?< ?-)) + ;; Cursor is pointing at removed text. This could be a removed + ;; function, in which case, going to the source buffer will + ;; not help since the function is now removed. Instead, + ;; try to figure out the function name just from the + ;; code-fragment. + (let ((old (if switched dst src))) + (with-temp-buffer + (insert (car old)) + (funcall (buffer-local-value 'major-mode buf)) + (goto-char (+ (point-min) (cdr old))) + (add-log-current-defun)))) + (with-current-buffer buf + (goto-char (+ (car pos) (cdr src))) + (add-log-current-defun))))))) + +(defun diff-ignore-whitespace-hunk () + "Re-diff the current hunk, ignoring whitespace differences." + (interactive) + (let* ((char-offset (- (point) (progn (diff-beginning-of-hunk 'try-harder) + (point)))) + (opts (case (char-after) (?@ "-bu") (?* "-bc") (t "-b"))) + (line-nb (and (or (looking-at "[^0-9]+\\([0-9]+\\)") + (error "Can't find line number")) + (string-to-number (match-string 1)))) + (inhibit-read-only t) + (hunk (delete-and-extract-region + (point) (save-excursion (diff-end-of-hunk) (point)))) + (lead (make-string (1- line-nb) ?\n)) ;Line nums start at 1. + (file1 (make-temp-file "diff1")) + (file2 (make-temp-file "diff2")) + (coding-system-for-read buffer-file-coding-system) + old new) + (unwind-protect + (save-excursion + (setq old (diff-hunk-text hunk nil char-offset)) + (setq new (diff-hunk-text hunk t char-offset)) + (write-region (concat lead (car old)) nil file1 nil 'nomessage) + (write-region (concat lead (car new)) nil file2 nil 'nomessage) + (with-temp-buffer + (let ((status + (call-process diff-command nil t nil + opts file1 file2))) + (case status + (0 nil) ;Nothing to reformat. + (1 (goto-char (point-min)) + ;; Remove the file-header. + (when (re-search-forward diff-hunk-header-re nil t) + (delete-region (point-min) (match-beginning 0)))) + (t (goto-char (point-max)) + (unless (bolp) (insert "\n")) + (insert hunk))) + (setq hunk (buffer-string)) + (unless (memq status '(0 1)) + (error "Diff returned: %s" status))))) + ;; Whatever happens, put back some equivalent text: either the new + ;; one or the original one in case some error happened. + (insert hunk) + (delete-file file1) + (delete-file file2)))) + +;;; Fine change highlighting. + +(defface diff-refine-change + '((((class color) (min-colors 88) (background light)) + :background "grey85") + (((class color) (min-colors 88) (background dark)) + :background "grey60") + (((class color) (background light)) + :background "yellow") + (((class color) (background dark)) + :background "green") + (t :weight bold)) + "Face used for char-based changes shown by `diff-refine-hunk'." + :group 'diff-mode) + +(defun diff-refine-preproc () + (while (re-search-forward "^[+>]" nil t) + ;; Remove spurious changes due to the fact that one side of the hunk is + ;; marked with leading + or > and the other with leading - or <. + ;; We used to replace all the prefix chars with " " but this only worked + ;; when we did char-based refinement (or when using + ;; smerge-refine-weight-hack) since otherwise, the `forward' motion done + ;; in chopup do not necessarily do the same as the ones in highlight + ;; since the "_" is not treated the same as " ". + (replace-match (cdr (assq (char-before) '((?+ . "-") (?> . "<")))))) + ) + +(defun diff-refine-hunk () + "Highlight changes of hunk at point at a finer granularity." + (interactive) + (eval-and-compile (require 'smerge-mode)) + (save-excursion + (diff-beginning-of-hunk 'try-harder) + (let* ((style (diff-hunk-style)) ;Skips the hunk header as well. + (beg (point)) + (props '((diff-mode . fine) (face diff-refine-change))) + (end (progn (diff-end-of-hunk) (point)))) + + (remove-overlays beg end 'diff-mode 'fine) + + (goto-char beg) + (case style + (unified + (while (re-search-forward "^\\(?:-.*\n\\)+\\(\\)\\(?:\\+.*\n\\)+" + end t) + (smerge-refine-subst (match-beginning 0) (match-end 1) + (match-end 1) (match-end 0) + props 'diff-refine-preproc))) + (context + (let* ((middle (save-excursion (re-search-forward "^---"))) + (other middle)) + (while (re-search-forward "^\\(?:!.*\n\\)+" middle t) + (smerge-refine-subst (match-beginning 0) (match-end 0) + (save-excursion + (goto-char other) + (re-search-forward "^\\(?:!.*\n\\)+" end) + (setq other (match-end 0)) + (match-beginning 0)) + other + props 'diff-refine-preproc)))) + (t ;; Normal diffs. + (let ((beg1 (1+ (point)))) + (when (re-search-forward "^---.*\n" end t) + ;; It's a combined add&remove, so there's something to do. + (smerge-refine-subst beg1 (match-beginning 0) + (match-end 0) end + props 'diff-refine-preproc)))))))) + + +(defun diff-add-change-log-entries-other-window () + "Iterate through the current diff and create ChangeLog entries. +I.e. like `add-change-log-entry-other-window' but applied to all hunks." + (interactive) + ;; XXX: Currently add-change-log-entry-other-window is only called + ;; once per hunk. Some hunks have multiple changes, it would be + ;; good to call it for each change. + (save-excursion + (goto-char (point-min)) + (let ((orig-buffer (current-buffer))) + (condition-case nil + ;; Call add-change-log-entry-other-window for each hunk in + ;; the diff buffer. + (while (progn + (diff-hunk-next) + ;; Move to where the changes are, + ;; `add-change-log-entry-other-window' works better in + ;; that case. + (re-search-forward + (concat "\n[!+-<>]" + ;; If the hunk is a context hunk with an empty first + ;; half, recognize the "--- NNN,MMM ----" line + "\\(-- [0-9]+\\(,[0-9]+\\)? ----\n" + ;; and skip to the next non-context line. + "\\( .*\n\\)*[+]\\)?") + nil t)) + (save-excursion + ;; FIXME: this pops up windows of all the buffers. + (add-change-log-entry nil nil t nil t))) + ;; When there's no more hunks, diff-hunk-next signals an error. + (error nil))))) + +;; provide the package +(provide 'diff-mode) + +;;; Old Change Log from when diff-mode wasn't part of Emacs: +;; Revision 1.11 1999/10/09 23:38:29 monnier +;; (diff-mode-load-hook): dropped. +;; (auto-mode-alist): also catch *.diffs. +;; (diff-find-file-name, diff-mode): add smarts to find the right file +;; for *.rej files (that lack any file name indication). +;; +;; Revision 1.10 1999/09/30 15:32:11 monnier +;; added support for "\ No newline at end of file". +;; +;; Revision 1.9 1999/09/15 00:01:13 monnier +;; - added basic `compile' support. +;; - have diff-kill-hunk call diff-kill-file if it's the only hunk. +;; - diff-kill-file now tries to kill the leading garbage as well. +;; +;; Revision 1.8 1999/09/13 21:10:09 monnier +;; - don't use CL in the autoloaded code +;; - accept diffs using -T +;; +;; Revision 1.7 1999/09/05 20:53:03 monnier +;; interface to ediff-patch +;; +;; Revision 1.6 1999/09/01 20:55:13 monnier +;; (ediff=patch-file): add bindings to call ediff-patch. +;; (diff-find-file-name): taken out of diff-goto-source. +;; (diff-unified->context, diff-context->unified, diff-reverse-direction, +;; diff-fixup-modifs): only use the region if a prefix arg is given. +;; +;; Revision 1.5 1999/08/31 19:18:52 monnier +;; (diff-beginning-of-file, diff-prev-file): fixed wrong parenthesis. +;; +;; Revision 1.4 1999/08/31 13:01:44 monnier +;; use `combine-after-change-calls' to minimize the slowdown of font-lock. +;; + +;; arch-tag: 2571d7ff-bc28-4cf9-8585-42e21890be66 +;;; diff-mode.el ends here diff -r 681cd08dc0f7 -r e93288477c43 lisp/vc/diff.el --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/lisp/vc/diff.el Sun Jun 13 22:57:55 2010 +0000 @@ -0,0 +1,205 @@ +;;; diff.el --- run `diff' in compilation-mode + +;; Copyright (C) 1992, 1994, 1996, 2001, 2002, 2003, 2004, 2005, 2006, +;; 2007, 2008, 2009, 2010 Free Software Foundation, Inc. + +;; Author: Frank Bresz +;; (according to authors.el) +;; Maintainer: FSF +;; Keywords: unix, vc, tools + +;; 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 3 of the License, 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. If not, see . + +;;; Commentary: + +;; This package helps you explore differences between files, using the +;; UNIX command diff(1). The commands are `diff' and `diff-backup'. +;; You can specify options with `diff-switches'. + +;;; Code: + +(defgroup diff nil + "Comparing files with `diff'." + :group 'tools) + +;;;###autoload +(defcustom diff-switches (purecopy "-c") + "A string or list of strings specifying switches to be passed to diff." + :type '(choice string (repeat string)) + :group 'diff) + +;;;###autoload +(defcustom diff-command (purecopy "diff") + "The command to use to run diff." + :type 'string + :group 'diff) + +(defvar diff-old-temp-file nil + "This is the name of a temp file to be deleted after diff finishes.") +(defvar diff-new-temp-file nil + "This is the name of a temp file to be deleted after diff finishes.") + +;; prompt if prefix arg present +(defun diff-switches () + (if current-prefix-arg + (read-string "Diff switches: " + (if (stringp diff-switches) + diff-switches + (mapconcat 'identity diff-switches " "))))) + +(defun diff-sentinel (code) + "Code run when the diff process exits. +CODE is the exit code of the process. It should be 0 only if no diffs +were found." + (if diff-old-temp-file (delete-file diff-old-temp-file)) + (if diff-new-temp-file (delete-file diff-new-temp-file)) + (save-excursion + (goto-char (point-max)) + (let ((inhibit-read-only t)) + (insert (format "\nDiff finished%s. %s\n" + (cond ((equal 0 code) " (no differences)") + ((equal 2 code) " (diff error)") + (t "")) + (current-time-string)))))) + +(defvar diff-old-file nil) +(defvar diff-new-file nil) +(defvar diff-extra-args nil) + +;;;###autoload +(defun diff (old new &optional switches no-async) + "Find and display the differences between OLD and NEW files. +When called interactively, read OLD and NEW using the minibuffer; +the default for NEW is the current buffer's file name, and the +default for OLD is a backup file for NEW, if one exists. +If NO-ASYNC is non-nil, call diff synchronously. + +When called interactively with a prefix argument, prompt +interactively for diff switches. Otherwise, the switches +specified in `diff-switches' are passed to the diff command." + (interactive + (let (oldf newf) + (setq newf (buffer-file-name) + newf (if (and newf (file-exists-p newf)) + (read-file-name + (concat "Diff new file (default " + (file-name-nondirectory newf) "): ") + nil newf t) + (read-file-name "Diff new file: " nil nil t))) + (setq oldf (file-newest-backup newf) + oldf (if (and oldf (file-exists-p oldf)) + (read-file-name + (concat "Diff original file (default " + (file-name-nondirectory oldf) "): ") + (file-name-directory oldf) oldf t) + (read-file-name "Diff original file: " + (file-name-directory newf) nil t))) + (list oldf newf (diff-switches)))) + (setq new (expand-file-name new) + old (expand-file-name old)) + (or switches (setq switches diff-switches)) ; If not specified, use default. + (let* ((old-alt (file-local-copy old)) + (new-alt (file-local-copy new)) + (command + (mapconcat 'identity + `(,diff-command + ;; Use explicitly specified switches + ,@(if (listp switches) switches (list switches)) + ,@(if (or old-alt new-alt) + (list "-L" old "-L" new)) + ,(shell-quote-argument (or old-alt old)) + ,(shell-quote-argument (or new-alt new))) + " ")) + (buf (get-buffer-create "*Diff*")) + (thisdir default-directory) + proc) + (save-excursion + (display-buffer buf) + (set-buffer buf) + (setq buffer-read-only nil) + (buffer-disable-undo (current-buffer)) + (let ((inhibit-read-only t)) + (erase-buffer)) + (buffer-enable-undo (current-buffer)) + (diff-mode) + ;; Use below 2 vars for backward-compatibility. + (set (make-local-variable 'diff-old-file) old) + (set (make-local-variable 'diff-new-file) new) + (set (make-local-variable 'diff-extra-args) (list switches no-async)) + (set (make-local-variable 'revert-buffer-function) + (lambda (ignore-auto noconfirm) + (apply 'diff diff-old-file diff-new-file diff-extra-args))) + (set (make-local-variable 'diff-old-temp-file) old-alt) + (set (make-local-variable 'diff-new-temp-file) new-alt) + (setq default-directory thisdir) + (let ((inhibit-read-only t)) + (insert command "\n")) + (if (and (not no-async) (fboundp 'start-process)) + (progn + (setq proc (start-process "Diff" buf shell-file-name + shell-command-switch command)) + (set-process-filter proc 'diff-process-filter) + (set-process-sentinel + proc (lambda (proc msg) + (with-current-buffer (process-buffer proc) + (diff-sentinel (process-exit-status proc)))))) + ;; Async processes aren't available. + (let ((inhibit-read-only t)) + (diff-sentinel + (call-process shell-file-name nil buf nil + shell-command-switch command))))) + buf)) + +(defun diff-process-filter (proc string) + (with-current-buffer (process-buffer proc) + (let ((moving (= (point) (process-mark proc)))) + (save-excursion + ;; Insert the text, advancing the process marker. + (goto-char (process-mark proc)) + (let ((inhibit-read-only t)) + (insert string)) + (set-marker (process-mark proc) (point))) + (if moving (goto-char (process-mark proc)))))) + +;;;###autoload +(defun diff-backup (file &optional switches) + "Diff this file with its backup file or vice versa. +Uses the latest backup, if there are several numerical backups. +If this file is a backup, diff it with its original. +The backup file is the first file given to `diff'. +With prefix arg, prompt for diff switches." + (interactive (list (read-file-name "Diff (file with backup): ") + (diff-switches))) + (let (bak ori) + (if (backup-file-name-p file) + (setq bak file + ori (file-name-sans-versions file)) + (setq bak (or (diff-latest-backup-file file) + (error "No backup found for %s" file)) + ori file)) + (diff bak ori switches))) + +(defun diff-latest-backup-file (fn) ; actually belongs into files.el + "Return the latest existing backup of FILE, or nil." + (let ((handler (find-file-name-handler fn 'diff-latest-backup-file))) + (if handler + (funcall handler 'diff-latest-backup-file fn) + (file-newest-backup fn)))) + +(provide 'diff) + +;; arch-tag: 7de2c29b-7ea5-4b85-9b9d-72dd860de2bd +;;; diff.el ends here diff -r 681cd08dc0f7 -r e93288477c43 lisp/vc/ediff-diff.el --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/lisp/vc/ediff-diff.el Sun Jun 13 22:57:55 2010 +0000 @@ -0,0 +1,1536 @@ +;;; ediff-diff.el --- diff-related utilities + +;; Copyright (C) 1994, 1995, 1996, 1997, 1998, 1999, 2000, 2001, +;; 2002, 2003, 2004, 2005, 2006, 2007, 2008, 2009, 2010 Free Software Foundation, Inc. + +;; Author: Michael Kifer + +;; 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 3 of the License, 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. If not, see . + +;;; Commentary: + +;;; Code: + + +(provide 'ediff-diff) + +(eval-when-compile + (require 'ediff-util)) + +(require 'ediff-init) + +(defgroup ediff-diff nil + "Diff related utilities." + :prefix "ediff-" + :group 'ediff) + +(defcustom ediff-diff-program "diff" + "Program to use for generating the differential of the two files." + :type 'string + :group 'ediff-diff) +(defcustom ediff-diff3-program "diff3" + "Program to be used for three-way comparison. +Must produce output compatible with Unix's diff3 program." + :type 'string + :group 'ediff-diff) + + +;; The following functions must precede all defcustom-defined variables. + +(fset 'ediff-set-actual-diff-options '(lambda () nil)) + +(defcustom ediff-shell + (cond ((eq system-type 'emx) "cmd") ; OS/2 + ((memq system-type '(ms-dos windows-nt windows-95)) + shell-file-name) ; no standard name on MS-DOS + (t "sh")) ; UNIX + "The shell used to run diff and patch. +If user's .profile or .cshrc files are set up correctly, any shell +will do. However, some people set $prompt or other things +incorrectly, which leads to undesirable output messages. These may +cause Ediff to fail. In such a case, set `ediff-shell' to a shell that +you are not using or, better, fix your shell's startup file." + :type 'string + :group 'ediff-diff) + +(defcustom ediff-cmp-program "cmp" + "Utility to use to determine if two files are identical. +It must return code 0, if its arguments are identical files." + :type 'string + :group 'ediff-diff) + +(defcustom ediff-cmp-options nil + "Options to pass to `ediff-cmp-program'. +If GNU diff is used as `ediff-cmp-program', then the most useful options +are `-I REGEXP', to ignore changes whose lines match the REGEXP." + :type '(repeat string) + :group 'ediff-diff) + +(defun ediff-set-diff-options (symbol value) + (set symbol value) + (ediff-set-actual-diff-options)) + +(defcustom ediff-diff-options + (if (memq system-type '(ms-dos windows-nt windows-95)) "--binary" "") + "Options to pass to `ediff-diff-program'. +If Unix diff is used as `ediff-diff-program', +then a useful option is `-w', to ignore space. +Options `-c', `-u', and `-i' are not allowed. Case sensitivity can be +toggled interactively using \\[ediff-toggle-ignore-case]. + +Do not remove the default options. If you need to change this variable, add new +options after the default ones. + +This variable is not for customizing the look of the differences produced by +the command \\[ediff-show-diff-output]. Use the variable +`ediff-custom-diff-options' for that." + :set 'ediff-set-diff-options + :type 'string + :group 'ediff-diff) + +(ediff-defvar-local ediff-ignore-case nil + "*If t, skip over difference regions that differ only in letter case. +This variable can be set either in .emacs or toggled interactively. +Use `setq-default' if setting it in .emacs") + +(defcustom ediff-ignore-case-option "-i" + "Option that causes the diff program to ignore case of letters." + :type 'string + :group 'ediff-diff) + +(defcustom ediff-ignore-case-option3 "" + "Option that causes the diff3 program to ignore case of letters. +GNU diff3 doesn't have such an option." + :type 'string + :group 'ediff-diff) + +;; the actual options used in comparison +(ediff-defvar-local ediff-actual-diff-options ediff-diff-options "") + +(defcustom ediff-custom-diff-program ediff-diff-program + "Program to use for generating custom diff output for saving it in a file. +This output is not used by Ediff internally." + :type 'string + :group 'ediff-diff) +(defcustom ediff-custom-diff-options "-c" + "Options to pass to `ediff-custom-diff-program'." + :type 'string + :group 'ediff-diff) + +;;; Support for diff3 + +(defvar ediff-match-diff3-line "^====\\(.?\\)\C-m?$" + "Pattern to match lines produced by diff3 that describe differences.") +(defcustom ediff-diff3-options "" + "Options to pass to `ediff-diff3-program'." + :set 'ediff-set-diff-options + :type 'string + :group 'ediff-diff) + +;; the actual options used in comparison +(ediff-defvar-local ediff-actual-diff3-options ediff-diff3-options "") + +(defcustom ediff-diff3-ok-lines-regexp + "^\\([1-3]:\\|====\\| \\|.*Warning *:\\|.*No newline\\|.*missing newline\\|^\C-m$\\)" + "Regexp that matches normal output lines from `ediff-diff3-program'. +Lines that do not match are assumed to be error messages." + :type 'regexp + :group 'ediff-diff) + +;; keeps the status of the current diff in 3-way jobs. +;; the status can be =diff(A), =diff(B), or =diff(A+B) +(ediff-defvar-local ediff-diff-status "" "") + + +;;; Fine differences + +(ediff-defvar-local ediff-auto-refine (if (ediff-has-face-support-p) 'on 'nix) + "If `on', Ediff auto-highlights fine diffs for the current diff region. +If `off', auto-highlighting is not used. If `nix', no fine diffs are shown +at all, unless the user force-refines the region by hitting `*'. + +This variable can be set either in .emacs or toggled interactively. +Use `setq-default' if setting it in .emacs") + +(ediff-defvar-local ediff-ignore-similar-regions nil + "*If t, skip over difference regions that differ only in the white space and line breaks. +This variable can be set either in .emacs or toggled interactively. +Use `setq-default' if setting it in .emacs") + +(ediff-defvar-local ediff-auto-refine-limit 14000 + "*Auto-refine only the regions of this size \(in bytes\) or less.") + +;;; General + +(defvar ediff-diff-ok-lines-regexp + (concat + "^\\(" + "[0-9,]+[acd][0-9,]+\C-m?$" + "\\|[<>] " + "\\|---" + "\\|.*Warning *:" + "\\|.*No +newline" + "\\|.*missing +newline" + "\\|^\C-m?$" + "\\)") + "Regexp that matches normal output lines from `ediff-diff-program'. +This is mostly lifted from Emerge, except that Ediff also considers +warnings and `Missing newline'-type messages to be normal output. +Lines that do not match are assumed to be error messages.") + +(defvar ediff-match-diff-line + (let ((x "\\([0-9]+\\)\\(\\|,\\([0-9]+\\)\\)")) + (concat "^" x "\\([acd]\\)" x "\C-m?$")) + "Pattern to match lines produced by diff that describe differences.") + +(ediff-defvar-local ediff-setup-diff-regions-function nil + "value is a function symbol depending on the kind of job is to be done. +For 2-way jobs and for ediff-merge, it should be `ediff-setup-diff-regions'. +For jobs requiring diff3, it should be `ediff-setup-diff-regions3'. + +The function should take three mandatory arguments, file-A, file-B, and +file-C. It may ignore file C for diff2 jobs. It should also take +one optional arguments, diff-number to refine.") + + +;;; Functions + +;; Generate the difference vector and overlays for the two files +;; With optional arg REG-TO-REFINE, refine this region. +;; File-C argument is not used here. It is there just because +;; ediff-setup-diff-regions is called via a funcall to +;; ediff-setup-diff-regions-function, which can also have the value +;; ediff-setup-diff-regions3, which takes 4 arguments. +(defun ediff-setup-diff-regions (file-A file-B file-C) + ;; looking for '-c', '-i', '-u', or 'c', 'i', 'u' among clustered non-long options + (if (string-match "^-[ciu]\\| -[ciu]\\|\\(^\\| \\)-[^- ]+[ciu]" + ediff-diff-options) + (error "Options `-c', `-u', and `-i' are not allowed in `ediff-diff-options'")) + + ;; create, if it doesn't exist + (or (ediff-buffer-live-p ediff-diff-buffer) + (setq ediff-diff-buffer + (get-buffer-create (ediff-unique-buffer-name "*ediff-diff" "*")))) + (ediff-make-diff2-buffer ediff-diff-buffer file-A file-B) + (ediff-prepare-error-list ediff-diff-ok-lines-regexp ediff-diff-buffer) + (ediff-convert-diffs-to-overlays + (ediff-extract-diffs + ediff-diff-buffer ediff-word-mode ediff-narrow-bounds))) + +;; Run the diff program on FILE1 and FILE2 and put the output in DIFF-BUFFER +;; Return the size of DIFF-BUFFER +;; The return code isn't used in the program at present. +(defun ediff-make-diff2-buffer (diff-buffer file1 file2) + (let ((file1-size (ediff-file-size file1)) + (file2-size (ediff-file-size file2))) + (cond ((not (numberp file1-size)) + (message "Can't find file: %s" + (ediff-abbreviate-file-name file1)) + (sit-for 2) + ;; 1 is an error exit code + 1) + ((not (numberp file2-size)) + (message "Can't find file: %s" + (ediff-abbreviate-file-name file2)) + (sit-for 2) + ;; 1 is an error exit code + 1) + (t (message "Computing differences between %s and %s ..." + (file-name-nondirectory file1) + (file-name-nondirectory file2)) + ;; this erases the diff buffer automatically + (ediff-exec-process ediff-diff-program + diff-buffer + 'synchronize + ediff-actual-diff-options file1 file2) + (message "") + (ediff-with-current-buffer diff-buffer + (buffer-size)))))) + + + +;; If file-A/B/C is nil, do 2-way comparison with the non-nil buffers +;; This function works for diff3 and diff2 jobs +(defun ediff-setup-fine-diff-regions (file-A file-B file-C reg-num) + (or (ediff-buffer-live-p ediff-fine-diff-buffer) + (setq ediff-fine-diff-buffer + (get-buffer-create + (ediff-unique-buffer-name "*ediff-fine-diff" "*")))) + + (let (diff3-job diff-program diff-options ok-regexp diff-list) + (setq diff3-job ediff-3way-job + diff-program (if diff3-job ediff-diff3-program ediff-diff-program) + diff-options (if diff3-job + ediff-actual-diff3-options + ediff-actual-diff-options) + ok-regexp (if diff3-job + ediff-diff3-ok-lines-regexp + ediff-diff-ok-lines-regexp)) + + (ediff-message-if-verbose "Refining difference region %d ..." (1+ reg-num)) + (ediff-exec-process diff-program ediff-fine-diff-buffer 'synchronize + diff-options + ;; The shuffle below is because we can compare 3-way + ;; or in several 2-way fashions, like fA fC, fA fB, + ;; or fB fC. + (if file-A file-A file-B) + (if file-B file-B file-A) + (if diff3-job + (if file-C file-C file-B)) + ) ; exec process + + (ediff-prepare-error-list ok-regexp ediff-fine-diff-buffer) + (ediff-message-if-verbose + "") + ;; "Refining difference region %d ... done" (1+ reg-num)) + + (setq diff-list + (if diff3-job + (ediff-extract-diffs3 + ediff-fine-diff-buffer '3way-comparison 'word-mode) + (ediff-extract-diffs ediff-fine-diff-buffer 'word-mode))) + ;; fixup diff-list + (if diff3-job + (cond ((not file-A) + (mapc (lambda (elt) + (aset elt 0 nil) + (aset elt 1 nil)) + (cdr diff-list))) + ((not file-B) + (mapc (lambda (elt) + (aset elt 2 nil) + (aset elt 3 nil)) + (cdr diff-list))) + ((not file-C) + (mapc (lambda (elt) + (aset elt 4 nil) + (aset elt 5 nil)) + (cdr diff-list))) + )) + + (ediff-convert-fine-diffs-to-overlays diff-list reg-num) + )) + + +(defun ediff-prepare-error-list (ok-regexp diff-buff) + (or (ediff-buffer-live-p ediff-error-buffer) + (setq ediff-error-buffer + (get-buffer-create (ediff-unique-buffer-name + "*ediff-errors" "*")))) + (ediff-with-current-buffer ediff-error-buffer + (setq buffer-undo-list t) + (erase-buffer) + (insert (ediff-with-current-buffer diff-buff (buffer-string))) + (goto-char (point-min)) + (delete-matching-lines ok-regexp)) + ;; If diff reports errors, show them then quit. + (if (/= 0 (ediff-with-current-buffer ediff-error-buffer (buffer-size))) + (let ((ctl-buf ediff-control-buffer) + (error-buf ediff-error-buffer)) + (ediff-skip-unsuitable-frames) + (switch-to-buffer error-buf) + (ediff-kill-buffer-carefully ctl-buf) + (error "Errors in diff output. Diff output is in %S" diff-buff)))) + +;; BOUNDS specifies visibility bounds to use. +;; WORD-MODE tells whether we are in the word-mode or not. +;; If WORD-MODE, also construct vector of diffs using word numbers. +;; Else, use point values. +;; This function handles diff-2 jobs including the case of +;; merging buffers and files without ancestor. +(defun ediff-extract-diffs (diff-buffer word-mode &optional bounds) + (let ((A-buffer ediff-buffer-A) + (B-buffer ediff-buffer-B) + (C-buffer ediff-buffer-C) + (a-prev 1) ; this is needed to set the first diff line correctly + (a-prev-pt nil) + (b-prev 1) + (b-prev-pt nil) + (c-prev 1) + (c-prev-pt nil) + diff-list shift-A shift-B + ) + + ;; diff list contains word numbers, unless changed later + (setq diff-list (cons (if word-mode 'words 'points) + diff-list)) + ;; we don't use visibility bounds for buffer C when merging + (if bounds + (setq shift-A + (ediff-overlay-start + (ediff-get-value-according-to-buffer-type 'A bounds)) + shift-B + (ediff-overlay-start + (ediff-get-value-according-to-buffer-type 'B bounds)))) + + ;; reset point in buffers A/B/C + (ediff-with-current-buffer A-buffer + (goto-char (if shift-A shift-A (point-min)))) + (ediff-with-current-buffer B-buffer + (goto-char (if shift-B shift-B (point-min)))) + (if (ediff-buffer-live-p C-buffer) + (ediff-with-current-buffer C-buffer + (goto-char (point-min)))) + + (ediff-with-current-buffer diff-buffer + (goto-char (point-min)) + (while (re-search-forward ediff-match-diff-line nil t) + (let* ((a-begin (string-to-number (buffer-substring (match-beginning 1) + (match-end 1)))) + (a-end (let ((b (match-beginning 3)) + (e (match-end 3))) + (if b + (string-to-number (buffer-substring b e)) + a-begin))) + (diff-type (buffer-substring (match-beginning 4) (match-end 4))) + (b-begin (string-to-number (buffer-substring (match-beginning 5) + (match-end 5)))) + (b-end (let ((b (match-beginning 7)) + (e (match-end 7))) + (if b + (string-to-number (buffer-substring b e)) + b-begin))) + a-begin-pt a-end-pt b-begin-pt b-end-pt + c-begin c-end c-begin-pt c-end-pt) + ;; fix the beginning and end numbers, because diff is somewhat + ;; strange about how it numbers lines + (if (string-equal diff-type "a") + (setq b-end (1+ b-end) + a-begin (1+ a-begin) + a-end a-begin) + (if (string-equal diff-type "d") + (setq a-end (1+ a-end) + b-begin (1+ b-begin) + b-end b-begin) + ;; (string-equal diff-type "c") + (setq a-end (1+ a-end) + b-end (1+ b-end)))) + + (if (eq ediff-default-variant 'default-B) + (setq c-begin b-begin + c-end b-end) + (setq c-begin a-begin + c-end a-end)) + + ;; compute main diff vector + (if word-mode + ;; make diff-list contain word numbers + (setq diff-list + (nconc diff-list + (list + (if (ediff-buffer-live-p C-buffer) + (vector (- a-begin a-prev) (- a-end a-begin) + (- b-begin b-prev) (- b-end b-begin) + (- c-begin c-prev) (- c-end c-begin) + nil nil ; dummy ancestor + nil ; state of diff + nil ; state of merge + nil ; state of ancestor + ) + (vector (- a-begin a-prev) (- a-end a-begin) + (- b-begin b-prev) (- b-end b-begin) + nil nil ; dummy buf C + nil nil ; dummy ancestor + nil ; state of diff + nil ; state of merge + nil ; state of ancestor + )) + )) + a-prev a-end + b-prev b-end + c-prev c-end) + ;; else convert lines to points + (ediff-with-current-buffer A-buffer + (let ((longlines-mode-val + (if (and (boundp 'longlines-mode) longlines-mode) 1 0))) + ;; we must disable and then restore longlines-mode + (if (eq longlines-mode-val 1) + (longlines-mode 0)) + (goto-char (or a-prev-pt shift-A (point-min))) + (forward-line (- a-begin a-prev)) + (setq a-begin-pt (point)) + (forward-line (- a-end a-begin)) + (setq a-end-pt (point) + a-prev a-end + a-prev-pt a-end-pt) + (if (eq longlines-mode-val 1) + (longlines-mode longlines-mode-val)) + )) + (ediff-with-current-buffer B-buffer + (let ((longlines-mode-val + (if (and (boundp 'longlines-mode) longlines-mode) 1 0))) + (if (eq longlines-mode-val 1) + (longlines-mode 0)) + (goto-char (or b-prev-pt shift-B (point-min))) + (forward-line (- b-begin b-prev)) + (setq b-begin-pt (point)) + (forward-line (- b-end b-begin)) + (setq b-end-pt (point) + b-prev b-end + b-prev-pt b-end-pt) + (if (eq longlines-mode-val 1) + (longlines-mode longlines-mode-val)) + )) + (if (ediff-buffer-live-p C-buffer) + (ediff-with-current-buffer C-buffer + (let ((longlines-mode-val + (if (and (boundp 'longlines-mode) longlines-mode) 1 0))) + (if (eq longlines-mode-val 1) + (longlines-mode 0)) + (goto-char (or c-prev-pt (point-min))) + (forward-line (- c-begin c-prev)) + (setq c-begin-pt (point)) + (forward-line (- c-end c-begin)) + (setq c-end-pt (point) + c-prev c-end + c-prev-pt c-end-pt) + (if (eq longlines-mode-val 1) + (longlines-mode longlines-mode-val)) + ))) + (setq diff-list + (nconc + diff-list + (list + (if (ediff-buffer-live-p C-buffer) + (vector + a-begin-pt a-end-pt b-begin-pt b-end-pt + c-begin-pt c-end-pt + nil nil ; dummy ancestor + ;; state of diff + ;; shows which buff is different from the other two + (if (eq ediff-default-variant 'default-B) 'A 'B) + ediff-default-variant ; state of merge + nil ; state of ancestor + ) + (vector a-begin-pt a-end-pt + b-begin-pt b-end-pt + nil nil ; dummy buf C + nil nil ; dummy ancestor + nil nil ; dummy state of diff & merge + nil ; dummy state of ancestor + ))) + ))) + + ))) ; end ediff-with-current-buffer + diff-list + )) + + +(defun ediff-convert-diffs-to-overlays (diff-list) + (ediff-set-diff-overlays-in-one-buffer 'A diff-list) + (ediff-set-diff-overlays-in-one-buffer 'B diff-list) + (if ediff-3way-job + (ediff-set-diff-overlays-in-one-buffer 'C diff-list)) + (if ediff-merge-with-ancestor-job + (ediff-set-diff-overlays-in-one-buffer 'Ancestor diff-list)) + ;; set up vector showing the status of merge regions + (if ediff-merge-job + (setq ediff-state-of-merge + (vconcat + (mapcar (lambda (elt) + (let ((state-of-merge (aref elt 9)) + (state-of-ancestor (aref elt 10))) + (vector + ;; state of merge: prefers/default-A/B or combined + (if state-of-merge (format "%S" state-of-merge)) + ;; whether the ancestor region is empty + state-of-ancestor))) + ;; the first elt designates type of list + (cdr diff-list)) + ))) + (message "Processing difference regions ... done")) + + +(defun ediff-set-diff-overlays-in-one-buffer (buf-type diff-list) + (let* ((current-diff -1) + (buff (ediff-get-buffer buf-type)) + (ctl-buf ediff-control-buffer) + ;; ediff-extract-diffs puts the type of diff-list as the first elt + ;; of this list. The type is either 'points or 'words + (diff-list-type (car diff-list)) + (shift (ediff-overlay-start + (ediff-get-value-according-to-buffer-type + buf-type ediff-narrow-bounds))) + (limit (ediff-overlay-end + (ediff-get-value-according-to-buffer-type + buf-type ediff-narrow-bounds))) + diff-overlay-list list-element total-diffs + begin end pt-saved overlay state-of-diff) + + (setq diff-list (cdr diff-list)) ; discard diff list type + (setq total-diffs (length diff-list)) + + ;; shift, if necessary + (ediff-with-current-buffer buff (setq pt-saved shift)) + + (while diff-list + (setq current-diff (1+ current-diff) + list-element (car diff-list) + begin (aref list-element (cond ((eq buf-type 'A) 0) + ((eq buf-type 'B) 2) + ((eq buf-type 'C) 4) + (t 6))) ; Ancestor + end (aref list-element (cond ((eq buf-type 'A) 1) + ((eq buf-type 'B) 3) + ((eq buf-type 'C) 5) + (t 7))) ; Ancestor + state-of-diff (aref list-element 8) + ) + + (cond ((and (not (eq buf-type state-of-diff)) + (not (eq buf-type 'Ancestor)) + (memq state-of-diff '(A B C))) + (setq state-of-diff + (car (delq buf-type (delq state-of-diff (list 'A 'B 'C))))) + (setq state-of-diff (format "=diff(%S)" state-of-diff)) + ) + (t (setq state-of-diff nil))) + + ;; Put overlays at appropriate places in buffer + ;; convert word numbers to points, if necessary + (if (eq diff-list-type 'words) + (progn + (ediff-with-current-buffer buff (goto-char pt-saved)) + (ediff-with-current-buffer ctl-buf + (setq begin (ediff-goto-word (1+ begin) buff) + end (ediff-goto-word end buff 'end))) + (if (> end limit) (setq end limit)) + (if (> begin end) (setq begin end)) + (setq pt-saved (ediff-with-current-buffer buff (point))))) + (setq overlay (ediff-make-bullet-proof-overlay begin end buff)) + + (ediff-overlay-put overlay 'priority ediff-shadow-overlay-priority) + (ediff-overlay-put overlay 'ediff-diff-num current-diff) + (if (and (ediff-has-face-support-p) + ediff-use-faces ediff-highlight-all-diffs) + (ediff-set-overlay-face + overlay (ediff-background-face buf-type current-diff))) + + (if (= 0 (mod current-diff 10)) + (message "Buffer %S: Processing difference region %d of %d" + buf-type current-diff total-diffs)) + ;; Record all overlays for this difference. + ;; The 2-d elt, nil, is a place holder for the fine diff vector. + ;; The 3-d elt, nil, is a place holder for no-fine-diffs flag. + ;; The 4-th elt says which diff region is different from the other two + ;; (3-way jobs only). + (setq diff-overlay-list + (nconc + diff-overlay-list + (list (vector overlay nil nil state-of-diff))) + diff-list + (cdr diff-list)) + ) ; while + + (set (ediff-get-symbol-from-alist buf-type ediff-difference-vector-alist) + (vconcat diff-overlay-list)) + )) + +;; `n' is the diff region to work on. Default is ediff-current-difference. +;; if `flag' is 'noforce then make fine-diffs only if this region's fine +;; diffs have not been computed before. +;; if `flag' is 'skip then don't compute fine diffs for this region. +(defun ediff-make-fine-diffs (&optional n flag) + (or n (setq n ediff-current-difference)) + + (if (< ediff-number-of-differences 1) + (error ediff-NO-DIFFERENCES)) + + (if ediff-word-mode + (setq flag 'skip + ediff-auto-refine 'nix)) + + (or (< n 0) + (>= n ediff-number-of-differences) + ;; n is within the range + (let ((tmp-buffer (get-buffer-create ediff-tmp-buffer)) + (file-A ediff-temp-file-A) + (file-B ediff-temp-file-B) + (file-C ediff-temp-file-C) + (empty-A (ediff-empty-diff-region-p n 'A)) + (empty-B (ediff-empty-diff-region-p n 'B)) + (empty-C (ediff-empty-diff-region-p n 'C)) + (whitespace-A (ediff-whitespace-diff-region-p n 'A)) + (whitespace-B (ediff-whitespace-diff-region-p n 'B)) + (whitespace-C (ediff-whitespace-diff-region-p n 'C)) + cumulative-fine-diff-length) + + (cond ;; If one of the regions is empty (or 2 in 3way comparison) + ;; then don't refine. + ;; If the region happens to be entirely whitespace or empty then + ;; mark as such. + ((> (length (delq nil (list empty-A empty-B empty-C))) 1) + (if (and (ediff-looks-like-combined-merge n) + ediff-merge-job) + (ediff-set-fine-overlays-in-one-buffer 'C nil n)) + (if ediff-3way-comparison-job + (ediff-message-if-verbose + "Region %d is empty in all buffers but %S" + (1+ n) + (cond ((not empty-A) 'A) + ((not empty-B) 'B) + ((not empty-C) 'C))) + (ediff-message-if-verbose + "Region %d in buffer %S is empty" + (1+ n) + (cond (empty-A 'A) + (empty-B 'B) + (empty-C 'C))) + ) + ;; if all regions happen to be whitespace + (if (and whitespace-A whitespace-B whitespace-C) + ;; mark as space only + (ediff-mark-diff-as-space-only n t) + ;; if some regions are white and others don't, then mark as + ;; non-white-space-only + (ediff-mark-diff-as-space-only n nil))) + + ;; don't compute fine diffs if diff vector exists + ((and (eq flag 'noforce) (ediff-get-fine-diff-vector n 'A)) + (if (ediff-no-fine-diffs-p n) + (message + "Only white-space differences in region %d %s" + (1+ n) + (cond ((eq (ediff-no-fine-diffs-p n) 'A) + "in buffers B & C") + ((eq (ediff-no-fine-diffs-p n) 'B) + "in buffers A & C") + ((eq (ediff-no-fine-diffs-p n) 'C) + "in buffers A & B") + (t ""))))) + ;; don't compute fine diffs for this region + ((eq flag 'skip) + (or (ediff-get-fine-diff-vector n 'A) + (memq ediff-auto-refine '(off nix)) + (ediff-message-if-verbose + "Region %d exceeds the auto-refinement limit. Type `%s' to refine" + (1+ n) + (substitute-command-keys + "\\[ediff-make-or-kill-fine-diffs]") + ))) + (t + ;; recompute fine diffs + (ediff-wordify + (ediff-get-diff-posn 'A 'beg n) + (ediff-get-diff-posn 'A 'end n) + ediff-buffer-A + tmp-buffer + ediff-control-buffer) + (setq file-A + (ediff-make-temp-file tmp-buffer "fineDiffA" file-A)) + + (ediff-wordify + (ediff-get-diff-posn 'B 'beg n) + (ediff-get-diff-posn 'B 'end n) + ediff-buffer-B + tmp-buffer + ediff-control-buffer) + (setq file-B + (ediff-make-temp-file tmp-buffer "fineDiffB" file-B)) + + (if ediff-3way-job + (progn + (ediff-wordify + (ediff-get-diff-posn 'C 'beg n) + (ediff-get-diff-posn 'C 'end n) + ediff-buffer-C + tmp-buffer + ediff-control-buffer) + (setq file-C + (ediff-make-temp-file + tmp-buffer "fineDiffC" file-C)))) + + ;; save temp file names. + (setq ediff-temp-file-A file-A + ediff-temp-file-B file-B + ediff-temp-file-C file-C) + + ;; set the new vector of fine diffs, if none exists + (cond ((and ediff-3way-job whitespace-A) + (ediff-setup-fine-diff-regions nil file-B file-C n)) + ((and ediff-3way-job whitespace-B) + (ediff-setup-fine-diff-regions file-A nil file-C n)) + ((and ediff-3way-job + ;; In merge-jobs, whitespace-C is t, since + ;; ediff-empty-diff-region-p returns t in this case + whitespace-C) + (ediff-setup-fine-diff-regions file-A file-B nil n)) + (t + (ediff-setup-fine-diff-regions file-A file-B file-C n))) + + (setq cumulative-fine-diff-length + (+ (length (ediff-get-fine-diff-vector n 'A)) + (length (ediff-get-fine-diff-vector n 'B)) + ;; in merge jobs, the merge buffer is never refined + (if (and file-C (not ediff-merge-job)) + (length (ediff-get-fine-diff-vector n 'C)) + 0))) + + (cond ((or + ;; all regions are white space + (and whitespace-A whitespace-B whitespace-C) + ;; none is white space and no fine diffs detected + (and (not whitespace-A) + (not whitespace-B) + (not (and ediff-3way-job whitespace-C)) + (eq cumulative-fine-diff-length 0))) + (ediff-mark-diff-as-space-only n t) + (ediff-message-if-verbose + "Only white-space differences in region %d" (1+ n))) + ((eq cumulative-fine-diff-length 0) + (ediff-message-if-verbose + "Only white-space differences in region %d %s" + (1+ n) + (cond (whitespace-A (ediff-mark-diff-as-space-only n 'A) + "in buffers B & C") + (whitespace-B (ediff-mark-diff-as-space-only n 'B) + "in buffers A & C") + (whitespace-C (ediff-mark-diff-as-space-only n 'C) + "in buffers A & B")))) + (t + (ediff-mark-diff-as-space-only n nil))) + ) + ) ; end cond + (ediff-set-fine-diff-properties n) + ))) + +;; Interface to ediff-make-fine-diffs. Checks for auto-refine limit, etc. +(defun ediff-install-fine-diff-if-necessary (n) + (cond ((and (eq ediff-auto-refine 'on) + ediff-use-faces + (not (eq ediff-highlighting-style 'off)) + (not (eq ediff-highlighting-style 'ascii))) + (if (and + (> ediff-auto-refine-limit + (- (ediff-get-diff-posn 'A 'end n) + (ediff-get-diff-posn 'A 'beg n))) + (> ediff-auto-refine-limit + (- (ediff-get-diff-posn 'B 'end n) + (ediff-get-diff-posn 'B 'beg n)))) + (ediff-make-fine-diffs n 'noforce) + (ediff-make-fine-diffs n 'skip))) + + ;; highlight if fine diffs already exist + ((eq ediff-auto-refine 'off) + (ediff-make-fine-diffs n 'skip)))) + + +;; if fine diff vector is not set for diff N, then do nothing +(defun ediff-set-fine-diff-properties (n &optional default) + (or (not (ediff-has-face-support-p)) + (< n 0) + (>= n ediff-number-of-differences) + ;; when faces are supported, set faces and priorities of fine overlays + (progn + (ediff-set-fine-diff-properties-in-one-buffer 'A n default) + (ediff-set-fine-diff-properties-in-one-buffer 'B n default) + (if ediff-3way-job + (ediff-set-fine-diff-properties-in-one-buffer 'C n default))))) + +(defun ediff-set-fine-diff-properties-in-one-buffer (buf-type + n &optional default) + (let ((fine-diff-vector (ediff-get-fine-diff-vector n buf-type)) + (face (if default + 'default + (ediff-get-symbol-from-alist + buf-type ediff-fine-diff-face-alist) + )) + (priority (if default + 0 + (1+ (or (ediff-overlay-get + (symbol-value + (ediff-get-symbol-from-alist + buf-type + ediff-current-diff-overlay-alist)) + 'priority) + 0))))) + (mapcar (lambda (overl) + (ediff-set-overlay-face overl face) + (ediff-overlay-put overl 'priority priority)) + fine-diff-vector))) + +;; Set overlays over the regions that denote delimiters +(defun ediff-set-fine-overlays-for-combined-merge (diff-list reg-num) + (let (overlay overlay-list) + (while diff-list + (condition-case nil + (setq overlay + (ediff-make-bullet-proof-overlay + (nth 0 diff-list) (nth 1 diff-list) ediff-buffer-C)) + (error "")) + (setq overlay-list (cons overlay overlay-list)) + (if (> (length diff-list) 1) + (setq diff-list (cdr (cdr diff-list))) + (error "ediff-set-fine-overlays-for-combined-merge: corrupt list of +delimiter regions")) + ) + (setq overlay-list (reverse overlay-list)) + (ediff-set-fine-diff-vector + reg-num 'C (apply 'vector overlay-list)) + )) + + +;; Convert diff list to overlays for a given DIFF-REGION +;; in buffer of type BUF-TYPE +(defun ediff-set-fine-overlays-in-one-buffer (buf-type diff-list region-num) + (let* ((current-diff -1) + (reg-start (ediff-get-diff-posn buf-type 'beg region-num)) + (buff (ediff-get-buffer buf-type)) + (ctl-buf ediff-control-buffer) + combined-merge-diff-list + diff-overlay-list list-element + begin end overlay) + + (ediff-clear-fine-differences-in-one-buffer region-num buf-type) + (setq diff-list (cdr diff-list)) ; discard list type (words or points) + (ediff-with-current-buffer buff (goto-char reg-start)) + + ;; if it is a combined merge then set overlays in buff C specially + (if (and ediff-merge-job (eq buf-type 'C) + (setq combined-merge-diff-list + (ediff-looks-like-combined-merge region-num))) + (ediff-set-fine-overlays-for-combined-merge + combined-merge-diff-list region-num) + ;; regular fine diff + (while diff-list + (setq current-diff (1+ current-diff) + list-element (car diff-list) + begin (aref list-element (cond ((eq buf-type 'A) 0) + ((eq buf-type 'B) 2) + (t 4))) ; buf C + end (aref list-element (cond ((eq buf-type 'A) 1) + ((eq buf-type 'B) 3) + (t 5)))) ; buf C + (if (not (or begin end)) + () ; skip this diff + ;; Put overlays at appropriate places in buffers + ;; convert lines to points, if necessary + (ediff-with-current-buffer ctl-buf + (setq begin (ediff-goto-word (1+ begin) buff) + end (ediff-goto-word end buff 'end))) + (setq overlay (ediff-make-bullet-proof-overlay begin end buff)) + ;; record all overlays for this difference region + (setq diff-overlay-list (nconc diff-overlay-list (list overlay)))) + + (setq diff-list (cdr diff-list)) + ) ; while + ;; convert the list of difference information into a vector + ;; for fast access + (ediff-set-fine-diff-vector + region-num buf-type (vconcat diff-overlay-list)) + ))) + + +(defun ediff-convert-fine-diffs-to-overlays (diff-list region-num) + (ediff-set-fine-overlays-in-one-buffer 'A diff-list region-num) + (ediff-set-fine-overlays-in-one-buffer 'B diff-list region-num) + (if ediff-3way-job + (ediff-set-fine-overlays-in-one-buffer 'C diff-list region-num) + )) + + +;; Stolen from emerge.el +(defun ediff-get-diff3-group (file) + ;; This save-excursion allows ediff-get-diff3-group to be called for the + ;; various groups of lines (1, 2, 3) in any order, and for the lines to + ;; appear in any order. The reason this is necessary is that Gnu diff3 + ;; can produce the groups in the order 1, 2, 3 or 1, 3, 2. + (save-excursion + (re-search-forward + (concat "^" file ":\\([0-9]+\\)\\(,\\([0-9]+\\)\\)?\\([ac]\\)\C-m?$")) + (beginning-of-line 2) + ;; treatment depends on whether it is an "a" group or a "c" group + (if (string-equal (buffer-substring (match-beginning 4) (match-end 4)) "c") + ;; it is a "c" group + (if (match-beginning 2) + ;; it has two numbers + (list (string-to-number + (buffer-substring (match-beginning 1) (match-end 1))) + (1+ (string-to-number + (buffer-substring (match-beginning 3) (match-end 3))))) + ;; it has one number + (let ((x (string-to-number + (buffer-substring (match-beginning 1) (match-end 1))))) + (list x (1+ x)))) + ;; it is an "a" group + (let ((x (1+ (string-to-number + (buffer-substring (match-beginning 1) (match-end 1)))))) + (list x x))))) + + +;; If WORD-MODE, construct vector of diffs using word numbers. +;; Else, use point values. +;; WORD-MODE also tells if we are in the word-mode or not. +;; If THREE-WAY-COMP, then it is a 3-way comparison. Else, it is merging +;; with ancestor, in which case buffer-C contents is identical to buffer-A/B, +;; contents (unless buffer-A is narrowed) depending on ediff-default-variant's +;; value. +;; BOUNDS specifies visibility bounds to use. +(defun ediff-extract-diffs3 (diff-buffer word-mode three-way-comp + &optional bounds) + (let ((A-buffer ediff-buffer-A) + (B-buffer ediff-buffer-B) + (C-buffer ediff-buffer-C) + (anc-buffer ediff-ancestor-buffer) + (a-prev 1) ; needed to set the first diff line correctly + (a-prev-pt nil) + (b-prev 1) + (b-prev-pt nil) + (c-prev 1) + (c-prev-pt nil) + (anc-prev 1) + diff-list shift-A shift-B shift-C + ) + + ;; diff list contains word numbers or points, depending on word-mode + (setq diff-list (cons (if word-mode 'words 'points) + diff-list)) + (if bounds + (setq shift-A + (ediff-overlay-start + (ediff-get-value-according-to-buffer-type 'A bounds)) + shift-B + (ediff-overlay-start + (ediff-get-value-according-to-buffer-type 'B bounds)) + shift-C + (if three-way-comp + (ediff-overlay-start + (ediff-get-value-according-to-buffer-type 'C bounds))))) + + ;; reset point in buffers A, B, C + (ediff-with-current-buffer A-buffer + (goto-char (if shift-A shift-A (point-min)))) + (ediff-with-current-buffer B-buffer + (goto-char (if shift-B shift-B (point-min)))) + (if three-way-comp + (ediff-with-current-buffer C-buffer + (goto-char (if shift-C shift-C (point-min))))) + (if (ediff-buffer-live-p anc-buffer) + (ediff-with-current-buffer anc-buffer + (goto-char (point-min)))) + + (ediff-with-current-buffer diff-buffer + (goto-char (point-min)) + (while (re-search-forward ediff-match-diff3-line nil t) + ;; leave point after matched line + (beginning-of-line 2) + (let ((agreement (buffer-substring (match-beginning 1) (match-end 1)))) + ;; if the files A and B are the same and not 3way-comparison, + ;; ignore the difference + (if (or three-way-comp (not (string-equal agreement "3"))) + (let* ((a-begin (car (ediff-get-diff3-group "1"))) + (a-end (nth 1 (ediff-get-diff3-group "1"))) + (b-begin (car (ediff-get-diff3-group "2"))) + (b-end (nth 1 (ediff-get-diff3-group "2"))) + (c-or-anc-begin (car (ediff-get-diff3-group "3"))) + (c-or-anc-end (nth 1 (ediff-get-diff3-group "3"))) + (state-of-merge + (cond ((string-equal agreement "1") 'prefer-A) + ((string-equal agreement "2") 'prefer-B) + (t ediff-default-variant))) + (state-of-diff-merge + (if (memq state-of-merge '(default-A prefer-A)) 'B 'A)) + (state-of-diff-comparison + (cond ((string-equal agreement "1") 'A) + ((string-equal agreement "2") 'B) + ((string-equal agreement "3") 'C))) + state-of-ancestor + c-begin c-end + a-begin-pt a-end-pt + b-begin-pt b-end-pt + c-begin-pt c-end-pt + anc-begin-pt anc-end-pt) + + (setq state-of-ancestor + (= c-or-anc-begin c-or-anc-end)) + + (cond (three-way-comp + (setq c-begin c-or-anc-begin + c-end c-or-anc-end)) + ((eq ediff-default-variant 'default-B) + (setq c-begin b-begin + c-end b-end)) + (t + (setq c-begin a-begin + c-end a-end))) + + ;; compute main diff vector + (if word-mode + ;; make diff-list contain word numbers + (setq diff-list + (nconc diff-list + (list (vector + (- a-begin a-prev) (- a-end a-begin) + (- b-begin b-prev) (- b-end b-begin) + (- c-begin c-prev) (- c-end c-begin) + nil nil ; dummy ancestor + nil ; state of diff + nil ; state of merge + nil ; state of ancestor + ))) + a-prev a-end + b-prev b-end + c-prev c-end) + ;; else convert lines to points + (ediff-with-current-buffer A-buffer + (let ((longlines-mode-val + (if (and (boundp 'longlines-mode) longlines-mode) 1 0))) + ;; we must disable and then restore longlines-mode + (if (eq longlines-mode-val 1) + (longlines-mode 0)) + (goto-char (or a-prev-pt shift-A (point-min))) + (forward-line (- a-begin a-prev)) + (setq a-begin-pt (point)) + (forward-line (- a-end a-begin)) + (setq a-end-pt (point) + a-prev a-end + a-prev-pt a-end-pt) + (if (eq longlines-mode-val 1) + (longlines-mode longlines-mode-val)) + )) + (ediff-with-current-buffer B-buffer + (let ((longlines-mode-val + (if (and (boundp 'longlines-mode) longlines-mode) 1 0))) + (if (eq longlines-mode-val 1) + (longlines-mode 0)) + (goto-char (or b-prev-pt shift-B (point-min))) + (forward-line (- b-begin b-prev)) + (setq b-begin-pt (point)) + (forward-line (- b-end b-begin)) + (setq b-end-pt (point) + b-prev b-end + b-prev-pt b-end-pt) + (if (eq longlines-mode-val 1) + (longlines-mode longlines-mode-val)) + )) + (ediff-with-current-buffer C-buffer + (let ((longlines-mode-val + (if (and (boundp 'longlines-mode) longlines-mode) 1 0))) + (if (eq longlines-mode-val 1) + (longlines-mode 0)) + (goto-char (or c-prev-pt shift-C (point-min))) + (forward-line (- c-begin c-prev)) + (setq c-begin-pt (point)) + (forward-line (- c-end c-begin)) + (setq c-end-pt (point) + c-prev c-end + c-prev-pt c-end-pt) + (if (eq longlines-mode-val 1) + (longlines-mode longlines-mode-val)) + )) + (if (ediff-buffer-live-p anc-buffer) + (ediff-with-current-buffer anc-buffer + (let ((longlines-mode-val + (if (and (boundp 'longlines-mode) longlines-mode) 1 0))) + (if (eq longlines-mode-val 1) + (longlines-mode 0)) + (forward-line (- c-or-anc-begin anc-prev)) + (setq anc-begin-pt (point)) + (forward-line (- c-or-anc-end c-or-anc-begin)) + (setq anc-end-pt (point) + anc-prev c-or-anc-end) + (if (eq longlines-mode-val 1) + (longlines-mode longlines-mode-val)) + ))) + (setq diff-list + (nconc + diff-list + ;; if comparing with ancestor, then there also is a + ;; state-of-difference marker + (if three-way-comp + (list (vector + a-begin-pt a-end-pt + b-begin-pt b-end-pt + c-begin-pt c-end-pt + nil nil ; ancestor begin/end + state-of-diff-comparison + nil ; state of merge + nil ; state of ancestor + )) + (list (vector a-begin-pt a-end-pt + b-begin-pt b-end-pt + c-begin-pt c-end-pt + anc-begin-pt anc-end-pt + state-of-diff-merge + state-of-merge + state-of-ancestor + ))) + ))) + )) + + ))) ; end ediff-with-current-buffer + diff-list + )) + +;; Generate the difference vector and overlays for three files +;; File-C is either the third file to compare (in case of 3-way comparison) +;; or it is the ancestor file. +(defun ediff-setup-diff-regions3 (file-A file-B file-C) + ;; looking for '-i' or a 'i' among clustered non-long options + (if (string-match "^-i\\| -i\\|\\(^\\| \\)-[^- ]+i" ediff-diff-options) + (error "Option `-i' is not allowed in `ediff-diff3-options'")) + + (or (ediff-buffer-live-p ediff-diff-buffer) + (setq ediff-diff-buffer + (get-buffer-create (ediff-unique-buffer-name "*ediff-diff" "*")))) + + (message "Computing differences ...") + (ediff-exec-process ediff-diff3-program ediff-diff-buffer 'synchronize + ediff-actual-diff3-options file-A file-B file-C) + + (ediff-prepare-error-list ediff-diff3-ok-lines-regexp ediff-diff-buffer) + ;;(message "Computing differences ... done") + (ediff-convert-diffs-to-overlays + (ediff-extract-diffs3 + ediff-diff-buffer + ediff-word-mode ediff-3way-comparison-job ediff-narrow-bounds) + )) + + +;; Execute PROGRAM asynchronously, unless OS/2, Windows-*, or DOS, or unless +;; SYNCH is non-nil. BUFFER must be a buffer object, and must be alive. The +;; OPTIONS arg is a list of options to pass to PROGRAM. It may be a blank +;; string. All elements in FILES must be strings. We also delete nil from +;; args. +(defun ediff-exec-process (program buffer synch options &rest files) + (let ((data (match-data)) + ;; If this is a buffer job, we are diffing temporary files + ;; produced by Emacs with ediff-coding-system-for-write, so + ;; use the same encoding to read the results. + (coding-system-for-read + (if (string-match "buffer" (symbol-name ediff-job-name)) + ediff-coding-system-for-write + ediff-coding-system-for-read)) + args) + (setq args (append (split-string options) files)) + (setq args (delete "" (delq nil args))) ; delete nil and "" from arguments + ;; the --binary option, if present, should be used only for buffer jobs + ;; or for refining the differences + (or (string-match "buffer" (symbol-name ediff-job-name)) + (eq buffer ediff-fine-diff-buffer) + (setq args (delete "--binary" args))) + (unwind-protect + (let ((directory default-directory) + proc) + (with-current-buffer buffer + (erase-buffer) + (setq default-directory directory) + (if (or (memq system-type '(emx ms-dos windows-nt windows-95)) + synch) + ;; In OS/2 (emx) do it synchronously, since OS/2 doesn't let us + ;; delete files used by other processes. Thus, in ediff-buffers + ;; and similar functions, we can't delete temp files because + ;; they might be used by the asynch process that computes + ;; custom diffs. So, we have to wait till custom diff + ;; subprocess is done. + ;; Similarly for Windows-* + ;; In DOS, must synchronize because DOS doesn't have + ;; asynchronous processes. + (apply 'call-process program nil buffer nil args) + ;; On other systems, do it asynchronously. + (setq proc (get-buffer-process buffer)) + (if proc (kill-process proc)) + (setq proc + (apply 'start-process "Custom Diff" buffer program args)) + (setq mode-line-process '(":%s")) + (set-process-sentinel proc 'ediff-process-sentinel) + (set-process-filter proc 'ediff-process-filter) + ))) + (store-match-data data)))) + +;; This is shell-command-filter from simple.el in Emacs. +;; Copied here because XEmacs doesn't have it. +(defun ediff-process-filter (proc string) + ;; Do save-excursion by hand so that we can leave point numerically unchanged + ;; despite an insertion immediately after it. + (let* ((obuf (current-buffer)) + (buffer (process-buffer proc)) + opoint + (window (get-buffer-window buffer)) + (pos (window-start window))) + (unwind-protect + (progn + (set-buffer buffer) + (or (= (point) (point-max)) + (setq opoint (point))) + (goto-char (point-max)) + (insert-before-markers string)) + ;; insert-before-markers moved this marker: set it back. + (set-window-start window pos) + ;; Finish our save-excursion. + (if opoint + (goto-char opoint)) + (set-buffer obuf)))) + +;; like shell-command-sentinel but doesn't print an exit status message +;; we do this because diff always exits with status 1, if diffs are found +;; so shell-command-sentinel displays a confusing message to the user +(defun ediff-process-sentinel (process signal) + (if (and (memq (process-status process) '(exit signal)) + (buffer-name (process-buffer process))) + (progn + (with-current-buffer (process-buffer process) + (setq mode-line-process nil)) + (delete-process process)))) + + +;;; Word functions used to refine the current diff + +(defvar ediff-forward-word-function 'ediff-forward-word + "*Function to call to move to the next word. +Used for splitting difference regions into individual words.") +(make-variable-buffer-local 'ediff-forward-word-function) + +;; \240 is unicode symbol for nonbreakable whitespace +(defvar ediff-whitespace " \n\t\f\r\240" + "*Characters constituting white space. +These characters are ignored when differing regions are split into words.") +(make-variable-buffer-local 'ediff-whitespace) + +(defvar ediff-word-1 + (if (featurep 'xemacs) "a-zA-Z---_" "-[:word:]_") + "*Characters that constitute words of type 1. +More precisely, [ediff-word-1] is a regexp that matches type 1 words. +See `ediff-forward-word' for more details.") +(make-variable-buffer-local 'ediff-word-1) + +(defvar ediff-word-2 "0-9.," + "*Characters that constitute words of type 2. +More precisely, [ediff-word-2] is a regexp that matches type 2 words. +See `ediff-forward-word' for more details.") +(make-variable-buffer-local 'ediff-word-2) + +(defvar ediff-word-3 "`'?!:;\"{}[]()" + "*Characters that constitute words of type 3. +More precisely, [ediff-word-3] is a regexp that matches type 3 words. +See `ediff-forward-word' for more details.") +(make-variable-buffer-local 'ediff-word-3) + +(defvar ediff-word-4 + (concat "^" ediff-word-1 ediff-word-2 ediff-word-3 ediff-whitespace) + "*Characters that constitute words of type 4. +More precisely, [ediff-word-4] is a regexp that matches type 4 words. +See `ediff-forward-word' for more details.") +(make-variable-buffer-local 'ediff-word-4) + +;; Split region along word boundaries. Each word will be on its own line. +;; Output to buffer out-buffer. +(defun ediff-forward-word () + "Move point one word forward. +There are four types of words, each of which consists entirely of +characters in `ediff-word-1', `ediff-word-2', `ediff-word-3', or +`ediff-word-4'. Words are recognized by passing these one after another as +arguments to `skip-chars-forward'." + (or (> (+ (skip-chars-forward ediff-word-1) + (skip-syntax-forward "w")) + 0) + (> (skip-chars-forward ediff-word-2) 0) + (> (skip-chars-forward ediff-word-3) 0) + (> (skip-chars-forward ediff-word-4) 0) + )) + + +(defun ediff-wordify (beg end in-buffer out-buffer &optional control-buf) + (let ((forward-word-function + ;; eval in control buf to let user create local versions for + ;; different invocations + (if control-buf + (ediff-with-current-buffer control-buf + ediff-forward-word-function) + ediff-forward-word-function)) + inbuf-syntax-tbl sv-point diff-string) + (with-current-buffer in-buffer + (setq inbuf-syntax-tbl + (if control-buf + (ediff-with-current-buffer control-buf + ediff-syntax-table) + (syntax-table))) + (setq diff-string (buffer-substring-no-properties beg end)) + + (set-buffer out-buffer) + ;; Make sure that temp buff syntax table is the same as the original buf + ;; syntax tbl, because we use ediff-forward-word in both and + ;; ediff-forward-word depends on the syntax classes of characters. + (set-syntax-table inbuf-syntax-tbl) + (erase-buffer) + (insert diff-string) + (goto-char (point-min)) + (skip-chars-forward ediff-whitespace) + (delete-region (point-min) (point)) + + (while (not (eobp)) + (funcall forward-word-function) + (setq sv-point (point)) + (skip-chars-forward ediff-whitespace) + (delete-region sv-point (point)) + (insert "\n"))))) + +;; copy string specified as BEG END from IN-BUF to OUT-BUF +(defun ediff-copy-to-buffer (beg end in-buffer out-buffer) + (with-current-buffer out-buffer + (erase-buffer) + (insert-buffer-substring in-buffer beg end) + (goto-char (point-min)))) + + +;; goto word #n starting at current position in buffer `buf' +;; For ediff, a word is determined by ediff-forward-word-function +;; If `flag' is non-nil, goto the end of the n-th word. +(defun ediff-goto-word (n buf &optional flag) + ;; remember val ediff-forward-word-function has in ctl buf + (let ((fwd-word-fun ediff-forward-word-function) + (syntax-tbl ediff-syntax-table)) + (ediff-with-current-buffer buf + (skip-chars-forward ediff-whitespace) + (ediff-with-syntax-table syntax-tbl + (while (> n 1) + (funcall fwd-word-fun) + (skip-chars-forward ediff-whitespace) + (setq n (1- n))) + (if (and flag (> n 0)) + (funcall fwd-word-fun))) + (point)))) + +(defun ediff-same-file-contents (f1 f2) + "Return t if files F1 and F2 have identical contents." + (if (and (not (file-directory-p f1)) + (not (file-directory-p f2))) + (let ((res + (apply 'call-process ediff-cmp-program nil nil nil + (append ediff-cmp-options (list (expand-file-name f1) + (expand-file-name f2)))) + )) + (and (numberp res) (eq res 0))) + )) + + +(defun ediff-same-contents (d1 d2 &optional filter-re) + "Return t if D1 and D2 have the same content. +D1 and D2 can either be both directories or both regular files. +Symlinks and the likes are not handled. +If FILTER-RE is non-nil, recursive checking in directories +affects only files whose names match the expression." + ;; Normalize empty filter RE to nil. + (unless (> (length filter-re) 0) (setq filter-re nil)) + ;; Indicate progress + (message "Comparing '%s' and '%s' modulo '%s'" d1 d2 filter-re) + (cond + ;; D1 & D2 directories => recurse + ((and (file-directory-p d1) + (file-directory-p d2)) + (if (null ediff-recurse-to-subdirectories) + (if (y-or-n-p "Compare subdirectories recursively? ") + (setq ediff-recurse-to-subdirectories 'yes) + (setq ediff-recurse-to-subdirectories 'no))) + (if (eq ediff-recurse-to-subdirectories 'yes) + (let* ((all-entries-1 (directory-files d1 t filter-re)) + (all-entries-2 (directory-files d2 t filter-re)) + (entries-1 (ediff-delete-all-matches "^\\.\\.?$" all-entries-1)) + (entries-2 (ediff-delete-all-matches "^\\.\\.?$" all-entries-2)) + ) + + (ediff-same-file-contents-lists entries-1 entries-2 filter-re) + )) + ) ; end of the directories case + ;; D1 & D2 are both files => compare directly + ((and (file-regular-p d1) + (file-regular-p d2)) + (ediff-same-file-contents d1 d2)) + ;; Otherwise => false: unequal contents + ) + ) + +;; If lists have the same length and names of files are pairwise equal +;; (removing the directories) then compare contents pairwise. +;; True if all contents are the same; false otherwise +(defun ediff-same-file-contents-lists (entries-1 entries-2 filter-re) + ;; First, check only the names (works quickly and ensures a + ;; precondition for subsequent code) + (if (and (= (length entries-1) (length entries-2)) + (equal (mapcar 'file-name-nondirectory entries-1) + (mapcar 'file-name-nondirectory entries-2))) + ;; With name equality established, compare the entries + ;; through recursion. + (let ((continue t)) + (while (and entries-1 continue) + (if (ediff-same-contents + (car entries-1) (car entries-2) filter-re) + (setq entries-1 (cdr entries-1) + entries-2 (cdr entries-2)) + (setq continue nil)) + ) + ;; if reached the end then lists are equal + (null entries-1)) + ) + ) + + +;; ARG1 is a regexp, ARG2 is a list of full-filenames +;; Delete all entries that match the regexp +(defun ediff-delete-all-matches (regex file-list-list) + (let (result elt) + (while file-list-list + (setq elt (car file-list-list)) + (or (string-match regex (file-name-nondirectory elt)) + (setq result (cons elt result))) + (setq file-list-list (cdr file-list-list))) + (reverse result))) + + +(defun ediff-set-actual-diff-options () + (if ediff-ignore-case + (setq ediff-actual-diff-options + (concat ediff-diff-options " " ediff-ignore-case-option) + ediff-actual-diff3-options + (concat ediff-diff3-options " " ediff-ignore-case-option3)) + (setq ediff-actual-diff-options ediff-diff-options + ediff-actual-diff3-options ediff-diff3-options) + ) + (setq-default ediff-actual-diff-options ediff-actual-diff-options + ediff-actual-diff3-options ediff-actual-diff3-options) + ) + + +;; Ignore case handling - some ideas from drew.adams@@oracle.com +(defun ediff-toggle-ignore-case () + (interactive) + (ediff-barf-if-not-control-buffer) + (setq ediff-ignore-case (not ediff-ignore-case)) + (ediff-set-actual-diff-options) + (if ediff-ignore-case + (message "Ignoring regions that differ only in case") + (message "Ignoring case differences turned OFF")) + (cond (ediff-merge-job + (message "Ignoring letter case is too dangerous in merge jobs")) + ((and ediff-diff3-job (string= ediff-ignore-case-option3 "")) + (message "Ignoring letter case is not supported by this diff3 program")) + ((and (not ediff-3way-job) (string= ediff-ignore-case-option "")) + (message "Ignoring letter case is not supported by this diff program")) + (t + (sit-for 1) + (ediff-update-diffs))) + ) + + + +;; 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: + +;; arch-tag: a86d448e-58d7-4572-a1d9-fdedfa22f648 +;;; ediff-diff.el ends here diff -r 681cd08dc0f7 -r e93288477c43 lisp/vc/ediff-help.el --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/lisp/vc/ediff-help.el Sun Jun 13 22:57:55 2010 +0000 @@ -0,0 +1,321 @@ +;;; ediff-help.el --- Code related to the contents of Ediff help buffers + +;; Copyright (C) 1996, 1997, 1998, 1999, 2000, 2001, 2002, 2003, +;; 2004, 2005, 2006, 2007, 2008, 2009, 2010 Free Software Foundation, Inc. + +;; Author: Michael Kifer + +;; 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 3 of the License, 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. If not, see . + +;;; Commentary: + +;;; Code: + + +;; Compiler pacifier start +(defvar ediff-multiframe) +;; end pacifier + +(require 'ediff-init) + +;; Help messages + +(defconst ediff-long-help-message-head + " Move around | Toggle features | Manipulate +=====================|===========================|=============================" + "The head of the full help message.") +(defconst ediff-long-help-message-tail + "=====================|===========================|============================= + R -show registry | = -compare regions | M -show session group + D -diff output | E -browse Ediff manual| G -send bug report + i -status info | ? -help off | z/q -suspend/quit +------------------------------------------------------------------------------- +For help on a specific command: Click Button 2 over it; or + Put the cursor over it and type RET." + "The tail of the full-help message.") + +(defconst ediff-long-help-message-compare3 + " +p,DEL -previous diff | | -vert/horiz split | xy -copy buf X's region to Y +n,SPC -next diff | h -hilighting | rx -restore buf X's old diff + j -jump to diff | @ -auto-refinement | * -refine current region + gx -goto X's point| ## -ignore whitespace | ! -update diff regions + C-l -recenter | #c -ignore case | + v/V -scroll up/dn | #f/#h -focus/hide regions | wx -save buf X + -scroll lt/rt | X -read-only in buf X | wd -save diff output + ~ -rotate buffers| m -wide display | +" + "Help message usually used for 3-way comparison. +Normally, not a user option. See `ediff-help-message' for details.") + +(defconst ediff-long-help-message-compare2 + " +p,DEL -previous diff | | -vert/horiz split |a/b -copy A/B's region to B/A +n,SPC -next diff | h -hilighting | rx -restore buf X's old diff + j -jump to diff | @ -auto-refinement | * -refine current region + gx -goto X's point| ## -ignore whitespace | ! -update diff regions + C-l -recenter | #c -ignore case | + v/V -scroll up/dn | #f/#h -focus/hide regions | wx -save buf X + -scroll lt/rt | X -read-only in buf X | wd -save diff output + ~ -swap variants | m -wide display | +" + "Help message usually used for 2-way comparison. +Normally, not a user option. See `ediff-help-message' for details.") + +(defconst ediff-long-help-message-narrow2 + " +p,DEL -previous diff | | -vert/horiz split |a/b -copy A/B's region to B/A +n,SPC -next diff | h -hilighting | rx -restore buf X's old diff + j -jump to diff | @ -auto-refinement | * -refine current region + gx -goto X's point| ## -ignore whitespace | ! -update diff regions + C-l -recenter | #c -ignore case | % -narrow/widen buffs + v/V -scroll up/dn | #f/#h -focus/hide regions | wx -save buf X + -scroll lt/rt | X -read-only in buf X | wd -save diff output + ~ -swap variants | m -wide display | +" + "Help message when comparing windows or regions line-by-line. +Normally, not a user option. See `ediff-help-message' for details.") + +(defconst ediff-long-help-message-word-mode + " +p,DEL -previous diff | | -vert/horiz split | xy -copy buf X's region to Y +n,SPC -next diff | h -hilighting | rx -restore buf X's old diff + j -jump to diff | | + gx -goto X's point| % -narrow/widen buffs | ! -recompute diffs + C-l -recenter | #c -ignore case | + v/V -scroll up/dn | #f/#h -focus/hide regions | wx -save buf X + -scroll lt/rt | X -read-only in buf X | wd -save diff output + ~ -swap variants | m -wide display | +" + "Help message when comparing windows or regions word-by-word. +Normally, not a user option. See `ediff-help-message' for details.") + +(defconst ediff-long-help-message-merge + " +p,DEL -previous diff | | -vert/horiz split | x -copy buf X's region to C +n,SPC -next diff | h -hilighting | r -restore buf C's old diff + j -jump to diff | @ -auto-refinement | * -refine current region + gx -goto X's point| ## -ignore whitespace | ! -update diff regions + C-l -recenter | #f/#h -focus/hide regions | + -combine diff regions + v/V -scroll up/dn | X -read-only in buf X | wx -save buf X + -scroll lt/rt | m -wide display | wd -save diff output + ~ -swap variants | s -shrink window C | / -show ancestor buff + | $$ -show clashes only | & -merge w/new default + | $* -skip changed regions | +" + "Help message for merge sessions. +Normally, not a user option. See `ediff-help-message' for details.") + +;; The actual long help message. +(ediff-defvar-local ediff-long-help-message "" + "Normally, not a user option. See `ediff-help-message' for details.") + +(defconst ediff-brief-message-string + " Type ? for help" + "Contents of the brief help message.") +;; The actual brief help message +(ediff-defvar-local ediff-brief-help-message "" + "Normally, not a user option. See `ediff-help-message' for details.") + +(ediff-defvar-local ediff-brief-help-message-function nil + "The brief help message that the user can customize. +If the user sets this to a parameter-less function, Ediff will use it to +produce the brief help message. This function must return a string.") +(ediff-defvar-local ediff-long-help-message-function nil + "The long help message that the user can customize. +See `ediff-brief-help-message-function' for more.") + +(defcustom ediff-use-long-help-message nil + "If t, Ediff displays a long help message. Short help message otherwise." + :type 'boolean + :group 'ediff-window) + +;; The actual help message. +(ediff-defvar-local ediff-help-message "" + "The actual help message. +Normally, the user shouldn't touch this. However, if you want Ediff to +start up with different help messages for different jobs, you can change +the value of this variable and the variables `ediff-help-message-*' in +`ediff-startup-hook'.") + + +;; the keymap that defines clicks over the quick help regions +(defvar ediff-help-region-map (make-sparse-keymap)) + +(define-key + ediff-help-region-map + (if (featurep 'emacs) [mouse-2] [button2]) + 'ediff-help-for-quick-help) + +;; runs in the control buffer +(defun ediff-set-help-overlays () + (goto-char (point-min)) + (let (overl beg end cmd) + (while (re-search-forward " *\\([^ \t\n|]+\\||\\) +-[^|\n]+" nil 'noerror) + (setq beg (match-beginning 0) + end (match-end 0) + cmd (buffer-substring (match-beginning 1) (match-end 1))) + (setq overl (ediff-make-overlay beg end)) + (if (featurep 'emacs) + (ediff-overlay-put overl 'mouse-face 'highlight) + (ediff-overlay-put overl 'highlight t)) + (ediff-overlay-put overl 'ediff-help-info cmd)))) + + +(defun ediff-help-for-quick-help () + "Explain Ediff commands in more detail." + (interactive) + (ediff-barf-if-not-control-buffer) + (let ((pos (ediff-event-point last-command-event)) + overl cmd) + + (if (featurep 'xemacs) + (setq overl (extent-at pos (current-buffer) 'ediff-help-info) + cmd (ediff-overlay-get overl 'ediff-help-info)) + (setq cmd (car (mapcar (lambda (elt) + (overlay-get elt 'ediff-help-info)) + (overlays-at pos))))) + + (if (not (stringp cmd)) + (error "Hmm... I don't see an Ediff command around here...")) + + (ediff-documentation "Quick Help Commands") + + (let (case-fold-search) + (cond ((string= cmd "?") (re-search-forward "^`\\?'")) + ((string= cmd "G") (re-search-forward "^`G'")) + ((string= cmd "E") (re-search-forward "^`E'")) + ((string= cmd "wd") (re-search-forward "^`wd'")) + ((string= cmd "wx") (re-search-forward "^`wa'")) + ((string= cmd "a/b") (re-search-forward "^`a'")) + ((string= cmd "x") (re-search-forward "^`a'")) + ((string= cmd "xy") (re-search-forward "^`ab'")) + ((string= cmd "p,DEL") (re-search-forward "^`p'")) + ((string= cmd "n,SPC") (re-search-forward "^`n'")) + ((string= cmd "j") (re-search-forward "^`j'")) + ((string= cmd "gx") (re-search-forward "^`ga'")) + ((string= cmd "!") (re-search-forward "^`!'")) + ((string= cmd "*") (re-search-forward "^`\\*'")) + ((string= cmd "m") (re-search-forward "^`m'")) + ((string= cmd "|") (re-search-forward "^`|'")) + ((string= cmd "@") (re-search-forward "^`@'")) + ((string= cmd "h") (re-search-forward "^`h'")) + ((string= cmd "r") (re-search-forward "^`r'")) + ((string= cmd "rx") (re-search-forward "^`ra'")) + ((string= cmd "##") (re-search-forward "^`##'")) + ((string= cmd "#c") (re-search-forward "^`#c'")) + ((string= cmd "#f/#h") (re-search-forward "^`#f'")) + ((string= cmd "X") (re-search-forward "^`A'")) + ((string= cmd "v/V") (re-search-forward "^`v'")) + ((string= cmd "") (re-search-forward "^`<'")) + ((string= cmd "~") (re-search-forward "^`~'")) + ((string= cmd "i") (re-search-forward "^`i'")) + ((string= cmd "D") (re-search-forward "^`D'")) + ((string= cmd "R") (re-search-forward "^`R'")) + ((string= cmd "M") (re-search-forward "^`M'")) + ((string= cmd "z/q") (re-search-forward "^`z'")) + ((string= cmd "%") (re-search-forward "^`%'")) + ((string= cmd "C-l") (re-search-forward "^`C-l'")) + ((string= cmd "$$") (re-search-forward "^`\\$\\$'")) + ((string= cmd "$*") (re-search-forward "^`\\$\\*'")) + ((string= cmd "/") (re-search-forward "^`/'")) + ((string= cmd "&") (re-search-forward "^`&'")) + ((string= cmd "s") (re-search-forward "^`s'")) + ((string= cmd "+") (re-search-forward "^`\\+'")) + ((string= cmd "=") (re-search-forward "^`='")) + (t (error "Undocumented command! Type `G' in Ediff Control Panel to drop a note to the Ediff maintainer"))) + ) ; let case-fold-search + )) + + +;; assuming we are in control window, calculate length of the first line in +;; help message +(defun ediff-help-message-line-length () + (save-excursion + (goto-char (point-min)) + (if ediff-use-long-help-message + (forward-line 1)) + (end-of-line) + (current-column))) + + +(defun ediff-indent-help-message () + (let* ((shift (/ (max 0 (- (window-width (selected-window)) + (ediff-help-message-line-length))) + 2)) + (str (make-string shift ?\ ))) + (save-excursion + (goto-char (point-min)) + (while (< (point) (point-max)) + (insert str) + (beginning-of-line) + (forward-line 1))))) + + +;; compose the help message as a string +(defun ediff-set-help-message () + (setq ediff-long-help-message + (cond ((and ediff-long-help-message-function + (or (symbolp ediff-long-help-message-function) + (consp ediff-long-help-message-function))) + (funcall ediff-long-help-message-function)) + (ediff-word-mode + (concat ediff-long-help-message-head + ediff-long-help-message-word-mode + ediff-long-help-message-tail)) + (ediff-narrow-job + (concat ediff-long-help-message-head + ediff-long-help-message-narrow2 + ediff-long-help-message-tail)) + (ediff-merge-job + (concat ediff-long-help-message-head + ediff-long-help-message-merge + ediff-long-help-message-tail)) + (ediff-diff3-job + (concat ediff-long-help-message-head + ediff-long-help-message-compare3 + ediff-long-help-message-tail)) + (t + (concat ediff-long-help-message-head + ediff-long-help-message-compare2 + ediff-long-help-message-tail)))) + (setq ediff-brief-help-message + (cond ((and ediff-brief-help-message-function + (or (symbolp ediff-brief-help-message-function) + (consp ediff-brief-help-message-function))) + (funcall ediff-brief-help-message-function)) + ((stringp ediff-brief-help-message-function) + ediff-brief-help-message-function) + ((ediff-multiframe-setup-p) ediff-brief-message-string) + (t ; long brief msg, not multiframe --- put in the middle + ediff-brief-message-string) + )) + (setq ediff-help-message (if ediff-use-long-help-message + ediff-long-help-message + ediff-brief-help-message)) + (run-hooks 'ediff-display-help-hook)) + +;;;###autoload +(defun ediff-customize () + (interactive) + (customize-group "ediff")) + + +(provide 'ediff-help) + + +;; arch-tag: 05659813-7fcf-4274-964f-d2f577431a9d +;;; ediff-help.el ends here diff -r 681cd08dc0f7 -r e93288477c43 lisp/vc/ediff-hook.el --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/lisp/vc/ediff-hook.el Sun Jun 13 22:57:55 2010 +0000 @@ -0,0 +1,263 @@ +;;; ediff-hook.el --- setup for Ediff's menus and autoloads + +;; Copyright (C) 1995, 1996, 1997, 1998, 1999, 2000, 2001, 2002, 2003, +;; 2004, 2005, 2006, 2007, 2008, 2009, 2010 Free Software Foundation, Inc. + +;; Author: Michael Kifer + +;; 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 3 of the License, 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. If not, see . + +;;; Commentary: + +;;; Code: + +;;; These must be placed in menu-bar.el in Emacs +;; +;; (define-key menu-bar-tools-menu [ediff-misc] +;; '("Ediff Miscellanea" . menu-bar-ediff-misc-menu)) +;; (define-key menu-bar-tools-menu [epatch] +;; '("Apply Patch" . menu-bar-epatch-menu)) +;; (define-key menu-bar-tools-menu [ediff-merge] +;; '("Merge" . menu-bar-ediff-merge-menu)) +;; (define-key menu-bar-tools-menu [ediff] +;; '("Compare" . menu-bar-ediff-menu)) + +;; Compiler pacifier +(defvar ediff-menu) +(defvar ediff-merge-menu) +(defvar epatch-menu) +(defvar ediff-misc-menu) +;; end pacifier + +;; allow menus to be set up without ediff-wind.el being loaded +(defvar ediff-window-setup-function) + +;; This autoload is useless in Emacs because ediff-hook.el is dumped with +;; emacs, but it is needed in XEmacs +;;;###autoload +(if (featurep 'xemacs) + (progn + (defun ediff-xemacs-init-menus () + (when (featurep 'menubar) + (add-submenu + '("Tools") ediff-menu "OO-Browser...") + (add-submenu + '("Tools") ediff-merge-menu "OO-Browser...") + (add-submenu + '("Tools") epatch-menu "OO-Browser...") + (add-submenu + '("Tools") ediff-misc-menu "OO-Browser...") + (add-menu-button + '("Tools") "-------" "OO-Browser...") + )) + (defvar ediff-menu + '("Compare" + ["Two Files..." ediff-files t] + ["Two Buffers..." ediff-buffers t] + ["Three Files..." ediff-files3 t] + ["Three Buffers..." ediff-buffers3 t] + "---" + ["Two Directories..." ediff-directories t] + ["Three Directories..." ediff-directories3 t] + "---" + ["File with Revision..." ediff-revision t] + ["Directory Revisions..." ediff-directory-revisions t] + "---" + ["Windows Word-by-word..." ediff-windows-wordwise t] + ["Windows Line-by-line..." ediff-windows-linewise t] + "---" + ["Regions Word-by-word..." ediff-regions-wordwise t] + ["Regions Line-by-line..." ediff-regions-linewise t] + )) + (defvar ediff-merge-menu + '("Merge" + ["Files..." ediff-merge-files t] + ["Files with Ancestor..." ediff-merge-files-with-ancestor t] + ["Buffers..." ediff-merge-buffers t] + ["Buffers with Ancestor..." + ediff-merge-buffers-with-ancestor t] + "---" + ["Directories..." ediff-merge-directories t] + ["Directories with Ancestor..." + ediff-merge-directories-with-ancestor t] + "---" + ["Revisions..." ediff-merge-revisions t] + ["Revisions with Ancestor..." + ediff-merge-revisions-with-ancestor t] + ["Directory Revisions..." ediff-merge-directory-revisions t] + ["Directory Revisions with Ancestor..." + ediff-merge-directory-revisions-with-ancestor t] + )) + (defvar epatch-menu + '("Apply Patch" + ["To a file..." ediff-patch-file t] + ["To a buffer..." ediff-patch-buffer t] + )) + (defvar ediff-misc-menu + '("Ediff Miscellanea" + ["Ediff Manual" ediff-documentation t] + ["Customize Ediff" ediff-customize t] + ["List Ediff Sessions" ediff-show-registry t] + ["Use separate frame for Ediff control buffer" + ediff-toggle-multiframe + :style toggle + :selected (if (and (featurep 'ediff-util) + (boundp 'ediff-window-setup-function)) + (eq ediff-window-setup-function + 'ediff-setup-windows-multiframe))] + ["Use a toolbar with Ediff control buffer" + ediff-toggle-use-toolbar + :style toggle + :selected (if (featurep 'ediff-tbar) + (ediff-use-toolbar-p))])) + + ;; put these menus before Object-Oriented-Browser in Tools menu + (if (and (featurep 'menubar) (not (featurep 'infodock)) + (not (featurep 'ediff-hook))) + (ediff-xemacs-init-menus))) + ;; Emacs + ;; initialize menu bar keymaps + (defvar menu-bar-ediff-misc-menu + (make-sparse-keymap "Ediff Miscellanea")) + (fset 'menu-bar-ediff-misc-menu + (symbol-value 'menu-bar-ediff-misc-menu)) + (defvar menu-bar-epatch-menu (make-sparse-keymap "Apply Patch")) + (fset 'menu-bar-epatch-menu (symbol-value 'menu-bar-epatch-menu)) + (defvar menu-bar-ediff-merge-menu (make-sparse-keymap "Merge")) + (fset 'menu-bar-ediff-merge-menu + (symbol-value 'menu-bar-ediff-merge-menu)) + (defvar menu-bar-ediff-menu (make-sparse-keymap "Compare")) + (fset 'menu-bar-ediff-menu (symbol-value 'menu-bar-ediff-menu)) + + ;; define ediff compare menu + (define-key menu-bar-ediff-menu [ediff-misc] + `(menu-item ,(purecopy "Ediff Miscellanea") menu-bar-ediff-misc-menu)) + (define-key menu-bar-ediff-menu [separator-ediff-misc] menu-bar-separator) + (define-key menu-bar-ediff-menu [window] + `(menu-item ,(purecopy "This Window and Next Window") compare-windows + :help ,(purecopy "Compare the current window and the next window"))) + (define-key menu-bar-ediff-menu [ediff-windows-linewise] + `(menu-item ,(purecopy "Windows Line-by-line...") ediff-windows-linewise + :help ,(purecopy "Compare windows line-wise"))) + (define-key menu-bar-ediff-menu [ediff-windows-wordwise] + `(menu-item ,(purecopy "Windows Word-by-word...") ediff-windows-wordwise + :help ,(purecopy "Compare windows word-wise"))) + (define-key menu-bar-ediff-menu [separator-ediff-windows] menu-bar-separator) + (define-key menu-bar-ediff-menu [ediff-regions-linewise] + `(menu-item ,(purecopy "Regions Line-by-line...") ediff-regions-linewise + :help ,(purecopy "Compare regions line-wise"))) + (define-key menu-bar-ediff-menu [ediff-regions-wordwise] + `(menu-item ,(purecopy "Regions Word-by-word...") ediff-regions-wordwise + :help ,(purecopy "Compare regions word-wise"))) + (define-key menu-bar-ediff-menu [separator-ediff-regions] menu-bar-separator) + (define-key menu-bar-ediff-menu [ediff-dir-revision] + `(menu-item ,(purecopy "Directory Revisions...") ediff-directory-revisions + :help ,(purecopy "Compare directory files with their older versions"))) + (define-key menu-bar-ediff-menu [ediff-revision] + `(menu-item ,(purecopy "File with Revision...") ediff-revision + :help ,(purecopy "Compare file with its older versions"))) + (define-key menu-bar-ediff-menu [separator-ediff-directories] menu-bar-separator) + (define-key menu-bar-ediff-menu [ediff-directories3] + `(menu-item ,(purecopy "Three Directories...") ediff-directories3 + :help ,(purecopy "Compare files common to three directories simultaneously"))) + (define-key menu-bar-ediff-menu [ediff-directories] + `(menu-item ,(purecopy "Two Directories...") ediff-directories + :help ,(purecopy "Compare files common to two directories simultaneously"))) + (define-key menu-bar-ediff-menu [separator-ediff-files] menu-bar-separator) + (define-key menu-bar-ediff-menu [ediff-buffers3] + `(menu-item ,(purecopy "Three Buffers...") ediff-buffers3 + :help ,(purecopy "Compare three buffers simultaneously"))) + (define-key menu-bar-ediff-menu [ediff-files3] + `(menu-item ,(purecopy "Three Files...") ediff-files3 + :help ,(purecopy "Compare three files simultaneously"))) + (define-key menu-bar-ediff-menu [ediff-buffers] + `(menu-item ,(purecopy "Two Buffers...") ediff-buffers + :help ,(purecopy "Compare two buffers simultaneously"))) + (define-key menu-bar-ediff-menu [ediff-files] + `(menu-item ,(purecopy "Two Files...") ediff-files + :help ,(purecopy "Compare two files simultaneously"))) + + ;; define ediff merge menu + (define-key + menu-bar-ediff-merge-menu [ediff-merge-dir-revisions-with-ancestor] + `(menu-item ,(purecopy "Directory Revisions with Ancestor...") + ediff-merge-directory-revisions-with-ancestor + :help ,(purecopy "Merge versions of the files in the same directory by comparing the files with common ancestors"))) + (define-key + menu-bar-ediff-merge-menu [ediff-merge-dir-revisions] + `(menu-item ,(purecopy "Directory Revisions...") ediff-merge-directory-revisions + :help ,(purecopy "Merge versions of the files in the same directory (without using ancestor information)"))) + (define-key + menu-bar-ediff-merge-menu [ediff-merge-revisions-with-ancestor] + `(menu-item ,(purecopy "Revisions with Ancestor...") + ediff-merge-revisions-with-ancestor + :help ,(purecopy "Merge versions of the same file by comparing them with a common ancestor"))) + (define-key menu-bar-ediff-merge-menu [ediff-merge-revisions] + `(menu-item ,(purecopy "Revisions...") ediff-merge-revisions + :help ,(purecopy "Merge versions of the same file (without using ancestor information)"))) + (define-key menu-bar-ediff-merge-menu [separator-ediff-merge] menu-bar-separator) + (define-key + menu-bar-ediff-merge-menu [ediff-merge-directories-with-ancestor] + `(menu-item ,(purecopy "Directories with Ancestor...") + ediff-merge-directories-with-ancestor + :help ,(purecopy "Merge files common to a pair of directories by comparing the files with common ancestors"))) + (define-key menu-bar-ediff-merge-menu [ediff-merge-directories] + `(menu-item ,(purecopy "Directories...") ediff-merge-directories + :help ,(purecopy "Merge files common to a pair of directories"))) + (define-key + menu-bar-ediff-merge-menu [separator-ediff-merge-dirs] menu-bar-separator) + (define-key + menu-bar-ediff-merge-menu [ediff-merge-buffers-with-ancestor] + `(menu-item ,(purecopy "Buffers with Ancestor...") ediff-merge-buffers-with-ancestor + :help ,(purecopy "Merge buffers by comparing their contents with a common ancestor"))) + (define-key menu-bar-ediff-merge-menu [ediff-merge-buffers] + `(menu-item ,(purecopy "Buffers...") ediff-merge-buffers + :help ,(purecopy "Merge buffers (without using ancestor information)"))) + (define-key menu-bar-ediff-merge-menu [ediff-merge-files-with-ancestor] + `(menu-item ,(purecopy "Files with Ancestor...") ediff-merge-files-with-ancestor + :help ,(purecopy "Merge files by comparing them with a common ancestor"))) + (define-key menu-bar-ediff-merge-menu [ediff-merge-files] + `(menu-item ,(purecopy "Files...") ediff-merge-files + :help ,(purecopy "Merge files (without using ancestor information)"))) + + ;; define epatch menu + (define-key menu-bar-epatch-menu [ediff-patch-buffer] + `(menu-item ,(purecopy "To a Buffer...") ediff-patch-buffer + :help ,(purecopy "Apply a patch to the contents of a buffer"))) + (define-key menu-bar-epatch-menu [ediff-patch-file] + `(menu-item ,(purecopy "To a File...") ediff-patch-file + :help ,(purecopy "Apply a patch to a file"))) + + ;; define ediff miscellanea + (define-key menu-bar-ediff-misc-menu [emultiframe] + `(menu-item ,(purecopy "Use separate control buffer frame") + ediff-toggle-multiframe + :help ,(purecopy "Switch between the single-frame presentation mode and the multi-frame mode"))) + (define-key menu-bar-ediff-misc-menu [eregistry] + `(menu-item ,(purecopy "List Ediff Sessions") ediff-show-registry + :help ,(purecopy "List all active Ediff sessions; it is a convenient way to find and resume such a session"))) + (define-key menu-bar-ediff-misc-menu [ediff-cust] + `(menu-item ,(purecopy "Customize Ediff") ediff-customize + :help ,(purecopy "Change some of the parameters that govern the behavior of Ediff"))) + (define-key menu-bar-ediff-misc-menu [ediff-doc] + `(menu-item ,(purecopy "Ediff Manual") ediff-documentation + :help ,(purecopy "Bring up the Ediff manual")))) + +(provide 'ediff-hook) + + +;; arch-tag: 512f8656-8a4b-4789-af5d-5c6144498df3 +;;; ediff-hook.el ends here diff -r 681cd08dc0f7 -r e93288477c43 lisp/vc/ediff-init.el --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/lisp/vc/ediff-init.el Sun Jun 13 22:57:55 2010 +0000 @@ -0,0 +1,1821 @@ +;;; ediff-init.el --- Macros, variables, and defsubsts used by Ediff + +;; Copyright (C) 1994, 1995, 1996, 1997, 1998, 1999, 2000, 2001, 2002, +;; 2003, 2004, 2005, 2006, 2007, 2008, 2009, 2010 Free Software Foundation, Inc. + +;; Author: Michael Kifer + +;; 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 3 of the License, 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. If not, see . + +;;; Commentary: + +;;; Code: + +;; Start compiler pacifier +(defvar ediff-metajob-name) +(defvar ediff-meta-buffer) +(defvar ediff-grab-mouse) +(defvar ediff-mouse-pixel-position) +(defvar ediff-mouse-pixel-threshold) +(defvar ediff-whitespace) +(defvar ediff-multiframe) +(defvar ediff-use-toolbar-p) +(defvar mswindowsx-bitmap-file-path) +;; end pacifier + +(defvar ediff-force-faces nil + "If t, Ediff will think that it is running on a display that supports faces. +This is provided as a temporary relief for users of face-capable displays +that Ediff doesn't know about.") + +;; Are we running as a window application or on a TTY? +(defsubst ediff-device-type () + (if (featurep 'xemacs) + (device-type (selected-device)) + window-system)) + +;; in XEmacs: device-type is tty on tty and stream in batch. +(defun ediff-window-display-p () + (and (ediff-device-type) (not (memq (ediff-device-type) '(tty pc stream))))) + +;; test if supports faces +(defun ediff-has-face-support-p () + (cond ((ediff-window-display-p)) + (ediff-force-faces) + ((ediff-color-display-p)) + ((featurep 'emacs) (memq (ediff-device-type) '(pc))) + ((featurep 'xemacs) (memq (ediff-device-type) '(tty pc))) + )) + +;; toolbar support for emacs hasn't been implemented in ediff +(defun ediff-has-toolbar-support-p () + (if (featurep 'xemacs) + (if (featurep 'toolbar) (console-on-window-system-p)))) + + +(defun ediff-has-gutter-support-p () + (if (featurep 'xemacs) + (if (featurep 'gutter) (console-on-window-system-p)))) + +(defun ediff-use-toolbar-p () + (and (ediff-has-toolbar-support-p) ;Can it do it ? + (boundp 'ediff-use-toolbar-p) + ediff-use-toolbar-p)) ;Does the user want it ? + +;; Defines VAR as an advertised local variable. +;; Performs a defvar, then executes `make-variable-buffer-local' on +;; the variable. Also sets the `permanent-local' property, +;; so that `kill-all-local-variables' (called by major-mode setting +;; commands) won't destroy Ediff control variables. +;; +;; Plagiarised from `emerge-defvar-local' for XEmacs. +(defmacro ediff-defvar-local (var value doc) + "Defines VAR as a local variable." + (declare (indent defun)) + `(progn + (defvar ,var ,value ,doc) + (make-variable-buffer-local ',var) + (put ',var 'permanent-local t))) + + + +;; Variables that control each Ediff session---local to the control buffer. + +;; Mode variables +;; The buffer in which the A variant is stored. +(ediff-defvar-local ediff-buffer-A nil "") +;; The buffer in which the B variant is stored. +(ediff-defvar-local ediff-buffer-B nil "") +;; The buffer in which the C variant is stored or where the merge buffer lives. +(ediff-defvar-local ediff-buffer-C nil "") +;; Ancestor buffer +(ediff-defvar-local ediff-ancestor-buffer nil "") +;; The Ediff control buffer +(ediff-defvar-local ediff-control-buffer nil "") + +(ediff-defvar-local ediff-temp-indirect-buffer nil + "If t, the buffer is a temporary indirect buffer. +It needs to be killed when we quit the session.") + + +;; Association between buff-type and ediff-buffer-* +(defconst ediff-buffer-alist + '((?A . ediff-buffer-A) + (?B . ediff-buffer-B) + (?C . ediff-buffer-C))) + +;;; Macros +(defmacro ediff-odd-p (arg) + `(eq (logand ,arg 1) 1)) + +(defmacro ediff-buffer-live-p (buf) + `(and ,buf (get-buffer ,buf) (buffer-name (get-buffer ,buf)))) + +(defmacro ediff-get-buffer (arg) + `(cond ((eq ,arg 'A) ediff-buffer-A) + ((eq ,arg 'B) ediff-buffer-B) + ((eq ,arg 'C) ediff-buffer-C) + ((eq ,arg 'Ancestor) ediff-ancestor-buffer) + )) + +(defmacro ediff-get-value-according-to-buffer-type (buf-type list) + `(cond ((eq ,buf-type 'A) (nth 0 ,list)) + ((eq ,buf-type 'B) (nth 1 ,list)) + ((eq ,buf-type 'C) (nth 2 ,list)) + )) + +(defmacro ediff-char-to-buftype (arg) + `(cond ((memq ,arg '(?a ?A)) 'A) + ((memq ,arg '(?b ?B)) 'B) + ((memq ,arg '(?c ?C)) 'C) + )) + + +;; A-list is supposed to be of the form (A . symb) (B . symb)...) +;; where the first part of any association is a buffer type and the second is +;; an appropriate symbol. Given buffer-type, this function returns the +;; symbol. This is used to avoid using `intern' +(defsubst ediff-get-symbol-from-alist (buf-type alist) + (cdr (assoc buf-type alist))) + +(defconst ediff-difference-vector-alist + '((A . ediff-difference-vector-A) + (B . ediff-difference-vector-B) + (C . ediff-difference-vector-C) + (Ancestor . ediff-difference-vector-Ancestor))) + +(defmacro ediff-get-difference (n buf-type) + `(aref + (symbol-value + (ediff-get-symbol-from-alist + ,buf-type ediff-difference-vector-alist)) + ,n)) + +;; Tell if it has been previously determined that the region has +;; no diffs other than the white space and newlines +;; The argument, N, is the diff region number used by Ediff to index the +;; diff vector. It is 1 less than the number seen by the user. +;; Returns: +;; t if the diffs are whitespace in all buffers +;; 'A (in 3-buf comparison only) if there are only whitespace +;; diffs in bufs B and C +;; 'B (in 3-buf comparison only) if there are only whitespace +;; diffs in bufs A and C +;; 'C (in 3-buf comparison only) if there are only whitespace +;; diffs in bufs A and B +;; +;; A Difference Vector has the form: +;; [diff diff diff ...] +;; where each diff has the form: +;; [overlay fine-diff-vector no-fine-diffs-flag state-of-difference] +;; fine-diff-vector is a vector [fine-diff fine-diff fine-diff ...] +;; no-fine-diffs-flag says if there are fine differences. +;; state-of-difference is A, B, C, or nil, indicating which buffer is +;; different from the other two (used only in 3-way jobs). +(defmacro ediff-no-fine-diffs-p (n) + `(aref (ediff-get-difference ,n 'A) 2)) + +(defmacro ediff-get-diff-overlay-from-diff-record (diff-rec) + `(aref ,diff-rec 0)) + +(defmacro ediff-get-diff-overlay (n buf-type) + `(ediff-get-diff-overlay-from-diff-record + (ediff-get-difference ,n ,buf-type))) + +(defmacro ediff-get-fine-diff-vector-from-diff-record (diff-rec) + `(aref ,diff-rec 1)) + +(defmacro ediff-set-fine-diff-vector (n buf-type fine-vec) + `(aset (ediff-get-difference ,n ,buf-type) 1 ,fine-vec)) + +(defmacro ediff-get-state-of-diff (n buf-type) + `(if (ediff-buffer-live-p ediff-buffer-C) + (aref (ediff-get-difference ,n ,buf-type) 3))) +(defmacro ediff-set-state-of-diff (n buf-type val) + `(aset (ediff-get-difference ,n ,buf-type) 3 ,val)) + +(defmacro ediff-get-state-of-merge (n) + `(if ediff-state-of-merge + (aref (aref ediff-state-of-merge ,n) 0))) +(defmacro ediff-set-state-of-merge (n val) + `(if ediff-state-of-merge + (aset (aref ediff-state-of-merge ,n) 0 ,val))) + +(defmacro ediff-get-state-of-ancestor (n) + `(if ediff-state-of-merge + (aref (aref ediff-state-of-merge ,n) 1))) + +;; if flag is t, puts a mark on diff region saying that +;; the differences are in white space only. If flag is nil, +;; the region is marked as essential (i.e., differences are +;; not just in the white space and newlines.) +(defmacro ediff-mark-diff-as-space-only (n flag) + `(aset (ediff-get-difference ,n 'A) 2 ,flag)) + +(defmacro ediff-get-fine-diff-vector (n buf-type) + `(ediff-get-fine-diff-vector-from-diff-record + (ediff-get-difference ,n ,buf-type))) + +;; Macro to switch to BUFFER, evaluate BODY, returns to original buffer. +;; Doesn't save the point and mark. +;; This is `with-current-buffer' with the added test for live buffers." +(defmacro ediff-with-current-buffer (buffer &rest body) + "Evaluates BODY in BUFFER." + (declare (indent 1) (debug (form body))) + `(if (ediff-buffer-live-p ,buffer) + (save-current-buffer + (set-buffer ,buffer) + ,@body) + (or (eq this-command 'ediff-quit) + (error ediff-KILLED-VITAL-BUFFER)) + )) + + +(defsubst ediff-multiframe-setup-p () + (and (ediff-window-display-p) ediff-multiframe)) + +(defmacro ediff-narrow-control-frame-p () + `(and (ediff-multiframe-setup-p) + (equal ediff-help-message ediff-brief-message-string))) + +(defmacro ediff-3way-comparison-job () + `(memq + ediff-job-name + '(ediff-files3 ediff-buffers3))) +(ediff-defvar-local ediff-3way-comparison-job nil "") + +(defmacro ediff-merge-job () + `(memq + ediff-job-name + '(ediff-merge-files + ediff-merge-buffers + ediff-merge-files-with-ancestor + ediff-merge-buffers-with-ancestor + ediff-merge-revisions + ediff-merge-revisions-with-ancestor))) +(ediff-defvar-local ediff-merge-job nil "") + +(defmacro ediff-patch-job () + `(eq ediff-job-name 'epatch)) + +(defmacro ediff-merge-with-ancestor-job () + `(memq + ediff-job-name + '(ediff-merge-files-with-ancestor + ediff-merge-buffers-with-ancestor + ediff-merge-revisions-with-ancestor))) +(ediff-defvar-local ediff-merge-with-ancestor-job nil "") + +(defmacro ediff-3way-job () + `(or ediff-3way-comparison-job ediff-merge-job)) +(ediff-defvar-local ediff-3way-job nil "") + +;; A diff3 job is like a 3way job, but ediff-merge doesn't require the use +;; of diff3. +(defmacro ediff-diff3-job () + `(or ediff-3way-comparison-job + ediff-merge-with-ancestor-job)) +(ediff-defvar-local ediff-diff3-job nil "") + +(defmacro ediff-windows-job () + `(memq ediff-job-name '(ediff-windows-wordwise ediff-windows-linewise))) +(ediff-defvar-local ediff-windows-job nil "") + +(defmacro ediff-word-mode-job () + `(memq ediff-job-name '(ediff-windows-wordwise ediff-regions-wordwise))) +(ediff-defvar-local ediff-word-mode-job nil "") + +(defmacro ediff-narrow-job () + `(memq ediff-job-name '(ediff-windows-wordwise + ediff-regions-wordwise + ediff-windows-linewise + ediff-regions-linewise))) +(ediff-defvar-local ediff-narrow-job nil "") + +;; Note: ediff-merge-directory-revisions-with-ancestor is not treated as an +;; ancestor metajob, since it behaves differently. +(defsubst ediff-ancestor-metajob (&optional metajob) + (memq (or metajob ediff-metajob-name) + '(ediff-merge-directories-with-ancestor + ediff-merge-filegroups-with-ancestor))) +(defsubst ediff-revision-metajob (&optional metajob) + (memq (or metajob ediff-metajob-name) + '(ediff-directory-revisions + ediff-merge-directory-revisions + ediff-merge-directory-revisions-with-ancestor))) +(defsubst ediff-patch-metajob (&optional metajob) + (memq (or metajob ediff-metajob-name) + '(ediff-multifile-patch))) +;; metajob involving only one group of files, such as multipatch or directory +;; revision +(defsubst ediff-one-filegroup-metajob (&optional metajob) + (or (ediff-revision-metajob metajob) + (ediff-patch-metajob metajob) + ;; add more here + )) +;; jobs suitable for the operation of collecting diffs into a multifile patch +(defsubst ediff-collect-diffs-metajob (&optional metajob) + (memq (or metajob ediff-metajob-name) + '(ediff-directories + ediff-merge-directories + ediff-merge-directories-with-ancestor + ediff-directory-revisions + ediff-merge-directory-revisions + ediff-merge-directory-revisions-with-ancestor + ;; add more here + ))) +(defsubst ediff-merge-metajob (&optional metajob) + (memq (or metajob ediff-metajob-name) + '(ediff-merge-directories + ediff-merge-directories-with-ancestor + ediff-merge-directory-revisions + ediff-merge-directory-revisions-with-ancestor + ediff-merge-filegroups-with-ancestor + ;; add more here + ))) + +(defsubst ediff-metajob3 (&optional metajob) + (memq (or metajob ediff-metajob-name) + '(ediff-merge-directories-with-ancestor + ediff-merge-filegroups-with-ancestor + ediff-directories3 + ediff-filegroups3))) +(defsubst ediff-comparison-metajob3 (&optional metajob) + (memq (or metajob ediff-metajob-name) + '(ediff-directories3 ediff-filegroups3))) + +;; with no argument, checks if we are in ediff-control-buffer +;; with argument, checks if we are in ediff-meta-buffer +(defun ediff-in-control-buffer-p (&optional meta-buf-p) + (and (boundp 'ediff-control-buffer) + (eq (if meta-buf-p ediff-meta-buffer ediff-control-buffer) + (current-buffer)))) + +(defsubst ediff-barf-if-not-control-buffer (&optional meta-buf-p) + (or (ediff-in-control-buffer-p meta-buf-p) + (error "%S: This command runs in Ediff Control Buffer only!" + this-command))) + +(defgroup ediff-highlighting nil + "Hilighting of difference regions in Ediff." + :prefix "ediff-" + :group 'ediff) + +(defgroup ediff-merge nil + "Merging utilities." + :prefix "ediff-" + :group 'ediff) + +(defgroup ediff-hook nil + "Hooks run by Ediff." + :prefix "ediff-" + :group 'ediff) + +;; Hook variables + +(defcustom ediff-before-setup-hook nil + "Hooks to run before Ediff begins to set up windows and buffers. +This hook can be used to save the previous window config, which can be restored +on ediff-quit or ediff-suspend." + :type 'hook + :group 'ediff-hook) +(defcustom ediff-before-setup-windows-hook nil + "Hooks to run before Ediff sets its window configuration. +This hook is run every time when Ediff arranges its windows. +This happens each time Ediff detects that the windows were messed up by the +user." + :type 'hook + :group 'ediff-hook) +(defcustom ediff-after-setup-windows-hook nil + "Hooks to run after Ediff sets its window configuration. +This can be used to set up control window or icon in a desired place." + :type 'hook + :group 'ediff-hook) +(defcustom ediff-before-setup-control-frame-hook nil + "Hooks run before setting up the frame to display Ediff Control Panel. +Can be used to change control frame parameters to position it where it +is desirable." + :type 'hook + :group 'ediff-hook) +(defcustom ediff-after-setup-control-frame-hook nil + "Hooks run after setting up the frame to display Ediff Control Panel. +Can be used to move the frame where it is desired." + :type 'hook + :group 'ediff-hook) +(defcustom ediff-startup-hook nil + "Hooks to run in the control buffer after Ediff has been set up and is ready for the job." + :type 'hook + :group 'ediff-hook) +(defcustom ediff-select-hook nil + "Hooks to run after a difference has been selected." + :type 'hook + :group 'ediff-hook) +(defcustom ediff-unselect-hook nil + "Hooks to run after a difference has been unselected." + :type 'hook + :group 'ediff-hook) +(defcustom ediff-prepare-buffer-hook nil + "Hooks run after buffers A, B, and C are set up. +For each buffer, the hooks are run with that buffer made current." + :type 'hook + :group 'ediff-hook) +(defcustom ediff-load-hook nil + "Hook run after Ediff is loaded. Can be used to change defaults." + :type 'hook + :group 'ediff-hook) + +(defcustom ediff-mode-hook nil + "Hook run just after ediff-mode is set up in the control buffer. +This is done before any windows or frames are created. One can use it to +set local variables that determine how the display looks like." + :type 'hook + :group 'ediff-hook) +(defcustom ediff-keymap-setup-hook nil + "Hook run just after the default bindings in Ediff keymap are set up." + :type 'hook + :group 'ediff-hook) + +(defcustom ediff-display-help-hook nil + "Hooks run after preparing the help message." + :type 'hook + :group 'ediff-hook) + +(defcustom ediff-suspend-hook nil + "Hooks to run in the Ediff control buffer when Ediff is suspended." + :type 'hook + :group 'ediff-hook) +(defcustom ediff-quit-hook nil + "Hooks to run in the Ediff control buffer after finishing Ediff." + :type 'hook + :group 'ediff-hook) +(defcustom ediff-cleanup-hook nil + "Hooks to run on exiting Ediff but before killing the control and variant buffers." + :type 'hook + :group 'ediff-hook) + +;; Error messages +(defconst ediff-KILLED-VITAL-BUFFER + "You have killed a vital Ediff buffer---you must leave Ediff now!") +(defconst ediff-NO-DIFFERENCES + "Sorry, comparison of identical variants is not what I am made for...") +(defconst ediff-BAD-DIFF-NUMBER + ;; %S stands for this-command, %d - diff number, %d - max diff + "%S: Bad diff region number, %d. Valid numbers are 1 to %d") +(defconst ediff-BAD-INFO (format " +*** The Info file for Ediff, a part of the standard distribution +*** of %sEmacs, does not seem to be properly installed. +*** +*** Please contact your system administrator. " + (if (featurep 'xemacs) "X" ""))) + +;; Selective browsing + +(ediff-defvar-local ediff-skip-diff-region-function 'ediff-show-all-diffs + "Function that determines the next/previous diff region to show. +Should return t for regions to be ignored and nil otherwise. +This function gets a region number as an argument. The region number +is the one used internally by Ediff. It is 1 less than the number seen +by the user.") + +(ediff-defvar-local ediff-hide-regexp-matches-function + 'ediff-hide-regexp-matches + "Function to use in determining which regions to hide. +See the documentation string of `ediff-hide-regexp-matches' for details.") +(ediff-defvar-local ediff-focus-on-regexp-matches-function + 'ediff-focus-on-regexp-matches + "Function to use in determining which regions to focus on. +See the documentation string of `ediff-focus-on-regexp-matches' for details.") + +;; Regexp that determines buf A regions to focus on when skipping to diff +(ediff-defvar-local ediff-regexp-focus-A "" "") +;; Regexp that determines buf B regions to focus on when skipping to diff +(ediff-defvar-local ediff-regexp-focus-B "" "") +;; Regexp that determines buf C regions to focus on when skipping to diff +(ediff-defvar-local ediff-regexp-focus-C "" "") +;; connective that determines whether to focus regions that match both or +;; one of the regexps +(ediff-defvar-local ediff-focus-regexp-connective 'and "") + +;; Regexp that determines buf A regions to ignore when skipping to diff +(ediff-defvar-local ediff-regexp-hide-A "" "") +;; Regexp that determines buf B regions to ignore when skipping to diff +(ediff-defvar-local ediff-regexp-hide-B "" "") +;; Regexp that determines buf C regions to ignore when skipping to diff +(ediff-defvar-local ediff-regexp-hide-C "" "") +;; connective that determines whether to hide regions that match both or +;; one of the regexps +(ediff-defvar-local ediff-hide-regexp-connective 'and "") + + +;;; Copying difference regions between buffers. + +;; A list of killed diffs. +;; A diff is saved here if it is replaced by a diff +;; from another buffer. This alist has the form: +;; \((num (buff-object . diff) (buff-object . diff) (buff-object . diff)) ...), +;; where some buffer-objects may be missing. +(ediff-defvar-local ediff-killed-diffs-alist nil "") + +;; Syntax table to use in ediff-forward-word-function +;; This is chosen by a heuristic. The important thing is for all buffers to +;; have the same syntax table. Which is not too important. +(ediff-defvar-local ediff-syntax-table nil "") + + +;; Highlighting +(defcustom ediff-before-flag-bol (if (featurep 'xemacs) (make-glyph "->>") "->>") + "Flag placed before a highlighted block of differences, if block starts at beginning of a line." + :type 'string + :tag "Region before-flag at beginning of line" + :group 'ediff) + +(defcustom ediff-after-flag-eol (if (featurep 'xemacs) (make-glyph "<<-") "<<-") + "Flag placed after a highlighted block of differences, if block ends at end of a line." + :type 'string + :tag "Region after-flag at end of line" + :group 'ediff) + +(defcustom ediff-before-flag-mol (if (featurep 'xemacs) (make-glyph "->>") "->>") + "Flag placed before a highlighted block of differences, if block starts in mid-line." + :type 'string + :tag "Region before-flag in the middle of line" + :group 'ediff) +(defcustom ediff-after-flag-mol (if (featurep 'xemacs) (make-glyph "<<-") "<<-") + "Flag placed after a highlighted block of differences, if block ends in mid-line." + :type 'string + :tag "Region after-flag in the middle of line" + :group 'ediff) + + +(ediff-defvar-local ediff-use-faces t "") +(defcustom ediff-use-faces t + "If t, differences are highlighted using faces, if device supports faces. +If nil, differences are highlighted using ASCII flags, ediff-before-flag +and ediff-after-flag. On a non-window system, differences are always +highlighted using ASCII flags." + :type 'boolean + :group 'ediff-highlighting) + +;; this indicates that diff regions are word-size, so fine diffs are +;; permanently nixed; used in ediff-windows-wordwise and ediff-regions-wordwise +(ediff-defvar-local ediff-word-mode nil "") +;; Name of the job (ediff-files, ediff-windows, etc.) +(ediff-defvar-local ediff-job-name nil "") + +;; Narrowing and ediff-region/windows support +;; This is a list (overlay-A overlay-B overlay-C) +;; If set, Ediff compares only those parts of buffers A/B/C that lie within +;; the bounds of these overlays. +(ediff-defvar-local ediff-narrow-bounds nil "") + +;; List (overlay-A overlay-B overlay-C), where each overlay spans the +;; entire corresponding buffer. +(ediff-defvar-local ediff-wide-bounds nil "") + +;; Current visibility boundaries in buffers A, B, and C. +;; This is also a list of overlays. When the user toggles narrow/widen, +;; this list changes from ediff-wide-bounds to ediff-narrow-bounds. +;; and back. +(ediff-defvar-local ediff-visible-bounds nil "") + +(ediff-defvar-local ediff-start-narrowed t + "Non-nil means start narrowed, if doing ediff-windows-* or ediff-regions-*") +(ediff-defvar-local ediff-quit-widened t + "*Non-nil means: when finished, Ediff widens buffers A/B. +Actually, Ediff restores the scope of visibility that existed at startup.") + +(defcustom ediff-keep-variants t + "nil means prompt to remove unmodified buffers A/B/C at session end. +Supplying a prefix argument to the quit command `q' temporarily reverses the +meaning of this variable." + :type 'boolean + :group 'ediff) + +(ediff-defvar-local ediff-highlight-all-diffs t "") +(defcustom ediff-highlight-all-diffs t + "If nil, only the selected differences are highlighted. +Otherwise, all difference regions are highlighted, but the selected region is +shown in brighter colors." + :type 'boolean + :group 'ediff-highlighting) + + +;; The suffix of the control buffer name. +(ediff-defvar-local ediff-control-buffer-suffix nil "") +;; Same as ediff-control-buffer-suffix, but without <,>. +;; It's a number rather than string. +(ediff-defvar-local ediff-control-buffer-number nil "") + + +;; The original values of ediff-protected-variables for buffer A +(ediff-defvar-local ediff-buffer-values-orig-A nil "") +;; The original values of ediff-protected-variables for buffer B +(ediff-defvar-local ediff-buffer-values-orig-B nil "") +;; The original values of ediff-protected-variables for buffer C +(ediff-defvar-local ediff-buffer-values-orig-C nil "") +;; The original values of ediff-protected-variables for buffer Ancestor +(ediff-defvar-local ediff-buffer-values-orig-Ancestor nil "") + +;; association between buff-type and ediff-buffer-values-orig-* +(defconst ediff-buffer-values-orig-alist + '((A . ediff-buffer-values-orig-A) + (B . ediff-buffer-values-orig-B) + (C . ediff-buffer-values-orig-C) + (Ancestor . ediff-buffer-values-orig-Ancestor))) + +;; Buffer-local variables to be saved then restored during Ediff sessions +(defconst ediff-protected-variables '( + ;;buffer-read-only + mode-line-format)) + +;; Vector of differences between the variants. Each difference is +;; represented by a vector of two overlays plus a vector of fine diffs, +;; plus a no-fine-diffs flag. The first overlay spans the +;; difference region in the A buffer and the second overlays the diff in +;; the B buffer. If a difference section is empty, the corresponding +;; overlay's endpoints coincide. +;; +;; The precise form of a Difference Vector for one buffer is: +;; [diff diff diff ...] +;; where each diff has the form: +;; [diff-overlay fine-diff-vector no-fine-diffs-flag state-of-diff] +;; fine-diff-vector is a vector [fine-diff-overlay fine-diff-overlay ...] +;; no-fine-diffs-flag says if there are fine differences. +;; state-of-difference is A, B, C, or nil, indicating which buffer is +;; different from the other two (used only in 3-way jobs. +(ediff-defvar-local ediff-difference-vector-A nil "") +(ediff-defvar-local ediff-difference-vector-B nil "") +(ediff-defvar-local ediff-difference-vector-C nil "") +(ediff-defvar-local ediff-difference-vector-Ancestor nil "") +;; A-list of diff vector types associated with buffer types +(defconst ediff-difference-vector-alist + '((A . ediff-difference-vector-A) + (B . ediff-difference-vector-B) + (C . ediff-difference-vector-C) + (Ancestor . ediff-difference-vector-Ancestor))) + +;; [ status status status ...] +;; Each status: [state-of-merge state-of-ancestor] +;; state-of-merge is default-A, default-B, prefer-A, or prefer-B. It +;; indicates the way a diff region was created in buffer C. +;; state-of-ancestor says if the corresponding region in ancestor buffer is +;; empty. +(ediff-defvar-local ediff-state-of-merge nil "") + +;; The difference that is currently selected. +(ediff-defvar-local ediff-current-difference -1 "") +;; Number of differences found. +(ediff-defvar-local ediff-number-of-differences nil "") + +;; Buffer containing the output of diff, which is used by Ediff to step +;; through files. +(ediff-defvar-local ediff-diff-buffer nil "") +;; Like ediff-diff-buffer, but contains context diff. It is not used by +;; Ediff, but it is saved in a file, if user requests so. +(ediff-defvar-local ediff-custom-diff-buffer nil "") +;; Buffer used for diff-style fine differences between regions. +(ediff-defvar-local ediff-fine-diff-buffer nil "") +;; Temporary buffer used for computing fine differences. +(defconst ediff-tmp-buffer " *ediff-tmp*" "") +;; Buffer used for messages +(defconst ediff-msg-buffer " *ediff-message*" "") +;; Buffer containing the output of diff when diff returns errors. +(ediff-defvar-local ediff-error-buffer nil "") +;; Buffer to display debug info +(ediff-defvar-local ediff-debug-buffer "*ediff-debug*" "") + +;; List of ediff control panels associated with each buffer A/B/C/Ancestor. +;; Not used any more, but may be needed in the future. +(ediff-defvar-local ediff-this-buffer-ediff-sessions nil "") + +;; to be deleted in due time +;; List of difference overlays disturbed by working with the current diff. +(defvar ediff-disturbed-overlays nil "") + +;; Priority of non-selected overlays. +(defvar ediff-shadow-overlay-priority 100 "") + +(defcustom ediff-version-control-package 'vc + "Version control package used. +Currently, Ediff supports vc.el, rcs.el, pcl-cvs.el, and generic-sc.el. The +standard Emacs interface to RCS, CVS, SCCS, etc., is vc.el. However, some +people find the other two packages more convenient. Set this variable to the +appropriate symbol: `rcs', `pcl-cvs', or `generic-sc' if you so desire." + :type 'symbol + :group 'ediff) + +(defcustom ediff-coding-system-for-read 'raw-text + "The coding system for read to use when running the diff program as a subprocess. +In most cases, the default will do. However, under certain circumstances in +MS-Windows you might need to use something like 'raw-text-dos here. +So, if the output that your diff program sends to Emacs contains extra ^M's, +you might need to experiment here, if the default or 'raw-text-dos doesn't +work." + :type 'symbol + :group 'ediff) + +(defcustom ediff-coding-system-for-write (if (featurep 'xemacs) + 'escape-quoted + 'emacs-internal) + "The coding system for write to use when writing out difference regions +to temp files in buffer jobs and when Ediff needs to find fine differences." + :type 'symbol + :group 'ediff) + + +(defalias 'ediff-read-event + (if (featurep 'xemacs) 'next-command-event 'read-event)) + +(defalias 'ediff-overlayp + (if (featurep 'xemacs) 'extentp 'overlayp)) + +(defalias 'ediff-make-overlay + (if (featurep 'xemacs) 'make-extent 'make-overlay)) + +(defalias 'ediff-delete-overlay + (if (featurep 'xemacs) 'delete-extent 'delete-overlay)) + +;; Assumes that emacs-major-version and emacs-minor-version are defined. +(defun ediff-check-version (op major minor &optional type-of-emacs) + "Check the current version against MAJOR and MINOR version numbers. +The comparison uses operator OP, which may be any of: =, >, >=, <, <=. +TYPE-OF-EMACS is either 'xemacs or 'emacs." + (and (cond ((eq type-of-emacs 'xemacs) (featurep 'xemacs)) + ((eq type-of-emacs 'emacs) (featurep 'emacs)) + (t)) + (cond ((eq op '=) (and (= emacs-minor-version minor) + (= emacs-major-version major))) + ((memq op '(> >= < <=)) + (and (or (funcall op emacs-major-version major) + (= emacs-major-version major)) + (if (= emacs-major-version major) + (funcall op emacs-minor-version minor) + t))) + (t + (error "%S: Invalid op in ediff-check-version" op))))) + +;; ediff-check-version seems to be totally unused anyway. +(make-obsolete 'ediff-check-version 'version< "23.1") + +(defun ediff-color-display-p () + (condition-case nil + (if (featurep 'xemacs) + (eq (device-class (selected-device)) 'color) ; xemacs form + (display-color-p)) ; emacs form + (error nil))) + + +;; A var local to each control panel buffer. Indicates highlighting style +;; in effect for this buffer: `face', `ascii', +;; `off' -- turned off \(on a dumb terminal only\). +(ediff-defvar-local ediff-highlighting-style + (if (and (ediff-has-face-support-p) ediff-use-faces) 'face 'ascii) + "") + + +(if (ediff-has-face-support-p) + (if (featurep 'xemacs) + (progn + (defalias 'ediff-valid-color-p 'valid-color-name-p) + (defalias 'ediff-get-face 'get-face)) + (defalias 'ediff-valid-color-p (if (fboundp 'color-defined-p) + 'color-defined-p + 'x-color-defined-p)) + (defalias 'ediff-get-face 'internal-get-face))) + +(if (ediff-window-display-p) + (if (featurep 'xemacs) + (progn + (defalias 'ediff-display-pixel-width 'device-pixel-width) + (defalias 'ediff-display-pixel-height 'device-pixel-height)) + (defalias 'ediff-display-pixel-width + (if (fboundp 'display-pixel-width) + 'display-pixel-width + 'x-display-pixel-width)) + (defalias 'ediff-display-pixel-height + (if (fboundp 'display-pixel-height) + 'display-pixel-height + 'x-display-pixel-height)))) + +;; A-list of current-diff-overlay symbols associated with buf types +(defconst ediff-current-diff-overlay-alist + '((A . ediff-current-diff-overlay-A) + (B . ediff-current-diff-overlay-B) + (C . ediff-current-diff-overlay-C) + (Ancestor . ediff-current-diff-overlay-Ancestor))) + +;; A-list of current-diff-face-* symbols associated with buf types +(defconst ediff-current-diff-face-alist + '((A . ediff-current-diff-A) + (B . ediff-current-diff-B) + (C . ediff-current-diff-C) + (Ancestor . ediff-current-diff-Ancestor))) + + +(defun ediff-set-overlay-face (extent face) + (ediff-overlay-put extent 'face face) + (ediff-overlay-put extent 'help-echo 'ediff-region-help-echo)) + +(defun ediff-region-help-echo (extent-or-window &optional overlay point) + (unless overlay + (setq overlay extent-or-window)) + (let ((is-current (ediff-overlay-get overlay 'ediff)) + (face (ediff-overlay-get overlay 'face)) + (diff-num (ediff-overlay-get overlay 'ediff-diff-num)) + face-help) + + ;; This happens only for refinement overlays + (if (stringp face) + (setq face (intern face))) + (setq face-help (and face (get face 'ediff-help-echo))) + + (cond ((and is-current diff-num) ; current diff region + (format "Difference region %S -- current" (1+ diff-num))) + (face-help) ; refinement of current diff region + (diff-num ; non-current + (format "Difference region %S -- non-current" (1+ diff-num))) + (t "")) ; none + )) + + +(defun ediff-set-face-pixmap (face pixmap) + "Set face pixmap on a monochrome display." + (if (and (ediff-window-display-p) (not (ediff-color-display-p))) + (condition-case nil + (set-face-background-pixmap face pixmap) + (error + (message "Pixmap not found for %S: %s" (face-name face) pixmap) + (sit-for 1))))) + +(defun ediff-hide-face (face) + (if (and (ediff-has-face-support-p) + (boundp 'add-to-list) + (boundp 'facemenu-unlisted-faces)) + (add-to-list 'facemenu-unlisted-faces face))) + + + +(defface ediff-current-diff-A + (if (featurep 'emacs) + '((((class color) (min-colors 16)) + (:foreground "firebrick" :background "pale green")) + (((class color)) + (:foreground "blue3" :background "yellow3")) + (t (:inverse-video t))) + '((((type tty)) (:foreground "blue3" :background "yellow3")) + (((class color)) (:foreground "firebrick" :background "pale green")) + (t (:inverse-video t)))) + "Face for highlighting the selected difference in buffer A." + :group 'ediff-highlighting) +;; An internal variable. Ediff takes the face from here. When unhighlighting, +;; this variable is set to nil, then again to the appropriate face. +(defvar ediff-current-diff-face-A 'ediff-current-diff-A + "Face for highlighting the selected difference in buffer A. +DO NOT CHANGE this variable. Instead, use the customization +widget to customize the actual face object `ediff-current-diff-A' +this variable represents.") +(ediff-hide-face ediff-current-diff-face-A) +;; Until custom.el for XEmacs starts supporting :inverse-video we do this. +;; This means that some user customization may be trashed. +(and (featurep 'xemacs) + (ediff-has-face-support-p) + (not (ediff-color-display-p)) + (copy-face 'modeline ediff-current-diff-face-A)) + + + +(defface ediff-current-diff-B + (if (featurep 'emacs) + '((((class color) (min-colors 16)) + (:foreground "DarkOrchid" :background "Yellow")) + (((class color)) + (:foreground "magenta3" :background "yellow3" + :weight bold)) + (t (:inverse-video t))) + '((((type tty)) (:foreground "magenta3" :background "yellow3" + :weight bold)) + (((class color)) (:foreground "DarkOrchid" :background "Yellow")) + (t (:inverse-video t)))) + "Face for highlighting the selected difference in buffer B." + :group 'ediff-highlighting) +;; An internal variable. Ediff takes the face from here. When unhighlighting, +;; this variable is set to nil, then again to the appropriate face. +(defvar ediff-current-diff-face-B 'ediff-current-diff-B + "Face for highlighting the selected difference in buffer B. + this variable. Instead, use the customization +widget to customize the actual face `ediff-current-diff-B' +this variable represents.") +(ediff-hide-face ediff-current-diff-face-B) +;; Until custom.el for XEmacs starts supporting :inverse-video we do this. +;; This means that some user customization may be trashed. +(and (featurep 'xemacs) + (ediff-has-face-support-p) + (not (ediff-color-display-p)) + (copy-face 'modeline ediff-current-diff-face-B)) + + +(defface ediff-current-diff-C + (if (featurep 'emacs) + '((((class color) (min-colors 16)) + (:foreground "Navy" :background "Pink")) + (((class color)) + (:foreground "cyan3" :background "yellow3" :weight bold)) + (t (:inverse-video t))) + '((((type tty)) (:foreground "cyan3" :background "yellow3" :weight bold)) + (((class color)) (:foreground "Navy" :background "Pink")) + (t (:inverse-video t)))) + "Face for highlighting the selected difference in buffer C." + :group 'ediff-highlighting) +;; An internal variable. Ediff takes the face from here. When unhighlighting, +;; this variable is set to nil, then again to the appropriate face. +(defvar ediff-current-diff-face-C 'ediff-current-diff-C + "Face for highlighting the selected difference in buffer C. +DO NOT CHANGE this variable. Instead, use the customization +widget to customize the actual face object `ediff-current-diff-C' +this variable represents.") +(ediff-hide-face ediff-current-diff-face-C) +;; Until custom.el for XEmacs starts supporting :inverse-video we do this. +;; This means that some user customization may be trashed. +(and (featurep 'xemacs) + (ediff-has-face-support-p) + (not (ediff-color-display-p)) + (copy-face 'modeline ediff-current-diff-face-C)) + + +(defface ediff-current-diff-Ancestor + (if (featurep 'emacs) + '((((class color) (min-colors 16)) + (:foreground "Black" :background "VioletRed")) + (((class color)) + (:foreground "black" :background "magenta3")) + (t (:inverse-video t))) + '((((type tty)) (:foreground "black" :background "magenta3")) + (((class color)) (:foreground "Black" :background "VioletRed")) + (t (:inverse-video t)))) + "Face for highlighting the selected difference in buffer Ancestor." + :group 'ediff-highlighting) +;; An internal variable. Ediff takes the face from here. When unhighlighting, +;; this variable is set to nil, then again to the appropriate face. +(defvar ediff-current-diff-face-Ancestor 'ediff-current-diff-Ancestor + "Face for highlighting the selected difference in buffer Ancestor. +DO NOT CHANGE this variable. Instead, use the customization +widget to customize the actual face object `ediff-current-diff-Ancestor' +this variable represents.") +(ediff-hide-face ediff-current-diff-face-Ancestor) +;; Until custom.el for XEmacs starts supporting :inverse-video we do this. +;; This means that some user customization may be trashed. +(and (featurep 'xemacs) + (ediff-has-face-support-p) + (not (ediff-color-display-p)) + (copy-face 'modeline ediff-current-diff-face-Ancestor)) + + +(defface ediff-fine-diff-A + (if (featurep 'emacs) + '((((class color) (min-colors 16)) + (:foreground "Navy" :background "sky blue")) + (((class color)) + (:foreground "white" :background "sky blue" :weight bold)) + (t (:underline t :stipple "gray3"))) + '((((type tty)) (:foreground "white" :background "sky blue" :weight bold)) + (((class color)) (:foreground "Navy" :background "sky blue")) + (t (:underline t :stipple "gray3")))) + "Face for highlighting the refinement of the selected diff in buffer A." + :group 'ediff-highlighting) +;; An internal variable. Ediff takes the face from here. When unhighlighting, +;; this variable is set to nil, then again to the appropriate face. +(defvar ediff-fine-diff-face-A 'ediff-fine-diff-A + "Face for highlighting the fine differences in buffer A. +DO NOT CHANGE this variable. Instead, use the customization +widget to customize the actual face object `ediff-fine-diff-A' +this variable represents.") +(ediff-hide-face ediff-fine-diff-face-A) + +(defface ediff-fine-diff-B + (if (featurep 'emacs) + '((((class color) (min-colors 16)) + (:foreground "Black" :background "cyan")) + (((class color)) + (:foreground "magenta3" :background "cyan3")) + (t (:underline t :stipple "gray3"))) + '((((type tty)) (:foreground "magenta3" :background "cyan3")) + (((class color)) (:foreground "Black" :background "cyan")) + (t (:underline t :stipple "gray3")))) + "Face for highlighting the refinement of the selected diff in buffer B." + :group 'ediff-highlighting) +;; An internal variable. Ediff takes the face from here. When unhighlighting, +;; this variable is set to nil, then again to the appropriate face. +(defvar ediff-fine-diff-face-B 'ediff-fine-diff-B + "Face for highlighting the fine differences in buffer B. +DO NOT CHANGE this variable. Instead, use the customization +widget to customize the actual face object `ediff-fine-diff-B' +this variable represents.") +(ediff-hide-face ediff-fine-diff-face-B) + +(defface ediff-fine-diff-C + (if (featurep 'emacs) + '((((type pc)) + (:foreground "white" :background "Turquoise")) + (((class color) (min-colors 16)) + (:foreground "Black" :background "Turquoise")) + (((class color)) + (:foreground "yellow3" :background "Turquoise" + :weight bold)) + (t (:underline t :stipple "gray3"))) + '((((type tty)) (:foreground "yellow3" :background "Turquoise" + :weight bold)) + (((type pc)) (:foreground "white" :background "Turquoise")) + (((class color)) (:foreground "Black" :background "Turquoise")) + (t (:underline t :stipple "gray3")))) + "Face for highlighting the refinement of the selected diff in buffer C." + :group 'ediff-highlighting) +;; An internal variable. Ediff takes the face from here. When unhighlighting, +;; this variable is set to nil, then again to the appropriate face. +(defvar ediff-fine-diff-face-C 'ediff-fine-diff-C + "Face for highlighting the fine differences in buffer C. +DO NOT CHANGE this variable. Instead, use the customization +widget to customize the actual face object `ediff-fine-diff-C' +this variable represents.") +(ediff-hide-face ediff-fine-diff-face-C) + +(defface ediff-fine-diff-Ancestor + (if (featurep 'emacs) + '((((class color) (min-colors 16)) + (:foreground "Black" :background "Green")) + (((class color)) + (:foreground "red3" :background "green")) + (t (:underline t :stipple "gray3"))) + '((((type tty)) (:foreground "red3" :background "green")) + (((class color)) (:foreground "Black" :background "Green")) + (t (:underline t :stipple "gray3")))) + "Face for highlighting the refinement of the selected diff in the ancestor buffer. +At present, this face is not used and no fine differences are computed for the +ancestor buffer." + :group 'ediff-highlighting) +;; An internal variable. Ediff takes the face from here. When unhighlighting, +;; this variable is set to nil, then again to the appropriate face. +(defvar ediff-fine-diff-face-Ancestor 'ediff-fine-diff-Ancestor + "Face for highlighting the fine differences in buffer Ancestor. +DO NOT CHANGE this variable. Instead, use the customization +widget to customize the actual face object `ediff-fine-diff-Ancestor' +this variable represents.") +(ediff-hide-face ediff-fine-diff-face-Ancestor) + +;; Some installs don't have stipple or Stipple. So, try them in turn. +(defvar stipple-pixmap + (cond ((not (ediff-has-face-support-p)) nil) + ((and (boundp 'x-bitmap-file-path) + (locate-library "stipple" t x-bitmap-file-path)) "stipple") + ((and (boundp 'mswindowsx-bitmap-file-path) + (locate-library "stipple" t mswindowsx-bitmap-file-path)) "stipple") + (t "Stipple"))) + +(defface ediff-even-diff-A + (if (featurep 'emacs) + `((((type pc)) + (:foreground "green3" :background "light grey")) + (((class color) (min-colors 16)) + (:foreground "Black" :background "light grey")) + (((class color)) + (:foreground "red3" :background "light grey" + :weight bold)) + (t (:italic t :stipple ,stipple-pixmap))) + `((((type tty)) (:foreground "red3" :background "light grey" + :weight bold)) + (((type pc)) (:foreground "green3" :background "light grey")) + (((class color)) (:foreground "Black" :background "light grey")) + (t (:italic t :stipple ,stipple-pixmap)))) + "Face for highlighting even-numbered non-current differences in buffer A." + :group 'ediff-highlighting) +;; An internal variable. Ediff takes the face from here. When unhighlighting, +;; this variable is set to nil, then again to the appropriate face. +(defvar ediff-even-diff-face-A 'ediff-even-diff-A + "Face for highlighting even-numbered non-current differences in buffer A. +DO NOT CHANGE this variable. Instead, use the customization +widget to customize the actual face object `ediff-even-diff-A' +this variable represents.") +(ediff-hide-face ediff-even-diff-face-A) + +(defface ediff-even-diff-B + (if (featurep 'emacs) + `((((class color) (min-colors 16)) + (:foreground "White" :background "Grey")) + (((class color)) + (:foreground "blue3" :background "Grey" :weight bold)) + (t (:italic t :stipple ,stipple-pixmap))) + `((((type tty)) (:foreground "blue3" :background "Grey" :weight bold)) + (((class color)) (:foreground "White" :background "Grey")) + (t (:italic t :stipple ,stipple-pixmap)))) + "Face for highlighting even-numbered non-current differences in buffer B." + :group 'ediff-highlighting) +;; An internal variable. Ediff takes the face from here. When unhighlighting, +;; this variable is set to nil, then again to the appropriate face. +(defvar ediff-even-diff-face-B 'ediff-even-diff-B + "Face for highlighting even-numbered non-current differences in buffer B. +DO NOT CHANGE this variable. Instead, use the customization +widget to customize the actual face object `ediff-even-diff-B' +this variable represents.") +(ediff-hide-face ediff-even-diff-face-B) + +(defface ediff-even-diff-C + (if (featurep 'emacs) + `((((type pc)) + (:foreground "yellow3" :background "light grey")) + (((class color) (min-colors 16)) + (:foreground "Black" :background "light grey")) + (((class color)) + (:foreground "yellow3" :background "light grey" + :weight bold)) + (t (:italic t :stipple ,stipple-pixmap))) + `((((type tty)) (:foreground "yellow3" :background "light grey" + :weight bold)) + (((type pc)) (:foreground "yellow3" :background "light grey")) + (((class color)) (:foreground "Black" :background "light grey")) + (t (:italic t :stipple ,stipple-pixmap)))) + "Face for highlighting even-numbered non-current differences in buffer C." + :group 'ediff-highlighting) +;; An internal variable. Ediff takes the face from here. When unhighlighting, +;; this variable is set to nil, then again to the appropriate face. +(defvar ediff-even-diff-face-C 'ediff-even-diff-C + "Face for highlighting even-numbered non-current differences in buffer C. +DO NOT CHANGE this variable. Instead, use the customization +widget to customize the actual face object `ediff-even-diff-C' +this variable represents.") +(ediff-hide-face ediff-even-diff-face-C) + +(defface ediff-even-diff-Ancestor + (if (featurep 'emacs) + `((((type pc)) + (:foreground "cyan3" :background "light grey")) + (((class color) (min-colors 16)) + (:foreground "White" :background "Grey")) + (((class color)) + (:foreground "cyan3" :background "light grey" + :weight bold)) + (t (:italic t :stipple ,stipple-pixmap))) + `((((type tty)) (:foreground "cyan3" :background "light grey" + :weight bold)) + (((type pc)) (:foreground "cyan3" :background "light grey")) + (((class color)) (:foreground "White" :background "Grey")) + (t (:italic t :stipple ,stipple-pixmap)))) + "Face for highlighting even-numbered non-current differences in the ancestor buffer." + :group 'ediff-highlighting) +;; An internal variable. Ediff takes the face from here. When unhighlighting, +;; this variable is set to nil, then again to the appropriate face. +(defvar ediff-even-diff-face-Ancestor 'ediff-even-diff-Ancestor + "Face for highlighting even-numbered non-current differences in buffer Ancestor. +DO NOT CHANGE this variable. Instead, use the customization +widget to customize the actual face object `ediff-even-diff-Ancestor' +this variable represents.") +(ediff-hide-face ediff-even-diff-face-Ancestor) + +;; Association between buffer types and even-diff-face symbols +(defconst ediff-even-diff-face-alist + '((A . ediff-even-diff-A) + (B . ediff-even-diff-B) + (C . ediff-even-diff-C) + (Ancestor . ediff-even-diff-Ancestor))) + +(defface ediff-odd-diff-A + (if (featurep 'emacs) + '((((type pc)) + (:foreground "green3" :background "gray40")) + (((class color) (min-colors 16)) + (:foreground "White" :background "Grey")) + (((class color)) + (:foreground "red3" :background "black" :weight bold)) + (t (:italic t :stipple "gray1"))) + '((((type tty)) (:foreground "red3" :background "black" :weight bold)) + (((type pc)) (:foreground "green3" :background "gray40")) + (((class color)) (:foreground "White" :background "Grey")) + (t (:italic t :stipple "gray1")))) + "Face for highlighting odd-numbered non-current differences in buffer A." + :group 'ediff-highlighting) +;; An internal variable. Ediff takes the face from here. When unhighlighting, +;; this variable is set to nil, then again to the appropriate face. +(defvar ediff-odd-diff-face-A 'ediff-odd-diff-A + "Face for highlighting odd-numbered non-current differences in buffer A. +DO NOT CHANGE this variable. Instead, use the customization +widget to customize the actual face object `ediff-odd-diff-A' +this variable represents.") +(ediff-hide-face ediff-odd-diff-face-A) + + +(defface ediff-odd-diff-B + (if (featurep 'emacs) + '((((type pc)) + (:foreground "White" :background "gray40")) + (((class color) (min-colors 16)) + (:foreground "Black" :background "light grey")) + (((class color)) + (:foreground "cyan3" :background "black" :weight bold)) + (t (:italic t :stipple "gray1"))) + '((((type tty)) (:foreground "cyan3" :background "black" :weight bold)) + (((type pc)) (:foreground "White" :background "gray40")) + (((class color)) (:foreground "Black" :background "light grey")) + (t (:italic t :stipple "gray1")))) + "Face for highlighting odd-numbered non-current differences in buffer B." + :group 'ediff-highlighting) +;; An internal variable. Ediff takes the face from here. When unhighlighting, +;; this variable is set to nil, then again to the appropriate face. +(defvar ediff-odd-diff-face-B 'ediff-odd-diff-B + "Face for highlighting odd-numbered non-current differences in buffer B. +DO NOT CHANGE this variable. Instead, use the customization +widget to customize the actual face object `ediff-odd-diff-B' +this variable represents.") +(ediff-hide-face ediff-odd-diff-face-B) + +(defface ediff-odd-diff-C + (if (featurep 'emacs) + '((((type pc)) + (:foreground "yellow3" :background "gray40")) + (((class color) (min-colors 16)) + (:foreground "White" :background "Grey")) + (((class color)) + (:foreground "yellow3" :background "black" :weight bold)) + (t (:italic t :stipple "gray1"))) + '((((type tty)) (:foreground "yellow3" :background "black" :weight bold)) + (((type pc)) (:foreground "yellow3" :background "gray40")) + (((class color)) (:foreground "White" :background "Grey")) + (t (:italic t :stipple "gray1")))) + "Face for highlighting odd-numbered non-current differences in buffer C." + :group 'ediff-highlighting) +;; An internal variable. Ediff takes the face from here. When unhighlighting, +;; this variable is set to nil, then again to the appropriate face. +(defvar ediff-odd-diff-face-C 'ediff-odd-diff-C + "Face for highlighting odd-numbered non-current differences in buffer C. +DO NOT CHANGE this variable. Instead, use the customization +widget to customize the actual face object `ediff-odd-diff-C' +this variable represents.") +(ediff-hide-face ediff-odd-diff-face-C) + +(defface ediff-odd-diff-Ancestor + (if (featurep 'emacs) + '((((class color) (min-colors 16)) + (:foreground "cyan3" :background "gray40")) + (((class color)) + (:foreground "green3" :background "black" :weight bold)) + (t (:italic t :stipple "gray1"))) + '((((type tty)) (:foreground "green3" :background "black" :weight bold)) + (((class color)) (:foreground "cyan3" :background "gray40")) + (t (:italic t :stipple "gray1")))) + "Face for highlighting odd-numbered non-current differences in the ancestor buffer." + :group 'ediff-highlighting) +;; An internal variable. Ediff takes the face from here. When unhighlighting, +;; this variable is set to nil, then again to the appropriate face. +(defvar ediff-odd-diff-face-Ancestor 'ediff-odd-diff-Ancestor + "Face for highlighting odd-numbered non-current differences in buffer Ancestor. +DO NOT CHANGE this variable. Instead, use the customization +widget to customize the actual face object `ediff-odd-diff-Ancestor' +this variable represents.") +(ediff-hide-face ediff-odd-diff-face-Ancestor) + +;; Association between buffer types and odd-diff-face symbols +(defconst ediff-odd-diff-face-alist + '((A . ediff-odd-diff-A) + (B . ediff-odd-diff-B) + (C . ediff-odd-diff-C) + (Ancestor . ediff-odd-diff-Ancestor))) + +;; A-list of fine-diff face symbols associated with buffer types +(defconst ediff-fine-diff-face-alist + '((A . ediff-fine-diff-A) + (B . ediff-fine-diff-B) + (C . ediff-fine-diff-C) + (Ancestor . ediff-fine-diff-Ancestor))) + +;; Help echo +(put ediff-fine-diff-face-A 'ediff-help-echo + "A `refinement' of the current difference region") +(put ediff-fine-diff-face-B 'ediff-help-echo + "A `refinement' of the current difference region") +(put ediff-fine-diff-face-C 'ediff-help-echo + "A `refinement' of the current difference region") +(put ediff-fine-diff-face-Ancestor 'ediff-help-echo + "A `refinement' of the current difference region") + +(add-hook 'ediff-quit-hook 'ediff-cleanup-mess) +(add-hook 'ediff-suspend-hook 'ediff-default-suspend-function) + + +;;; Overlays + +(ediff-defvar-local ediff-current-diff-overlay-A nil + "Overlay for the current difference region in buffer A.") +(ediff-defvar-local ediff-current-diff-overlay-B nil + "Overlay for the current difference region in buffer B.") +(ediff-defvar-local ediff-current-diff-overlay-C nil + "Overlay for the current difference region in buffer C.") +(ediff-defvar-local ediff-current-diff-overlay-Ancestor nil + "Overlay for the current difference region in the ancestor buffer.") + +;; Compute priority of a current ediff overlay. +(defun ediff-highest-priority (start end buffer) + (let ((pos (max 1 (1- start))) + ovr-list) + (if (featurep 'xemacs) + (1+ ediff-shadow-overlay-priority) + (ediff-with-current-buffer buffer + (while (< pos (min (point-max) (1+ end))) + (setq ovr-list (append (overlays-at pos) ovr-list)) + (setq pos (next-overlay-change pos))) + (+ 1 ediff-shadow-overlay-priority + (apply 'max + (cons + 1 + (mapcar + (lambda (ovr) + (if (and ovr + ;; exclude ediff overlays from priority + ;; calculation, or else priority will keep + ;; increasing + (null (ediff-overlay-get ovr 'ediff)) + (null (ediff-overlay-get ovr 'ediff-diff-num))) + ;; use the overlay priority or 0 + (or (ediff-overlay-get ovr 'priority) 0) + 0)) + ovr-list)))))))) + + +(defvar ediff-toggle-read-only-function nil + "*Specifies the function to be used to toggle read-only. +If nil, Ediff tries to deduce the function from the binding of C-x C-q. +Normally, this is the `toggle-read-only' function, but, if version +control is used, it could be `vc-toggle-read-only' or `rcs-toggle-read-only'.") + +(defcustom ediff-make-buffers-readonly-at-startup nil + "Make all variant buffers read-only when Ediff starts up. +This property can be toggled interactively." + :type 'boolean + :group 'ediff) + + +;;; Misc + +;; if nil, this silences some messages +(defvar ediff-verbose-p t) + +(defcustom ediff-autostore-merges 'group-jobs-only + "Save the results of merge jobs automatically. +With value nil, don't save automatically. With value t, always +save. Anything else means save automatically only if the merge +job is part of a group of jobs, such as `ediff-merge-directory' +or `ediff-merge-directory-revisions'." + :type '(choice (const nil) (const t) (const group-jobs-only)) + :group 'ediff-merge) +(make-variable-buffer-local 'ediff-autostore-merges) + +;; file where the result of the merge is to be saved. used internally +(ediff-defvar-local ediff-merge-store-file nil "") + +(defcustom ediff-merge-filename-prefix "merge_" + "Prefix to be attached to saved merge buffers." + :type 'string + :group 'ediff-merge) + +(defcustom ediff-no-emacs-help-in-control-buffer nil + "Non-nil means C-h should not invoke Emacs help in control buffer. +Instead, C-h would jump to previous difference." + :type 'boolean + :group 'ediff) + +;; This is the same as temporary-file-directory from Emacs 20.3. +;; Copied over here because XEmacs doesn't have this variable. +(defcustom ediff-temp-file-prefix + (file-name-as-directory + (cond ((boundp 'temporary-file-directory) temporary-file-directory) + ((fboundp 'temp-directory) (temp-directory)) + (t "/tmp/"))) +;;; (file-name-as-directory +;;; (cond ((memq system-type '(ms-dos windows-nt)) +;;; (or (getenv "TEMP") (getenv "TMPDIR") (getenv "TMP") "c:/temp")) +;;; (t +;;; (or (getenv "TMPDIR") (getenv "TMP") (getenv "TEMP") "/tmp")))) + "Prefix to put on Ediff temporary file names. +Do not start with `~/' or `~USERNAME/'." + :type 'string + :group 'ediff) + +(defcustom ediff-temp-file-mode 384 ; u=rw only + "Mode for Ediff temporary files." + :type 'integer + :group 'ediff) + +;; Metacharacters that have to be protected from the shell when executing +;; a diff/diff3 command. +(defcustom ediff-metachars "[ \t\n!\"#$&'()*;<=>?[\\^`{|~]" + "Regexp that matches characters that must be quoted with `\\' in shell command line. +This default should work without changes." + :type 'string + :group 'ediff) + +;; needed to simulate frame-char-width in XEmacs. +(defvar ediff-H-glyph (if (featurep 'xemacs) (make-glyph "H"))) + + +;; Temporary file used for refining difference regions in buffer A. +(ediff-defvar-local ediff-temp-file-A nil "") +;; Temporary file used for refining difference regions in buffer B. +(ediff-defvar-local ediff-temp-file-B nil "") +;; Temporary file used for refining difference regions in buffer C. +(ediff-defvar-local ediff-temp-file-C nil "") + + +(defun ediff-file-remote-p (file-name) + (file-remote-p file-name)) + +;; File for which we can get attributes, such as size or date +(defun ediff-listable-file (file-name) + (let ((handler (find-file-name-handler file-name 'file-local-copy))) + (or (null handler) (eq handler 'dired-handler-fn)))) + + +(defsubst ediff-frame-unsplittable-p (frame) + (cdr (assq 'unsplittable (frame-parameters frame)))) + +(defsubst ediff-get-next-window (wind prev-wind) + (cond ((window-live-p wind) wind) + (prev-wind (next-window wind)) + (t (selected-window)) + )) + + +(defsubst ediff-kill-buffer-carefully (buf) + "Kill buffer BUF if it exists." + (if (ediff-buffer-live-p buf) + (kill-buffer (get-buffer buf)))) + +(defsubst ediff-background-face (buf-type dif-num) + ;; The value of dif-num is always 1- the one that user sees. + ;; This is why even face is used when dif-num is odd. + (ediff-get-symbol-from-alist + buf-type (if (ediff-odd-p dif-num) + ediff-even-diff-face-alist + ediff-odd-diff-face-alist) + )) + + +;; activate faces on diff regions in buffer +(defun ediff-paint-background-regions-in-one-buffer (buf-type unhighlight) + (let ((diff-vector + (eval (ediff-get-symbol-from-alist + buf-type ediff-difference-vector-alist))) + overl diff-num) + (mapcar (lambda (rec) + (setq overl (ediff-get-diff-overlay-from-diff-record rec) + diff-num (ediff-overlay-get overl 'ediff-diff-num)) + (if (ediff-overlay-buffer overl) + ;; only if overlay is alive + (ediff-set-overlay-face + overl + (if (not unhighlight) + (ediff-background-face buf-type diff-num)))) + ) + diff-vector))) + + +;; activate faces on diff regions in all buffers +(defun ediff-paint-background-regions (&optional unhighlight) + (ediff-paint-background-regions-in-one-buffer + 'A unhighlight) + (ediff-paint-background-regions-in-one-buffer + 'B unhighlight) + (ediff-paint-background-regions-in-one-buffer + 'C unhighlight) + (ediff-paint-background-regions-in-one-buffer + 'Ancestor unhighlight)) + + +;; arg is a record for a given diff in a difference vector +;; this record is itself a vector +(defsubst ediff-clear-fine-diff-vector (diff-record) + (if diff-record + (mapc 'ediff-delete-overlay + (ediff-get-fine-diff-vector-from-diff-record diff-record)))) + +(defsubst ediff-clear-fine-differences-in-one-buffer (n buf-type) + (ediff-clear-fine-diff-vector (ediff-get-difference n buf-type)) + (ediff-set-fine-diff-vector n buf-type nil)) + +(defsubst ediff-clear-fine-differences (n) + (ediff-clear-fine-differences-in-one-buffer n 'A) + (ediff-clear-fine-differences-in-one-buffer n 'B) + (if ediff-3way-job + (ediff-clear-fine-differences-in-one-buffer n 'C))) + + +(defsubst ediff-mouse-event-p (event) + (if (featurep 'xemacs) + (button-event-p event) + (string-match "mouse" (format "%S" (event-basic-type event))))) + + +(defsubst ediff-key-press-event-p (event) + (if (featurep 'xemacs) + (key-press-event-p event) + (or (char-or-string-p event) (symbolp event)))) + +(defun ediff-event-point (event) + (cond ((ediff-mouse-event-p event) + (if (featurep 'xemacs) + (event-point event) + (posn-point (event-start event)))) + ((ediff-key-press-event-p event) + (point)) + (t (error "Error")))) + +(defun ediff-event-buffer (event) + (cond ((ediff-mouse-event-p event) + (if (featurep 'xemacs) + (event-buffer event) + (window-buffer (posn-window (event-start event))))) + ((ediff-key-press-event-p event) + (current-buffer)) + (t (error "Error")))) + +(defun ediff-event-key (event-or-key) + (if (featurep 'xemacs) + ;;(if (eventp event-or-key) (event-key event-or-key) event-or-key) + (if (eventp event-or-key) (event-to-character event-or-key t t) event-or-key) + event-or-key)) + +(defun ediff-last-command-char () + (ediff-event-key last-command-event)) + + +(defsubst ediff-frame-iconified-p (frame) + (and (ediff-window-display-p) (frame-live-p frame) + (if (featurep 'xemacs) + (frame-iconified-p frame) + (eq (frame-visible-p frame) 'icon)))) + +(defsubst ediff-window-visible-p (wind) + ;; under TTY, window-live-p also means window is visible + (and (window-live-p wind) + (or (not (ediff-window-display-p)) + (frame-visible-p (window-frame wind))))) + + +(defsubst ediff-frame-char-width (frame) + (if (featurep 'xemacs) + (/ (frame-pixel-width frame) (frame-width frame)) + (frame-char-width frame))) + +(defun ediff-reset-mouse (&optional frame do-not-grab-mouse) + (or frame (setq frame (selected-frame))) + (if (ediff-window-display-p) + (let ((frame-or-wind frame)) + (if (featurep 'xemacs) + (setq frame-or-wind (frame-selected-window frame))) + (or do-not-grab-mouse + ;; don't set mouse if the user said to never do this + (not ediff-grab-mouse) + ;; Don't grab on quit, if the user doesn't want to. + ;; If ediff-grab-mouse = t, then mouse won't be grabbed for + ;; sessions that are not part of a group (this is done in + ;; ediff-recenter). The condition below affects only terminating + ;; sessions in session groups (in which case mouse is warped into + ;; a meta buffer). + (and (eq ediff-grab-mouse 'maybe) + (memq this-command '(ediff-quit ediff-update-diffs))) + (set-mouse-position frame-or-wind 1 0)) + ))) + +(defsubst ediff-spy-after-mouse () + (setq ediff-mouse-pixel-position (mouse-pixel-position))) + +;; It is not easy to find out when the user grabs the mouse, since emacs and +;; xemacs behave differently when mouse is not in any frame. Also, this is +;; sensitive to when the user grabbed mouse. Not used for now. +(defun ediff-user-grabbed-mouse () + (if ediff-mouse-pixel-position + (cond ((not (eq (car ediff-mouse-pixel-position) + (car (mouse-pixel-position))))) + ((and (car (cdr ediff-mouse-pixel-position)) + (car (cdr (mouse-pixel-position))) + (cdr (cdr ediff-mouse-pixel-position)) + (cdr (cdr (mouse-pixel-position)))) + (not (and (< (abs (- (car (cdr ediff-mouse-pixel-position)) + (car (cdr (mouse-pixel-position))))) + ediff-mouse-pixel-threshold) + (< (abs (- (cdr (cdr ediff-mouse-pixel-position)) + (cdr (cdr (mouse-pixel-position))))) + ediff-mouse-pixel-threshold)))) + (t nil)))) + +(defsubst ediff-frame-char-height (frame) + (if (featurep 'xemacs) + (glyph-height ediff-H-glyph (frame-selected-window frame)) + (frame-char-height frame))) + +;; Some overlay functions + +(defsubst ediff-overlay-start (overl) + (if (ediff-overlayp overl) + (if (featurep 'xemacs) + (extent-start-position overl) + (overlay-start overl)))) + +(defsubst ediff-overlay-end (overl) + (if (ediff-overlayp overl) + (if (featurep 'xemacs) + (extent-end-position overl) + (overlay-end overl)))) + +(defsubst ediff-empty-overlay-p (overl) + (= (ediff-overlay-start overl) (ediff-overlay-end overl))) + +;; like overlay-buffer in Emacs. In XEmacs, returns nil if the extent is +;; dead. Otherwise, works like extent-buffer +(defun ediff-overlay-buffer (overl) + (if (featurep 'xemacs) + (and (extent-live-p overl) (extent-object overl)) + (overlay-buffer overl))) + +;; like overlay-get in Emacs. In XEmacs, returns nil if the extent is +;; dead. Otherwise, like extent-property +(defun ediff-overlay-get (overl property) + (if (featurep 'xemacs) + (and (extent-live-p overl) (extent-property overl property)) + (overlay-get overl property))) + + +;; These two functions are here because XEmacs refuses to +;; handle overlays whose buffers were deleted. +(defun ediff-move-overlay (overlay beg end &optional buffer) + "Calls `move-overlay' in Emacs and `set-extent-endpoints' in Lemacs. +Checks if overlay's buffer exists before actually doing the move." + (let ((buf (and overlay (ediff-overlay-buffer overlay)))) + (if (ediff-buffer-live-p buf) + (if (featurep 'xemacs) + (set-extent-endpoints overlay beg end) + (move-overlay overlay beg end buffer)) + ;; buffer's dead + (if overlay + (ediff-delete-overlay overlay))))) + +(defun ediff-overlay-put (overlay prop value) + "Calls `overlay-put' or `set-extent-property' depending on Emacs version. +Checks if overlay's buffer exists." + (if (ediff-buffer-live-p (ediff-overlay-buffer overlay)) + (if (featurep 'xemacs) + (set-extent-property overlay prop value) + (overlay-put overlay prop value)) + (ediff-delete-overlay overlay))) + +;; temporarily uses DIR to abbreviate file name +;; if DIR is nil, use default-directory +(defun ediff-abbreviate-file-name (file &optional dir) + (cond ((stringp dir) + (let ((directory-abbrev-alist (list (cons dir "")))) + (abbreviate-file-name file))) + (t + (if (featurep 'xemacs) + ;; XEmacs requires addl argument + (abbreviate-file-name file t) + (abbreviate-file-name file))))) + +;; Takes a directory and returns the parent directory. +;; does nothing to `/'. If the ARG is a regular file, +;; strip the file AND the last dir. +(defun ediff-strip-last-dir (dir) + (if (not (stringp dir)) (setq dir default-directory)) + (setq dir (expand-file-name dir)) + (or (file-directory-p dir) (setq dir (file-name-directory dir))) + (let* ((pos (1- (length dir))) + (last-char (aref dir pos))) + (if (and (> pos 0) (= last-char ?/)) + (setq dir (substring dir 0 pos))) + (ediff-abbreviate-file-name (file-name-directory dir)))) + +(defun ediff-truncate-string-left (str newlen) + ;; leave space for ... on the left + (let ((len (length str)) + substr) + (if (<= len newlen) + str + (setq newlen (max 0 (- newlen 3))) + (setq substr (substring str (max 0 (- len 1 newlen)))) + (concat "..." substr)))) + +(defsubst ediff-nonempty-string-p (string) + (and (stringp string) (not (string= string "")))) + +(unless (fboundp 'subst-char-in-string) + (defun subst-char-in-string (fromchar tochar string &optional inplace) + "Replace FROMCHAR with TOCHAR in STRING each time it occurs. +Unless optional argument INPLACE is non-nil, return a new string." + (let ((i (length string)) + (newstr (if inplace string (copy-sequence string)))) + (while (> i 0) + (setq i (1- i)) + (if (eq (aref newstr i) fromchar) + (aset newstr i tochar))) + newstr))) + +(defun ediff-abbrev-jobname (jobname) + (cond ((eq jobname 'ediff-directories) + "Compare two directories") + ((eq jobname 'ediff-files) + "Compare two files") + ((eq jobname 'ediff-buffers) + "Compare two buffers") + ((eq jobname 'ediff-directories3) + "Compare three directories") + ((eq jobname 'ediff-files3) + "Compare three files") + ((eq jobname 'ediff-buffers3) + "Compare three buffers") + ((eq jobname 'ediff-revision) + "Compare file with a version") + ((eq jobname 'ediff-directory-revisions) + "Compare dir files with versions") + ((eq jobname 'ediff-merge-directory-revisions) + "Merge dir files with versions") + ((eq jobname 'ediff-merge-directory-revisions-with-ancestor) + "Merge dir versions via ancestors") + (t + (capitalize + (subst-char-in-string ?- ?\s (substring (symbol-name jobname) 6)))) + )) + + +;; If ediff modified mode line, strip the modification +(defsubst ediff-strip-mode-line-format () + (if (member (car mode-line-format) '(" A: " " B: " " C: " " Ancestor: ")) + (setq mode-line-format (nth 2 mode-line-format)))) + +;; Verify that we have a difference selected. +(defsubst ediff-valid-difference-p (&optional n) + (or n (setq n ediff-current-difference)) + (and (>= n 0) (< n ediff-number-of-differences))) + +(defsubst ediff-show-all-diffs (n) + "Don't skip difference regions." + nil) + +(defsubst ediff-message-if-verbose (string &rest args) + (if ediff-verbose-p + (apply 'message string args))) + +(defun ediff-file-attributes (filename attr-number) + (if (ediff-listable-file filename) + (nth attr-number (file-attributes filename)) + -1) + ) + +(defsubst ediff-file-size (filename) + (ediff-file-attributes filename 7)) +(defsubst ediff-file-modtime (filename) + (ediff-file-attributes filename 5)) + + +(defun ediff-convert-standard-filename (fname) + (if (fboundp 'convert-standard-filename) + (convert-standard-filename fname) + fname)) + +(if (featurep 'emacs) + (defalias 'ediff-with-syntax-table 'with-syntax-table) + (if (fboundp 'with-syntax-table) + (defalias 'ediff-with-syntax-table 'with-syntax-table) + ;; stolen from subr.el in emacs 21 + (defmacro ediff-with-syntax-table (table &rest body) + (let ((old-table (make-symbol "table")) + (old-buffer (make-symbol "buffer"))) + `(let ((,old-table (syntax-table)) + (,old-buffer (current-buffer))) + (unwind-protect + (progn + (set-syntax-table (copy-syntax-table ,table)) + ,@body) + (save-current-buffer + (set-buffer ,old-buffer) + (set-syntax-table ,old-table)))))))) + + +(provide 'ediff-init) + + + +;; 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: + +;; arch-tag: fa31d384-1e70-4d4b-82a7-3e96307c46f5 +;;; ediff-init.el ends here diff -r 681cd08dc0f7 -r e93288477c43 lisp/vc/ediff-merg.el --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/lisp/vc/ediff-merg.el Sun Jun 13 22:57:55 2010 +0000 @@ -0,0 +1,397 @@ +;;; ediff-merg.el --- merging utilities + +;; Copyright (C) 1994, 1995, 1996, 1997, 1998, 1999, 2000, 2001, 2002, +;; 2003, 2004, 2005, 2006, 2007, 2008, 2009, 2010 Free Software Foundation, Inc. + +;; Author: Michael Kifer + +;; 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 3 of the License, 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. If not, see . + +;;; Commentary: + +;;; Code: + + +;; compiler pacifier +(defvar ediff-window-A) +(defvar ediff-window-B) +(defvar ediff-window-C) +(defvar ediff-merge-window-share) +(defvar ediff-window-config-saved) + +(eval-when-compile + (require 'ediff-util)) +;; end pacifier + +(require 'ediff-init) + +(defcustom ediff-quit-merge-hook 'ediff-maybe-save-and-delete-merge + "Hooks to run before quitting a merge job. +The most common use is to save and delete the merge buffer." + :type 'hook + :group 'ediff-merge) + + +(defcustom ediff-default-variant 'combined + "The variant to be used as a default for buffer C in merging. +Valid values are the symbols `default-A', `default-B', and `combined'." + :type '(radio (const default-A) (const default-B) (const combined)) + :group 'ediff-merge) + +(defcustom ediff-combination-pattern + '("<<<<<<< variant A" A ">>>>>>> variant B" B "####### Ancestor" Ancestor "======= end") + "Pattern to be used for combining difference regions in buffers A and B. +The value must be a list of the form +\(STRING1 bufspec1 STRING2 bufspec2 STRING3 bufspec3 STRING4) +where bufspec is the symbol A, B, or Ancestor. For instance, if the value is +'(STRING1 A STRING2 Ancestor STRING3 B STRING4) then the +combined text will look like this: + +STRING1 +diff region from variant A +STRING2 +diff region from the ancestor +STRING3 +diff region from variant B +STRING4 +" + :type '(choice (list string symbol string symbol string) + (list string symbol string symbol string symbol string)) + :group 'ediff-merge) + +(defcustom ediff-show-clashes-only nil + "If t, show only those diff regions where both buffers disagree with the ancestor. +This means that regions that have status prefer-A or prefer-B will be +skipped over. A value of nil means show all regions." + :type 'boolean + :group 'ediff-merge + ) +(make-variable-buffer-local 'ediff-show-clashes-only) + +(defcustom ediff-skip-merge-regions-that-differ-from-default nil + "If t, show only the regions that have not been changed by the user. +A region is considered to have been changed if it is different from the current +default (`default-A', `default-B', `combined') and it hasn't been marked as +`prefer-A' or `prefer-B'. +A region is considered to have been changed also when it is marked as +as `prefer-A', but is different from the corresponding difference region in +Buffer A or if it is marked as `prefer-B' and is different from the region in +Buffer B." + :type 'boolean + :group 'ediff-merge + ) +(make-variable-buffer-local 'ediff-skip-merge-regions-that-differ-from-default) + +;; check if there is no clash between the ancestor and one of the variants. +;; if it is not a merge job then return true +(defun ediff-merge-region-is-non-clash (n) + (if (ediff-merge-job) + (string-match "prefer" (or (ediff-get-state-of-merge n) "")) + t)) + +;; If ediff-show-clashes-only, check if there is no clash between the ancestor +;; and one of the variants. +(defun ediff-merge-region-is-non-clash-to-skip (n) + (and (ediff-merge-job) + ediff-show-clashes-only + (ediff-merge-region-is-non-clash n))) + +;; If ediff-skip-changed-regions, check if the merge region differs from +;; the current default. If a region is different from the default, it means +;; that the user has made determination as to how to merge for this particular +;; region. +(defun ediff-skip-merge-region-if-changed-from-default-p (n) + (and (ediff-merge-job) + ediff-skip-merge-regions-that-differ-from-default + (ediff-merge-changed-from-default-p n 'prefers-too))) + + +(defun ediff-get-combined-region (n) + (let ((pattern-list ediff-combination-pattern) + (combo-region "") + (err-msg + "ediff-combination-pattern: Invalid format. Please consult the documentation") + region-delim region-spec) + + (if (< (length pattern-list) 5) + (error err-msg)) + + (while (> (length pattern-list) 2) + (setq region-delim (nth 0 pattern-list) + region-spec (nth 1 pattern-list)) + (or (and (stringp region-delim) (memq region-spec '(A B Ancestor))) + (error err-msg)) + + (condition-case nil + (setq combo-region + (concat combo-region + region-delim "\n" + (ediff-get-region-contents + n region-spec ediff-control-buffer))) + (error "")) + (setq pattern-list (cdr (cdr pattern-list))) + ) + + (setq region-delim (nth 0 pattern-list)) + (or (stringp region-delim) + (error err-msg)) + (setq combo-region (concat combo-region region-delim "\n")) + )) + +;;(defsubst ediff-make-combined-diff (regA regB) +;; (concat (nth 0 ediff-combination-pattern) "\n" +;; regA +;; (nth 1 ediff-combination-pattern) "\n" +;; regB +;; (nth 2 ediff-combination-pattern) "\n")) + +(defsubst ediff-set-state-of-all-diffs-in-all-buffers (ctl-buf) + (let ((n 0)) + (while (< n ediff-number-of-differences) + (ediff-set-state-of-diff-in-all-buffers n ctl-buf) + (setq n (1+ n))))) + +(defun ediff-set-state-of-diff-in-all-buffers (n ctl-buf) + (let ((regA (ediff-get-region-contents n 'A ctl-buf)) + (regB (ediff-get-region-contents n 'B ctl-buf)) + (regC (ediff-get-region-contents n 'C ctl-buf))) + (cond ((and (string= regA regB) (string= regA regC)) + (ediff-set-state-of-diff n 'A "=diff(B)") + (ediff-set-state-of-diff n 'B "=diff(C)") + (ediff-set-state-of-diff n 'C "=diff(A)")) + ((string= regA regB) + (ediff-set-state-of-diff n 'A "=diff(B)") + (ediff-set-state-of-diff n 'B "=diff(A)") + (ediff-set-state-of-diff n 'C nil)) + ((string= regA regC) + (ediff-set-state-of-diff n 'A "=diff(C)") + (ediff-set-state-of-diff n 'C "=diff(A)") + (ediff-set-state-of-diff n 'B nil)) + ((string= regB regC) + (ediff-set-state-of-diff n 'C "=diff(B)") + (ediff-set-state-of-diff n 'B "=diff(C)") + (ediff-set-state-of-diff n 'A nil)) + ((string= regC (ediff-get-combined-region n)) + (ediff-set-state-of-diff n 'A nil) + (ediff-set-state-of-diff n 'B nil) + (ediff-set-state-of-diff n 'C "=diff(A+B)")) + (t (ediff-set-state-of-diff n 'A nil) + (ediff-set-state-of-diff n 'B nil) + (ediff-set-state-of-diff n 'C nil))) + )) + +(defun ediff-set-merge-mode () + (normal-mode t) + (remove-hook 'local-write-file-hooks 'ediff-set-merge-mode)) + + +;; Go over all diffs starting with DIFF-NUM and copy regions into buffer C +;; according to the state of the difference. +;; Since ediff-copy-diff refuses to copy identical diff regions, there is +;; no need to optimize ediff-do-merge any further. +;; +;; If re-merging, change state of merge in all diffs starting with +;; DIFF-NUM, except those where the state is prefer-* or where it is +;; `default-*' or `combined' but the buf C region appears to be modified +;; since last set by default. +(defun ediff-do-merge (diff-num &optional remerging) + (if (< diff-num 0) (setq diff-num 0)) + (let ((n diff-num) + ;;(default-state-of-merge (format "%S" ediff-default-variant)) + do-not-copy state-of-merge) + (while (< n ediff-number-of-differences) + (setq do-not-copy nil) ; reset after each cycle + (if (= (mod n 10) 0) + (message "%s buffers A & B into C ... region %d of %d" + (if remerging "Re-merging" "Merging") + n + ediff-number-of-differences)) + + (setq state-of-merge (ediff-get-state-of-merge n)) + + (if remerging + ;;(let ((reg-A (ediff-get-region-contents n 'A ediff-control-buffer)) + ;; (reg-B (ediff-get-region-contents n 'B ediff-control-buffer)) + ;; (reg-C (ediff-get-region-contents n 'C ediff-control-buffer))) + (progn + + ;; if region was edited since it was first set by default + (if (or (ediff-merge-changed-from-default-p n) + ;; was preferred + (string-match "prefer" state-of-merge)) + ;; then ignore + (setq do-not-copy t)) + + ;; change state of merge for this diff, if necessary + (if (and (string-match "\\(default\\|combined\\)" state-of-merge) + (not do-not-copy)) + (ediff-set-state-of-merge + n (format "%S" ediff-default-variant))) + )) + + ;; state-of-merge may have changed via ediff-set-state-of-merge, so + ;; check it once again + (setq state-of-merge (ediff-get-state-of-merge n)) + + (or do-not-copy + (if (string= state-of-merge "combined") + ;; use n+1 because ediff-combine-diffs works via user numbering + ;; of diffs, which is 1+ to what ediff uses internally + (ediff-combine-diffs (1+ n) 'batch) + (ediff-copy-diff + n (if (string-match "-A" state-of-merge) 'A 'B) 'C 'batch))) + (setq n (1+ n))) + (message "Merging buffers A & B into C ... Done") + )) + + +(defun ediff-re-merge () + "Remerge unmodified diff regions using a new default. Start with the current region." + (interactive) + (let* ((default-variant-alist + (list '("default-A") '("default-B") '("combined"))) + (actual-alist + (delete (list (symbol-name ediff-default-variant)) + default-variant-alist))) + (setq ediff-default-variant + (intern + (completing-read + (format "Current merge default is `%S'. New default: " + ediff-default-variant) + actual-alist nil 'must-match))) + (ediff-do-merge ediff-current-difference 'remerge) + (ediff-recenter) + )) + +(defun ediff-shrink-window-C (arg) + "Shrink window C to just one line. +With a prefix argument, returns window C to its normal size. +Used only for merging jobs." + (interactive "P") + (if (not ediff-merge-job) + (error "ediff-shrink-window-C can be used only for merging jobs")) + (cond ((eq arg '-) (setq arg -1)) + ((not (numberp arg)) (setq arg nil))) + (cond ((null arg) + (let ((ediff-merge-window-share + (if (< (window-height ediff-window-C) 3) + ediff-merge-window-share 0))) + (setq ediff-window-config-saved "") ; force redisplay + (ediff-recenter 'no-rehighlight))) + ((and (< arg 0) (> (window-height ediff-window-C) 2)) + (setq ediff-merge-window-share (* ediff-merge-window-share 0.9)) + (setq ediff-window-config-saved "") ; force redisplay + (ediff-recenter 'no-rehighlight)) + ((and (> arg 0) (> (window-height ediff-window-A) 2)) + (setq ediff-merge-window-share (* ediff-merge-window-share 1.1)) + (setq ediff-window-config-saved "") ; force redisplay + (ediff-recenter 'no-rehighlight)))) + + +;; N here is the user's region number. It is 1+ what Ediff uses internally. +(defun ediff-combine-diffs (n &optional batch-invocation) + "Combine Nth diff regions of buffers A and B and place the combination in C. +N is a prefix argument. If nil, combine the current difference regions. +Combining is done according to the specifications in variable +`ediff-combination-pattern'." + (interactive "P") + (setq n (if (numberp n) (1- n) ediff-current-difference)) + + (let (reg-combined) + ;;(setq regA (ediff-get-region-contents n 'A ediff-control-buffer) + ;; regB (ediff-get-region-contents n 'B ediff-control-buffer)) + ;;(setq reg-combined (ediff-make-combined-diff regA regB)) + (setq reg-combined (ediff-get-combined-region n)) + + (ediff-copy-diff n nil 'C batch-invocation reg-combined)) + (or batch-invocation (ediff-jump-to-difference (1+ n)))) + + +;; Checks if the region in buff C looks like a combination of the regions +;; in buffers A and B. Return a list (reg-a-beg reg-a-end reg-b-beg reg-b-end) +;; These refer to where the delimiters for region A, B, Ancestor start and end +;; in buffer C +(defun ediff-looks-like-combined-merge (region-num) + (if ediff-merge-job + (let ((combined (string-match (regexp-quote "(A+B)") + (or (ediff-get-state-of-diff region-num 'C) + ""))) + (mrgreg-beg (ediff-get-diff-posn 'C 'beg region-num)) + (mrgreg-end (ediff-get-diff-posn 'C 'end region-num)) + (pattern-list ediff-combination-pattern) + delim reg-beg reg-end delim-regs-list) + + (if combined + (ediff-with-current-buffer ediff-buffer-C + (while pattern-list + (goto-char mrgreg-beg) + (setq delim (nth 0 pattern-list)) + (search-forward delim mrgreg-end 'noerror) + (setq reg-beg (match-beginning 0)) + (setq reg-end (match-end 0)) + (if (and reg-beg reg-end) + (setq delim-regs-list + ;; in reverse + (cons reg-end (cons reg-beg delim-regs-list)))) + (if (> (length pattern-list) 1) + (setq pattern-list (cdr (cdr pattern-list))) + (setq pattern-list nil)) + ))) + + (reverse delim-regs-list) + ))) + +(defvar state-of-merge) ; dynamic var + +;; Check if the non-preferred merge has been modified since originally set. +;; This affects only the regions that are marked as default-A/B or combined. +;; If PREFERS-TOO is non-nil, then look at the regions marked as prefers-A/B as +;; well. +(defun ediff-merge-changed-from-default-p (diff-num &optional prefers-too) + (let ((reg-A (ediff-get-region-contents diff-num 'A ediff-control-buffer)) + (reg-B (ediff-get-region-contents diff-num 'B ediff-control-buffer)) + (reg-C (ediff-get-region-contents diff-num 'C ediff-control-buffer))) + + (setq state-of-merge (ediff-get-state-of-merge diff-num)) + + ;; if region was edited since it was first set by default + (or (and (string= state-of-merge "default-A") + (not (string= reg-A reg-C))) + (and (string= state-of-merge "default-B") + (not (string= reg-B reg-C))) + (and (string= state-of-merge "combined") + ;;(not (string= (ediff-make-combined-diff reg-A reg-B) reg-C))) + (not (string= (ediff-get-combined-region diff-num) reg-C))) + (and prefers-too + (string= state-of-merge "prefer-A") + (not (string= reg-A reg-C))) + (and prefers-too + (string= state-of-merge "prefer-B") + (not (string= reg-B reg-C))) + ))) + + +(provide 'ediff-merg) + + +;; 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: + +;; arch-tag: 9b798cf9-02ba-487f-a62e-b63aa823dbfb +;;; ediff-merg.el ends here diff -r 681cd08dc0f7 -r e93288477c43 lisp/vc/ediff-mult.el --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/lisp/vc/ediff-mult.el Sun Jun 13 22:57:55 2010 +0000 @@ -0,0 +1,2476 @@ +;;; ediff-mult.el --- support for multi-file/multi-buffer processing in Ediff + +;; Copyright (C) 1995, 1996, 1997, 1998, 1999, 2000, 2001, 2002, +;; 2003, 2004, 2005, 2006, 2007, 2008, 2009, 2010 Free Software Foundation, Inc. + +;; Author: Michael Kifer + +;; 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 3 of the License, 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. If not, see . + +;;; 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 a 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) + +(defgroup ediff-mult nil + "Multi-file and multi-buffer processing in Ediff." + :prefix "ediff-" + :group 'ediff) + + +;; compiler pacifier +(eval-when-compile + (require 'ediff-ptch) + (require 'ediff)) +;; end pacifier + +(require 'ediff-init) + +;; meta-buffer +(ediff-defvar-local ediff-meta-buffer nil "") +(ediff-defvar-local ediff-parent-meta-buffer nil "") +;; the registry buffer +(defvar ediff-registry-buffer nil) + +(defconst ediff-meta-buffer-brief-message "Ediff Session Group Panel: %s + + Type ? to show useful commands in this buffer + +") + +(defconst ediff-meta-buffer-verbose-message "Ediff Session Group Panel: %s + +Useful commands (type ? to hide them and free up screen): + 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-local ediff-meta-buffer-map nil + "The keymap for the meta buffer.") +(defvar ediff-dir-diffs-buffer-map (make-sparse-keymap) + "The keymap to be installed in the buffer showing differences between +directories.") + +;; 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-local ediff-meta-action-function nil "") +;; Tells ediff-update-meta-buffer how to redraw it +(ediff-defvar-local ediff-meta-redraw-function nil "") +;; Tells ediff-filegroup-action and similar procedures how to invoke Ediff for +;; the sessions in a given session group +(ediff-defvar-local ediff-session-action-function nil "") + +(ediff-defvar-local ediff-metajob-name nil "") + +;; buffer used to collect custom diffs from individual sessions in the group +(ediff-defvar-local ediff-meta-diff-buffer nil "") + +;; t means recurse into subdirs when deciding which files have same contents +(ediff-defvar-local ediff-recurse-to-subdirectories nil "") + +;; history var to use for filtering groups of files +(defvar ediff-filtering-regexp-history nil "") + +(defcustom ediff-default-filtering-regexp nil + "The default regular expression used as a filename filter in multifile comparisons. +Should be a sexp. For instance (car ediff-filtering-regexp-history) or nil." + :type 'sexp + :group 'ediff-mult) + +;; 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-local ediff-meta-list nil "") + +(ediff-defvar-local ediff-meta-session-number nil "") + + +;; the difference list between directories in a directory session group +(ediff-defvar-local ediff-dir-difference-list nil "") +(ediff-defvar-local ediff-dir-diffs-buffer nil "") + +;; The registry of Ediff sessions. A list of control buffers. +(defvar ediff-session-registry nil) + +(defcustom ediff-meta-truncate-filenames t + "If non-nil, truncate long file names in the session group buffers. +This can be toggled with `ediff-toggle-filename-truncation'." + :type 'boolean + :group 'ediff-mult) + +(defcustom ediff-meta-mode-hook nil + "Hooks run just after setting up meta mode." + :type 'hook + :group 'ediff-mult) + +(defcustom ediff-registry-setup-hook nil + "Hooks run just after the registry control panel is set up." + :type 'hook + :group 'ediff-mult) + +(defcustom ediff-before-session-group-setup-hooks nil + "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 restored +on `ediff-quit', `ediff-suspend', or `ediff-quit-session-group-hook'." + :type 'hook + :group 'ediff-hook) +(defcustom ediff-after-session-group-setup-hook nil + "Hooks run just after a meta-buffer controlling a session group, such as +ediff-directories, is run." + :type 'hook + :group 'ediff-mult) +(defcustom ediff-quit-session-group-hook nil + "Hooks run just before exiting a session group." + :type 'hook + :group 'ediff-mult) +(defcustom ediff-show-registry-hook nil + "Hooks run just after the registry buffer is shown." + :type 'hook + :group 'ediff-mult) +(defcustom ediff-show-session-group-hook '(delete-other-windows) + "Hooks run just after a session group buffer is shown." + :type 'hook + :group 'ediff-mult) +(defcustom ediff-meta-buffer-keymap-setup-hook nil + "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 meta +buffers." + :type 'hook + :group 'ediff-mult) + +;; Buffer holding the multi-file patch. Local to the meta buffer +(ediff-defvar-local ediff-meta-patchbufer nil "") + +;;; 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 +(defsubst ediff-get-group-buffer (meta-list) + (nth 0 (car meta-list))) + +(defsubst ediff-get-group-regexp (meta-list) + (nth 1 (car meta-list))) +;; group objects +(defsubst ediff-get-group-objA (meta-list) + (nth 2 (car meta-list))) +(defsubst ediff-get-group-objB (meta-list) + (nth 3 (car meta-list))) +(defsubst ediff-get-group-objC (meta-list) + (nth 4 (car meta-list))) +(defsubst ediff-get-group-merge-autostore-dir (meta-list) + (nth 5 (car meta-list))) +(defsubst ediff-get-group-comparison-func (meta-list) + (nth 6 (car meta-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 +(defsubst ediff-get-session-buffer (elt) + (nth 0 elt)) +(defsubst ediff-get-session-status (elt) + (nth 1 elt)) +(defsubst ediff-set-session-status (session-info new-status) + (setcar (cdr session-info) new-status)) +;; session objects +(defsubst ediff-get-session-objA (elt) + (nth 2 elt)) +(defsubst ediff-get-session-objB (elt) + (nth 3 elt)) +(defsubst ediff-get-session-objC (elt) + (nth 4 elt)) +;; Take the "name" component of the object into acount. ObjA/C/B is of the form +;; (name . equality-indicator) +(defsubst ediff-get-session-objA-name (elt) + (car (nth 2 elt))) +(defsubst ediff-get-session-objB-name (elt) + (car (nth 3 elt))) +(defsubst ediff-get-session-objC-name (elt) + (car (nth 4 elt))) +;; equality indicators +(defsubst ediff-get-file-eqstatus (elt) + (nth 1 elt)) +(defsubst ediff-set-file-eqstatus (elt value) + (setcar (cdr elt) 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 `='. +;; Currently, the eq-indicator is used only for 2 and 3-file jobs. +(defun ediff-make-new-meta-list-element (obj1 obj2 obj3) + (list nil nil (list obj1 nil) (list obj2 nil) (list obj3 nil))) + +;; 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. +(defun ediff-make-new-meta-list-header (regexp + objA objB objC + merge-auto-store-dir + comparison-func) + (list regexp objA objB objC merge-auto-store-dir comparison-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 +(defun ediff-get-session-activity-marker (session) + (let ((session-buf (ediff-get-session-buffer session))) + (cond ((null session-buf) nil) ; virgin session + ((ediff-buffer-live-p session-buf) ?+) ;active session + (t ?-)))) + +;; checks if the session is a meta session +(defun ediff-meta-session-p (session-info) + (and (stringp (ediff-get-session-objA-name session-info)) + (file-directory-p (ediff-get-session-objA-name session-info)) + (stringp (ediff-get-session-objB-name session-info)) + (file-directory-p (ediff-get-session-objB-name session-info)) + (if (stringp (ediff-get-session-objC-name session-info)) + (file-directory-p (ediff-get-session-objC-name session-info)) t))) + + +(ediff-defvar-local ediff-verbose-help-enabled nil + "If t, display redundant help in ediff-directories and other meta buffers. +Toggled by ediff-toggle-verbose-help-meta-buffer" ) + +;; Toggle verbose help in meta-buffers +;; TODO: Someone who understands all this can make it better. +(defun ediff-toggle-verbose-help-meta-buffer () + "Toggle showing tediously verbose help in meta buffers." + (interactive) + (setq ediff-verbose-help-enabled (not ediff-verbose-help-enabled)) + (ediff-update-meta-buffer (current-buffer) 'must-redraw)) + +;; set up the keymap in the meta buffer +(defun ediff-setup-meta-map () + (setq ediff-meta-buffer-map (make-sparse-keymap)) + (suppress-keymap ediff-meta-buffer-map) + (define-key ediff-meta-buffer-map "?" 'ediff-toggle-verbose-help-meta-buffer) + (define-key ediff-meta-buffer-map "q" 'ediff-quit-meta-buffer) + (define-key ediff-meta-buffer-map "T" 'ediff-toggle-filename-truncation) + (define-key ediff-meta-buffer-map "R" 'ediff-show-registry) + (define-key ediff-meta-buffer-map "E" 'ediff-documentation) + (define-key ediff-meta-buffer-map "v" ediff-meta-action-function) + (define-key ediff-meta-buffer-map "\C-m" ediff-meta-action-function) + (define-key ediff-meta-buffer-map " " 'ediff-next-meta-item) + (define-key ediff-meta-buffer-map "n" 'ediff-next-meta-item) + (define-key ediff-meta-buffer-map "\C-?" 'ediff-previous-meta-item) + (define-key ediff-meta-buffer-map "p" 'ediff-previous-meta-item) + (define-key ediff-meta-buffer-map [delete] 'ediff-previous-meta-item) + (define-key ediff-meta-buffer-map [backspace] 'ediff-previous-meta-item) + + (let ((menu-map (make-sparse-keymap "Ediff-Meta"))) + (define-key ediff-meta-buffer-map [menu-bar ediff-meta-mode] + (cons "Ediff-Meta" menu-map)) + (define-key menu-map [ediff-quit-meta-buffer] + '(menu-item "Quit" ediff-quit-meta-buffer + :help "Quit the meta buffer")) + (define-key menu-map [ediff-toggle-filename-truncation] + '(menu-item "Truncate filenames" ediff-toggle-filename-truncation + :help "Toggle truncation of long file names in session group buffers" + :button (:toggle . ediff-meta-truncate-filenames))) + (define-key menu-map [ediff-show-registry] + '(menu-item "Display Ediff Registry" ediff-show-registry + :help "Display Ediff's registry")) + (define-key menu-map [ediff-documentation] + '(menu-item "Show Manual" ediff-documentation + :help "Display Ediff's manual")) + + (or (ediff-one-filegroup-metajob) + (progn + (define-key ediff-meta-buffer-map "=" nil) + (define-key ediff-meta-buffer-map "==" 'ediff-meta-mark-equal-files) + (define-key ediff-meta-buffer-map "=m" 'ediff-meta-mark-equal-files) + (define-key ediff-meta-buffer-map "=h" 'ediff-meta-mark-equal-files))) + + + (define-key menu-map [ediff-next-meta-item] + '(menu-item "Next" ediff-next-meta-item + :help "Move to the next item in Ediff registry or session group buffer")) + (define-key menu-map [ediff-previous-meta-item] + '(menu-item "Previous" ediff-previous-meta-item + :help "Move to the previous item in Ediff registry or session group buffer"))) + + + (if ediff-no-emacs-help-in-control-buffer + (define-key ediff-meta-buffer-map "\C-h" 'ediff-previous-meta-item)) + (if (featurep 'emacs) + (define-key ediff-meta-buffer-map [mouse-2] ediff-meta-action-function) + (define-key ediff-meta-buffer-map [button2] ediff-meta-action-function)) + + (use-local-map ediff-meta-buffer-map) + ;; modify ediff-meta-buffer-map here + (run-hooks 'ediff-meta-buffer-keymap-setup-hook)) + + +(defun ediff-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) + (setq major-mode 'ediff-meta-mode) + (setq mode-name "MetaEdiff") + ;; don't use run-mode-hooks here! + (run-hooks 'ediff-meta-mode-hook)) + + +;; the keymap for the buffer showing directory differences +(suppress-keymap ediff-dir-diffs-buffer-map) +(define-key ediff-dir-diffs-buffer-map "q" 'ediff-bury-dir-diffs-buffer) +(define-key ediff-dir-diffs-buffer-map " " 'next-line) +(define-key ediff-dir-diffs-buffer-map "n" 'next-line) +(define-key ediff-dir-diffs-buffer-map "\C-?" 'previous-line) +(define-key ediff-dir-diffs-buffer-map "p" 'previous-line) +(define-key ediff-dir-diffs-buffer-map "C" 'ediff-dir-diff-copy-file) +(if (featurep 'emacs) + (define-key ediff-dir-diffs-buffer-map [mouse-2] 'ediff-dir-diff-copy-file) + (define-key ediff-dir-diffs-buffer-map [button2] 'ediff-dir-diff-copy-file)) +(define-key ediff-dir-diffs-buffer-map [delete] 'previous-line) +(define-key ediff-dir-diffs-buffer-map [backspace] 'previous-line) + +(defun ediff-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") + (or count (setq count 1)) + (let (overl) + (while (< 0 count) + (setq count (1- count)) + (ediff-next-meta-item1) + (setq overl (ediff-get-meta-overlay-at-pos (point))) + ;; skip invisible ones + (while (and overl (ediff-overlay-get overl 'invisible)) + (ediff-next-meta-item1) + (setq overl (ediff-get-meta-overlay-at-pos (point))))))) + +;; Move to the next meta item +(defun ediff-next-meta-item1 () + (let (pos) + (setq pos (ediff-next-meta-overlay-start (point))) + (if pos (goto-char pos)) + (if (eq ediff-metajob-name 'ediff-registry) + (if (and (ediff-get-meta-info (current-buffer) pos 'noerror) + (search-forward "*Ediff" nil t)) + (skip-chars-backward "a-zA-Z*")) + (if (> (skip-chars-forward "-+?H* \t0-9") 0) + (backward-char 1))))) + + +(defun ediff-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") + (or count (setq count 1)) + (let (overl) + (while (< 0 count) + (setq count (1- count)) + (ediff-previous-meta-item1) + (setq overl (ediff-get-meta-overlay-at-pos (point))) + ;; skip invisible ones + (while (and overl (ediff-overlay-get overl 'invisible)) + (ediff-previous-meta-item1) + (setq overl (ediff-get-meta-overlay-at-pos (point))))))) + +(defun ediff-previous-meta-item1 () + (let (pos) + (setq pos (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))) + + (if pos (goto-char pos)) + (if (eq ediff-metajob-name 'ediff-registry) + (if (and (ediff-get-meta-info (current-buffer) pos 'noerror) + (search-forward "*Ediff" nil t)) + (skip-chars-backward "a-zA-Z*")) + (if (> (skip-chars-forward "-+?H* \t0-9") 0) + (backward-char 1))) + )) + +(defsubst ediff-add-slash-if-directory (dir file) + (if (file-directory-p (concat dir file)) + (file-name-as-directory file) + file)) + +(defun ediff-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 default +behavior." + (interactive) + (setq ediff-meta-truncate-filenames (not ediff-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 +(defvar ediff-membership-code1 2) +(defvar ediff-membership-code2 3) +(defvar ediff-membership-code3 5) +(defvar ediff-product-of-memcodes (* ediff-membership-code1 + ediff-membership-code2 + ediff-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 nil or a filter regexp; only file names that match the regexp +;; are considered. +;; 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. +;; +(defun ediff-intersect-directories (jobname + regexp dir1 dir2 + &optional + dir3 merge-autostore-dir comparison-func) + (setq comparison-func (or comparison-func 'string=)) + (let (lis1 lis2 lis3 common auxdir1 auxdir2 auxdir3 common-part difflist) + + (setq auxdir1 (file-name-as-directory dir1) + lis1 (directory-files auxdir1 nil regexp) + lis1 (delete "." lis1) + lis1 (delete ".." lis1) + lis1 (mapcar + (lambda (elt) + (ediff-add-slash-if-directory auxdir1 elt)) + lis1) + auxdir2 (file-name-as-directory dir2) + lis2 (directory-files auxdir2 nil regexp) + lis2 (delete "." lis2) + lis2 (delete ".." lis2) + lis2 (mapcar + (lambda (elt) + (ediff-add-slash-if-directory auxdir2 elt)) + lis2)) + + (if (stringp dir3) + (setq auxdir3 (file-name-as-directory dir3) + lis3 (directory-files auxdir3 nil regexp) + lis3 (delete "." lis3) + lis3 (delete ".." lis3) + lis3 (mapcar + (lambda (elt) + (ediff-add-slash-if-directory auxdir3 elt)) + lis3))) + + (if (ediff-nonempty-string-p merge-autostore-dir) + (setq merge-autostore-dir + (file-name-as-directory merge-autostore-dir))) + (setq common (ediff-intersection lis1 lis2 comparison-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 (and lis3 (ediff-comparison-metajob3 jobname)) + (setq common (ediff-intersection common lis3 comparison-func))) + + ;; copying is needed because sort sorts via side effects + (setq common (sort (ediff-copy-list common) 'string-lessp)) + + ;; compute difference list + (setq difflist (ediff-set-difference + (ediff-union (ediff-union lis1 lis2 comparison-func) + lis3 + comparison-func) + common + comparison-func) + difflist (delete "." difflist) + ;; copying is needed because sort sorts via side effects + difflist (sort (ediff-copy-list (delete ".." difflist)) + 'string-lessp)) + + (setq difflist (mapcar (lambda (elt) (cons elt 1)) 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. + (mapc (lambda (elt) + (if (member (car elt) lis1) + (setcdr elt (* (cdr elt) ediff-membership-code1))) + (if (member (car elt) lis2) + (setcdr elt (* (cdr elt) ediff-membership-code2))) + (if (member (car elt) lis3) + (setcdr elt (* (cdr elt) ediff-membership-code3))) + ) + difflist) + (setq difflist (cons + ;; diff metalist header + (ediff-make-new-meta-list-header regexp + auxdir1 auxdir2 auxdir3 + merge-autostore-dir + comparison-func) + difflist)) + + (setq common-part + (cons + ;; metalist header + (ediff-make-new-meta-list-header regexp + auxdir1 auxdir2 auxdir3 + merge-autostore-dir + comparison-func) + (mapcar + (lambda (elt) + (ediff-make-new-meta-list-element + (expand-file-name (concat auxdir1 elt)) + (expand-file-name (concat auxdir2 elt)) + (if lis3 + (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. + (setq elt (ediff-add-slash-if-directory auxdir3 elt)) + (if (file-exists-p (concat auxdir3 elt)) + (expand-file-name (concat auxdir3 elt))))))) + common))) + ;; return result + (cons common-part difflist) + )) + +;; 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. +(defun ediff-get-directory-files-under-revision (jobname + regexp dir1 + &optional merge-autostore-dir) + (let (lis1 elt common auxdir1) + (setq auxdir1 (file-name-as-directory dir1) + lis1 (directory-files auxdir1 nil regexp)) + + (if (ediff-nonempty-string-p merge-autostore-dir) + (setq merge-autostore-dir + (file-name-as-directory merge-autostore-dir))) + + (while lis1 + (setq elt (car lis1) + lis1 (cdr lis1)) + ;; take files under revision control + (cond ((file-directory-p (concat auxdir1 elt)) + (setq common + (cons (ediff-add-slash-if-directory auxdir1 elt) common))) + ((and (featurep 'vc-hooks) (vc-backend (concat auxdir1 elt))) + (setq common (cons elt common))) + ;; The following two are needed only if vc-hooks isn't loaded. + ;; They won't recognize CVS files. + ((file-exists-p (concat auxdir1 elt ",v")) + (setq common (cons elt common))) + ((file-exists-p (concat auxdir1 "RCS/" elt ",v")) + (setq common (cons elt common))) + ) ; cond + ) ; while + + (setq common (delete "./" common) + common (delete "../" common) + common (delete "RCS" common) + common (delete "CVS" common) + ) + + ;; copying is needed because sort sorts via side effects + (setq common (sort (ediff-copy-list common) 'string-lessp)) + + ;; return result + (cons + ;; header -- has 6 elements. Meta buffer is prepended later by + ;; ediff-prepare-meta-buffer + (ediff-make-new-meta-list-header regexp + auxdir1 nil nil + merge-autostore-dir nil) + (mapcar (lambda (elt) (ediff-make-new-meta-list-element + (expand-file-name (concat auxdir1 elt)) nil nil)) + 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. +(defun ediff-prepare-meta-buffer (action-func meta-list + meta-buffer-name redraw-function + jobname &optional startup-hooks) + (let* ((meta-buffer-name + (ediff-unique-buffer-name meta-buffer-name "*")) + (meta-buffer (get-buffer-create meta-buffer-name))) + (ediff-with-current-buffer meta-buffer + + ;; comes first + (ediff-meta-mode) + + (setq ediff-meta-action-function action-func + ediff-meta-redraw-function redraw-function + ediff-metajob-name jobname + ediff-meta-buffer meta-buffer) + + ;; comes after ediff-meta-action-function is set + (ediff-setup-meta-map) + + (if (eq ediff-metajob-name 'ediff-registry) + (progn + (setq ediff-registry-buffer meta-buffer + ediff-meta-list meta-list) + ;; this func is used only from registry buffer, not from other + ;; meta-buffs. + (define-key + ediff-meta-buffer-map "M" 'ediff-show-meta-buff-from-registry)) + ;; Initialize the meta list -- we don't do this for registry. + (setq ediff-meta-list + ;; add meta-buffer to the list header + (cons (cons meta-buffer (car meta-list)) + (cdr meta-list)))) + + (or (eq meta-buffer ediff-registry-buffer) + (setq ediff-session-registry + (cons meta-buffer ediff-session-registry))) + + ;; redraw-function uses ediff-meta-list + (funcall redraw-function ediff-meta-list) + + ;; set read-only/non-modified + (setq buffer-read-only t) + (set-buffer-modified-p nil) + + (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 (eq action-func 'ediff-filegroup-action) + (progn + ;; put meta buffer in (car ediff-dir-difference-list) + (setq ediff-dir-difference-list + (cons (cons meta-buffer (car ediff-dir-difference-list)) + (cdr ediff-dir-difference-list))) + + (or (ediff-one-filegroup-metajob jobname) + (ediff-draw-dir-diffs ediff-dir-difference-list)) + (define-key + ediff-meta-buffer-map "h" 'ediff-mark-for-hiding-at-pos) + (define-key ediff-meta-buffer-map "x" 'ediff-hide-marked-sessions) + (define-key + ediff-meta-buffer-map "m" 'ediff-mark-for-operation-at-pos) + (define-key ediff-meta-buffer-map "u" nil) + (define-key + ediff-meta-buffer-map "um" 'ediff-unmark-all-for-operation) + (define-key + ediff-meta-buffer-map "uh" 'ediff-unmark-all-for-hiding) + + (define-key ediff-meta-buffer-map + [menu-bar ediff-meta-mode ediff-hide-marked-sessions] + '(menu-item "Hide marked" ediff-hide-marked-sessions + :help "Hide marked sessions. With prefix arg, unhide")) + + (define-key ediff-meta-buffer-map + [menu-bar ediff-meta-mode ediff-mark-for-hiding-at-pos] + '(menu-item "Mark for hiding" ediff-mark-for-hiding-at-pos + :help "Mark session for hiding. With prefix arg, unmark")) + + (define-key ediff-meta-buffer-map + [menu-bar ediff-meta-mode ediff-mark-for-operation-at-pos] + '(menu-item "Mark for group operation" ediff-mark-for-operation-at-pos + :help "Mark session for a group operation. With prefix arg, unmark")) + + (define-key ediff-meta-buffer-map + [menu-bar ediff-meta-mode ediff-unmark-all-for-hiding] + '(menu-item "Unmark all for hiding" ediff-unmark-all-for-hiding + :help "Unmark all sessions marked for hiding")) + + (define-key ediff-meta-buffer-map + [menu-bar ediff-meta-mode ediff-unmark-all-for-operation] + '(menu-item "Unmark all for group operation" ediff-unmark-all-for-operation + :help "Unmark all sessions marked for operation")) + + (cond ((ediff-collect-diffs-metajob jobname) + (define-key ediff-meta-buffer-map + [menu-bar ediff-meta-mode ediff-collect-custom-diffs] + '(menu-item "Collect diffs" ediff-collect-custom-diffs + :help "Collect custom diffs of marked sessions in buffer `*Ediff Multifile Diffs*'")) + (define-key + ediff-meta-buffer-map "P" 'ediff-collect-custom-diffs)) + ((ediff-patch-metajob jobname) + (define-key ediff-meta-buffer-map + [menu-bar ediff-meta-mode ediff-meta-show-patch] + '(menu-item "Show multi-file patch" ediff-meta-show-patch + :help "Show the multi-file patch associated with this group session")) + (define-key + ediff-meta-buffer-map "P" 'ediff-meta-show-patch))) + (define-key ediff-meta-buffer-map "^" 'ediff-up-meta-hierarchy) + (define-key ediff-meta-buffer-map "D" 'ediff-show-dir-diffs) + + (define-key ediff-meta-buffer-map + [menu-bar ediff-meta-mode ediff-up-meta-hierarchy] + '(menu-item "Go to parent session" ediff-up-meta-hierarchy + :help "Go to the parent session group buffer")) + + (define-key ediff-meta-buffer-map + [menu-bar ediff-meta-mode ediff-show-dir-diffs] + '(menu-item "Diff directories" ediff-show-dir-diffs + :help "Display differences among the directories involved in session group")))) + + (if (eq ediff-metajob-name 'ediff-registry) + (run-hooks 'ediff-registry-setup-hook) + (run-hooks 'ediff-after-session-group-setup-hook)) + ) ; eval in meta-buffer + meta-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) +(defun ediff-insert-session-activity-marker-in-meta-buffer (session) + (insert + (cond ((ediff-get-session-activity-marker session)) + ;; 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) +(defun ediff-insert-session-status-in-meta-buffer (session) + (insert + (cond ((ediff-get-session-status session)) ; 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 +(defun ediff-replace-session-activity-marker-in-meta-buffer (point new-marker) + (let* ((overl (ediff-get-meta-overlay-at-pos point)) + (session-info (ediff-overlay-get overl 'ediff-meta-info)) + (activity-marker (ediff-get-session-activity-marker session-info)) + buffer-read-only) + (or new-marker activity-marker (setq new-marker ?\s)) + (goto-char (ediff-overlay-start overl)) + (if (eq (char-after (point)) new-marker) + () ; if marker shown in buffer is the same as new-marker, do nothing + (insert new-marker) + (delete-char 1) + (set-buffer-modified-p nil)))) + +;; If NEW-STATUS is non-nil, use it to substitute the current status marker in +;; the meta buffer. If nil, use SPC +(defun ediff-replace-session-status-in-meta-buffer (point new-status) + (let* ((overl (ediff-get-meta-overlay-at-pos point)) + (session-info (ediff-overlay-get overl 'ediff-meta-info)) + (status (ediff-get-session-status session-info)) + buffer-read-only) + (setq new-status (or new-status status ?\s)) + (goto-char (ediff-overlay-start overl)) + (forward-char 1) ; 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 + (insert new-status) + (delete-char 1) + (set-buffer-modified-p nil)))) + +;; insert all file info in meta buffer for a given session +(defun ediff-insert-session-info-in-meta-buffer (session-info sessionNum) + (let ((f1 (ediff-get-session-objA session-info)) + (f2 (ediff-get-session-objB session-info)) + (f3 (ediff-get-session-objC session-info)) + (pt (point)) + (hidden (eq (ediff-get-session-status session-info) ?I))) + ;; insert activity marker, i.e., SPC, - or + + (ediff-insert-session-activity-marker-in-meta-buffer session-info) + ;; insert session status, i.e., *, H + (ediff-insert-session-status-in-meta-buffer session-info) + (insert " Session " (int-to-string sessionNum) ":\n") + (ediff-meta-insert-file-info1 f1) + (ediff-meta-insert-file-info1 f2) + (ediff-meta-insert-file-info1 f3) + (ediff-set-meta-overlay pt (point) session-info sessionNum hidden))) + + +;; this is a setup function for ediff-directories +;; must return meta-buffer +(defun ediff-redraw-directory-group-buffer (meta-list) + ;; extract directories + (let ((meta-buf (ediff-get-group-buffer meta-list)) + (empty t) + (sessionNum 0) + regexp elt merge-autostore-dir + point tmp-list buffer-read-only) + (ediff-with-current-buffer meta-buf + (setq point (point)) + (erase-buffer) + ;; delete phony overlays that used to represent sessions before the buff + ;; was redrawn + (if (featurep 'xemacs) + (map-extents 'delete-extent) + (mapc 'delete-overlay (overlays-in 1 1))) + + (setq regexp (ediff-get-group-regexp meta-list) + merge-autostore-dir + (ediff-get-group-merge-autostore-dir meta-list)) + + (if ediff-verbose-help-enabled + (progn + (insert (format ediff-meta-buffer-verbose-message + (ediff-abbrev-jobname ediff-metajob-name))) + + (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 sessions for hiding\n" + " =m:\tlike ==, but also marks sessions for operation\n\n"))) + (insert (format ediff-meta-buffer-brief-message + (ediff-abbrev-jobname ediff-metajob-name)))) + + (insert "\n") + (if (and (stringp regexp) (> (length regexp) 0)) + (insert + (format "*** Filter-through regular expression: %s\n" regexp))) + (ediff-insert-dirs-in-meta-buffer meta-list) + (if (and ediff-autostore-merges (ediff-merge-metajob) + (ediff-nonempty-string-p merge-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 + (setq meta-list (cdr meta-list) + tmp-list meta-list) + (while (and tmp-list empty) + (if (and (car tmp-list) + (not (eq (ediff-get-session-status (car tmp-list)) ?I))) + (setq empty nil)) + (setq tmp-list (cdr tmp-list))) + + (if empty + (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. + (while meta-list + (setq elt (car meta-list) + meta-list (cdr meta-list) + sessionNum (1+ sessionNum)) + (if (eq (ediff-get-session-status elt) ?I) + () + (ediff-insert-session-info-in-meta-buffer elt sessionNum))) + (set-buffer-modified-p nil) + (goto-char point) + meta-buf))) + +(defun ediff-update-markers-in-dir-meta-buffer (meta-list) + (let ((meta-buf (ediff-get-group-buffer meta-list)) + session-info point overl buffer-read-only) + (ediff-with-current-buffer meta-buf + (setq point (point)) + (goto-char (point-min)) + (ediff-next-meta-item1) + (while (not (bobp)) + (setq session-info (ediff-get-meta-info meta-buf (point) 'no-error) + overl (ediff-get-meta-overlay-at-pos (point))) + (if session-info + (progn + (cond ((eq (ediff-get-session-status session-info) ?I) + ;; Do hiding + (if overl (ediff-overlay-put overl 'invisible t))) + ((and (eq (ediff-get-session-status session-info) ?H) + overl (ediff-overlay-get overl 'invisible)) + ;; Do unhiding + (ediff-overlay-put overl 'invisible nil)) + (t (ediff-replace-session-activity-marker-in-meta-buffer + (point) + (ediff-get-session-activity-marker session-info)) + (ediff-replace-session-status-in-meta-buffer + (point) + (ediff-get-session-status session-info)))))) + (ediff-next-meta-item1) ; advance to the next item + ) ; end while + (set-buffer-modified-p nil) + (goto-char point)) + meta-buf)) + +(defun ediff-update-session-marker-in-dir-meta-buffer (session-num) + (let (buffer-meta-overlays session-info overl buffer-read-only) + (setq overl + (if (featurep 'xemacs) + (map-extents + (lambda (ext maparg) + (if (and + (ediff-overlay-get ext 'ediff-meta-info) + (eq (ediff-overlay-get ext 'ediff-meta-session-number) + session-num)) + ext))) + ;; Emacs doesn't have map-extents, so try harder + ;; Splice overlay lists to get all buffer overlays + (setq buffer-meta-overlays (overlay-lists) + buffer-meta-overlays (append (car buffer-meta-overlays) + (cdr buffer-meta-overlays))) + (car + (delq nil + (mapcar + (lambda (overl) + (if (and + (ediff-overlay-get overl 'ediff-meta-info) + (eq (ediff-overlay-get + overl 'ediff-meta-session-number) + session-num)) + overl)) + buffer-meta-overlays))))) + (or overl + (error + "Bug in ediff-update-session-marker-in-dir-meta-buffer: no overlay with given number %S" + session-num)) + (setq session-info (ediff-overlay-get overl 'ediff-meta-info)) + (goto-char (ediff-overlay-start overl)) + (ediff-replace-session-activity-marker-in-meta-buffer + (point) + (ediff-get-session-activity-marker session-info)) + (ediff-replace-session-status-in-meta-buffer + (point) + (ediff-get-session-status session-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 +(defun ediff-problematic-session-p (session) + (let ((f1 (ediff-get-session-objA-name session)) + (f2 (ediff-get-session-objB-name session)) + (f3 (ediff-get-session-objC-name session))) + (cond ((and (stringp f1) (not (file-directory-p f1)) + (stringp f2) (not (file-directory-p f2)) + ;; either invalid file name or a directory + (or (not (stringp f3)) (file-directory-p f3)) + (ediff-ancestor-metajob)) + ;; more may be added later + 'ancestor-is-dir) + (t nil)))) + +(defun ediff-meta-insert-file-info1 (fileinfo) + (let ((fname (car fileinfo)) + (feq (ediff-get-file-eqstatus fileinfo)) + (max-filename-width (if ediff-meta-truncate-filenames + (- (window-width) 41) + 500)) + file-modtime file-size) + (cond ((not (stringp fname)) (setq file-size -2)) ; file doesn't exits + ((ediff-listable-file fname) + (if (file-exists-p fname) + ;; set real size and modtime + (setq file-size (ediff-file-size fname) + file-modtime (ediff-file-modtime fname)) + (setq file-size -2))) ; file doesn't exist + ( t (setq file-size -1))) ; remote file + (if (stringp fname) + (insert + (format + "%s %s %-20s %s\n" + (if feq "=" " ") ; equality indicator + (format "%10s" (cond ((= file-size -1) "--") + ((< file-size -1) "--") + (t file-size))) + (cond ((= file-size -1) "*remote file*") + ((< file-size -1) "*file doesn't exist*") + (t (ediff-format-date (decode-time file-modtime)))) + + ;; dir names in meta lists have training slashes, so we just + ;; abbreviate the file name, if file exists + (if (and (not (stringp fname)) (< file-size -1)) + "-------" ; file doesn't exist + (ediff-truncate-string-left + (ediff-abbreviate-file-name fname) + max-filename-width))))))) + +(defconst ediff-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 +(defsubst ediff-fill-leading-zero (num) + (if (< num 10) + (format "0%d" num) + (number-to-string num))) + +;; TIME is like the output of decode-time +(defun ediff-format-date (time) + (format "%s %2d %4d %s:%s:%s" + (cdr (assoc (nth 4 time) ediff-months)) ; month + (nth 3 time) ; day + (nth 5 time) ; year + (ediff-fill-leading-zero (nth 2 time)) ; hour + (ediff-fill-leading-zero (nth 1 time)) ; min + (ediff-fill-leading-zero (nth 0 time)) ; sec + )) + +;; Draw the directories +(defun ediff-insert-dirs-in-meta-buffer (meta-list) + (let* ((dir1 (ediff-abbreviate-file-name (ediff-get-group-objA meta-list))) + (dir2 (ediff-get-group-objB meta-list)) + (dir2 (if (stringp dir2) (ediff-abbreviate-file-name dir2))) + (dir3 (ediff-get-group-objC meta-list)) + (dir3 (if (stringp dir3) (ediff-abbreviate-file-name dir3)))) + (insert "*** Directory A: " dir1 "\n") + (if dir2 (insert "*** Directory B: " dir2 "\n")) + (if dir3 (insert "*** Directory C: " dir3 "\n")) + (insert "\n"))) + +(defun ediff-draw-dir-diffs (diff-list &optional buf-name) + (if (null diff-list) (error "Lost difference info on these directories")) + (setq buf-name + (or buf-name + (ediff-unique-buffer-name "*Ediff File Group Differences" "*"))) + (let* ((regexp (ediff-get-group-regexp diff-list)) + (dir1 (ediff-abbreviate-file-name (ediff-get-group-objA diff-list))) + (dir2 (ediff-abbreviate-file-name (ediff-get-group-objB diff-list))) + (dir3 (ediff-get-group-objC diff-list)) + (dir3 (if (stringp dir3) (ediff-abbreviate-file-name dir3))) + (meta-buf (ediff-get-group-buffer diff-list)) + (underline (make-string 26 ?-)) + file membership-code saved-point + buffer-read-only) + ;; skip the directory part + (setq diff-list (cdr diff-list)) + (setq ediff-dir-diffs-buffer (get-buffer-create buf-name)) + (ediff-with-current-buffer ediff-dir-diffs-buffer + (setq saved-point (point)) + (use-local-map ediff-dir-diffs-buffer-map) + (erase-buffer) + (setq ediff-meta-buffer meta-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)) + (if dir2 (insert (format "*** Directory B: %s\n" dir2))) + (if dir3 (insert (format "*** Directory C: %s\n" dir3))) + (if (and (stringp regexp) (> (length regexp) 0)) + (insert + (format "*** Filter-through regular expression: %s\n" regexp))) + (insert "\n") + (insert (format "\n%-27s%-26s" "Directory A" "Directory B")) + (if dir3 + (insert (format " %-25s\n" "Directory C")) + (insert "\n")) + (insert (format "%s%s" underline underline)) + (if (stringp dir3) + (insert (format "%s\n\n" underline)) + (insert "\n\n")) + + (if (null diff-list) + (insert "\n\t*** No differences ***\n")) + + (while diff-list + (setq file (car (car diff-list)) + membership-code (cdr (car diff-list)) + diff-list (cdr diff-list)) + (if (= (mod membership-code ediff-membership-code1) 0) ; dir1 + (let ((beg (point))) + (insert (format "%-27s" + (ediff-truncate-string-left + (ediff-abbreviate-file-name + (if (file-directory-p (concat dir1 file)) + (file-name-as-directory file) + file)) + 24))) + ;; format of meta info in the dir-diff-buffer: + ;; (filename-tail filename-full otherdir1 otherdir2 otherdir3) + (ediff-set-meta-overlay + beg (point) + (list meta-buf file (concat dir1 file) dir1 dir2 dir3))) + (insert (format "%-27s" "---"))) + (if (= (mod membership-code ediff-membership-code2) 0) ; dir2 + (let ((beg (point))) + (insert (format "%-26s" + (ediff-truncate-string-left + (ediff-abbreviate-file-name + (if (file-directory-p (concat dir2 file)) + (file-name-as-directory file) + file)) + 24))) + (ediff-set-meta-overlay + beg (point) + (list meta-buf file (concat dir2 file) dir1 dir2 dir3))) + (insert (format "%-26s" "---"))) + (if (stringp dir3) + (if (= (mod membership-code ediff-membership-code3) 0) ; dir3 + (let ((beg (point))) + (insert (format " %-25s" + (ediff-truncate-string-left + (ediff-abbreviate-file-name + (if (file-directory-p (concat dir3 file)) + (file-name-as-directory file) + file)) + 24))) + (ediff-set-meta-overlay + beg (point) + (list meta-buf file (concat dir3 file) dir1 dir2 dir3))) + (insert (format " %-25s" "---")))) + (insert "\n")) + (setq buffer-read-only t) + (set-buffer-modified-p nil) + (goto-char saved-point)) ; end eval in diff buffer + )) + +(defun ediff-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 ((buf ediff-meta-buffer) + wind) + (ediff-kill-buffer-carefully ediff-dir-diffs-buffer) + (if (setq wind (ediff-get-visible-buffer-window buf)) + (select-window wind) + (set-window-buffer (selected-window) buf)))) + +;; executes in dir session group buffer +;; show buffer differences +(defun ediff-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-p ediff-dir-diffs-buffer) + (ediff-draw-dir-diffs ediff-dir-difference-list)) + (let ((buf ediff-dir-diffs-buffer)) + (other-window 1) + (set-window-buffer (selected-window) buf) + (goto-char (point-min)))) + +;; Format of meta info in dir-diff-buffer: +;; (filename-tail filename-full otherdir1 otherdir2) +(defun ediff-dir-diff-copy-file () + "Copy file described at point to directories where this file is missing." + (interactive) + (let* ((pos (ediff-event-point last-command-event)) + (info (ediff-get-meta-info (current-buffer) pos 'noerror)) + (meta-buf (car info)) + (file-tail (nth 1 info)) + (file-abs (nth 2 info)) + (otherdir1 (nth 3 info)) + (otherfile1 (if otherdir1 (concat otherdir1 file-tail))) + (otherdir2 (nth 4 info)) + (otherfile2 (if otherdir2 (concat otherdir2 file-tail))) + (otherdir3 (nth 5 info)) + (otherfile3 (if otherdir3 (concat otherdir3 file-tail))) + meta-list dir-diff-list + ) + (if (null info) + (error "No file suitable for copying described at this location")) + (ediff-with-current-buffer meta-buf + (setq meta-list ediff-meta-list + dir-diff-list ediff-dir-difference-list)) + + ;; copy file to directories where it doesn't exist, update + ;; ediff-dir-difference-list and redisplay + (mapc + (lambda (otherfile-struct) + (let ((otherfile (car otherfile-struct)) + (file-mem-code (cdr otherfile-struct))) + (if otherfile + (or (file-exists-p otherfile) + (if (y-or-n-p + (format "Copy %s to %s? " file-abs otherfile)) + (let* ((file-diff-record (assoc file-tail dir-diff-list)) + (new-mem-code + (* (cdr file-diff-record) file-mem-code))) + (copy-file file-abs otherfile) + (setcdr file-diff-record new-mem-code) + (ediff-draw-dir-diffs dir-diff-list (buffer-name)) + (sit-for 0) + ;; 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-code ediff-product-of-memcodes) + (and (> new-mem-code ediff-membership-code3) + (null otherfile3))) + (delq file-diff-record dir-diff-list)) + )))) + )) + ;; 2,3,5 are numbers used to encode membership of a file in + ;; dir1/2/3. See ediff-intersect-directories. + (list (cons otherfile1 2) (cons otherfile2 3) (cons otherfile3 5))) + + (if (and (file-exists-p otherfile1) + (file-exists-p otherfile2) + (or (not otherfile3) (file-exists-p otherfile3))) + ;; update ediff-meta-list by direct modification + (nconc meta-list + (list (ediff-make-new-meta-list-element + (expand-file-name otherfile1) + (expand-file-name otherfile2) + (if otherfile3 + (expand-file-name otherfile3))))) + ) + (ediff-update-meta-buffer meta-buf 'must-redraw) + )) + +(defun ediff-up-meta-hierarchy () + "Go to the parent session group buffer." + (interactive) + (if (ediff-buffer-live-p ediff-parent-meta-buffer) + (ediff-show-meta-buffer + ediff-parent-meta-buffer ediff-meta-session-number) + (error "This session group has no parent"))) + + +;; argument is ignored +(defun ediff-redraw-registry-buffer (&optional ignore) + (ediff-with-current-buffer ediff-registry-buffer + (let ((point (point)) + elt bufAname bufBname bufCname cur-diff total-diffs pt + job-name meta-list registry-list buffer-read-only) + (erase-buffer) + ;; delete phony overlays that used to represent sessions before the buff + ;; was redrawn + (if (featurep 'xemacs) + (map-extents 'delete-extent) + (mapc 'delete-overlay (overlays-in 1 1))) + + (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 + (mapc (lambda (elt) + (if (not (ediff-buffer-live-p elt)) + (setq ediff-session-registry + (delq elt ediff-session-registry)))) + ediff-session-registry) + + (if (null ediff-session-registry) + (insert " ******* No active Ediff sessions *******\n")) + + (setq registry-list ediff-session-registry) + (while registry-list + (setq elt (car registry-list) + registry-list (cdr registry-list)) + + (if (ediff-buffer-live-p elt) + (if (ediff-with-current-buffer elt + (setq job-name ediff-metajob-name + meta-list ediff-meta-list) + (and ediff-metajob-name + (not (eq ediff-metajob-name 'ediff-registry)))) + (progn + (setq pt (point)) + (insert (format " *group*\t%s: %s\n" + (buffer-name elt) + (ediff-abbrev-jobname job-name))) + (insert (format "\t\t %s %s %s\n" + (ediff-abbreviate-file-name + (ediff-get-group-objA meta-list)) + (ediff-abbreviate-file-name + (if (stringp + (ediff-get-group-objB meta-list)) + (ediff-get-group-objB meta-list) + "")) + (ediff-abbreviate-file-name + (if (stringp + (ediff-get-group-objC meta-list)) + (ediff-get-group-objC meta-list) + "")))) + (ediff-set-meta-overlay pt (point) elt)) + (progn + (ediff-with-current-buffer elt + (setq bufAname (if (ediff-buffer-live-p ediff-buffer-A) + (buffer-name ediff-buffer-A) + "!!!killed buffer!!!") + bufBname (if (ediff-buffer-live-p ediff-buffer-B) + (buffer-name ediff-buffer-B) + "!!!killed buffer!!!") + bufCname (cond ((not (ediff-3way-job)) + "") + ((ediff-buffer-live-p ediff-buffer-C) + (buffer-name ediff-buffer-C)) + (t "!!!killed buffer!!!"))) + (setq total-diffs (format "%-4d" ediff-number-of-differences) + cur-diff + (cond ((= ediff-current-difference -1) " _") + ((= ediff-current-difference + ediff-number-of-differences) + " $") + (t (format + "%4d" (1+ ediff-current-difference)))) + job-name ediff-job-name)) + ;; back in the meta buf + (setq pt (point)) + (insert cur-diff "/" total-diffs "\t" + (buffer-name elt) + (format ": %s" (ediff-abbrev-jobname job-name))) + (insert + "\n\t\t " bufAname " " bufBname " " bufCname "\n") + (ediff-set-meta-overlay pt (point) elt)))) + ) ; while + (set-buffer-modified-p nil) + (goto-char point) + ))) + +;; 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) +(defun ediff-set-meta-overlay (b e prop &optional session-number hidden) + (let (overl) + (setq overl (ediff-make-overlay b e)) + (if (featurep 'emacs) + (ediff-overlay-put overl 'mouse-face 'highlight) + (ediff-overlay-put overl 'highlight t)) + (ediff-overlay-put overl 'ediff-meta-info prop) + (ediff-overlay-put overl 'invisible hidden) + (ediff-overlay-put overl 'follow-link t) + (if (numberp session-number) + (ediff-overlay-put overl 'ediff-meta-session-number session-number)))) + +(defun ediff-mark-for-hiding-at-pos (unmark) + "Mark session for hiding. With prefix arg, unmark." + (interactive "P") + (let* ((pos (ediff-event-point last-command-event)) + (meta-buf (ediff-event-buffer last-command-event)) + ;; ediff-get-meta-info gives error if meta-buf or pos are invalid + (info (ediff-get-meta-info meta-buf pos)) + (session-number (ediff-get-session-number-at-pos pos))) + (ediff-mark-session-for-hiding info unmark) + (ediff-next-meta-item 1) + (save-excursion + (ediff-update-meta-buffer meta-buf nil session-number)) + )) + +;; Returns whether session was marked or unmarked +(defun ediff-mark-session-for-hiding (info unmark) + (let ((session-buf (ediff-get-session-buffer info)) + ignore) + (cond ((eq unmark 'mark) (setq unmark nil)) + ((eq (ediff-get-session-status info) ?H) (setq unmark t)) + (unmark ; says unmark, but the marker is different from H + (setq ignore t))) + (cond (ignore) + (unmark (ediff-set-session-status info nil)) +;;; (if (ediff-buffer-live-p session-buf) +;;; (error "Can't hide active session, %s" (buffer-name session-buf))) + (t (ediff-set-session-status info ?H)))) + unmark) + + +(defun ediff-mark-for-operation-at-pos (unmark) + "Mark session for a group operation. With prefix arg, unmark." + (interactive "P") + (let* ((pos (ediff-event-point last-command-event)) + (meta-buf (ediff-event-buffer last-command-event)) + ;; ediff-get-meta-info gives error if meta-buf or pos are invalid + (info (ediff-get-meta-info meta-buf pos)) + (session-number (ediff-get-session-number-at-pos pos))) + (ediff-mark-session-for-operation info unmark) + (ediff-next-meta-item 1) + (save-excursion + (ediff-update-meta-buffer meta-buf nil session-number)) + )) + + +;; returns whether session was unmarked. +;; remember: this is a toggle op +(defun ediff-mark-session-for-operation (info unmark) + (let (ignore) + (cond ((eq unmark 'mark) (setq unmark nil)) + ((eq (ediff-get-session-status info) ?*) (setq unmark t)) + (unmark ; says unmark, but the marker is different from * + (setq ignore t))) + (cond (ignore) + (unmark (ediff-set-session-status info nil)) + (t (ediff-set-session-status info ?*)))) + unmark) + + +(defun ediff-hide-marked-sessions (unhide) + "Hide marked sessions. With prefix arg, unhide." + (interactive "P") + (let ((grp-buf (ediff-get-group-buffer ediff-meta-list)) + (meta-list (cdr ediff-meta-list)) + (from (if unhide ?I ?H)) + (to (if unhide ?H ?I)) + (numMarked 0) + active-sessions-exist session-buf elt) + (while meta-list + (setq elt (car meta-list) + meta-list (cdr meta-list) + session-buf (ediff-get-session-buffer elt)) + + (if (eq (ediff-get-session-status elt) from) + (progn + (setq numMarked (1+ numMarked)) + (if (and (eq to ?I) (buffer-live-p session-buf)) + ;; shouldn't hide active sessions + (setq active-sessions-exist t) + (ediff-set-session-status elt to))))) + (if (> numMarked 0) + (ediff-update-meta-buffer grp-buf 'must-redraw) + (beep) + (if unhide + (message "Nothing to reveal...") + (message "Nothing to hide..."))) + (if active-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 +(defun ediff-operate-on-marked-sessions (operation) + (let ((grp-buf (ediff-get-group-buffer ediff-meta-list)) + (meta-list (cdr ediff-meta-list)) + (marksym ?*) + (numMarked 0) + (sessionNum 0) + (diff-buffer ediff-meta-diff-buffer) + session-buf elt) + (while meta-list + (setq elt (car meta-list) + meta-list (cdr meta-list) + sessionNum (1+ sessionNum)) + (cond ((eq (ediff-get-session-status elt) marksym) + (save-excursion + (setq numMarked (1+ numMarked)) + (funcall operation elt sessionNum))) + ;; The following goes into a session represented by a subdirectory + ;; and applies operation to marked sessions there + ((and (ediff-meta-session-p elt) + (ediff-buffer-live-p + (setq session-buf (ediff-get-session-buffer elt)))) + (setq numMarked + (+ numMarked + (ediff-with-current-buffer session-buf + ;; pass meta-diff along + (setq ediff-meta-diff-buffer diff-buffer) + ;; collect diffs in child group + (ediff-operate-on-marked-sessions operation))))))) + (ediff-update-meta-buffer grp-buf 'must-redraw) ; just in case + numMarked + )) + +(defun ediff-append-custom-diff (session sessionNum) + (or (ediff-collect-diffs-metajob) + (error "Can't compute multifile patch in this context")) + (let ((session-buf (ediff-get-session-buffer session)) + (meta-diff-buff ediff-meta-diff-buffer) + (metajob ediff-metajob-name) + tmp-buf custom-diff-buf) + (if (ediff-buffer-live-p session-buf) + (ediff-with-current-buffer session-buf + (if (eq ediff-control-buffer session-buf) ; individual session + (progn + (ediff-compute-custom-diffs-maybe) + (setq custom-diff-buf ediff-custom-diff-buffer))))) + + (or (ediff-buffer-live-p meta-diff-buff) + (error "Ediff: something wrong--killed multiple diff's buffer")) + + (cond ((ediff-buffer-live-p custom-diff-buf) + ;; for live session buffers we do them first because the user may + ;; have changed them with respect to the underlying files + (with-current-buffer meta-diff-buff + (goto-char (point-max)) + (insert-buffer-substring custom-diff-buf) + (insert "\n"))) + ;; if ediff session is not live, run diff directly on the files + ((memq metajob '(ediff-directories + ediff-merge-directories + ediff-merge-directories-with-ancestor)) + ;; get diffs by calling shell command on ediff-custom-diff-program + (with-current-buffer + (setq tmp-buf (get-buffer-create ediff-tmp-buffer)) + (erase-buffer) + (shell-command + (format + "%s %s %s %s" + (shell-quote-argument ediff-custom-diff-program) + ediff-custom-diff-options + (shell-quote-argument (ediff-get-session-objA-name session)) + (shell-quote-argument (ediff-get-session-objB-name session)) + ) + t) + ) + (with-current-buffer meta-diff-buff + (goto-char (point-max)) + (insert-buffer-substring tmp-buf) + (insert "\n"))) + (t + (ediff-kill-buffer-carefully meta-diff-buff) + (error "Session %d compares versions of file. Such session must be active to enable multifile patch collection" sessionNum ))) + )) + +(defun ediff-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 produce +multifile patches. For `ediff-directory-revisions', we insist that +all marked sessions must be active." + (interactive) + (let ((coding-system-for-read ediff-coding-system-for-read)) + (or (ediff-buffer-live-p ediff-meta-diff-buffer) + (setq ediff-meta-diff-buffer + (get-buffer-create + (ediff-unique-buffer-name "*Ediff Multifile Diffs" "*")))) + (ediff-with-current-buffer ediff-meta-diff-buffer + (setq buffer-read-only nil) + (erase-buffer)) + (if (> (ediff-operate-on-marked-sessions 'ediff-append-custom-diff) 0) + ;; did something + (progn + (display-buffer ediff-meta-diff-buffer 'not-this-window) + (ediff-with-current-buffer ediff-meta-diff-buffer + (set-buffer-modified-p nil) + (setq buffer-read-only t)) + (if (fboundp 'diff-mode) + (with-current-buffer ediff-meta-diff-buffer + (diff-mode)))) + (beep) + (message "No marked sessions found")))) + +(defun ediff-meta-show-patch () + "Show the multi-file patch associated with this group session." + (interactive) + (let* ((pos (ediff-event-point last-command-event)) + (meta-buf (ediff-event-buffer last-command-event)) + (info (ediff-get-meta-info meta-buf pos 'noerror)) + (patchbuffer ediff-meta-patchbufer)) + (if (ediff-buffer-live-p patchbuffer) + (ediff-with-current-buffer patchbuffer + (save-restriction + (if (not info) + (widen) + (narrow-to-region + (ediff-get-session-objB-name info) + (ediff-get-session-objC-name info))) + (set-buffer (get-buffer-create ediff-tmp-buffer)) + (erase-buffer) + (insert-buffer-substring patchbuffer) + (goto-char (point-min)) + (display-buffer ediff-tmp-buffer 'not-this-window) + )) + (error "The patch buffer wasn't found")))) + + +;; This function executes in meta buffer. It knows where event happened. +(defun ediff-filegroup-action () + "Execute appropriate action for a selected session." + (interactive) + (let* ((pos (ediff-event-point last-command-event)) + (meta-buf (ediff-event-buffer last-command-event)) + ;; ediff-get-meta-info gives error if meta-buf or pos are invalid + (info (ediff-get-meta-info meta-buf pos)) + (session-buf (ediff-get-session-buffer info)) + (session-number (ediff-get-session-number-at-pos pos meta-buf)) + (default-regexp (eval ediff-default-filtering-regexp)) + merge-autostore-dir file1 file2 file3 regexp) + + (setq file1 (ediff-get-session-objA-name info) + file2 (ediff-get-session-objB-name info) + file3 (ediff-get-session-objC-name info)) + + ;; make sure we don't start on hidden sessions + ;; ?H means marked for hiding. ?I means invalid (hidden). + (if (memq (ediff-get-session-status info) '(?I)) + (progn + (beep) + (if (y-or-n-p "This session is marked as hidden, unmark? ") + (progn + (ediff-set-session-status info nil) + (ediff-update-meta-buffer meta-buf nil session-number)) + (error "Aborted")))) + + (ediff-with-current-buffer meta-buf + (setq merge-autostore-dir + (ediff-get-group-merge-autostore-dir ediff-meta-list)) + (goto-char pos) ; 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-p info) + ;; do ediff/ediff-merge on subdirectories + (if (ediff-buffer-live-p session-buf) + (ediff-show-meta-buffer session-buf) + (setq regexp + (read-string + (if (stringp default-regexp) + (format + "Filter through regular expression (default %s): " + default-regexp) + "Filter through regular expression: ") + nil + 'ediff-filtering-regexp-history + (eval ediff-default-filtering-regexp))) + (ediff-directories-internal + file1 file2 file3 regexp + ediff-session-action-function + ediff-metajob-name + ;; make it update (car info) after startup + `(list (lambda () + ;; child session group should know its parent + (setq ediff-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-p file1)) + (if (ediff-buffer-live-p session-buf) + (ediff-show-meta-buffer session-buf) + (setq regexp (read-string "Filter through regular expression: " + nil 'ediff-filtering-regexp-history)) + (ediff-directory-revisions-internal + file1 regexp + ediff-session-action-function ediff-metajob-name + ;; make it update (car info) after startup + `(list (lambda () + ;; child session group should know its parent and + ;; its number + (setq ediff-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-p session-buf) + (ediff-with-current-buffer session-buf + (setq ediff-mouse-pixel-position (mouse-pixel-position)) + (ediff-recenter 'no-rehighlight))) + + ((ediff-problematic-session-p info) + (beep) + (if (y-or-n-p + "This session has no ancestor. Merge without the ancestor? ") + (ediff-merge-files + file1 file2 + ;; 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) + (setq ediff-meta-buffer ,(current-buffer) + ediff-meta-session-number + ,session-number) + (setq ediff-merge-store-file + ,(if (ediff-nonempty-string-p + merge-autostore-dir) + (concat + merge-autostore-dir + ediff-merge-filename-prefix + (file-name-nondirectory file1)) + )) + ;; 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 + (funcall ediff-session-action-function + file1 + ;; 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) + (setq ediff-meta-buffer ,(current-buffer) + ediff-meta-session-number + ,session-number) + (setq ediff-merge-store-file + ,(if (ediff-nonempty-string-p + merge-autostore-dir) + (concat + merge-autostore-dir + ediff-merge-filename-prefix + (file-name-nondirectory file1))) ) + ;; 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 + (funcall ediff-session-action-function + file1 file2 + ;; 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) + (setq ediff-meta-buffer ,(current-buffer) + ediff-meta-session-number + ,session-number) + (setq ediff-merge-store-file + ,(if (ediff-nonempty-string-p + merge-autostore-dir) + (concat + merge-autostore-dir + ediff-merge-filename-prefix + (file-name-nondirectory file1))) ) + ;; 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 + (funcall ediff-session-action-function + file1 file2 file3 + ;; 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) + (setq ediff-merge-store-file + ,(if (ediff-nonempty-string-p + merge-autostore-dir) + (concat + merge-autostore-dir + ediff-merge-filename-prefix + (file-name-nondirectory file1))) ) + (setq ediff-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 + )) + +(defun ediff-registry-action () + "Switch to a selected session." + (interactive) + (let* ((pos (ediff-event-point last-command-event)) + (buf (ediff-event-buffer last-command-event)) + (ctl-buf (ediff-get-meta-info buf pos))) + + (if (ediff-buffer-live-p ctl-buf) + ;; check if this is ediff-control-buffer or ediff-meta-buffer + (if (ediff-with-current-buffer ctl-buf + (eq (key-binding "q") 'ediff-quit-meta-buffer)) + ;; it's a meta-buffer -- last action should just display it + (ediff-show-meta-buffer ctl-buf t) + ;; it's a session buffer -- invoke go back to session + (ediff-with-current-buffer ctl-buf + (setq ediff-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-buffer buf + (goto-char pos)) + )) + + +;; If session number is t, means don't update meta buffer +(defun ediff-show-meta-buffer (&optional meta-buf session-number) + "Show the session group buffer." + (interactive) + (run-hooks 'ediff-before-directory-setup-hooks) + (let (wind frame silent) + (if meta-buf (setq silent t)) + + (setq meta-buf (or meta-buf ediff-meta-buffer)) + (cond ((not (bufferp meta-buf)) + (error "This Ediff session is not part of a session group")) + ((not (ediff-buffer-live-p meta-buf)) + (error + "Can't find this session's group panel -- session itself is ok"))) + + (cond ((numberp session-number) + (ediff-update-meta-buffer meta-buf nil session-number)) + ;; if session-number is t, don't update + (session-number) + (t (ediff-cleanup-meta-buffer meta-buf))) + + (ediff-with-current-buffer meta-buf + (save-excursion + (cond ((setq wind (ediff-get-visible-buffer-window meta-buf)) + (or silent + (message + "Already showing the group panel for this session")) + (set-window-buffer wind meta-buf) + (select-window wind)) + ((window-live-p (setq wind ediff-window-C)) ;in merge--merge buf + (set-window-buffer ediff-window-C meta-buf) + (select-window wind)) + ((window-live-p (setq wind ediff-window-A)) + (set-window-buffer ediff-window-A meta-buf) + (select-window wind)) + ((window-live-p (setq wind ediff-window-B)) + (set-window-buffer ediff-window-B meta-buf) + (select-window wind)) + ((and + (setq wind + (ediff-get-visible-buffer-window ediff-registry-buffer)) + (ediff-window-display-p)) + (select-window wind) + (other-window 1) + (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 + (setq wind (ediff-get-visible-buffer-window meta-buf)))) + (progn + (setq frame (window-frame wind)) + (raise-frame frame) + (ediff-reset-mouse frame))) + (sit-for 0) ; 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) + )) + +(defun ediff-show-current-session-meta-buffer () + (interactive) + (ediff-show-meta-buffer nil ediff-meta-session-number)) + +(defun ediff-show-meta-buff-from-registry () + "Display the session group buffer for a selected session group." + (interactive) + (let* ((pos (ediff-event-point last-command-event)) + (meta-buf (ediff-event-buffer last-command-event)) + (info (ediff-get-meta-info meta-buf pos)) + (meta-or-session-buf info)) + (ediff-with-current-buffer meta-or-session-buf + (ediff-show-meta-buffer nil t)))) + +;;;###autoload +(defun ediff-show-registry () + "Display Ediff's registry." + (interactive) + (ediff-update-registry) + (if (not (ediff-buffer-live-p ediff-registry-buffer)) + (error "No active Ediff sessions or corrupted session registry")) + (let (wind frame) + ;; for some reason, point moves in ediff-registry-buffer, so we preserve it + ;; explicitly + (ediff-with-current-buffer ediff-registry-buffer + (save-excursion + (cond ((setq wind + (ediff-get-visible-buffer-window ediff-registry-buffer)) + (message "Already showing the registry") + (set-window-buffer wind ediff-registry-buffer) + (select-window wind)) + ((window-live-p ediff-window-C) + (set-window-buffer ediff-window-C ediff-registry-buffer) + (select-window ediff-window-C)) + ((window-live-p ediff-window-A) + (set-window-buffer ediff-window-A ediff-registry-buffer) + (select-window ediff-window-A)) + ((window-live-p ediff-window-B) + (set-window-buffer ediff-window-B ediff-registry-buffer) + (select-window ediff-window-B)) + ((and (setq wind + (ediff-get-visible-buffer-window ediff-meta-buffer)) + (ediff-window-display-p)) + (select-window wind) + (other-window 1) + (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 + (setq frame + (window-frame + (ediff-get-visible-buffer-window ediff-registry-buffer))) + (raise-frame frame) + (ediff-reset-mouse frame))) + (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. +(defun ediff-update-meta-buffer (meta-buf &optional must-redraw session-number) + (if (ediff-buffer-live-p meta-buf) + (ediff-with-current-buffer meta-buf + (let (overl) + (cond (must-redraw ; completely redraw the meta buffer + (funcall ediff-meta-redraw-function ediff-meta-list)) + ((numberp session-number) ; redraw only for the given session + (ediff-update-session-marker-in-dir-meta-buffer + session-number)) + (t ; update what changed only, but scan the entire meta buffer + (ediff-update-markers-in-dir-meta-buffer ediff-meta-list))) + (setq overl (ediff-get-meta-overlay-at-pos (point))) + ;; skip the invisible sessions + (while (and overl (ediff-overlay-get overl 'invisible)) + (ediff-next-meta-item1) + (setq overl (ediff-get-meta-overlay-at-pos (point)))) + )))) + +(defun ediff-update-registry () + (ediff-with-current-buffer (current-buffer) + (if (ediff-buffer-live-p ediff-registry-buffer) + (ediff-redraw-registry-buffer) + (ediff-prepare-meta-buffer + 'ediff-registry-action + ediff-session-registry + "*Ediff Registry" + 'ediff-redraw-registry-buffer + 'ediff-registry)) + )) + +;; If meta-buf exists, it is redrawn along with parent. +;; Otherwise, nothing happens. +(defun ediff-cleanup-meta-buffer (meta-buffer) + (if (ediff-buffer-live-p meta-buffer) + (ediff-with-current-buffer meta-buffer + (ediff-update-meta-buffer meta-buffer) + (if (ediff-buffer-live-p ediff-parent-meta-buffer) + (ediff-update-meta-buffer + ediff-parent-meta-buffer nil ediff-meta-session-number))))) + +;; t if no session is in progress +(defun ediff-safe-to-quit (meta-buffer) + (if (ediff-buffer-live-p meta-buffer) + (let ((lis ediff-meta-list) + (cont t) + buffer-read-only) + ;;(ediff-update-meta-buffer meta-buffer) + (ediff-with-current-buffer meta-buffer + (setq lis (cdr lis)) ; discard the description part of meta-list + (while (and cont lis) + (if (ediff-buffer-live-p + (ediff-get-group-buffer lis)) ; in progress + (setq cont nil)) + (setq lis (cdr lis))) + cont)))) + +(defun ediff-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-buffer ediff-dir-diffs-buffer) + (meta-diff-buffer ediff-meta-diff-buffer) + (session-number ediff-meta-session-number) + (parent-buf ediff-parent-meta-buffer) + (dont-show-registry (eq buf ediff-registry-buffer))) + (if dont-show-registry + (bury-buffer) + ;;(ediff-cleanup-meta-buffer buf) + (cond ((and (ediff-safe-to-quit buf) + (y-or-n-p "Quit this session group? ")) + (run-hooks 'ediff-quit-session-group-hook) + (message "") + (ediff-dispose-of-meta-buffer buf)) + ((ediff-safe-to-quit buf) + (bury-buffer)) + (t + (error + "This session group has active sessions---cannot exit"))) + (ediff-update-meta-buffer parent-buf nil session-number) + (ediff-kill-buffer-carefully dir-diffs-buffer) + (ediff-kill-buffer-carefully meta-diff-buffer) + (if (ediff-buffer-live-p parent-buf) + (progn + (setq dont-show-registry t) + (ediff-show-meta-buffer parent-buf session-number))) + ) + (or dont-show-registry + (ediff-show-registry)))) + +(defun ediff-dispose-of-meta-buffer (buf) + (setq ediff-session-registry (delq buf ediff-session-registry)) + (ediff-with-current-buffer buf + (if (ediff-buffer-live-p ediff-dir-diffs-buffer) + (kill-buffer ediff-dir-diffs-buffer))) + (kill-buffer buf)) + + +;; 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. +(defun ediff-get-meta-info (buf point &optional noerror) + (let (result olist tmp) + (if (and point (ediff-buffer-live-p buf)) + (ediff-with-current-buffer buf + (if (featurep 'xemacs) + (setq result + (if (setq tmp (extent-at point buf 'ediff-meta-info)) + (ediff-overlay-get tmp 'ediff-meta-info))) + (setq olist + (mapcar (lambda (elt) + (unless (overlay-get elt 'invisible) + (overlay-get elt 'ediff-meta-info))) + (overlays-at point))) + (while (and olist (null (car olist))) + (setq olist (cdr olist))) + (setq result (car olist))))) + (or result + (unless noerror + (ediff-update-registry) + (error "No session info in this line"))))) + + +(defun ediff-get-meta-overlay-at-pos (point) + (if (featurep 'xemacs) + (extent-at point (current-buffer) 'ediff-meta-info) + (let* ((overl-list (overlays-at point)) + (overl (car overl-list))) + (while (and overl (null (overlay-get overl 'ediff-meta-info))) + (setq overl-list (cdr overl-list) + overl (car overl-list))) + overl))) + +(defun ediff-get-session-number-at-pos (point &optional meta-buffer) + (setq meta-buffer (if (ediff-buffer-live-p meta-buffer) + meta-buffer + (current-buffer))) + (ediff-with-current-buffer meta-buffer + (ediff-overlay-get + (ediff-get-meta-overlay-at-pos point) 'ediff-meta-session-number))) + + +;; Return location of the next meta overlay after point +(defun ediff-next-meta-overlay-start (point) + (if (eobp) + (goto-char (point-min)) + (let ((overl (ediff-get-meta-overlay-at-pos point))) + (if (featurep 'xemacs) + (progn ; xemacs + (if overl + (setq overl (next-extent overl)) + (setq overl (next-extent (current-buffer)))) + (if overl + (extent-start-position overl) + (point-max))) + ;; emacs + (if overl + ;; note: end of current overlay is the beginning of the next one + (overlay-end overl) + (next-overlay-change point)))))) + + +(defun ediff-previous-meta-overlay-start (point) + (if (bobp) + (goto-char (point-max)) + (let ((overl (ediff-get-meta-overlay-at-pos point))) + (if (featurep 'xemacs) + (progn + (if overl + (setq overl (previous-extent overl)) + (setq overl (previous-extent (current-buffer)))) + (if overl + (extent-start-position overl) + (point-min))) + (if overl (setq point (overlay-start overl))) + ;; 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 + (setq point (1- point))) + (setq point (previous-overlay-change point)) + ;; 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-at point)) + (setq point (point-min))) + point)))) + +;; this is the action invoked when the user selects a patch from the meta +;; buffer. +(defun ediff-patch-file-form-meta (file &optional startup-hooks) + (let* ((pos (ediff-event-point last-command-event)) + (meta-buf (ediff-event-buffer last-command-event)) + ;; ediff-get-meta-info gives error if meta-buf or pos are invalid + (info (ediff-get-meta-info meta-buf pos)) + (meta-patchbuf ediff-meta-patchbufer) + session-buf beg-marker end-marker) + + (if (or (file-directory-p file) (string-match "/dev/null" file)) + (error "`%s' is not an ordinary file" (file-name-as-directory file))) + (setq session-buf (ediff-get-session-buffer info) + beg-marker (ediff-get-session-objB-name info) + end-marker (ediff-get-session-objC-name info)) + + (or (ediff-buffer-live-p session-buf) ; either an active patch session + (null session-buf) ; or it is a virgin session + (error + "Patch has already been applied to this file -- can't repeat!")) + + (ediff-with-current-buffer meta-patchbuf + (save-restriction + (widen) + (narrow-to-region beg-marker end-marker) + (ediff-patch-file-internal meta-patchbuf file startup-hooks))))) + + +(defun ediff-unmark-all-for-operation () + "Unmark all sessions marked for operation." + (interactive) + (let ((list (cdr ediff-meta-list)) + elt) + (while (setq elt (car list)) + (ediff-mark-session-for-operation elt 'unmark) + (setq list (cdr list)))) + (ediff-update-meta-buffer (current-buffer) 'must-redraw)) + +(defun ediff-unmark-all-for-hiding () + "Unmark all sessions marked for hiding." + (interactive) + (let ((list (cdr ediff-meta-list)) + elt) + (while (setq elt (car list)) + (ediff-mark-session-for-hiding elt 'unmark) + (setq list (cdr list)))) + (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 +(defun ediff-meta-mark-equal-files (&optional action) + "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, mark +for operation, or simply indicate which are equal files. If it is nil, then +`(ediff-last-command-char)' is used to decide which action to take." + (interactive) + (if (null action) + (setq action (ediff-last-command-char))) + (let ((list (cdr ediff-meta-list)) + marked1 marked2 marked3 + fileinfo1 fileinfo2 fileinfo3 elt) + (message "Comparing files...") + (while (setq elt (car list)) + (setq fileinfo1 (ediff-get-session-objA elt) + fileinfo2 (ediff-get-session-objB elt) + fileinfo3 (ediff-get-session-objC elt)) + (ediff-set-file-eqstatus fileinfo1 nil) + (ediff-set-file-eqstatus fileinfo2 nil) + (ediff-set-file-eqstatus fileinfo3 nil) + + (setq marked1 t + marked2 t + marked3 t) + (or (ediff-mark-if-equal fileinfo1 fileinfo2) + (setq marked1 nil)) + (if (ediff-metajob3) + (progn + (or (ediff-mark-if-equal fileinfo1 fileinfo3) + (setq marked2 nil)) + (or (ediff-mark-if-equal fileinfo2 fileinfo3) + (setq marked3 nil)))) + (if (and marked1 marked2 marked3) + (cond ((eq action ?h) + (ediff-mark-session-for-hiding elt 'mark)) + ((eq action ?m) + (ediff-mark-session-for-operation elt 'mark)) + )) + (setq list (cdr list))) + (message "Comparing files... Done")) + (setq ediff-recurse-to-subdirectories nil) + (ediff-update-meta-buffer (current-buffer) 'must-redraw)) + +;; mark files 1 and 2 as equal, if they are. +;; returns t, if something was marked +(defun ediff-mark-if-equal (fileinfo1 fileinfo2) + (let ((f1 (car fileinfo1)) + (f2 (car fileinfo2))) + (if (and (stringp f1) (stringp f2) (ediff-same-contents f1 f2)) + (progn + (ediff-set-file-eqstatus fileinfo1 t) + (ediff-set-file-eqstatus fileinfo2 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: + +;; arch-tag: c8a76898-f96f-4d9c-be9d-129134017188 +;;; ediff-mult.el ends here diff -r 681cd08dc0f7 -r e93288477c43 lisp/vc/ediff-ptch.el --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/lisp/vc/ediff-ptch.el Sun Jun 13 22:57:55 2010 +0000 @@ -0,0 +1,844 @@ +;;; ediff-ptch.el --- Ediff's patch support + +;; Copyright (C) 1996, 1997, 1998, 1999, 2000, 2001, 2002, +;; 2003, 2004, 2005, 2006, 2007, 2008, 2009, 2010 Free Software Foundation, Inc. + +;; Author: Michael Kifer + +;; 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 3 of the License, 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. If not, see . + +;;; Commentary: + +;;; Code: + + +(provide 'ediff-ptch) + +(defgroup ediff-ptch nil + "Ediff patch support." + :tag "Patch" + :prefix "ediff-" + :group 'ediff) + +;; compiler pacifier +(eval-when-compile + (require 'ediff)) +;; end pacifier + +(require 'ediff-init) + +(defcustom ediff-patch-program "patch" + "Name of the program that applies patches. +It is recommended to use GNU-compatible versions." + :type 'string + :group 'ediff-ptch) +(defcustom ediff-patch-options "-f" + "Options to pass to ediff-patch-program. + +Note: the `-b' option should be specified in `ediff-backup-specs'. + +It is recommended to pass the `-f' option to the patch program, so it won't ask +questions. However, some implementations don't accept this option, in which +case the default value for this variable should be changed." + :type 'string + :group 'ediff-ptch) + +(defvar ediff-last-dir-patch nil + "Last directory used by an Ediff command for file to patch.") + +;; the default backup extension +(defconst ediff-default-backup-extension + (if (memq system-type '(emx ms-dos)) + "_orig" ".orig")) + + +(defcustom ediff-backup-extension ediff-default-backup-extension + "Backup extension used by the patch program. +See also `ediff-backup-specs'." + :type 'string + :group 'ediff-ptch) + +(defun ediff-test-patch-utility () + (condition-case nil + (cond ((eq 0 (call-process ediff-patch-program nil nil nil "-z." "-b")) + ;; GNU `patch' v. >= 2.2 + 'gnu) + ((eq 0 (call-process ediff-patch-program nil nil nil "-b")) + 'posix) + (t 'traditional)) + (file-error nil))) + +(defcustom ediff-backup-specs + (let ((type (ediff-test-patch-utility))) + (cond ((eq type 'gnu) + ;; GNU `patch' v. >= 2.2 + (format "-z%s -b" ediff-backup-extension)) + ((eq type 'posix) + ;; POSIX `patch' -- ediff-backup-extension must be ".orig" + (setq ediff-backup-extension ediff-default-backup-extension) + "-b") + (t + ;; traditional `patch' + (format "-b %s" ediff-backup-extension)))) + "Backup directives to pass to the patch program. +Ediff requires that the old version of the file \(before applying the patch\) +be saved in a file named `the-patch-file.extension'. Usually `extension' is +`.orig', but this can be changed by the user and may depend on the system. +Therefore, Ediff needs to know the backup extension used by the patch program. + +Some versions of the patch program let you specify `-b backup-extension'. +Other versions only permit `-b', which assumes the extension `.orig' +\(in which case ediff-backup-extension MUST be also `.orig'\). The latest +versions of GNU patch require `-b -z backup-extension'. + +Note that both `ediff-backup-extension' and `ediff-backup-specs' +must be set properly. If your patch program takes the option `-b', +but not `-b extension', the variable `ediff-backup-extension' must +still be set so Ediff will know which extension to use. + +Ediff tries to guess the appropriate value for this variables. It is believed +to be working for `traditional' patch, all versions of GNU patch, and for POSIX +patch. So, don't change these variables, unless the default doesn't work." + :type 'string + :group 'ediff-ptch) + + +(defcustom ediff-patch-default-directory nil + "Default directory to look for patches." + :type '(choice (const nil) string) + :group 'ediff-ptch) + +;; This context diff does not recognize spaces inside files, but removing ' ' +;; from [^ \t] breaks normal patches for some reason +(defcustom ediff-context-diff-label-regexp + (concat "\\(" ; context diff 2-liner + "^\\*\\*\\* +\\([^ \t]+\\)[^*]+[\t ]*\n--- +\\([^ \t]+\\)" + "\\|" ; unified format diff 2-liner + "^--- +\\([^ \t]+\\).*\n\\+\\+\\+ +\\([^ \t]+\\)" + "\\)") + "Regexp matching filename 2-liners at the start of each context diff. +You probably don't want to change that, unless you are using an obscure patch +program." + :type 'regexp + :group 'ediff-ptch) + +;; The buffer of the patch file. Local to control buffer. +(ediff-defvar-local ediff-patchbufer nil "") + +;; The buffer where patch displays its diagnostics. +(ediff-defvar-local ediff-patch-diagnostics nil "") + +;; Map of patch buffer. Has the form: +;; ((filename1 marker1 marker2) (filename2 marker1 marker2) ...) +;; where filenames are files to which patch would have applied the patch; +;; marker1 delimits the beginning of the corresponding patch and marker2 does +;; it for the end. +(ediff-defvar-local ediff-patch-map nil "") + +;; strip prefix from filename +;; returns /dev/null, if can't strip prefix +(defsubst ediff-file-name-sans-prefix (filename prefix) + (if prefix + (save-match-data + (if (string-match (concat "^" (if (stringp prefix) + (regexp-quote prefix) + "")) + filename) + (substring filename (match-end 0)) + (concat "/null/" filename))) + filename) + ) + + + +;; no longer used +;; return the number of matches of regexp in buf starting from the beginning +(defun ediff-count-matches (regexp buf) + (ediff-with-current-buffer buf + (let ((count 0) opoint) + (save-excursion + (goto-char (point-min)) + (while (and (not (eobp)) + (progn (setq opoint (point)) + (re-search-forward regexp nil t))) + (if (= opoint (point)) + (forward-char 1) + (setq count (1+ count))))) + count))) + +;; Scan BUF (which is supposed to contain a patch) and make a list of the form +;; ((nil nil filename-spec1 marker1 marker2) +;; (nil nil filename-spec2 marker1 marker2) ...) +;; where filename-spec[12] are files to which the `patch' program would +;; have applied the patch. +;; nin, nil are placeholders. See ediff-make-new-meta-list-element in +;; ediff-meta.el for the explanations. +;; In the beginning we don't know exactly which files need to be patched. +;; We usually come up with two candidates and ediff-file-name-sans-prefix +;; resolves this later. +;; +;; The marker `marker1' delimits the beginning of the corresponding patch and +;; `marker2' does it for the end. +;; The result of ediff-map-patch-buffer is a list, which is then assigned +;; to ediff-patch-map. +;; The function returns the number of elements in the list ediff-patch-map +(defun ediff-map-patch-buffer (buf) + (ediff-with-current-buffer buf + (let ((count 0) + (mark1 (move-marker (make-marker) (point-min))) + (mark1-end (point-min)) + (possible-file-names '("/dev/null" . "/dev/null")) + mark2-end mark2 filenames + beg1 beg2 end1 end2 + patch-map opoint) + (save-excursion + (goto-char (point-min)) + (setq opoint (point)) + (while (and (not (eobp)) + (re-search-forward ediff-context-diff-label-regexp nil t)) + (if (= opoint (point)) + (forward-char 1) ; ensure progress towards the end + (setq mark2 (move-marker (make-marker) (match-beginning 0)) + mark2-end (match-end 0) + beg1 (or (match-beginning 2) (match-beginning 4)) + end1 (or (match-end 2) (match-end 4)) + beg2 (or (match-beginning 3) (match-beginning 5)) + end2 (or (match-end 3) (match-end 5))) + ;; possible-file-names is holding the new file names until we + ;; insert the old file name in the patch map + ;; It is a pair + ;; (filename-from-1st-header-line . filename-from-2nd-line) + (setq possible-file-names + (cons (if (and beg1 end1) + (buffer-substring beg1 end1) + "/dev/null") + (if (and beg2 end2) + (buffer-substring beg2 end2) + "/dev/null"))) + ;; check for any `Index:' or `Prereq:' lines, but don't use them + (if (re-search-backward "^Index:" mark1-end 'noerror) + (move-marker mark2 (match-beginning 0))) + (if (re-search-backward "^Prereq:" mark1-end 'noerror) + (move-marker mark2 (match-beginning 0))) + + (goto-char mark2-end) + + (if filenames + (setq patch-map + (cons (ediff-make-new-meta-list-element + filenames mark1 mark2) + patch-map))) + (setq mark1 mark2 + mark1-end mark2-end + filenames possible-file-names)) + (setq opoint (point) + count (1+ count)))) + (setq mark2 (point-max-marker) + patch-map (cons (ediff-make-new-meta-list-element + possible-file-names mark1 mark2) + patch-map)) + (setq ediff-patch-map (nreverse patch-map)) + count))) + +;; Fix up the file names in the list using the argument FILENAME +;; Algorithm: find the files' directories in the patch and, if a directory is +;; absolute, cut it out from the corresponding file name in the patch. +;; Relative directories are not cut out. +;; Prepend the directory of FILENAME to each resulting file (which came +;; originally from the patch). +;; In addition, the first file in the patch document is replaced by FILENAME. +;; Each file is actually a pair of files found in the context diff header +;; In the end, for each pair, we ask the user which file to patch. +;; Note: Ediff doesn't recognize multi-file patches that are separated +;; with the `Index:' line. It treats them as a single-file patch. +;; +;; Executes inside the patch buffer +(defun ediff-fixup-patch-map (filename) + (setq filename (expand-file-name filename)) + (let ((actual-dir (if (file-directory-p filename) + ;; directory part of filename + (file-name-as-directory filename) + (file-name-directory filename))) + ;; In case 2 files are possible patch targets, the user will be offered + ;; to choose file1 or file2. In a multifile patch, if the user chooses + ;; 1 or 2, this choice is preserved to decide future alternatives. + chosen-alternative + ) + + ;; chop off base-dirs + (mapc (lambda (session-info) + (let* ((proposed-file-names + ;; Filename-spec is objA; it is represented as + ;; (file1 . file2). Get it using ediff-get-session-objA. + (ediff-get-session-objA-name session-info)) + ;; base-dir1 is the dir part of the 1st file in the patch + (base-dir1 + (or (file-name-directory (car proposed-file-names)) + "")) + ;; directory part of the 2nd file in the patch + (base-dir2 + (or (file-name-directory (cdr proposed-file-names)) + "")) + ) + ;; If both base-dir1 and base-dir2 are relative and exist, + ;; assume that + ;; these dirs lead to the actual files starting at the present + ;; directory. So, we don't strip these relative dirs from the + ;; file names. This is a heuristic intended to improve guessing + (let ((default-directory (file-name-directory filename))) + (unless (or (file-name-absolute-p base-dir1) + (file-name-absolute-p base-dir2) + (not (file-exists-p base-dir1)) + (not (file-exists-p base-dir2))) + (setq base-dir1 "" + base-dir2 ""))) + (or (string= (car proposed-file-names) "/dev/null") + (setcar proposed-file-names + (ediff-file-name-sans-prefix + (car proposed-file-names) base-dir1))) + (or (string= + (cdr proposed-file-names) "/dev/null") + (setcdr proposed-file-names + (ediff-file-name-sans-prefix + (cdr proposed-file-names) base-dir2))) + )) + ediff-patch-map) + + ;; take the given file name into account + (or (file-directory-p filename) + (string= "/dev/null" filename) + (setcar (ediff-get-session-objA (car ediff-patch-map)) + (cons (file-name-nondirectory filename) + (file-name-nondirectory filename)))) + + ;; prepend actual-dir + (mapc (lambda (session-info) + (let ((proposed-file-names + (ediff-get-session-objA-name session-info))) + (if (and (string-match "^/null/" (car proposed-file-names)) + (string-match "^/null/" (cdr proposed-file-names))) + ;; couldn't intuit the file name to patch, so + ;; something is amiss + (progn + (with-output-to-temp-buffer ediff-msg-buffer + (ediff-with-current-buffer standard-output + (fundamental-mode)) + (princ + (format " +The patch file contains a context diff for + %s + %s +However, Ediff cannot infer the name of the actual file +to be patched on your system. If you know the correct file name, +please enter it now. + +If you don't know and still would like to apply patches to +other files, enter /dev/null +" + (substring (car proposed-file-names) 6) + (substring (cdr proposed-file-names) 6)))) + (let ((directory t) + user-file) + (while directory + (setq user-file + (read-file-name + "Please enter file name: " + actual-dir actual-dir t)) + (if (not (file-directory-p user-file)) + (setq directory nil) + (setq directory t) + (beep) + (message "%s is a directory" user-file) + (sit-for 2))) + (setcar (ediff-get-session-objA session-info) + (cons user-file user-file)))) + (setcar proposed-file-names + (expand-file-name + (concat actual-dir (car proposed-file-names)))) + (setcdr proposed-file-names + (expand-file-name + (concat actual-dir (cdr proposed-file-names))))) + )) + ediff-patch-map) + ;; Check for the existing files in each pair and discard the nonexisting + ;; ones. If both exist, ask the user. + (mapcar (lambda (session-info) + (let* ((file1 (car (ediff-get-session-objA-name session-info))) + (file2 (cdr (ediff-get-session-objA-name session-info))) + (session-file-object + (ediff-get-session-objA session-info)) + (f1-exists (file-exists-p file1)) + (f2-exists (file-exists-p file2))) + (cond + ((and + ;; The patch program prefers the shortest file as the patch + ;; target. However, this is a questionable heuristic. In an + ;; interactive program, like ediff, we can offer the user a + ;; choice. + ;; (< (length file2) (length file1)) + (not f1-exists) + f2-exists) + ;; replace file-pair with the winning file2 + (setcar session-file-object file2)) + ((and + ;; (< (length file1) (length file2)) + (not f2-exists) + f1-exists) + ;; replace file-pair with the winning file1 + (setcar session-file-object file1)) + ((and f1-exists f2-exists + (string= file1 file2)) + (setcar session-file-object file1)) + ((and f1-exists f2-exists (eq chosen-alternative 1)) + (setcar session-file-object file1)) + ((and f1-exists f2-exists (eq chosen-alternative 2)) + (setcar session-file-object file2)) + ((and f1-exists f2-exists) + (with-output-to-temp-buffer ediff-msg-buffer + (ediff-with-current-buffer standard-output + (fundamental-mode)) + (princ (format " +Ediff has inferred that + %s + %s +are two possible targets for applying the patch. +Both files seem to be plausible alternatives. + +Please advice: + Type `y' to use %s as the target; + Type `n' to use %s as the target. +" + file1 file2 file1 file2))) + (setcar session-file-object + (if (y-or-n-p (format "Use %s ? " file1)) + (progn + (setq chosen-alternative 1) + file1) + (setq chosen-alternative 2) + file2)) + ) + (f2-exists (setcar session-file-object file2)) + (f1-exists (setcar session-file-object file1)) + (t + (with-output-to-temp-buffer ediff-msg-buffer + (ediff-with-current-buffer standard-output + (fundamental-mode)) + (princ "\nEdiff has inferred that") + (if (string= file1 file2) + (princ (format " + %s +is assumed to be the target for this patch. However, this file does not exist." + file1)) + (princ (format " + %s + %s +are two possible targets for this patch. However, these files do not exist." + file1 file2))) + (princ " +\nPlease enter an alternative patch target ...\n")) + (let ((directory t) + target) + (while directory + (setq target (read-file-name + "Please enter a patch target: " + actual-dir actual-dir t)) + (if (not (file-directory-p target)) + (setq directory nil) + (beep) + (message "%s is a directory" target) + (sit-for 2))) + (setcar session-file-object target)))))) + ediff-patch-map) + )) + +(defun ediff-show-patch-diagnostics () + (interactive) + (cond ((window-live-p ediff-window-A) + (set-window-buffer ediff-window-A ediff-patch-diagnostics)) + ((window-live-p ediff-window-B) + (set-window-buffer ediff-window-B ediff-patch-diagnostics)) + (t (display-buffer ediff-patch-diagnostics 'not-this-window)))) + +;; prompt for file, get the buffer +(defun ediff-prompt-for-patch-file () + (let ((dir (cond (ediff-use-last-dir ediff-last-dir-patch) + (ediff-patch-default-directory) ; try patch default dir + (t default-directory))) + (coding-system-for-read ediff-coding-system-for-read) + patch-file-name) + (setq patch-file-name + (read-file-name + (format "Patch is in file%s: " + (cond ((and buffer-file-name + (equal (expand-file-name dir) + (file-name-directory buffer-file-name))) + (concat + " (default " + (file-name-nondirectory buffer-file-name) + ")")) + (t ""))) + dir buffer-file-name 'must-match)) + (if (file-directory-p patch-file-name) + (error "Patch file cannot be a directory: %s" patch-file-name) + (find-file-noselect patch-file-name)) + )) + + +;; Try current buffer, then the other window's buffer. Else, give up. +(defun ediff-prompt-for-patch-buffer () + (get-buffer + (read-buffer + "Buffer that holds the patch: " + (cond ((save-excursion + (goto-char (point-min)) + (re-search-forward ediff-context-diff-label-regexp nil t)) + (current-buffer)) + ((save-window-excursion + (other-window 1) + (save-excursion + (goto-char (point-min)) + (and (re-search-forward ediff-context-diff-label-regexp nil t) + (current-buffer))))) + ((save-window-excursion + (other-window -1) + (save-excursion + (goto-char (point-min)) + (and (re-search-forward ediff-context-diff-label-regexp nil t) + (current-buffer))))) + (t (ediff-other-buffer (current-buffer)))) + 'must-match))) + + +(defun ediff-get-patch-buffer (&optional arg patch-buf) + "Obtain patch buffer. If patch is already in a buffer---use it. +Else, read patch file into a new buffer. If patch buffer is passed as an +optional argument, then use it." + (let ((last-nonmenu-event t) ; Emacs: don't use dialog box + last-command-event) ; XEmacs: don't use dialog box + + (cond ((ediff-buffer-live-p patch-buf)) + ;; even prefix arg: patch in buffer + ((and (integerp arg) (eq 0 (mod arg 2))) + (setq patch-buf (ediff-prompt-for-patch-buffer))) + ;; odd prefix arg: get patch from a file + ((and (integerp arg) (eq 1 (mod arg 2))) + (setq patch-buf (ediff-prompt-for-patch-file))) + (t (setq patch-buf + (if (y-or-n-p "Is the patch already in a buffer? ") + (ediff-prompt-for-patch-buffer) + (ediff-prompt-for-patch-file))))) + + (ediff-with-current-buffer patch-buf + (goto-char (point-min)) + (or (ediff-get-visible-buffer-window patch-buf) + (progn + (pop-to-buffer patch-buf 'other-window) + (select-window (previous-window))))) + (ediff-map-patch-buffer patch-buf) + patch-buf)) + +;; Dispatch the right patch file function: regular or meta-level, +;; depending on how many patches are in the patch file. +;; At present, there is no support for meta-level patches. +;; Should return either the ctl buffer or the meta-buffer +(defun ediff-dispatch-file-patching-job (patch-buf filename + &optional startup-hooks) + (ediff-with-current-buffer patch-buf + ;; relativize names in the patch with respect to source-file + (ediff-fixup-patch-map filename) + (if (< (length ediff-patch-map) 2) + (ediff-patch-file-internal + patch-buf + (if (and ediff-patch-map + (not (string-match + "^/dev/null" + ;; this is the file to patch + (ediff-get-session-objA-name (car ediff-patch-map)))) + (> (length + (ediff-get-session-objA-name (car ediff-patch-map))) + 1)) + (ediff-get-session-objA-name (car ediff-patch-map)) + filename) + startup-hooks) + (ediff-multi-patch-internal patch-buf startup-hooks)) + )) + + +;; When patching a buffer, never change the orig file. Instead, create a new +;; buffer, ***_patched, even if the buff visits a file. +;; Users who want to actually patch the buffer should use +;; ediff-patch-file, not ediff-patch-buffer. +(defun ediff-patch-buffer-internal (patch-buf + buf-to-patch-name + &optional startup-hooks) + (let* ((buf-to-patch (get-buffer buf-to-patch-name)) + (visited-file (if buf-to-patch (buffer-file-name buf-to-patch))) + (buf-mod-status (buffer-modified-p buf-to-patch)) + (multifile-patch-p (> (length (ediff-with-current-buffer patch-buf + ediff-patch-map)) 1)) + default-dir file-name ctl-buf) + (if multifile-patch-p + (error + "To apply multi-file patches, please use `ediff-patch-file'")) + + ;; create a temp file to patch + (ediff-with-current-buffer buf-to-patch + (setq default-dir default-directory) + (setq file-name (ediff-make-temp-file buf-to-patch)) + ;; temporarily switch visited file name, if any + (set-visited-file-name file-name) + ;; don't create auto-save file, if buff was visiting a file + (or visited-file + (setq buffer-auto-save-file-name nil)) + ;; don't confuse the user with a new bufname + (rename-buffer buf-to-patch-name) + (set-buffer-modified-p nil) + (set-visited-file-modtime) ; sync buffer and temp file + (setq default-directory default-dir) + ) + + ;; dispatch a patch function + (setq ctl-buf (ediff-dispatch-file-patching-job + patch-buf file-name startup-hooks)) + + (ediff-with-current-buffer ctl-buf + (delete-file (buffer-file-name ediff-buffer-A)) + (delete-file (buffer-file-name ediff-buffer-B)) + (ediff-with-current-buffer ediff-buffer-A + (if default-dir (setq default-directory default-dir)) + (set-visited-file-name visited-file) ; visited-file might be nil + (rename-buffer buf-to-patch-name) + (set-buffer-modified-p buf-mod-status)) + (ediff-with-current-buffer ediff-buffer-B + (setq buffer-auto-save-file-name nil) ; don't create auto-save file + (if default-dir (setq default-directory default-dir)) + (set-visited-file-name nil) + (rename-buffer (ediff-unique-buffer-name + (concat buf-to-patch-name "_patched") "")) + (set-buffer-modified-p t))) + )) + + +;; Traditional patch has weird return codes. +;; GNU and Posix return 1 if some hanks failed and 2 in case of trouble. +;; 0 is a good code in all cases. +;; We'll do the concervative thing. +(defun ediff-patch-return-code-ok (code) + (eq code 0)) +;;; (if (eq (ediff-test-patch-utility) 'traditional) +;;; (eq code 0) +;;; (not (eq code 2)))) + +(defun ediff-patch-file-internal (patch-buf source-filename + &optional startup-hooks) + (setq source-filename (expand-file-name source-filename)) + + (let* ((shell-file-name ediff-shell) + (patch-diagnostics (get-buffer-create "*ediff patch diagnostics*")) + ;; ediff-find-file may use a temp file to do the patch + ;; so, we save source-filename and true-source-filename as a var + ;; that initially is source-filename but may be changed to a temp + ;; file for the purpose of patching. + (true-source-filename source-filename) + (target-filename source-filename) + ;; this ensures that the patch process gets patch buffer in the + ;; encoding that Emacs thinks is right for that type of text + (coding-system-for-write + (if (boundp 'buffer-file-coding-system) buffer-file-coding-system)) + target-buf buf-to-patch file-name-magic-p + patch-return-code ctl-buf backup-style aux-wind) + + (if (string-match "V" ediff-patch-options) + (error + "Ediff doesn't take the -V option in `ediff-patch-options'--sorry")) + + ;; Make a temp file, if source-filename has a magic file handler (or if + ;; it is handled via auto-mode-alist and similar magic). + ;; Check if there is a buffer visiting source-filename and if they are in + ;; sync; arrange for the deletion of temp file. + (ediff-find-file 'true-source-filename 'buf-to-patch + 'ediff-last-dir-patch 'startup-hooks) + + ;; Check if source file name has triggered black magic, such as file name + ;; handlers or auto mode alist, and make a note of it. + ;; true-source-filename should be either the original name or a + ;; temporary file where we put the after-product of the file handler. + (setq file-name-magic-p (not (equal (file-truename true-source-filename) + (file-truename source-filename)))) + + ;; Checkout orig file, if necessary, so that the patched file + ;; could be checked back in. + (ediff-maybe-checkout buf-to-patch) + + (ediff-with-current-buffer patch-diagnostics + (insert-buffer-substring patch-buf) + (message "Applying patch ... ") + ;; fix environment for gnu patch, so it won't make numbered extensions + (setq backup-style (getenv "VERSION_CONTROL")) + (setenv "VERSION_CONTROL" nil) + (setq patch-return-code + (call-process-region + (point-min) (point-max) + shell-file-name + t ; delete region (which contains the patch + t ; insert output (patch diagnostics) in current buffer + nil ; don't redisplay + shell-command-switch ; usually -c + (format "%s %s %s %s" + ediff-patch-program + ediff-patch-options + ediff-backup-specs + (expand-file-name true-source-filename)) + )) + + ;; restore environment for gnu patch + (setenv "VERSION_CONTROL" backup-style)) + + (message "Applying patch ... done") + (message "") + + (switch-to-buffer patch-diagnostics) + (sit-for 0) ; synchronize - let the user see diagnostics + + (or (and (ediff-patch-return-code-ok patch-return-code) + (file-exists-p + (concat true-source-filename ediff-backup-extension))) + (progn + (with-output-to-temp-buffer ediff-msg-buffer + (ediff-with-current-buffer standard-output + (fundamental-mode)) + (princ (format + "Patch program has failed due to a bad patch file, +it couldn't apply all hunks, OR +it couldn't create the backup for the file being patched. + +The former could be caused by a corrupt patch file or because the %S +program doesn't understand the format of the patch file in use. + +The second problem might be due to an incompatibility among these settings: + ediff-patch-program = %S ediff-patch-options = %S + ediff-backup-extension = %S ediff-backup-specs = %S + +See Ediff on-line manual for more details on these variables. +In particular, check the documentation for `ediff-backup-specs'. + +In any of the above cases, Ediff doesn't compare files automatically. +However, if the patch was applied partially and the backup file was created, +you can still examine the changes via M-x ediff-files" + ediff-patch-program + ediff-patch-program + ediff-patch-options + ediff-backup-extension + ediff-backup-specs + ))) + (beep 1) + (if (setq aux-wind (get-buffer-window ediff-msg-buffer)) + (progn + (select-window aux-wind) + (goto-char (point-max)))) + (switch-to-buffer-other-window patch-diagnostics) + (error "Patch appears to have failed"))) + + ;; If black magic is involved, apply patch to a temp copy of the + ;; file. Otherwise, apply patch to the orig copy. If patch is applied + ;; to temp copy, we name the result old-name_patched for local files + ;; and temp-copy_patched for remote files. The orig file name isn't + ;; changed, and the temp copy of the original is later deleted. + ;; Without magic, the original file is renamed (usually into + ;; old-name_orig) and the result of patching will have the same name as + ;; the original. + (if (not file-name-magic-p) + (ediff-with-current-buffer buf-to-patch + (set-visited-file-name + (concat source-filename ediff-backup-extension)) + (set-buffer-modified-p nil)) + + ;; Black magic in effect. + ;; If orig file was remote, put the patched file in the temp directory. + ;; If orig file is local, put the patched file in the directory of + ;; the orig file. + (setq target-filename + (concat + (if (ediff-file-remote-p (file-truename source-filename)) + true-source-filename + source-filename) + "_patched")) + + (rename-file true-source-filename target-filename t) + + ;; arrange that the temp copy of orig will be deleted + (rename-file (concat true-source-filename ediff-backup-extension) + true-source-filename t)) + + ;; make orig buffer read-only + (setq startup-hooks + (cons 'ediff-set-read-only-in-buf-A startup-hooks)) + + ;; set up a buf for the patched file + (setq target-buf (find-file-noselect target-filename)) + + (setq ctl-buf + (ediff-buffers-internal + buf-to-patch target-buf nil + startup-hooks 'epatch)) + (ediff-with-current-buffer ctl-buf + (setq ediff-patchbufer patch-buf + ediff-patch-diagnostics patch-diagnostics)) + + (bury-buffer patch-diagnostics) + (message "Type `P', if you need to see patch diagnostics") + ctl-buf)) + +(defun ediff-multi-patch-internal (patch-buf &optional startup-hooks) + (let (meta-buf) + (setq startup-hooks + ;; this sets various vars in the meta buffer inside + ;; ediff-prepare-meta-buffer + (cons `(lambda () + ;; tell what to do if the user clicks on a session record + (setq ediff-session-action-function + 'ediff-patch-file-form-meta + ediff-meta-patchbufer patch-buf) ) + startup-hooks)) + (setq meta-buf (ediff-prepare-meta-buffer + 'ediff-filegroup-action + (ediff-with-current-buffer patch-buf + (cons (ediff-make-new-meta-list-header + nil ; regexp + (format "%S" patch-buf) ; obj A + nil nil ; objects B,C + nil ; merge-auto-store-dir + nil ; comparison-func + ) + ediff-patch-map)) + "*Ediff Session Group Panel" + 'ediff-redraw-directory-group-buffer + 'ediff-multifile-patch + startup-hooks)) + (ediff-show-meta-buffer meta-buf) + )) + + + + +;; 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: + +;; arch-tag: 2fe2161e-e116-469b-90fa-5cbb44c1bd1b +;;; ediff-ptch.el ends here diff -r 681cd08dc0f7 -r e93288477c43 lisp/vc/ediff-util.el --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/lisp/vc/ediff-util.el Sun Jun 13 22:57:55 2010 +0000 @@ -0,0 +1,4291 @@ +;;; ediff-util.el --- the core commands and utilities of ediff + +;; Copyright (C) 1994, 1995, 1996, 1997, 1998, 1999, 2000, 2001, 2002, +;; 2003, 2004, 2005, 2006, 2007, 2008, 2009, 2010 Free Software Foundation, Inc. + +;; Author: Michael Kifer + +;; 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 3 of the License, 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. If not, see . + +;;; Commentary: + +;;; Code: + + +(provide 'ediff-util) + +;; Compiler pacifier +(defvar ediff-use-toolbar-p) +(defvar ediff-toolbar-height) +(defvar ediff-toolbar) +(defvar ediff-toolbar-3way) +(defvar bottom-toolbar) +(defvar bottom-toolbar-visible-p) +(defvar bottom-toolbar-height) +(defvar mark-active) + +(defvar ediff-after-quit-hook-internal nil) + +(eval-and-compile + (unless (fboundp 'declare-function) (defmacro declare-function (&rest r)))) + +(eval-when-compile + (require 'ediff)) + +;; end pacifier + + +(require 'ediff-init) +(require 'ediff-help) +(require 'ediff-mult) +(require 'ediff-wind) +(require 'ediff-diff) +(require 'ediff-merg) +;; for compatibility with current stable version of xemacs +(if (featurep 'xemacs) + (require 'ediff-tbar)) + + +;;; Functions + +(defun ediff-mode () + "Ediff mode controls all operations in a single Ediff session. +This mode is entered through one of the following commands: + `ediff' + `ediff-files' + `ediff-buffers' + `ebuffers' + `ediff3' + `ediff-files3' + `ediff-buffers3' + `ebuffers3' + `ediff-merge' + `ediff-merge-files' + `ediff-merge-files-with-ancestor' + `ediff-merge-buffers' + `ediff-merge-buffers-with-ancestor' + `ediff-merge-revisions' + `ediff-merge-revisions-with-ancestor' + `ediff-windows-wordwise' + `ediff-windows-linewise' + `ediff-regions-wordwise' + `ediff-regions-linewise' + `epatch' + `ediff-patch-file' + `ediff-patch-buffer' + `epatch-buffer' + `erevision' + `ediff-revision' + +Commands: +\\{ediff-mode-map}" + (kill-all-local-variables) + (setq major-mode 'ediff-mode) + (setq mode-name "Ediff") + ;; We use run-hooks instead of run-mode-hooks for two reasons. + ;; The ediff control buffer is read-only and it is not supposed to be + ;; modified by minor modes and such. So, run-mode-hooks doesn't do anything + ;; useful here on top of what run-hooks does. + ;; Second, changing run-hooks to run-mode-hooks would require an + ;; if-statement, since XEmacs doesn't have this. + (run-hooks 'ediff-mode-hook)) + + + +;;; Build keymaps + +(ediff-defvar-local ediff-mode-map nil + "Local keymap used in Ediff mode. +This is local to each Ediff Control Panel, so they may vary from invocation +to invocation.") + +;; Set up the keymap in the control buffer +(defun ediff-set-keys () + "Set up Ediff keymap, if necessary." + (if (null ediff-mode-map) + (ediff-setup-keymap)) + (use-local-map ediff-mode-map)) + +;; Reload Ediff keymap. For debugging only. +(defun ediff-reload-keymap () + (interactive) + (setq ediff-mode-map nil) + (ediff-set-keys)) + + +(defun ediff-setup-keymap () + "Set up the keymap used in the control buffer of Ediff." + (setq ediff-mode-map (make-sparse-keymap)) + (suppress-keymap ediff-mode-map) + + (define-key ediff-mode-map + (if (featurep 'emacs) [mouse-2] [button2]) 'ediff-help-for-quick-help) + (define-key ediff-mode-map "\C-m" 'ediff-help-for-quick-help) + + (define-key ediff-mode-map "p" 'ediff-previous-difference) + (define-key ediff-mode-map "\C-?" 'ediff-previous-difference) + (define-key ediff-mode-map [delete] 'ediff-previous-difference) + (define-key ediff-mode-map "\C-h" (if ediff-no-emacs-help-in-control-buffer + 'ediff-previous-difference nil)) + ;; must come after C-h, or else C-h wipes out backspace's binding in XEmacs + (define-key ediff-mode-map [backspace] 'ediff-previous-difference) + (define-key ediff-mode-map "n" 'ediff-next-difference) + (define-key ediff-mode-map " " 'ediff-next-difference) + (define-key ediff-mode-map "j" 'ediff-jump-to-difference) + (define-key ediff-mode-map "g" nil) + (define-key ediff-mode-map "ga" 'ediff-jump-to-difference-at-point) + (define-key ediff-mode-map "gb" 'ediff-jump-to-difference-at-point) + (define-key ediff-mode-map "q" 'ediff-quit) + (define-key ediff-mode-map "D" 'ediff-show-diff-output) + (define-key ediff-mode-map "z" 'ediff-suspend) + (define-key ediff-mode-map "\C-l" 'ediff-recenter) + (define-key ediff-mode-map "|" 'ediff-toggle-split) + (define-key ediff-mode-map "h" 'ediff-toggle-hilit) + (or ediff-word-mode + (define-key ediff-mode-map "@" 'ediff-toggle-autorefine)) + (if ediff-narrow-job + (define-key ediff-mode-map "%" 'ediff-toggle-narrow-region)) + (define-key ediff-mode-map "~" 'ediff-swap-buffers) + (define-key ediff-mode-map "v" 'ediff-scroll-vertically) + (define-key ediff-mode-map "\C-v" 'ediff-scroll-vertically) + (define-key ediff-mode-map "^" 'ediff-scroll-vertically) + (define-key ediff-mode-map "\M-v" 'ediff-scroll-vertically) + (define-key ediff-mode-map "V" 'ediff-scroll-vertically) + (define-key ediff-mode-map "<" 'ediff-scroll-horizontally) + (define-key ediff-mode-map ">" 'ediff-scroll-horizontally) + (define-key ediff-mode-map "i" 'ediff-status-info) + (define-key ediff-mode-map "E" 'ediff-documentation) + (define-key ediff-mode-map "?" 'ediff-toggle-help) + (define-key ediff-mode-map "!" 'ediff-update-diffs) + (define-key ediff-mode-map "M" 'ediff-show-current-session-meta-buffer) + (define-key ediff-mode-map "R" 'ediff-show-registry) + (or ediff-word-mode + (define-key ediff-mode-map "*" 'ediff-make-or-kill-fine-diffs)) + (define-key ediff-mode-map "a" nil) + (define-key ediff-mode-map "b" nil) + (define-key ediff-mode-map "r" nil) + (cond (ediff-merge-job + ;; Will barf if no ancestor + (define-key ediff-mode-map "/" 'ediff-show-ancestor) + ;; In merging, we allow only A->C and B->C copying. + (define-key ediff-mode-map "a" 'ediff-copy-A-to-C) + (define-key ediff-mode-map "b" 'ediff-copy-B-to-C) + (define-key ediff-mode-map "r" 'ediff-restore-diff-in-merge-buffer) + (define-key ediff-mode-map "s" 'ediff-shrink-window-C) + (define-key ediff-mode-map "+" 'ediff-combine-diffs) + (define-key ediff-mode-map "$" nil) + (define-key ediff-mode-map "$$" 'ediff-toggle-show-clashes-only) + (define-key ediff-mode-map "$*" 'ediff-toggle-skip-changed-regions) + (define-key ediff-mode-map "&" 'ediff-re-merge)) + (ediff-3way-comparison-job + (define-key ediff-mode-map "ab" 'ediff-copy-A-to-B) + (define-key ediff-mode-map "ba" 'ediff-copy-B-to-A) + (define-key ediff-mode-map "ac" 'ediff-copy-A-to-C) + (define-key ediff-mode-map "bc" 'ediff-copy-B-to-C) + (define-key ediff-mode-map "c" nil) + (define-key ediff-mode-map "ca" 'ediff-copy-C-to-A) + (define-key ediff-mode-map "cb" 'ediff-copy-C-to-B) + (define-key ediff-mode-map "ra" 'ediff-restore-diff) + (define-key ediff-mode-map "rb" 'ediff-restore-diff) + (define-key ediff-mode-map "rc" 'ediff-restore-diff) + (define-key ediff-mode-map "C" 'ediff-toggle-read-only)) + (t ; 2-way comparison + (define-key ediff-mode-map "a" 'ediff-copy-A-to-B) + (define-key ediff-mode-map "b" 'ediff-copy-B-to-A) + (define-key ediff-mode-map "ra" 'ediff-restore-diff) + (define-key ediff-mode-map "rb" 'ediff-restore-diff)) + ) ; cond + (define-key ediff-mode-map "G" 'ediff-submit-report) + (define-key ediff-mode-map "#" nil) + (define-key ediff-mode-map "#h" 'ediff-toggle-regexp-match) + (define-key ediff-mode-map "#f" 'ediff-toggle-regexp-match) + (define-key ediff-mode-map "#c" 'ediff-toggle-ignore-case) + (or ediff-word-mode + (define-key ediff-mode-map "##" 'ediff-toggle-skip-similar)) + (define-key ediff-mode-map "o" nil) + (define-key ediff-mode-map "A" 'ediff-toggle-read-only) + (define-key ediff-mode-map "B" 'ediff-toggle-read-only) + (define-key ediff-mode-map "w" nil) + (define-key ediff-mode-map "wa" 'ediff-save-buffer) + (define-key ediff-mode-map "wb" 'ediff-save-buffer) + (define-key ediff-mode-map "wd" 'ediff-save-buffer) + (define-key ediff-mode-map "=" 'ediff-inferior-compare-regions) + (if (and (fboundp 'ediff-show-patch-diagnostics) (ediff-patch-job)) + (define-key ediff-mode-map "P" 'ediff-show-patch-diagnostics)) + (if ediff-3way-job + (progn + (define-key ediff-mode-map "wc" 'ediff-save-buffer) + (define-key ediff-mode-map "gc" 'ediff-jump-to-difference-at-point) + )) + + (define-key ediff-mode-map "m" 'ediff-toggle-wide-display) + + ;; Allow ediff-mode-map to be referenced indirectly + (fset 'ediff-mode-map ediff-mode-map) + (run-hooks 'ediff-keymap-setup-hook)) + + +;;; Setup functions + +;; Common startup entry for all Ediff functions It now returns control buffer +;; so other functions can do post-processing SETUP-PARAMETERS is a list of the +;; form ((param .val) (param . val)...) This serves a similar purpose to +;; STARTUP-HOOKS, but these parameters are set in the new control buffer right +;; after this buf is created and before any windows are set and such. +(defun ediff-setup (buffer-A file-A buffer-B file-B buffer-C file-C + startup-hooks setup-parameters + &optional merge-buffer-file) + (run-hooks 'ediff-before-setup-hook) + ;; ediff-convert-standard-filename puts file names in the form appropriate + ;; for the OS at hand. + (setq file-A (ediff-convert-standard-filename (expand-file-name file-A))) + (setq file-B (ediff-convert-standard-filename (expand-file-name file-B))) + (if (stringp file-C) + (setq file-C + (ediff-convert-standard-filename (expand-file-name file-C)))) + (if (stringp merge-buffer-file) + (progn + (setq merge-buffer-file + (ediff-convert-standard-filename + (expand-file-name merge-buffer-file))) + ;; check the directory exists + (or (file-exists-p (file-name-directory merge-buffer-file)) + (error "Directory %s given as place to save the merge doesn't exist" + (abbreviate-file-name + (file-name-directory merge-buffer-file)))) + (if (and (file-exists-p merge-buffer-file) + (file-directory-p merge-buffer-file)) + (error "The merge buffer file %s must not be a directory" + (abbreviate-file-name merge-buffer-file))) + )) + (let* ((control-buffer-name + (ediff-unique-buffer-name "*Ediff Control Panel" "*")) + (control-buffer (ediff-with-current-buffer buffer-A + (get-buffer-create control-buffer-name)))) + (ediff-with-current-buffer control-buffer + (ediff-mode) + + (make-local-variable 'ediff-use-long-help-message) + (make-local-variable 'ediff-prefer-iconified-control-frame) + (make-local-variable 'ediff-split-window-function) + (make-local-variable 'ediff-default-variant) + (make-local-variable 'ediff-merge-window-share) + (make-local-variable 'ediff-window-setup-function) + (make-local-variable 'ediff-keep-variants) + + (make-local-variable 'window-min-height) + (setq window-min-height 2) + + (if (featurep 'xemacs) + (make-local-hook 'ediff-after-quit-hook-internal)) + + ;; unwrap set up parameters passed as argument + (while setup-parameters + (set (car (car setup-parameters)) (cdr (car setup-parameters))) + (setq setup-parameters (cdr setup-parameters))) + + ;; set variables classifying the current ediff job + ;; must come AFTER setup-parameters + (setq ediff-3way-comparison-job (ediff-3way-comparison-job) + ediff-merge-job (ediff-merge-job) + ediff-merge-with-ancestor-job (ediff-merge-with-ancestor-job) + ediff-3way-job (ediff-3way-job) + ediff-diff3-job (ediff-diff3-job) + ediff-narrow-job (ediff-narrow-job) + ediff-windows-job (ediff-windows-job) + ediff-word-mode-job (ediff-word-mode-job)) + + ;; Don't delete variants in case of ediff-buffer-* jobs without asking. + ;; This is because one may loose work---dangerous. + (if (string-match "buffer" (symbol-name ediff-job-name)) + (setq ediff-keep-variants t)) + + (if (featurep 'xemacs) + (make-local-hook 'pre-command-hook)) + + (if (ediff-window-display-p) + (add-hook 'pre-command-hook 'ediff-spy-after-mouse nil 'local)) + (setq ediff-mouse-pixel-position (mouse-pixel-position)) + + ;; adjust for merge jobs + (if ediff-merge-job + (let ((buf + ;; If default variant is `combined', the right stuff is + ;; inserted by ediff-do-merge + ;; Note: at some point, we tried to put ancestor buffer here + ;; (which is currently buffer C. This didn't work right + ;; because the merge buffer will contain lossage: diff regions + ;; in the ancestor, which correspond to revisions that agree + ;; in both buf A and B. + (cond ((eq ediff-default-variant 'default-B) + buffer-B) + (t buffer-A)))) + + (setq ediff-split-window-function + ediff-merge-split-window-function) + + ;; remember the ancestor buffer, if any + (setq ediff-ancestor-buffer buffer-C) + + (setq buffer-C + (get-buffer-create + (ediff-unique-buffer-name "*ediff-merge" "*"))) + (with-current-buffer buffer-C + (insert-buffer-substring buf) + (goto-char (point-min)) + (funcall (ediff-with-current-buffer buf major-mode)) + (widen) ; merge buffer is always widened + (add-hook 'local-write-file-hooks 'ediff-set-merge-mode nil t) + ))) + (setq buffer-read-only nil + ediff-buffer-A buffer-A + ediff-buffer-B buffer-B + ediff-buffer-C buffer-C + ediff-control-buffer control-buffer) + + (ediff-choose-syntax-table) + + (setq ediff-control-buffer-suffix + (if (string-match "<[0-9]*>" control-buffer-name) + (substring control-buffer-name + (match-beginning 0) (match-end 0)) + "") + ediff-control-buffer-number + (max + 0 + (1- + (string-to-number + (substring + ediff-control-buffer-suffix + (or + (string-match "[0-9]+" ediff-control-buffer-suffix) + 0)))))) + + (setq ediff-error-buffer + (get-buffer-create (ediff-unique-buffer-name "*ediff-errors" "*"))) + + (with-current-buffer ediff-error-buffer + (setq buffer-undo-list t)) + + (ediff-with-current-buffer buffer-A (ediff-strip-mode-line-format)) + (ediff-with-current-buffer buffer-B (ediff-strip-mode-line-format)) + (if ediff-3way-job + (ediff-with-current-buffer buffer-C (ediff-strip-mode-line-format))) + (if (ediff-buffer-live-p ediff-ancestor-buffer) + (ediff-with-current-buffer ediff-ancestor-buffer + (ediff-strip-mode-line-format))) + + (ediff-save-protected-variables) ; save variables to be restored on exit + + ;; ediff-setup-diff-regions-function must be set after setup + ;; parameters are processed. + (setq ediff-setup-diff-regions-function + (if ediff-diff3-job + 'ediff-setup-diff-regions3 + 'ediff-setup-diff-regions)) + + (setq ediff-wide-bounds + (list (ediff-make-bullet-proof-overlay + '(point-min) '(point-max) ediff-buffer-A) + (ediff-make-bullet-proof-overlay + '(point-min) '(point-max) ediff-buffer-B) + (ediff-make-bullet-proof-overlay + '(point-min) '(point-max) ediff-buffer-C))) + + ;; This has effect only on ediff-windows/regions + ;; In all other cases, ediff-visible-region sets visibility bounds to + ;; ediff-wide-bounds, and ediff-narrow-bounds are ignored. + (if ediff-start-narrowed + (setq ediff-visible-bounds ediff-narrow-bounds) + (setq ediff-visible-bounds ediff-wide-bounds)) + + (ediff-set-keys) ; comes after parameter setup + + ;; set up ediff-narrow-bounds, if not set + (or ediff-narrow-bounds + (setq ediff-narrow-bounds ediff-wide-bounds)) + + ;; All these must be inside ediff-with-current-buffer control-buffer, + ;; since these vars are local to control-buffer + ;; These won't run if there are errors in diff + (ediff-with-current-buffer ediff-buffer-A + (ediff-nuke-selective-display) + (run-hooks 'ediff-prepare-buffer-hook) + (if (ediff-with-current-buffer control-buffer ediff-merge-job) + (setq buffer-read-only t)) + ;; add control-buffer to the list of sessions--no longer used, but may + ;; be used again in the future + (or (memq control-buffer ediff-this-buffer-ediff-sessions) + (setq ediff-this-buffer-ediff-sessions + (cons control-buffer ediff-this-buffer-ediff-sessions))) + (if ediff-make-buffers-readonly-at-startup + (setq buffer-read-only t)) + ) + + (ediff-with-current-buffer ediff-buffer-B + (ediff-nuke-selective-display) + (run-hooks 'ediff-prepare-buffer-hook) + (if (ediff-with-current-buffer control-buffer ediff-merge-job) + (setq buffer-read-only t)) + ;; add control-buffer to the list of sessions + (or (memq control-buffer ediff-this-buffer-ediff-sessions) + (setq ediff-this-buffer-ediff-sessions + (cons control-buffer ediff-this-buffer-ediff-sessions))) + (if ediff-make-buffers-readonly-at-startup + (setq buffer-read-only t)) + ) + + (if ediff-3way-job + (ediff-with-current-buffer ediff-buffer-C + (ediff-nuke-selective-display) + ;; the merge bufer should never be narrowed + ;; (it can happen if it is on rmail-mode or similar) + (if (ediff-with-current-buffer control-buffer ediff-merge-job) + (widen)) + (run-hooks 'ediff-prepare-buffer-hook) + ;; add control-buffer to the list of sessions + (or (memq control-buffer ediff-this-buffer-ediff-sessions) + (setq ediff-this-buffer-ediff-sessions + (cons control-buffer + ediff-this-buffer-ediff-sessions))) + (if ediff-make-buffers-readonly-at-startup + (setq buffer-read-only t) + (setq buffer-read-only nil)) + )) + + (if (ediff-buffer-live-p ediff-ancestor-buffer) + (ediff-with-current-buffer ediff-ancestor-buffer + (ediff-nuke-selective-display) + (setq buffer-read-only t) + (run-hooks 'ediff-prepare-buffer-hook) + (or (memq control-buffer ediff-this-buffer-ediff-sessions) + (setq ediff-this-buffer-ediff-sessions + (cons control-buffer + ediff-this-buffer-ediff-sessions))) + )) + + ;; the following must be after setting up ediff-narrow-bounds AND after + ;; nuking selective display + (funcall ediff-setup-diff-regions-function file-A file-B file-C) + (setq ediff-number-of-differences (length ediff-difference-vector-A)) + (setq ediff-current-difference -1) + + (ediff-make-current-diff-overlay 'A) + (ediff-make-current-diff-overlay 'B) + (if ediff-3way-job + (ediff-make-current-diff-overlay 'C)) + (if ediff-merge-with-ancestor-job + (ediff-make-current-diff-overlay 'Ancestor)) + + (ediff-setup-windows buffer-A buffer-B buffer-C control-buffer) + + (let ((shift-A (ediff-overlay-start + (ediff-get-value-according-to-buffer-type + 'A ediff-narrow-bounds))) + (shift-B (ediff-overlay-start + (ediff-get-value-according-to-buffer-type + 'B ediff-narrow-bounds))) + (shift-C (ediff-overlay-start + (ediff-get-value-according-to-buffer-type + 'C ediff-narrow-bounds)))) + ;; position point in buf A + (save-excursion + (select-window ediff-window-A) + (goto-char shift-A)) + ;; position point in buf B + (save-excursion + (select-window ediff-window-B) + (goto-char shift-B)) + (if ediff-3way-job + (save-excursion + (select-window ediff-window-C) + (goto-char shift-C))) + ) + + (select-window ediff-control-window) + (ediff-visible-region) + + (run-hooks 'startup-hooks) + (ediff-arrange-autosave-in-merge-jobs merge-buffer-file) + + (ediff-refresh-mode-lines) + (setq buffer-read-only t) + (setq ediff-session-registry + (cons control-buffer ediff-session-registry)) + (ediff-update-registry) + (if (ediff-buffer-live-p ediff-meta-buffer) + (ediff-update-meta-buffer + ediff-meta-buffer nil ediff-meta-session-number)) + (run-hooks 'ediff-startup-hook) + ) ; eval in control-buffer + control-buffer)) + + +;; This function assumes that we are in the window where control buffer is +;; to reside. +(defun ediff-setup-control-buffer (ctl-buf) + "Set up window for control buffer." + (if (window-dedicated-p (selected-window)) + (set-buffer ctl-buf) ; we are in control frame but just in case + (switch-to-buffer ctl-buf)) + (let ((window-min-height 2)) + (erase-buffer) + (ediff-set-help-message) + (insert ediff-help-message) + (shrink-window-if-larger-than-buffer) + (or (ediff-multiframe-setup-p) + (ediff-indent-help-message)) + (ediff-set-help-overlays) + + (set-buffer-modified-p nil) + (ediff-refresh-mode-lines) + (setq ediff-control-window (selected-window)) + (setq ediff-window-config-saved + (format "%S%S%S%S%S%S%S" + ediff-control-window + ediff-window-A + ediff-window-B + ediff-window-C + ediff-split-window-function + (ediff-multiframe-setup-p) + ediff-wide-display-p)) + + (set-window-dedicated-p (selected-window) t) + ;; In multiframe, toolbar is set in ediff-setup-control-frame + (if (not (ediff-multiframe-setup-p)) + (ediff-make-bottom-toolbar)) ; this checks if toolbar is requested + (goto-char (point-min)) + (skip-chars-forward ediff-whitespace))) + +;; This executes in control buffer and sets auto-save, visited file name, etc, +;; in the merge buffer +(defun ediff-arrange-autosave-in-merge-jobs (merge-buffer-file) + (if (not ediff-merge-job) + () + (if (stringp merge-buffer-file) + (setq ediff-autostore-merges t + ediff-merge-store-file merge-buffer-file)) + (if (stringp ediff-merge-store-file) + (progn + ;; save before leaving ctl buffer + (ediff-verify-file-merge-buffer ediff-merge-store-file) + (setq merge-buffer-file ediff-merge-store-file) + (ediff-with-current-buffer ediff-buffer-C + (set-visited-file-name merge-buffer-file)))) + (ediff-with-current-buffer ediff-buffer-C + (setq buffer-offer-save t) ; ask before killing buffer + ;; make sure the contents is auto-saved + (auto-save-mode 1)) + )) + + +;;; Commands for working with Ediff + +(defun ediff-update-diffs () + "Recompute difference regions in buffers A, B, and C. +Buffers are not synchronized with their respective files, so changes done +to these buffers are not saved at this point---the user can do this later, +if necessary." + (interactive) + (ediff-barf-if-not-control-buffer) + (if (and (ediff-buffer-live-p ediff-ancestor-buffer) + (not + (y-or-n-p + "Ancestor buffer will not be used. Recompute diffs anyway? "))) + (error "Recomputation of differences canceled")) + + (let ((point-A (ediff-with-current-buffer ediff-buffer-A (point))) + ;;(point-B (ediff-with-current-buffer ediff-buffer-B (point))) + (tmp-buffer (get-buffer-create ediff-tmp-buffer)) + (buf-A-file-name (buffer-file-name ediff-buffer-A)) + (buf-B-file-name (buffer-file-name ediff-buffer-B)) + ;; (null ediff-buffer-C) is no problem, as we later check if + ;; ediff-buffer-C is alive + (buf-C-file-name (buffer-file-name ediff-buffer-C)) + (overl-A (ediff-get-value-according-to-buffer-type + 'A ediff-narrow-bounds)) + (overl-B (ediff-get-value-according-to-buffer-type + 'B ediff-narrow-bounds)) + (overl-C (ediff-get-value-according-to-buffer-type + 'C ediff-narrow-bounds)) + beg-A end-A beg-B end-B beg-C end-C + file-A file-B file-C) + + (if (stringp buf-A-file-name) + (setq buf-A-file-name (file-name-nondirectory buf-A-file-name))) + (if (stringp buf-B-file-name) + (setq buf-B-file-name (file-name-nondirectory buf-B-file-name))) + (if (stringp buf-C-file-name) + (setq buf-C-file-name (file-name-nondirectory buf-C-file-name))) + + (ediff-unselect-and-select-difference -1) + + (setq beg-A (ediff-overlay-start overl-A) + beg-B (ediff-overlay-start overl-B) + beg-C (ediff-overlay-start overl-C) + end-A (ediff-overlay-end overl-A) + end-B (ediff-overlay-end overl-B) + end-C (ediff-overlay-end overl-C)) + + (if ediff-word-mode + (progn + (ediff-wordify beg-A end-A ediff-buffer-A tmp-buffer) + (setq file-A (ediff-make-temp-file tmp-buffer "regA")) + (ediff-wordify beg-B end-B ediff-buffer-B tmp-buffer) + (setq file-B (ediff-make-temp-file tmp-buffer "regB")) + (if ediff-3way-job + (progn + (ediff-wordify beg-C end-C ediff-buffer-C tmp-buffer) + (setq file-C (ediff-make-temp-file tmp-buffer "regC")))) + ) + ;; not word-mode + (setq file-A (ediff-make-temp-file ediff-buffer-A buf-A-file-name)) + (setq file-B (ediff-make-temp-file ediff-buffer-B buf-B-file-name)) + (if ediff-3way-job + (setq file-C (ediff-make-temp-file ediff-buffer-C buf-C-file-name))) + ) + + (ediff-clear-diff-vector 'ediff-difference-vector-A 'fine-diffs-also) + (ediff-clear-diff-vector 'ediff-difference-vector-B 'fine-diffs-also) + (ediff-clear-diff-vector 'ediff-difference-vector-C 'fine-diffs-also) + (ediff-clear-diff-vector + 'ediff-difference-vector-Ancestor 'fine-diffs-also) + ;; let them garbage collect. we can't use the ancestor after recomputing + ;; the diffs. + (setq ediff-difference-vector-Ancestor nil + ediff-ancestor-buffer nil + ediff-state-of-merge nil) + + (setq ediff-killed-diffs-alist nil) ; invalidate saved killed diff regions + + ;; In case of merge job, fool it into thinking that it is just doing + ;; comparison + (let ((ediff-setup-diff-regions-function ediff-setup-diff-regions-function) + (ediff-3way-comparison-job ediff-3way-comparison-job) + (ediff-merge-job ediff-merge-job) + (ediff-merge-with-ancestor-job ediff-merge-with-ancestor-job) + (ediff-job-name ediff-job-name)) + (if ediff-merge-job + (setq ediff-setup-diff-regions-function 'ediff-setup-diff-regions3 + ediff-3way-comparison-job t + ediff-merge-job nil + ediff-merge-with-ancestor-job nil + ediff-job-name 'ediff-files3)) + (funcall ediff-setup-diff-regions-function file-A file-B file-C)) + + (setq ediff-number-of-differences (length ediff-difference-vector-A)) + (delete-file file-A) + (delete-file file-B) + (if file-C + (delete-file file-C)) + + (if ediff-3way-job + (ediff-set-state-of-all-diffs-in-all-buffers ediff-control-buffer)) + + (ediff-jump-to-difference (ediff-diff-at-point 'A point-A)) + (message "") + )) + +;; Not bound to any key---to dangerous. A user can do it if necessary. +(defun ediff-revert-buffers-then-recompute-diffs (noconfirm) + "Revert buffers A, B and C. Then rerun Ediff on file A and file B." + (interactive "P") + (ediff-barf-if-not-control-buffer) + (let ((bufA ediff-buffer-A) + (bufB ediff-buffer-B) + (bufC ediff-buffer-C) + (ctl-buf ediff-control-buffer) + (keep-variants ediff-keep-variants) + (ancestor-buf ediff-ancestor-buffer) + (ancestor-job ediff-merge-with-ancestor-job) + (merge ediff-merge-job) + (comparison ediff-3way-comparison-job)) + (ediff-with-current-buffer bufA + (revert-buffer t noconfirm)) + (ediff-with-current-buffer bufB + (revert-buffer t noconfirm)) + ;; this should only be executed in a 3way comparison, not in merge + (if comparison + (ediff-with-current-buffer bufC + (revert-buffer t noconfirm))) + (if merge + (progn + (set-buffer ctl-buf) + ;; the argument says whether to reverse the meaning of + ;; ediff-keep-variants, i.e., ediff-really-quit runs here with + ;; variants kept. + (ediff-really-quit (not keep-variants)) + (kill-buffer bufC) + (if ancestor-job + (ediff-merge-buffers-with-ancestor bufA bufB ancestor-buf) + (ediff-merge-buffers bufA bufB))) + (ediff-update-diffs)))) + + +;; optional NO-REHIGHLIGHT says to not rehighlight buffers +(defun ediff-recenter (&optional no-rehighlight) + "Bring the highlighted region of all buffers being compared into view. +Reestablish the default three-window display." + (interactive) + (ediff-barf-if-not-control-buffer) + (let (buffer-read-only) + (if (and (ediff-buffer-live-p ediff-buffer-A) + (ediff-buffer-live-p ediff-buffer-B) + (or (not ediff-3way-job) + (ediff-buffer-live-p ediff-buffer-C))) + (ediff-setup-windows + ediff-buffer-A ediff-buffer-B ediff-buffer-C ediff-control-buffer) + (or (eq this-command 'ediff-quit) + (message ediff-KILLED-VITAL-BUFFER + (beep 1))) + )) + + ;; set visibility range appropriate to this invocation of Ediff. + (ediff-visible-region) + ;; raise + (if (and (ediff-window-display-p) + (symbolp this-command) + (symbolp last-command) + ;; Either one of the display-changing commands + (or (memq this-command + '(ediff-recenter + ediff-dir-action ediff-registry-action + ediff-patch-action + ediff-toggle-wide-display ediff-toggle-multiframe)) + ;; Or one of the movement cmds and prev cmd was an Ediff cmd + ;; This avoids raising frames unnecessarily. + (and (memq this-command + '(ediff-next-difference + ediff-previous-difference + ediff-jump-to-difference + ediff-jump-to-difference-at-point)) + (not (string-match "^ediff-" (symbol-name last-command))) + ))) + (progn + (if (window-live-p ediff-window-A) + (raise-frame (window-frame ediff-window-A))) + (if (window-live-p ediff-window-B) + (raise-frame (window-frame ediff-window-B))) + (if (window-live-p ediff-window-C) + (raise-frame (window-frame ediff-window-C))))) + (if (and (ediff-window-display-p) + (frame-live-p ediff-control-frame) + (not ediff-use-long-help-message) + (not (ediff-frame-iconified-p ediff-control-frame))) + (raise-frame ediff-control-frame)) + + ;; Redisplay whatever buffers are showing, if there is a selected difference + (let ((control-frame ediff-control-frame) + (control-buf ediff-control-buffer)) + (if (and (ediff-buffer-live-p ediff-buffer-A) + (ediff-buffer-live-p ediff-buffer-B) + (or (not ediff-3way-job) + (ediff-buffer-live-p ediff-buffer-C))) + (progn + (or no-rehighlight + (ediff-select-difference ediff-current-difference)) + + (ediff-recenter-one-window 'A) + (ediff-recenter-one-window 'B) + (if ediff-3way-job + (ediff-recenter-one-window 'C)) + + (ediff-with-current-buffer control-buf + (ediff-recenter-ancestor) ; check if ancestor is alive + + (if (and (ediff-multiframe-setup-p) + (not ediff-use-long-help-message) + (not (ediff-frame-iconified-p ediff-control-frame))) + ;; never grab mouse on quit in this place + (ediff-reset-mouse + control-frame + (eq this-command 'ediff-quit)))) + )) + + (or no-rehighlight + (ediff-restore-highlighting)) + (ediff-with-current-buffer control-buf (ediff-refresh-mode-lines)) + )) + +;; this function returns to the window it was called from +;; (which was the control window) +(defun ediff-recenter-one-window (buf-type) + (if (ediff-valid-difference-p) + ;; context must be saved before switching to windows A/B/C + (let* ((ctl-wind (selected-window)) + (shift (ediff-overlay-start + (ediff-get-value-according-to-buffer-type + buf-type ediff-narrow-bounds))) + (job-name ediff-job-name) + (control-buf ediff-control-buffer) + (window-name (ediff-get-symbol-from-alist + buf-type ediff-window-alist)) + (window (if (window-live-p (symbol-value window-name)) + (symbol-value window-name)))) + + (if (and window ediff-windows-job) + (set-window-start window shift)) + (if window + (progn + (select-window window) + (ediff-deactivate-mark) + (ediff-position-region + (ediff-get-diff-posn buf-type 'beg nil control-buf) + (ediff-get-diff-posn buf-type 'end nil control-buf) + (ediff-get-diff-posn buf-type 'beg nil control-buf) + job-name + ))) + (select-window ctl-wind) + ))) + +(defun ediff-recenter-ancestor () + ;; do half-hearted job by recentering the ancestor buffer, if it is alive and + ;; visible. + (if (and (ediff-buffer-live-p ediff-ancestor-buffer) + (ediff-valid-difference-p)) + (let ((window (ediff-get-visible-buffer-window ediff-ancestor-buffer)) + (ctl-wind (selected-window)) + (job-name ediff-job-name) + (ctl-buf ediff-control-buffer)) + (ediff-with-current-buffer ediff-ancestor-buffer + (goto-char (ediff-get-diff-posn 'Ancestor 'beg nil ctl-buf)) + (if window + (progn + (select-window window) + (ediff-position-region + (ediff-get-diff-posn 'Ancestor 'beg nil ctl-buf) + (ediff-get-diff-posn 'Ancestor 'end nil ctl-buf) + (ediff-get-diff-posn 'Ancestor 'beg nil ctl-buf) + job-name)))) + (select-window ctl-wind) + ))) + + +;; This will have to be refined for 3way jobs +(defun ediff-toggle-split () + "Toggle vertical/horizontal window split. +Does nothing if file-A and file-B are in different frames." + (interactive) + (ediff-barf-if-not-control-buffer) + (let* ((wind-A (if (window-live-p ediff-window-A) ediff-window-A)) + (wind-B (if (window-live-p ediff-window-B) ediff-window-B)) + (wind-C (if (window-live-p ediff-window-C) ediff-window-C)) + (frame-A (if wind-A (window-frame wind-A))) + (frame-B (if wind-B (window-frame wind-B))) + (frame-C (if wind-C (window-frame wind-C)))) + (if (or (eq frame-A frame-B) + (not (frame-live-p frame-A)) + (not (frame-live-p frame-B)) + (if ediff-3way-comparison-job + (or (not (frame-live-p frame-C)) + (eq frame-A frame-C) (eq frame-B frame-C)))) + (setq ediff-split-window-function + (if (eq ediff-split-window-function 'split-window-vertically) + 'split-window-horizontally + 'split-window-vertically)) + (message "Buffers being compared are in different frames")) + (ediff-recenter 'no-rehighlight))) + +(defun ediff-toggle-hilit () + "Switch between highlighting using ASCII flags and highlighting using faces. +On a dumb terminal, switches between ASCII highlighting and no highlighting." + (interactive) + (ediff-barf-if-not-control-buffer) + + (ediff-unselect-and-select-difference + ediff-current-difference 'unselect-only) + ;; cycle through highlighting + (cond ((and ediff-use-faces + (ediff-has-face-support-p) + ediff-highlight-all-diffs) + (message "Unhighlighting unselected difference regions") + (setq ediff-highlight-all-diffs nil + ediff-highlighting-style 'face)) + ((or (and ediff-use-faces (ediff-has-face-support-p) + (eq ediff-highlighting-style 'face)) ; has face support + (and (not (ediff-has-face-support-p)) ; no face support + (eq ediff-highlighting-style 'off))) + (message "Highlighting with ASCII flags") + (setq ediff-highlighting-style 'ascii + ediff-highlight-all-diffs nil + ediff-use-faces nil)) + ((eq ediff-highlighting-style 'ascii) + (message "ASCII highlighting flags removed") + (setq ediff-highlighting-style 'off + ediff-highlight-all-diffs nil)) + ((ediff-has-face-support-p) ; catch-all for cases with face support + (message "Re-highlighting all difference regions") + (setq ediff-use-faces t + ediff-highlighting-style 'face + ediff-highlight-all-diffs t))) + + (if (and ediff-use-faces ediff-highlight-all-diffs) + (ediff-paint-background-regions) + (ediff-paint-background-regions 'unhighlight)) + + (ediff-unselect-and-select-difference + ediff-current-difference 'select-only)) + + +(defun ediff-toggle-autorefine () + "Toggle auto-refine mode." + (interactive) + (ediff-barf-if-not-control-buffer) + (if ediff-word-mode + (error "No fine differences in this mode")) + (cond ((eq ediff-auto-refine 'nix) + (setq ediff-auto-refine 'on) + (ediff-make-fine-diffs ediff-current-difference 'noforce) + (message "Auto-refining is ON")) + ((eq ediff-auto-refine 'on) + (message "Auto-refining is OFF") + (setq ediff-auto-refine 'off)) + (t ;; nix 'em + (ediff-set-fine-diff-properties ediff-current-difference 'default) + (message "Refinements are HIDDEN") + (setq ediff-auto-refine 'nix)) + )) + +(defun ediff-show-ancestor () + "Show the ancestor buffer in a suitable window." + (interactive) + (ediff-recenter) + (or (ediff-buffer-live-p ediff-ancestor-buffer) + (if ediff-merge-with-ancestor-job + (error "Lost connection to ancestor buffer...sorry") + (error "Not merging with ancestor"))) + (let (wind) + (cond ((setq wind (ediff-get-visible-buffer-window ediff-ancestor-buffer)) + (raise-frame (window-frame wind))) + (t (set-window-buffer ediff-window-C ediff-ancestor-buffer))))) + +(defun ediff-make-or-kill-fine-diffs (arg) + "Compute fine diffs. With negative prefix arg, kill fine diffs. +In both cases, operates on the current difference region." + (interactive "P") + (ediff-barf-if-not-control-buffer) + (cond ((eq arg '-) + (ediff-clear-fine-differences ediff-current-difference)) + ((and (numberp arg) (< arg 0)) + (ediff-clear-fine-differences ediff-current-difference)) + (t (ediff-make-fine-diffs)))) + + +(defun ediff-toggle-help () + "Toggle short/long help message." + (interactive) + (ediff-barf-if-not-control-buffer) + (let (buffer-read-only) + (erase-buffer) + (setq ediff-use-long-help-message (not ediff-use-long-help-message)) + (ediff-set-help-message)) + ;; remember the icon status of the control frame when the user requested + ;; full control message + (if (and ediff-use-long-help-message (ediff-multiframe-setup-p)) + (setq ediff-prefer-iconified-control-frame + (ediff-frame-iconified-p ediff-control-frame))) + + (setq ediff-window-config-saved "") ; force redisplay + (ediff-recenter 'no-rehighlight)) + + +;; If BUF, this is the buffer to toggle, not current buffer. +(defun ediff-toggle-read-only (&optional buf) + "Toggle read-only in current buffer. +If buffer is under version control and locked, check it out first. +If optional argument BUF is specified, toggle read-only in that buffer instead +of the current buffer." + (interactive) + (ediff-barf-if-not-control-buffer) + (let ((ctl-buf (if (null buf) (current-buffer))) + (buf-type (ediff-char-to-buftype (ediff-last-command-char)))) + (or buf (ediff-recenter)) + (or buf + (setq buf (ediff-get-buffer buf-type))) + + (ediff-with-current-buffer buf ; eval in buf A/B/C + (let* ((file (buffer-file-name buf)) + (file-writable (and file + (file-exists-p file) + (file-writable-p file))) + (toggle-ro-cmd (cond (ediff-toggle-read-only-function) + ((ediff-file-checked-out-p file) + 'toggle-read-only) + (file-writable 'toggle-read-only) + (t (key-binding "\C-x\C-q"))))) + ;; If the file is checked in, make sure we don't make buffer modifiable + ;; without warning the user. The user can fool our checks by making the + ;; buffer non-RO without checking the file out. We regard this as a + ;; user problem. + (if (and (ediff-file-checked-in-p file) + ;; If ctl-buf is null, this means we called this + ;; non-interactively, in which case don't ask questions + ctl-buf) + (cond ((not buffer-read-only) + (setq toggle-ro-cmd 'toggle-read-only)) + ((and (or (beep 1) t) ; always beep + (y-or-n-p + (format + "File %s is under version control. Check it out? " + (ediff-abbreviate-file-name file)))) + ;; if we checked the file out, we should also change the + ;; original state of buffer-read-only to nil. If we don't + ;; do this, the mode line will show %%, since the file was + ;; RO before ediff started, so the user will think the file + ;; is checked in. + (ediff-with-current-buffer ctl-buf + (ediff-change-saved-variable + 'buffer-read-only nil buf-type))) + (t + (setq toggle-ro-cmd 'toggle-read-only) + (beep 1) (beep 1) + (message + "Boy, this is risky! Don't modify this file...") + (sit-for 3)))) ; let the user see the warning + (if (and toggle-ro-cmd + (string-match "toggle-read-only" (symbol-name toggle-ro-cmd))) + (save-excursion + (save-window-excursion + (select-window (ediff-get-visible-buffer-window buf)) + (command-execute toggle-ro-cmd))) + (error "Don't know how to toggle read-only in buffer %S" buf)) + + ;; Check if we made the current buffer updatable, but its file is RO. + ;; Signal a warning in this case. + (if (and file (not buffer-read-only) + (eq this-command 'ediff-toggle-read-only) + (file-exists-p file) + (not (file-writable-p file))) + (progn + (beep 1) + (message "Warning: file %s is read-only" + (ediff-abbreviate-file-name file)))) + )))) + +;; checkout if visited file is checked in +(defun ediff-maybe-checkout (buf) + (let ((file (expand-file-name (buffer-file-name buf))) + (checkout-function (key-binding "\C-x\C-q"))) + (if (and (ediff-file-checked-in-p file) + (or (beep 1) t) + (y-or-n-p + (format + "File %s is under version control. Check it out? " + (ediff-abbreviate-file-name file)))) + (ediff-with-current-buffer buf + (command-execute checkout-function))))) + + +;; This is a simple-minded check for whether a file is under version control. +;; If file,v exists but file doesn't, this file is considered to be not checked +;; in and not checked out for the purpose of patching (since patch won't be +;; able to read such a file anyway). +;; FILE is a string representing file name +;;(defun ediff-file-under-version-control (file) +;; (let* ((filedir (file-name-directory file)) +;; (file-nondir (file-name-nondirectory file)) +;; (trial (concat file-nondir ",v")) +;; (full-trial (concat filedir trial)) +;; (full-rcs-trial (concat filedir "RCS/" trial))) +;; (and (stringp file) +;; (file-exists-p file) +;; (or +;; (and +;; (file-exists-p full-trial) +;; ;; in FAT FS, `file,v' and `file' may turn out to be the same! +;; ;; don't be fooled by this! +;; (not (equal (file-attributes file) +;; (file-attributes full-trial)))) +;; ;; check if a version is in RCS/ directory +;; (file-exists-p full-rcs-trial))) +;; )) + + +(defun ediff-file-checked-out-p (file) + (or (not (featurep 'vc-hooks)) + (and (vc-backend file) + (if (fboundp 'vc-state) + (or (memq (vc-state file) '(edited needs-merge)) + (stringp (vc-state file))) + ;; XEmacs has no vc-state + (when (featurep 'xemacs) (vc-locking-user file))) + ))) + +(defun ediff-file-checked-in-p (file) + (and (featurep 'vc-hooks) + ;; Only RCS and SCCS files are considered checked in + (memq (vc-backend file) '(RCS SCCS)) + (if (fboundp 'vc-state) + (and + (not (memq (vc-state file) '(edited needs-merge))) + (not (stringp (vc-state file)))) + ;; XEmacs has no vc-state + (when (featurep 'xemacs) (not (vc-locking-user file)))) + )) + +(defun ediff-file-compressed-p (file) + (condition-case nil + (require 'jka-compr) + (error)) + (if (featurep 'jka-compr) + (string-match (jka-compr-build-file-regexp) file))) + + +(defun ediff-swap-buffers () + "Rotate the display of buffers A, B, and C." + (interactive) + (ediff-barf-if-not-control-buffer) + (if (and (window-live-p ediff-window-A) (window-live-p ediff-window-B)) + (let ((buf ediff-buffer-A) + (values ediff-buffer-values-orig-A) + (diff-vec ediff-difference-vector-A) + (hide-regexp ediff-regexp-hide-A) + (focus-regexp ediff-regexp-focus-A) + (wide-visibility-p (eq ediff-visible-bounds ediff-wide-bounds)) + (overlay (if (ediff-has-face-support-p) + ediff-current-diff-overlay-A))) + (if ediff-3way-comparison-job + (progn + (set-window-buffer ediff-window-A ediff-buffer-C) + (set-window-buffer ediff-window-B ediff-buffer-A) + (set-window-buffer ediff-window-C ediff-buffer-B) + ) + (set-window-buffer ediff-window-A ediff-buffer-B) + (set-window-buffer ediff-window-B ediff-buffer-A)) + ;; swap diff buffers + (if ediff-3way-comparison-job + (setq ediff-buffer-A ediff-buffer-C + ediff-buffer-C ediff-buffer-B + ediff-buffer-B buf) + (setq ediff-buffer-A ediff-buffer-B + ediff-buffer-B buf)) + + ;; swap saved buffer characteristics + (if ediff-3way-comparison-job + (setq ediff-buffer-values-orig-A ediff-buffer-values-orig-C + ediff-buffer-values-orig-C ediff-buffer-values-orig-B + ediff-buffer-values-orig-B values) + (setq ediff-buffer-values-orig-A ediff-buffer-values-orig-B + ediff-buffer-values-orig-B values)) + + ;; swap diff vectors + (if ediff-3way-comparison-job + (setq ediff-difference-vector-A ediff-difference-vector-C + ediff-difference-vector-C ediff-difference-vector-B + ediff-difference-vector-B diff-vec) + (setq ediff-difference-vector-A ediff-difference-vector-B + ediff-difference-vector-B diff-vec)) + + ;; swap hide/focus regexp + (if ediff-3way-comparison-job + (setq ediff-regexp-hide-A ediff-regexp-hide-C + ediff-regexp-hide-C ediff-regexp-hide-B + ediff-regexp-hide-B hide-regexp + ediff-regexp-focus-A ediff-regexp-focus-C + ediff-regexp-focus-C ediff-regexp-focus-B + ediff-regexp-focus-B focus-regexp) + (setq ediff-regexp-hide-A ediff-regexp-hide-B + ediff-regexp-hide-B hide-regexp + ediff-regexp-focus-A ediff-regexp-focus-B + ediff-regexp-focus-B focus-regexp)) + + ;; The following is needed for XEmacs, since there one can't move + ;; overlay to another buffer. In Emacs, this swap is redundant. + (if (ediff-has-face-support-p) + (if ediff-3way-comparison-job + (setq ediff-current-diff-overlay-A ediff-current-diff-overlay-C + ediff-current-diff-overlay-C ediff-current-diff-overlay-B + ediff-current-diff-overlay-B overlay) + (setq ediff-current-diff-overlay-A ediff-current-diff-overlay-B + ediff-current-diff-overlay-B overlay))) + + ;; swap wide bounds + (setq ediff-wide-bounds + (cond (ediff-3way-comparison-job + (list (nth 2 ediff-wide-bounds) + (nth 0 ediff-wide-bounds) + (nth 1 ediff-wide-bounds))) + (ediff-3way-job + (list (nth 1 ediff-wide-bounds) + (nth 0 ediff-wide-bounds) + (nth 2 ediff-wide-bounds))) + (t + (list (nth 1 ediff-wide-bounds) + (nth 0 ediff-wide-bounds))))) + ;; swap narrow bounds + (setq ediff-narrow-bounds + (cond (ediff-3way-comparison-job + (list (nth 2 ediff-narrow-bounds) + (nth 0 ediff-narrow-bounds) + (nth 1 ediff-narrow-bounds))) + (ediff-3way-job + (list (nth 1 ediff-narrow-bounds) + (nth 0 ediff-narrow-bounds) + (nth 2 ediff-narrow-bounds))) + (t + (list (nth 1 ediff-narrow-bounds) + (nth 0 ediff-narrow-bounds))))) + (if wide-visibility-p + (setq ediff-visible-bounds ediff-wide-bounds) + (setq ediff-visible-bounds ediff-narrow-bounds)) + )) + (if ediff-3way-job + (ediff-set-state-of-all-diffs-in-all-buffers ediff-control-buffer)) + (ediff-recenter 'no-rehighlight) + ) + + +(defun ediff-toggle-wide-display () + "Toggle wide/regular display. +This is especially useful when comparing buffers side-by-side." + (interactive) + (ediff-barf-if-not-control-buffer) + (or (ediff-window-display-p) + (error "%sEmacs is not running as a window application" + (if (featurep 'emacs) "" "X"))) + (ediff-recenter 'no-rehighlight) ; make sure buffs are displayed in windows + (let ((ctl-buf ediff-control-buffer)) + (setq ediff-wide-display-p (not ediff-wide-display-p)) + (if (not ediff-wide-display-p) + (ediff-with-current-buffer ctl-buf + (modify-frame-parameters + ediff-wide-display-frame ediff-wide-display-orig-parameters) + ;;(sit-for (if (featurep 'xemacs) 0.4 0)) + ;; restore control buf, since ctl window may have been deleted + ;; during resizing + (set-buffer ctl-buf) + (setq ediff-wide-display-orig-parameters nil + ediff-window-B nil) ; force update of window config + (ediff-recenter 'no-rehighlight)) + (funcall ediff-make-wide-display-function) + ;;(sit-for (if (featurep 'xemacs) 0.4 0)) + (ediff-with-current-buffer ctl-buf + (setq ediff-window-B nil) ; force update of window config + (ediff-recenter 'no-rehighlight))))) + +;;;###autoload +(defun ediff-toggle-multiframe () + "Switch from multiframe display to single-frame display and back. +To change the default, set the variable `ediff-window-setup-function', +which see." + (interactive) + (let (window-setup-func) + (or (ediff-window-display-p) + (error "%sEmacs is not running as a window application" + (if (featurep 'emacs) "" "X"))) + + (cond ((eq ediff-window-setup-function 'ediff-setup-windows-multiframe) + (setq ediff-multiframe nil) + (setq window-setup-func 'ediff-setup-windows-plain)) + ((eq ediff-window-setup-function 'ediff-setup-windows-plain) + (if (ediff-in-control-buffer-p) + (ediff-kill-bottom-toolbar)) + (if (and (ediff-buffer-live-p ediff-control-buffer) + (window-live-p ediff-control-window)) + (set-window-dedicated-p ediff-control-window nil)) + (setq ediff-multiframe t) + (setq window-setup-func 'ediff-setup-windows-multiframe)) + (t + (if (and (ediff-buffer-live-p ediff-control-buffer) + (window-live-p ediff-control-window)) + (set-window-dedicated-p ediff-control-window nil)) + (setq ediff-multiframe t) + (setq window-setup-func 'ediff-setup-windows-multiframe)) + ) + + ;; change default + (setq-default ediff-window-setup-function window-setup-func) + ;; change in all active ediff sessions + (mapc (lambda(buf) (ediff-with-current-buffer buf + (setq ediff-window-setup-function window-setup-func + ediff-window-B nil))) + ediff-session-registry) + (if (ediff-in-control-buffer-p) + (progn + (set-window-dedicated-p (selected-window) nil) + (ediff-recenter 'no-rehighlight))))) + + +;;;###autoload +(defun ediff-toggle-use-toolbar () + "Enable or disable Ediff toolbar. +Works only in versions of Emacs that support toolbars. +To change the default, set the variable `ediff-use-toolbar-p', which see." + (interactive) + (if (featurep 'ediff-tbar) + (progn + (or (ediff-window-display-p) + (error "%sEmacs is not running as a window application" + (if (featurep 'emacs) "" "X"))) + (if (ediff-use-toolbar-p) + (ediff-kill-bottom-toolbar)) + ;; do this only after killing the toolbar + (setq ediff-use-toolbar-p (not ediff-use-toolbar-p)) + + (mapc (lambda(buf) + (ediff-with-current-buffer buf + ;; force redisplay + (setq ediff-window-config-saved "") + )) + ediff-session-registry) + (if (ediff-in-control-buffer-p) + (ediff-recenter 'no-rehighlight))))) + + +;; if was using toolbar, kill it +(defun ediff-kill-bottom-toolbar () + ;; Using ctl-buffer or ediff-control-window for LOCALE does not + ;; work properly in XEmacs 19.14: we have to use + ;;(selected-frame). + ;; The problem with this is that any previous bottom-toolbar + ;; will not re-appear after our cleanup here. Is there a way + ;; to do "push" and "pop" toolbars ? --marcpa + (if (featurep 'xemacs) + (when (ediff-use-toolbar-p) + (set-specifier bottom-toolbar (list (selected-frame) nil)) + (set-specifier bottom-toolbar-visible-p (list (selected-frame) nil))))) + +;; If wants to use toolbar, make it. +;; If not, zero the toolbar for XEmacs. +;; Do nothing for Emacs. +(defun ediff-make-bottom-toolbar (&optional frame) + (when (ediff-window-display-p) + (setq frame (or frame (selected-frame))) + (if (featurep 'xemacs) + (cond ((ediff-use-toolbar-p) ; this checks for XEmacs + (set-specifier + bottom-toolbar + (list frame (if (ediff-3way-comparison-job) + ediff-toolbar-3way ediff-toolbar))) + (set-specifier bottom-toolbar-visible-p (list frame t)) + (set-specifier bottom-toolbar-height + (list frame ediff-toolbar-height))) + ((ediff-has-toolbar-support-p) + (set-specifier bottom-toolbar-height (list frame 0))))))) + +;; Merging + +(defun ediff-toggle-show-clashes-only () + "Toggle the mode that shows only the merge regions where both variants differ from the ancestor." + (interactive) + (ediff-barf-if-not-control-buffer) + (if (not ediff-merge-with-ancestor-job) + (error "This command makes sense only when merging with an ancestor")) + (setq ediff-show-clashes-only (not ediff-show-clashes-only)) + (if ediff-show-clashes-only + (message "Focus on regions where both buffers differ from the ancestor") + (message "Canceling focus on regions where changes clash"))) + +(defun ediff-toggle-skip-changed-regions () + "Toggle the mode that skips the merge regions that differ from the default." + (interactive) + (ediff-barf-if-not-control-buffer) + (setq ediff-skip-merge-regions-that-differ-from-default + (not ediff-skip-merge-regions-that-differ-from-default)) + (if ediff-skip-merge-regions-that-differ-from-default + (message "Skipping regions that differ from default setting") + (message "Showing regions that differ from default setting"))) + + + +;; Widening/narrowing + +(defun ediff-toggle-narrow-region () + "Toggle narrowing in buffers A, B, and C. +Used in ediff-windows/regions only." + (interactive) + (if (eq ediff-buffer-A ediff-buffer-B) + (error ediff-NO-DIFFERENCES)) + (if (eq ediff-visible-bounds ediff-wide-bounds) + (setq ediff-visible-bounds ediff-narrow-bounds) + (setq ediff-visible-bounds ediff-wide-bounds)) + (ediff-recenter 'no-rehighlight)) + +;; Narrow bufs A/B/C to ediff-visible-bounds. If this is currently set to +;; ediff-wide-bounds, then this actually widens. +;; This function does nothing if job-name is not +;; ediff-regions-wordwise/linewise or ediff-windows-wordwise/linewise. +;; Does nothing if buffer-A = buffer-B since we can't narrow +;; to two different regions in one buffer. +(defun ediff-visible-region () + (if (or (eq ediff-buffer-A ediff-buffer-B) + (eq ediff-buffer-A ediff-buffer-C) + (eq ediff-buffer-C ediff-buffer-B)) + () + ;; If ediff-*-regions/windows, ediff-visible-bounds is already set + ;; Otherwise, always use full range. + (if (not ediff-narrow-job) + (setq ediff-visible-bounds ediff-wide-bounds)) + (let ((overl-A (ediff-get-value-according-to-buffer-type + 'A ediff-visible-bounds)) + (overl-B (ediff-get-value-according-to-buffer-type + 'B ediff-visible-bounds)) + (overl-C (ediff-get-value-according-to-buffer-type + 'C ediff-visible-bounds)) + ) + (ediff-with-current-buffer ediff-buffer-A + (if (ediff-overlay-buffer overl-A) + (narrow-to-region + (ediff-overlay-start overl-A) (ediff-overlay-end overl-A)))) + (ediff-with-current-buffer ediff-buffer-B + (if (ediff-overlay-buffer overl-B) + (narrow-to-region + (ediff-overlay-start overl-B) (ediff-overlay-end overl-B)))) + + (if (and ediff-3way-job (ediff-overlay-buffer overl-C)) + (ediff-with-current-buffer ediff-buffer-C + (narrow-to-region + (ediff-overlay-start overl-C) (ediff-overlay-end overl-C)))) + ))) + + +;; Window scrolling operations + +;; Performs some operation on the two file windows (if they are showing). +;; Traps all errors on the operation in windows A/B/C. +;; Usually, errors come from scrolling off the +;; beginning or end of the buffer, and this gives error messages. +(defun ediff-operate-on-windows (operation arg) + + ;; make sure windows aren't dead + (if (not (and (window-live-p ediff-window-A) (window-live-p ediff-window-B))) + (ediff-recenter 'no-rehighlight)) + (if (not (and (ediff-buffer-live-p ediff-buffer-A) + (ediff-buffer-live-p ediff-buffer-B) + (or (not ediff-3way-job) ediff-buffer-C) + )) + (error ediff-KILLED-VITAL-BUFFER)) + + (let* ((wind (selected-window)) + (wind-A ediff-window-A) + (wind-B ediff-window-B) + (wind-C ediff-window-C) + (coefA (ediff-get-region-size-coefficient 'A operation)) + (coefB (ediff-get-region-size-coefficient 'B operation)) + (three-way ediff-3way-job) + (coefC (if three-way + (ediff-get-region-size-coefficient 'C operation)))) + + (select-window wind-A) + (condition-case nil + (funcall operation (round (* coefA arg))) + (error)) + (select-window wind-B) + (condition-case nil + (funcall operation (round (* coefB arg))) + (error)) + (if three-way + (progn + (select-window wind-C) + (condition-case nil + (funcall operation (round (* coefC arg))) + (error)))) + (select-window wind))) + +(defun ediff-scroll-vertically (&optional arg) + "Vertically scroll buffers A, B \(and C if appropriate\). +With optional argument ARG, scroll ARG lines; otherwise scroll by nearly +the one half of the height of window-A." + (interactive "P") + (ediff-barf-if-not-control-buffer) + + ;; make sure windows aren't dead + (if (not (and (window-live-p ediff-window-A) (window-live-p ediff-window-B))) + (ediff-recenter 'no-rehighlight)) + (if (not (and (ediff-buffer-live-p ediff-buffer-A) + (ediff-buffer-live-p ediff-buffer-B) + (or (not ediff-3way-job) + (ediff-buffer-live-p ediff-buffer-C)) + )) + (error ediff-KILLED-VITAL-BUFFER)) + + (ediff-operate-on-windows + (if (memq (ediff-last-command-char) '(?v ?\C-v)) + 'scroll-up + 'scroll-down) + ;; calculate argument to scroll-up/down + ;; if there is an explicit argument + (if (and arg (not (equal arg '-))) + ;; use it + (prefix-numeric-value arg) + ;; if not, see if we can determine a default amount (the window height) + (let (default-amount) + (setq default-amount + (- (/ (min (window-height ediff-window-A) + (window-height ediff-window-B) + (if ediff-3way-job + (window-height ediff-window-C) + 500)) ; some large number + 2) + 1 next-screen-context-lines)) + ;; window found + (if arg + ;; C-u as argument means half of default amount + (/ default-amount 2) + ;; no argument means default amount + default-amount))))) + + +(defun ediff-scroll-horizontally (&optional arg) + "Horizontally scroll buffers A, B \(and C if appropriate\). +If an argument is given, that is how many columns are scrolled, else nearly +the width of the A/B/C windows." + (interactive "P") + (ediff-barf-if-not-control-buffer) + + ;; make sure windows aren't dead + (if (not (and (window-live-p ediff-window-A) (window-live-p ediff-window-B))) + (ediff-recenter 'no-rehighlight)) + (if (not (and (ediff-buffer-live-p ediff-buffer-A) + (ediff-buffer-live-p ediff-buffer-B) + (or (not ediff-3way-job) + (ediff-buffer-live-p ediff-buffer-C)) + )) + (error ediff-KILLED-VITAL-BUFFER)) + + (ediff-operate-on-windows + ;; Arrange for scroll-left and scroll-right being called + ;; interactively so that they set the window's min_hscroll. + ;; Otherwise, automatic hscrolling will undo the effect of + ;; hscrolling. + (if (= (ediff-last-command-char) ?<) + (lambda (arg) + (let ((prefix-arg arg)) + (call-interactively 'scroll-left))) + (lambda (arg) + (let ((prefix-arg arg)) + (call-interactively 'scroll-right)))) + ;; calculate argument to scroll-left/right + ;; if there is an explicit argument + (if (and arg (not (equal arg '-))) + ;; use it + (prefix-numeric-value arg) + ;; if not, see if we can determine a default amount + ;; (half the window width) + (if (null ediff-control-window) + ;; no control window, use nil + nil + (let ((default-amount + (- (/ (min (window-width ediff-window-A) + (window-width ediff-window-B) + (if ediff-3way-comparison-job + (window-width ediff-window-C) + 500) ; some large number + ) + 2) + 3))) + ;; window found + (if arg + ;; C-u as argument means half of default amount + (/ default-amount 2) + ;; no argument means default amount + default-amount)))))) + + +;;BEG, END show the region to be positioned. +;;JOB-NAME holds ediff-job-name. The ediff-windows job positions regions +;;differently. +(defun ediff-position-region (beg end pos job-name) + (if (> end (point-max)) + (setq end (point-max))) + (if ediff-windows-job + (if (pos-visible-in-window-p end) + () ; do nothing, wind is already positioned + ;; at this point, windows are positioned at the beginning of the + ;; file regions (not diff-regions) being compared. + (save-excursion + (move-to-window-line (- (window-height) 2)) + (let ((amount (+ 2 (count-lines (point) end)))) + (scroll-up amount)))) + (set-window-start (selected-window) beg) + (if (pos-visible-in-window-p end) + ;; Determine the number of lines that the region occupies + (let ((lines 0) + (prev-point 0)) + (while ( and (> end (progn + (move-to-window-line lines) + (point))) + ;; `end' may be beyond the window bottom, so check + ;; that we are making progress + (< prev-point (point))) + (setq prev-point (point)) + (setq lines (1+ lines))) + ;; And position the beginning on the right line + (goto-char beg) + (recenter (/ (1+ (max (- (1- (window-height (selected-window))) + lines) + 1) + ) + 2)))) + (goto-char pos) + )) + +;; get number of lines from window start to region end +(defun ediff-get-lines-to-region-end (buf-type &optional n ctl-buf) + (or n (setq n ediff-current-difference)) + (or ctl-buf (setq ctl-buf ediff-control-buffer)) + (ediff-with-current-buffer ctl-buf + (let* ((buf (ediff-get-buffer buf-type)) + (wind (eval (ediff-get-symbol-from-alist + buf-type ediff-window-alist))) + (beg (window-start wind)) + (end (ediff-get-diff-posn buf-type 'end)) + lines) + (ediff-with-current-buffer buf + (if (< beg end) + (setq lines (count-lines beg end)) + (setq lines 0)) + lines + )))) + +;; Calculate the number of lines from window end to the start of diff region +(defun ediff-get-lines-to-region-start (buf-type &optional diff-num ctl-buf) + (or diff-num (setq diff-num ediff-current-difference)) + (or ctl-buf (setq ctl-buf ediff-control-buffer)) + (ediff-with-current-buffer ctl-buf + (let* ((buf (ediff-get-buffer buf-type)) + (wind (eval (ediff-get-symbol-from-alist + buf-type ediff-window-alist))) + (end (or (window-end wind) (window-end wind t))) + (beg (ediff-get-diff-posn buf-type 'beg diff-num))) + (ediff-with-current-buffer buf + (if (< beg end) + (count-lines (max beg (point-min)) (min end (point-max))) 0)) + ))) + + +;; region size coefficient is a coefficient by which to adjust scrolling +;; up/down of the window displaying buffer of type BUFTYPE. +;; The purpose of this coefficient is to make the windows scroll in sync, so +;; that it won't happen that one diff region is scrolled off while the other is +;; still seen. +;; +;; If the difference region is invalid, the coefficient is 1 +(defun ediff-get-region-size-coefficient (buf-type op &optional n ctl-buf) + (ediff-with-current-buffer (or ctl-buf ediff-control-buffer) + (if (ediff-valid-difference-p n) + (let* ((func (cond ((eq op 'scroll-down) + 'ediff-get-lines-to-region-start) + ((eq op 'scroll-up) + 'ediff-get-lines-to-region-end) + (t '(lambda (a b c) 0)))) + (max-lines (max (funcall func 'A n ctl-buf) + (funcall func 'B n ctl-buf) + (if (ediff-buffer-live-p ediff-buffer-C) + (funcall func 'C n ctl-buf) + 0)))) + ;; this covers the horizontal coefficient as well: + ;; if max-lines = 0 then coef = 1 + (if (> max-lines 0) + (/ (+ (funcall func buf-type n ctl-buf) 0.0) + (+ max-lines 0.0)) + 1)) + 1))) + + +(defun ediff-next-difference (&optional arg) + "Advance to the next difference. +With a prefix argument, go forward that many differences." + (interactive "p") + (ediff-barf-if-not-control-buffer) + (if (< ediff-current-difference ediff-number-of-differences) + (let ((n (min ediff-number-of-differences + (+ ediff-current-difference (or arg 1)))) + non-clash-skip skip-changed regexp-skip) + + (ediff-visible-region) + (or (>= n ediff-number-of-differences) + (setq regexp-skip (funcall ediff-skip-diff-region-function n)) + ;; this won't exec if regexp-skip is t + (setq non-clash-skip (ediff-merge-region-is-non-clash-to-skip n) + skip-changed + (ediff-skip-merge-region-if-changed-from-default-p n)) + (ediff-install-fine-diff-if-necessary n)) + ;; Skip loop + (while (and (< n ediff-number-of-differences) + (or + ;; regexp skip + regexp-skip + ;; skip clashes, if necessary + non-clash-skip + ;; skip processed regions + skip-changed + ;; skip difference regions that differ in white space + (and ediff-ignore-similar-regions + (ediff-merge-region-is-non-clash n) + (or (eq (ediff-no-fine-diffs-p n) t) + (and (ediff-merge-job) + (eq (ediff-no-fine-diffs-p n) 'C))) + ))) + (setq n (1+ n)) + (if (= 0 (mod n 20)) + (message "Skipped over region %d and counting ..." n)) + (or (>= n ediff-number-of-differences) + (setq regexp-skip (funcall ediff-skip-diff-region-function n)) + ;; this won't exec if regexp-skip is t + (setq non-clash-skip (ediff-merge-region-is-non-clash-to-skip n) + skip-changed + (ediff-skip-merge-region-if-changed-from-default-p n)) + (ediff-install-fine-diff-if-necessary n)) + ) + (message "") + (ediff-unselect-and-select-difference n) + ) ; let + (ediff-visible-region) + (error "At end of the difference list"))) + +(defun ediff-previous-difference (&optional arg) + "Go to the previous difference. +With a prefix argument, go back that many differences." + (interactive "p") + (ediff-barf-if-not-control-buffer) + (if (> ediff-current-difference -1) + (let ((n (max -1 (- ediff-current-difference (or arg 1)))) + non-clash-skip skip-changed regexp-skip) + + (ediff-visible-region) + (or (< n 0) + (setq regexp-skip (funcall ediff-skip-diff-region-function n)) + ;; this won't exec if regexp-skip is t + (setq non-clash-skip (ediff-merge-region-is-non-clash-to-skip n) + skip-changed + (ediff-skip-merge-region-if-changed-from-default-p n)) + (ediff-install-fine-diff-if-necessary n)) + (while (and (> n -1) + (or + ;; regexp skip + regexp-skip + ;; skip clashes, if necessary + non-clash-skip + ;; skipp changed regions + skip-changed + ;; skip difference regions that differ in white space + (and ediff-ignore-similar-regions + (ediff-merge-region-is-non-clash n) + (or (eq (ediff-no-fine-diffs-p n) t) + (and (ediff-merge-job) + (eq (ediff-no-fine-diffs-p n) 'C))) + ))) + (if (= 0 (mod (1+ n) 20)) + (message "Skipped over region %d and counting ..." (1+ n))) + (setq n (1- n)) + (or (< n 0) + (setq regexp-skip (funcall ediff-skip-diff-region-function n)) + ;; this won't exec if regexp-skip is t + (setq non-clash-skip (ediff-merge-region-is-non-clash-to-skip n) + skip-changed + (ediff-skip-merge-region-if-changed-from-default-p n)) + (ediff-install-fine-diff-if-necessary n)) + ) + (message "") + (ediff-unselect-and-select-difference n) + ) ; let + (ediff-visible-region) + (error "At beginning of the difference list"))) + +;; The diff number is as perceived by the user (i.e., 1+ the internal +;; representation) +(defun ediff-jump-to-difference (difference-number) + "Go to the difference specified as a prefix argument. +If the prefix is negative, count differences from the end." + (interactive "p") + (ediff-barf-if-not-control-buffer) + (setq difference-number + (cond ((< difference-number 0) + (+ ediff-number-of-differences difference-number)) + ((> difference-number 0) (1- difference-number)) + (t -1))) + ;; -1 is allowed by ediff-unselect-and-select-difference --- it is the + ;; position before the first one. + (if (and (>= difference-number -1) + (<= difference-number ediff-number-of-differences)) + (ediff-unselect-and-select-difference difference-number) + (error ediff-BAD-DIFF-NUMBER + this-command (1+ difference-number) ediff-number-of-differences))) + +(defun ediff-jump-to-difference-at-point (arg) + "Go to difference closest to the point in buffer A, B, or C. +The buffer depends on last command character \(a, b, or c\) that invoked this +command. For instance, if the command was `ga' then the point value in buffer +A is used. +With a prefix argument, synchronize all files around the current point position +in the specified buffer." + (interactive "P") + (ediff-barf-if-not-control-buffer) + (let* ((buf-type (ediff-char-to-buftype (ediff-last-command-char))) + (buffer (ediff-get-buffer buf-type)) + (pt (ediff-with-current-buffer buffer (point))) + (diff-no (ediff-diff-at-point buf-type nil (if arg 'after))) + (past-last-diff (< ediff-number-of-differences diff-no)) + (beg (if past-last-diff + (ediff-with-current-buffer buffer (point-max)) + (ediff-get-diff-posn buf-type 'beg (1- diff-no)))) + ctl-wind wind-A wind-B wind-C + shift) + (if past-last-diff + (ediff-jump-to-difference -1) + (ediff-jump-to-difference diff-no)) + (setq ctl-wind (selected-window) + wind-A ediff-window-A + wind-B ediff-window-B + wind-C ediff-window-C) + (if arg + (progn + (ediff-with-current-buffer buffer + (setq shift (- beg pt))) + (select-window wind-A) + (if past-last-diff (goto-char (point-max))) + (condition-case nil + (backward-char shift) ; noerror, if beginning of buffer + (error)) + (recenter) + (select-window wind-B) + (if past-last-diff (goto-char (point-max))) + (condition-case nil + (backward-char shift) ; noerror, if beginning of buffer + (error)) + (recenter) + (if (window-live-p wind-C) + (progn + (select-window wind-C) + (if past-last-diff (goto-char (point-max))) + (condition-case nil + (backward-char shift) ; noerror, if beginning of buffer + (error)) + (recenter) + )) + (select-window ctl-wind) + )) + )) + + +;; find region most related to the current point position (or POS, if given) +;; returns diff number as seen by the user (i.e., 1+ the internal +;; representation) +;; The optional argument WHICH-DIFF can be `after' or `before'. If `after', +;; find the diff after the point. If `before', find the diff before the +;; point. If the point is inside a diff, return that diff. +(defun ediff-diff-at-point (buf-type &optional pos which-diff) + (let ((buffer (ediff-get-buffer buf-type)) + (ctl-buffer ediff-control-buffer) + (max-dif-num (1- ediff-number-of-differences)) + (diff-no -1) + (prev-beg 0) + (prev-end 0) + (beg 0) + (end 0)) + + (ediff-with-current-buffer buffer + (setq pos (or pos (point))) + (while (and (or (< pos prev-beg) (> pos beg)) + (< diff-no max-dif-num)) + (setq diff-no (1+ diff-no)) + (setq prev-beg beg + prev-end end) + (setq beg (ediff-get-diff-posn buf-type 'beg diff-no ctl-buffer) + end (ediff-get-diff-posn buf-type 'end diff-no ctl-buffer)) + ) + + ;; boost diff-no by 1, if past the last diff region + (if (and (memq which-diff '(after before)) + (> pos beg) (= diff-no max-dif-num)) + (setq diff-no (1+ diff-no))) + + (cond ((eq which-diff 'after) (1+ diff-no)) + ((eq which-diff 'before) diff-no) + ((< (abs (count-lines pos (max 1 prev-end))) + (abs (count-lines pos (max 1 beg)))) + diff-no) ; choose prev difference + (t + (1+ diff-no))) ; choose next difference + ))) + + +;;; Copying diffs. + +(defun ediff-diff-to-diff (arg &optional keys) + "Copy buffer-X'th difference region to buffer Y \(X,Y are A, B, or C\). +If numerical prefix argument, copy the difference specified in the arg. +Otherwise, copy the difference given by `ediff-current-difference'. +This command assumes it is bound to a 2-character key sequence, `ab', `ba', +`ac', etc., which is used to determine the types of buffers to be used for +copying difference regions. The first character in the sequence specifies +the source buffer and the second specifies the target. + +If the second optional argument, a 2-character string, is given, use it to +determine the source and the target buffers instead of the command keys." + (interactive "P") + (ediff-barf-if-not-control-buffer) + (or keys (setq keys (this-command-keys))) + (if (eq arg '-) (setq arg -1)) ; translate neg arg to -1 + (if (numberp arg) (ediff-jump-to-difference arg)) + + (let* ((key1 (aref keys 0)) + (key2 (aref keys 1)) + (char1 (ediff-event-key key1)) + (char2 (ediff-event-key key2)) + ediff-verbose-p) + (ediff-copy-diff ediff-current-difference + (ediff-char-to-buftype char1) + (ediff-char-to-buftype char2)) + ;; recenter with rehighlighting, but no messages + (ediff-recenter))) + +(defun ediff-copy-A-to-B (arg) + "Copy ARGth difference region from buffer A to B. +ARG is a prefix argument. If nil, copy the current difference region." + (interactive "P") + (ediff-diff-to-diff arg "ab")) + +(defun ediff-copy-B-to-A (arg) + "Copy ARGth difference region from buffer B to A. +ARG is a prefix argument. If nil, copy the current difference region." + (interactive "P") + (ediff-diff-to-diff arg "ba")) + +(defun ediff-copy-A-to-C (arg) + "Copy ARGth difference region from buffer A to buffer C. +ARG is a prefix argument. If nil, copy the current difference region." + (interactive "P") + (ediff-diff-to-diff arg "ac")) + +(defun ediff-copy-B-to-C (arg) + "Copy ARGth difference region from buffer B to buffer C. +ARG is a prefix argument. If nil, copy the current difference region." + (interactive "P") + (ediff-diff-to-diff arg "bc")) + +(defun ediff-copy-C-to-B (arg) + "Copy ARGth difference region from buffer C to B. +ARG is a prefix argument. If nil, copy the current difference region." + (interactive "P") + (ediff-diff-to-diff arg "cb")) + +(defun ediff-copy-C-to-A (arg) + "Copy ARGth difference region from buffer C to A. +ARG is a prefix argument. If nil, copy the current difference region." + (interactive "P") + (ediff-diff-to-diff arg "ca")) + + + +;; Copy diff N from FROM-BUF-TYPE \(given as A, B or C\) to TO-BUF-TYPE. +;; If optional DO-NOT-SAVE is non-nil, do not save the old value of the +;; target diff. This is used in merging, when constructing the merged +;; version. +(defun ediff-copy-diff (n from-buf-type to-buf-type + &optional batch-invocation reg-to-copy) + (let* ((to-buf (ediff-get-buffer to-buf-type)) + ;;(from-buf (if (not reg-to-copy) (ediff-get-buffer from-buf-type))) + (ctrl-buf ediff-control-buffer) + (saved-p t) + (three-way ediff-3way-job) + messg + ediff-verbose-p + reg-to-delete reg-to-delete-beg reg-to-delete-end) + + (setq reg-to-delete-beg + (ediff-get-diff-posn to-buf-type 'beg n ctrl-buf)) + (setq reg-to-delete-end + (ediff-get-diff-posn to-buf-type 'end n ctrl-buf)) + + (if reg-to-copy + (setq from-buf-type nil) + (setq reg-to-copy (ediff-get-region-contents n from-buf-type ctrl-buf))) + + (setq reg-to-delete (ediff-get-region-contents + n to-buf-type ctrl-buf + reg-to-delete-beg reg-to-delete-end)) + + (if (string= reg-to-delete reg-to-copy) + (setq saved-p nil) ; don't copy identical buffers + ;; seems ok to copy + (if (or batch-invocation (ediff-test-save-region n to-buf-type)) + (condition-case conds + (progn + (ediff-with-current-buffer to-buf + ;; to prevent flags from interfering if buffer is writable + (let ((inhibit-read-only (null buffer-read-only))) + + (goto-char reg-to-delete-end) + (insert reg-to-copy) + + (if (> reg-to-delete-end reg-to-delete-beg) + (kill-region reg-to-delete-beg reg-to-delete-end)) + )) + (or batch-invocation + (setq + messg + (ediff-save-diff-region n to-buf-type reg-to-delete)))) + (error (message "ediff-copy-diff: %s %s" + (car conds) + (mapconcat 'prin1-to-string (cdr conds) " ")) + (beep 1) + (sit-for 2) ; let the user see the error msg + (setq saved-p nil) + ))) + ) + + ;; adjust state of difference in case 3-way and diff was copied ok + (if (and saved-p three-way) + (ediff-set-state-of-diff-in-all-buffers n ctrl-buf)) + + (if batch-invocation + (ediff-clear-fine-differences n) + ;; If diff3 job, we should recompute fine diffs so we clear them + ;; before reinserting flags (and thus before ediff-recenter). + (if (and saved-p three-way) + (ediff-clear-fine-differences n)) + + (ediff-refresh-mode-lines) + + ;; For diff2 jobs, don't recompute fine diffs, since we know there + ;; aren't any. So we clear diffs after ediff-recenter. + (if (and saved-p (not three-way)) + (ediff-clear-fine-differences n)) + ;; Make sure that the message about saving and how to restore is seen + ;; by the user + (message "%s" messg)) + )) + +;; Save Nth diff of buffer BUF-TYPE \(A, B, or C\). +;; That is to say, the Nth diff on the `ediff-killed-diffs-alist'. REG +;; is the region to save. It is redundant here, but is passed anyway, for +;; convenience. +(defun ediff-save-diff-region (n buf-type reg) + (let* ((n-th-diff-saved (assoc n ediff-killed-diffs-alist)) + (buf (ediff-get-buffer buf-type)) + (this-buf-n-th-diff-saved (assoc buf (cdr n-th-diff-saved)))) + + (if this-buf-n-th-diff-saved + ;; either nothing saved for n-th diff and buffer or we OK'ed + ;; overriding + (setcdr this-buf-n-th-diff-saved reg) + (if n-th-diff-saved ;; n-th diff saved, but for another buffer + (nconc n-th-diff-saved (list (cons buf reg))) + (setq ediff-killed-diffs-alist ;; create record for n-th diff + (cons (list n (cons buf reg)) + ediff-killed-diffs-alist)))) + (message "Saving old diff region #%d of buffer %S. To recover, type `r%s'" + (1+ n) buf-type + (if ediff-merge-job + "" (downcase (symbol-name buf-type)))) + )) + +;; Test if saving Nth difference region of buffer BUF-TYPE is possible. +(defun ediff-test-save-region (n buf-type) + (let* ((n-th-diff-saved (assoc n ediff-killed-diffs-alist)) + (buf (ediff-get-buffer buf-type)) + (this-buf-n-th-diff-saved (assoc buf (cdr n-th-diff-saved)))) + + (if this-buf-n-th-diff-saved + (if (yes-or-no-p + (format + "You've previously copied diff region %d to buffer %S. Confirm? " + (1+ n) buf-type)) + t + (error "Quit")) + t))) + +(defun ediff-pop-diff (n buf-type) + "Pop last killed Nth diff region from buffer BUF-TYPE." + (let* ((n-th-record (assoc n ediff-killed-diffs-alist)) + (buf (ediff-get-buffer buf-type)) + (saved-rec (assoc buf (cdr n-th-record))) + (three-way ediff-3way-job) + (ctl-buf ediff-control-buffer) + ediff-verbose-p + saved-diff reg-beg reg-end recovered) + + (if (cdr saved-rec) + (setq saved-diff (cdr saved-rec)) + (if (> ediff-number-of-differences 0) + (error "Nothing saved for diff %d in buffer %S" (1+ n) buf-type) + (error ediff-NO-DIFFERENCES))) + + (setq reg-beg (ediff-get-diff-posn buf-type 'beg n ediff-control-buffer)) + (setq reg-end (ediff-get-diff-posn buf-type 'end n ediff-control-buffer)) + + (condition-case conds + (ediff-with-current-buffer buf + (let ((inhibit-read-only (null buffer-read-only))) + + (goto-char reg-end) + (insert saved-diff) + + (if (> reg-end reg-beg) + (kill-region reg-beg reg-end)) + + (setq recovered t) + )) + (error (message "ediff-pop-diff: %s %s" + (car conds) + (mapconcat 'prin1-to-string (cdr conds) " ")) + (beep 1))) + + ;; Clearing fine diffs is necessary for + ;; ediff-unselect-and-select-difference to properly recompute them. We + ;; can't rely on ediff-copy-diff to clear this vector, as the user might + ;; have modified diff regions after copying and, thus, may have recomputed + ;; fine diffs. + (if recovered + (ediff-clear-fine-differences n)) + + ;; adjust state of difference + (if (and three-way recovered) + (ediff-set-state-of-diff-in-all-buffers n ctl-buf)) + + (ediff-refresh-mode-lines) + + (if recovered + (progn + (setq n-th-record (delq saved-rec n-th-record)) + (message "Diff region %d in buffer %S restored" (1+ n) buf-type) + )) + )) + +(defun ediff-restore-diff (arg &optional key) + "Restore ARGth diff from `ediff-killed-diffs-alist'. +ARG is a prefix argument. If ARG is nil, restore the current-difference. +If the second optional argument, a character, is given, use it to +determine the target buffer instead of (ediff-last-command-char)" + (interactive "P") + (ediff-barf-if-not-control-buffer) + (if (numberp arg) + (ediff-jump-to-difference arg)) + (ediff-pop-diff ediff-current-difference + (ediff-char-to-buftype (or key (ediff-last-command-char)))) + ;; recenter with rehighlighting, but no messages + (let (ediff-verbose-p) + (ediff-recenter))) + +(defun ediff-restore-diff-in-merge-buffer (arg) + "Restore ARGth diff in the merge buffer. +ARG is a prefix argument. If nil, restore the current diff." + (interactive "P") + (ediff-restore-diff arg ?c)) + + +(defun ediff-toggle-regexp-match () + "Toggle between focusing and hiding of difference regions that match +a regular expression typed in by the user." + (interactive) + (ediff-barf-if-not-control-buffer) + (let ((regexp-A "") + (regexp-B "") + (regexp-C "") + msg-connective alt-msg-connective alt-connective) + (cond + ((or (and (eq ediff-skip-diff-region-function + ediff-focus-on-regexp-matches-function) + (eq (ediff-last-command-char) ?f)) + (and (eq ediff-skip-diff-region-function + ediff-hide-regexp-matches-function) + (eq (ediff-last-command-char) ?h))) + (message "Selective browsing by regexp turned off") + (setq ediff-skip-diff-region-function 'ediff-show-all-diffs)) + ((eq (ediff-last-command-char) ?h) + (setq ediff-skip-diff-region-function ediff-hide-regexp-matches-function + regexp-A + (read-string + (format + "Ignore A-regions matching this regexp (default %s): " + ediff-regexp-hide-A)) + regexp-B + (read-string + (format + "Ignore B-regions matching this regexp (default %s): " + ediff-regexp-hide-B))) + (if ediff-3way-comparison-job + (setq regexp-C + (read-string + (format + "Ignore C-regions matching this regexp (default %s): " + ediff-regexp-hide-C)))) + (if (eq ediff-hide-regexp-connective 'and) + (setq msg-connective "BOTH" + alt-msg-connective "ONE OF" + alt-connective 'or) + (setq msg-connective "ONE OF" + alt-msg-connective "BOTH" + alt-connective 'and)) + (if (y-or-n-p + (format + "Ignore regions that match %s regexps, OK? " + msg-connective)) + (message "Will ignore regions that match %s regexps" msg-connective) + (setq ediff-hide-regexp-connective alt-connective) + (message "Will ignore regions that match %s regexps" + alt-msg-connective)) + + (or (string= regexp-A "") (setq ediff-regexp-hide-A regexp-A)) + (or (string= regexp-B "") (setq ediff-regexp-hide-B regexp-B)) + (or (string= regexp-C "") (setq ediff-regexp-hide-C regexp-C))) + + ((eq (ediff-last-command-char) ?f) + (setq ediff-skip-diff-region-function + ediff-focus-on-regexp-matches-function + regexp-A + (read-string + (format + "Focus on A-regions matching this regexp (default %s): " + ediff-regexp-focus-A)) + regexp-B + (read-string + (format + "Focus on B-regions matching this regexp (default %s): " + ediff-regexp-focus-B))) + (if ediff-3way-comparison-job + (setq regexp-C + (read-string + (format + "Focus on C-regions matching this regexp (default %s): " + ediff-regexp-focus-C)))) + (if (eq ediff-focus-regexp-connective 'and) + (setq msg-connective "BOTH" + alt-msg-connective "ONE OF" + alt-connective 'or) + (setq msg-connective "ONE OF" + alt-msg-connective "BOTH" + alt-connective 'and)) + (if (y-or-n-p + (format + "Focus on regions that match %s regexps, OK? " + msg-connective)) + (message "Will focus on regions that match %s regexps" + msg-connective) + (setq ediff-focus-regexp-connective alt-connective) + (message "Will focus on regions that match %s regexps" + alt-msg-connective)) + + (or (string= regexp-A "") (setq ediff-regexp-focus-A regexp-A)) + (or (string= regexp-B "") (setq ediff-regexp-focus-B regexp-B)) + (or (string= regexp-C "") (setq ediff-regexp-focus-C regexp-C)))))) + +(defun ediff-toggle-skip-similar () + (interactive) + (ediff-barf-if-not-control-buffer) + (if (not (eq ediff-auto-refine 'on)) + (error + "Can't skip over whitespace regions: first turn auto-refining on")) + (setq ediff-ignore-similar-regions (not ediff-ignore-similar-regions)) + (if ediff-ignore-similar-regions + (message + "Skipping regions that differ only in white space & line breaks") + (message "Skipping over white-space differences turned off"))) + +(defun ediff-focus-on-regexp-matches (n) + "Focus on diffs that match regexp `ediff-regexp-focus-A/B'. +Regions to be ignored according to this function are those where +buf A region doesn't match `ediff-regexp-focus-A' and buf B region +doesn't match `ediff-regexp-focus-B'. +This function returns nil if the region number N (specified as +an argument) is not to be ignored and t if region N is to be ignored. + +N is a region number used by Ediff internally. It is 1 less +the number seen by the user." + (if (ediff-valid-difference-p n) + (let* ((ctl-buf ediff-control-buffer) + (regex-A ediff-regexp-focus-A) + (regex-B ediff-regexp-focus-B) + (regex-C ediff-regexp-focus-C) + (reg-A-match (ediff-with-current-buffer ediff-buffer-A + (save-restriction + (narrow-to-region + (ediff-get-diff-posn 'A 'beg n ctl-buf) + (ediff-get-diff-posn 'A 'end n ctl-buf)) + (goto-char (point-min)) + (re-search-forward regex-A nil t)))) + (reg-B-match (ediff-with-current-buffer ediff-buffer-B + (save-restriction + (narrow-to-region + (ediff-get-diff-posn 'B 'beg n ctl-buf) + (ediff-get-diff-posn 'B 'end n ctl-buf)) + (re-search-forward regex-B nil t)))) + (reg-C-match (if ediff-3way-comparison-job + (ediff-with-current-buffer ediff-buffer-C + (save-restriction + (narrow-to-region + (ediff-get-diff-posn 'C 'beg n ctl-buf) + (ediff-get-diff-posn 'C 'end n ctl-buf)) + (re-search-forward regex-C nil t)))))) + (not (eval (if ediff-3way-comparison-job + (list ediff-focus-regexp-connective + reg-A-match reg-B-match reg-C-match) + (list ediff-focus-regexp-connective + reg-A-match reg-B-match)))) + ))) + +(defun ediff-hide-regexp-matches (n) + "Hide diffs that match regexp `ediff-regexp-hide-A/B/C'. +Regions to be ignored are those where buf A region matches +`ediff-regexp-hide-A' and buf B region matches `ediff-regexp-hide-B'. +This function returns nil if the region number N (specified as +an argument) is not to be ignored and t if region N is to be ignored. + +N is a region number used by Ediff internally. It is 1 less +the number seen by the user." + (if (ediff-valid-difference-p n) + (let* ((ctl-buf ediff-control-buffer) + (regex-A ediff-regexp-hide-A) + (regex-B ediff-regexp-hide-B) + (regex-C ediff-regexp-hide-C) + (reg-A-match (ediff-with-current-buffer ediff-buffer-A + (save-restriction + (narrow-to-region + (ediff-get-diff-posn 'A 'beg n ctl-buf) + (ediff-get-diff-posn 'A 'end n ctl-buf)) + (goto-char (point-min)) + (re-search-forward regex-A nil t)))) + (reg-B-match (ediff-with-current-buffer ediff-buffer-B + (save-restriction + (narrow-to-region + (ediff-get-diff-posn 'B 'beg n ctl-buf) + (ediff-get-diff-posn 'B 'end n ctl-buf)) + (goto-char (point-min)) + (re-search-forward regex-B nil t)))) + (reg-C-match (if ediff-3way-comparison-job + (ediff-with-current-buffer ediff-buffer-C + (save-restriction + (narrow-to-region + (ediff-get-diff-posn 'C 'beg n ctl-buf) + (ediff-get-diff-posn 'C 'end n ctl-buf)) + (goto-char (point-min)) + (re-search-forward regex-C nil t)))))) + (eval (if ediff-3way-comparison-job + (list ediff-hide-regexp-connective + reg-A-match reg-B-match reg-C-match) + (list ediff-hide-regexp-connective reg-A-match reg-B-match))) + ))) + + + +;;; Quitting, suspending, etc. + +(defun ediff-quit (reverse-default-keep-variants) + "Finish an Ediff session and exit Ediff. +Unselects the selected difference, if any, restores the read-only and modified +flags of the compared file buffers, kills Ediff buffers for this session +\(but not buffers A, B, C\). + +If `ediff-keep-variants' is nil, the user will be asked whether the buffers +containing the variants should be removed \(if they haven't been modified\). +If it is t, they will be preserved unconditionally. A prefix argument, +temporarily reverses the meaning of this variable." + (interactive "P") + (ediff-barf-if-not-control-buffer) + (let ((ctl-buf (current-buffer)) + (ctl-frm (selected-frame)) + (minibuffer-auto-raise t)) + (if (y-or-n-p (format "Quit this Ediff session%s? " + (if (ediff-buffer-live-p ediff-meta-buffer) + " & show containing session group" ""))) + (progn + (message "") + (set-buffer ctl-buf) + (ediff-really-quit reverse-default-keep-variants)) + (select-frame ctl-frm) + (raise-frame ctl-frm) + (message "")))) + + +;; Perform the quit operations. +(defun ediff-really-quit (reverse-default-keep-variants) + (ediff-unhighlight-diffs-totally) + (ediff-clear-diff-vector 'ediff-difference-vector-A 'fine-diffs-also) + (ediff-clear-diff-vector 'ediff-difference-vector-B 'fine-diffs-also) + (ediff-clear-diff-vector 'ediff-difference-vector-C 'fine-diffs-also) + (ediff-clear-diff-vector 'ediff-difference-vector-Ancestor 'fine-diffs-also) + + (ediff-delete-temp-files) + + ;; Restore the visibility range. This affects only ediff-*-regions/windows. + ;; Since for other job names ediff-visible-region sets + ;; ediff-visible-bounds to ediff-wide-bounds, the settings below are + ;; ignored for such jobs. + (if ediff-quit-widened + (setq ediff-visible-bounds ediff-wide-bounds) + (setq ediff-visible-bounds ediff-narrow-bounds)) + + ;; Apply selective display to narrow or widen + (ediff-visible-region) + (mapc (lambda (overl) + (if (ediff-overlayp overl) + (ediff-delete-overlay overl))) + ediff-wide-bounds) + (mapc (lambda (overl) + (if (ediff-overlayp overl) + (ediff-delete-overlay overl))) + ediff-narrow-bounds) + + ;; restore buffer mode line id's in buffer-A/B/C + (let ((control-buffer ediff-control-buffer) + (meta-buffer ediff-meta-buffer) + (after-quit-hook-internal ediff-after-quit-hook-internal) + (session-number ediff-meta-session-number) + ;; suitable working frame + (warp-frame (if (and (ediff-window-display-p) (eq ediff-grab-mouse t)) + (cond ((window-live-p ediff-window-A) + (window-frame ediff-window-A)) + ((window-live-p ediff-window-B) + (window-frame ediff-window-B)) + (t (next-frame)))))) + (condition-case nil + (ediff-with-current-buffer ediff-buffer-A + (setq ediff-this-buffer-ediff-sessions + (delq control-buffer ediff-this-buffer-ediff-sessions)) + (kill-local-variable 'mode-line-buffer-identification) + (kill-local-variable 'mode-line-format) + ) + (error)) + + (condition-case nil + (ediff-with-current-buffer ediff-buffer-B + (setq ediff-this-buffer-ediff-sessions + (delq control-buffer ediff-this-buffer-ediff-sessions)) + (kill-local-variable 'mode-line-buffer-identification) + (kill-local-variable 'mode-line-format) + ) + (error)) + + (condition-case nil + (ediff-with-current-buffer ediff-buffer-C + (setq ediff-this-buffer-ediff-sessions + (delq control-buffer ediff-this-buffer-ediff-sessions)) + (kill-local-variable 'mode-line-buffer-identification) + (kill-local-variable 'mode-line-format) + ) + (error)) + + (condition-case nil + (ediff-with-current-buffer ediff-ancestor-buffer + (setq ediff-this-buffer-ediff-sessions + (delq control-buffer ediff-this-buffer-ediff-sessions)) + (kill-local-variable 'mode-line-buffer-identification) + (kill-local-variable 'mode-line-format) + ) + (error)) + + (setq ediff-session-registry + (delq ediff-control-buffer ediff-session-registry)) + (ediff-update-registry) + ;; restore state of buffers to what it was before ediff + (ediff-restore-protected-variables) + + ;; If the user interrupts (canceling saving the merge buffer), continue + ;; normally. + (condition-case nil + (if (ediff-merge-job) + (run-hooks 'ediff-quit-merge-hook)) + (quit)) + + (run-hooks 'ediff-cleanup-hook) + + (ediff-janitor + 'ask + ;; reverse-default-keep-variants is t if the user quits with a prefix arg + (if reverse-default-keep-variants + (not ediff-keep-variants) + ediff-keep-variants)) + + ;; one hook here is ediff-cleanup-mess, which kills the control buffer and + ;; other auxiliary buffers. we made it into a hook to let the users do their + ;; own cleanup, if needed. + (run-hooks 'ediff-quit-hook) + (ediff-update-meta-buffer meta-buffer nil session-number) + + ;; warp mouse into a working window + (setq warp-frame ; if mouse is over a reasonable frame, use it + (cond ((ediff-good-frame-under-mouse)) + (t warp-frame))) + (if (and (ediff-window-display-p) (frame-live-p warp-frame) ediff-grab-mouse) + (set-mouse-position (if (featurep 'emacs) + warp-frame + (frame-selected-window warp-frame)) + 2 1)) + + (run-hooks 'after-quit-hook-internal) + )) + +;; Returns frame under mouse, if this frame is not a minibuffer +;; frame. Otherwise: nil +(defun ediff-good-frame-under-mouse () + (let ((frame-or-win (car (mouse-position))) + (buf-name "") + frame obj-ok) + (setq obj-ok + (if (featurep 'emacs) + (frame-live-p frame-or-win) + (window-live-p frame-or-win))) + (if obj-ok + (setq frame (if (featurep 'emacs) frame-or-win (window-frame frame-or-win)) + buf-name + (buffer-name (window-buffer (frame-selected-window frame))))) + (if (string-match "Minibuf" buf-name) + nil + frame))) + + +(defun ediff-delete-temp-files () + (if (and (stringp ediff-temp-file-A) (file-exists-p ediff-temp-file-A)) + (delete-file ediff-temp-file-A)) + (if (and (stringp ediff-temp-file-B) (file-exists-p ediff-temp-file-B)) + (delete-file ediff-temp-file-B)) + (if (and (stringp ediff-temp-file-C) (file-exists-p ediff-temp-file-C)) + (delete-file ediff-temp-file-C))) + + +;; Kill control buffer, other auxiliary Ediff buffers. +;; Leave one of the frames split between buffers A/B/C +(defun ediff-cleanup-mess () + (let* ((buff-A ediff-buffer-A) + (buff-B ediff-buffer-B) + (buff-C ediff-buffer-C) + (ctl-buf ediff-control-buffer) + (ctl-wind (ediff-get-visible-buffer-window ctl-buf)) + (ctl-frame ediff-control-frame) + (three-way-job ediff-3way-job) + (main-frame (cond ((window-live-p ediff-window-A) + (window-frame ediff-window-A)) + ((window-live-p ediff-window-B) + (window-frame ediff-window-B))))) + + (ediff-kill-buffer-carefully ediff-diff-buffer) + (ediff-kill-buffer-carefully ediff-custom-diff-buffer) + (ediff-kill-buffer-carefully ediff-fine-diff-buffer) + (ediff-kill-buffer-carefully ediff-tmp-buffer) + (ediff-kill-buffer-carefully ediff-error-buffer) + (ediff-kill-buffer-carefully ediff-msg-buffer) + (ediff-kill-buffer-carefully ediff-debug-buffer) + (if (boundp 'ediff-patch-diagnostics) + (ediff-kill-buffer-carefully ediff-patch-diagnostics)) + + ;; delete control frame or window + (cond ((and (ediff-window-display-p) (frame-live-p ctl-frame)) + (delete-frame ctl-frame)) + ((window-live-p ctl-wind) + (delete-window ctl-wind))) + + ;; Hide bottom toolbar. --marcpa + (if (not (ediff-multiframe-setup-p)) + (ediff-kill-bottom-toolbar)) + + (ediff-kill-buffer-carefully ctl-buf) + + (if (frame-live-p main-frame) + (select-frame main-frame)) + + ;; display only if not visible + (condition-case nil + (or (ediff-get-visible-buffer-window buff-B) + (switch-to-buffer buff-B)) + (error)) + (condition-case nil + (or (ediff-get-visible-buffer-window buff-A) + (progn + (if (and (ediff-get-visible-buffer-window buff-B) + (ediff-buffer-live-p buff-A)) + (funcall ediff-split-window-function)) + (switch-to-buffer buff-A))) + (error)) + (if three-way-job + (condition-case nil + (or (ediff-get-visible-buffer-window buff-C) + (progn + (if (and (or (ediff-get-visible-buffer-window buff-A) + (ediff-get-visible-buffer-window buff-B)) + (ediff-buffer-live-p buff-C)) + (funcall ediff-split-window-function)) + (switch-to-buffer buff-C))) + (error))) + (balance-windows) + (message "") + )) + +(defun ediff-janitor (ask keep-variants) + "Kill buffers A, B, and, possibly, C, if these buffers aren't modified. +In merge jobs, buffer C is not deleted here, but rather according to +ediff-quit-merge-hook. +A side effect of cleaning up may be that you should be careful when comparing +the same buffer in two separate Ediff sessions: quitting one of them might +delete this buffer in another session as well." + (ediff-dispose-of-variant-according-to-user + ediff-buffer-A 'A ask keep-variants) + (ediff-dispose-of-variant-according-to-user + ediff-buffer-B 'B ask keep-variants) + (if ediff-merge-job ; don't del buf C if merging--del ancestor buf instead + (ediff-dispose-of-variant-according-to-user + ediff-ancestor-buffer 'Ancestor ask keep-variants) + (ediff-dispose-of-variant-according-to-user + ediff-buffer-C 'C ask keep-variants) + )) + +;; Kill the variant buffer, according to user directives (ask, kill +;; unconditionaly, keep) +;; BUFF is the buffer, BUFF-TYPE is either 'A, or 'B, 'C, 'Ancestor +(defun ediff-dispose-of-variant-according-to-user (buff bufftype ask keep-variants) + ;; if this is indirect buffer, kill it and substitute with direct buf + (if (and (ediff-buffer-live-p buff) + (ediff-with-current-buffer buff ediff-temp-indirect-buffer)) + (let ((wind (ediff-get-visible-buffer-window buff)) + (base (buffer-base-buffer buff)) + (modified-p (buffer-modified-p buff))) + (if (and (window-live-p wind) (ediff-buffer-live-p base)) + (set-window-buffer wind base)) + ;; Kill indirect buffer even if it is modified, because the base buffer + ;; is still there. Note that if the base buffer is dead then so will be + ;; the indirect buffer + (ediff-with-current-buffer buff + (set-buffer-modified-p nil)) + (ediff-kill-buffer-carefully buff) + (ediff-with-current-buffer base + (set-buffer-modified-p modified-p))) + ;; otherwise, ask or use the value of keep-variants + (or (not (ediff-buffer-live-p buff)) + keep-variants + (buffer-modified-p buff) + (and ask + (not (y-or-n-p (format "Kill buffer %S [%s]? " + bufftype (buffer-name buff))))) + (ediff-kill-buffer-carefully buff)) + )) + +(defun ediff-maybe-save-and-delete-merge (&optional save-and-continue) + "Default hook to run on quitting a merge job. +This can also be used to save merge buffer in the middle of an Ediff session. + +If the optional SAVE-AND-CONTINUE argument is non-nil, save merge buffer and +continue. Otherwise: +If `ediff-autostore-merges' is nil, this does nothing. +If it is t, it saves the merge buffer in the file `ediff-merge-store-file' +or asks the user, if the latter is nil. It then asks the user whether to +delete the merge buffer. +If `ediff-autostore-merges' is neither nil nor t, the merge buffer is saved +only if this merge job is part of a group, i.e., was invoked from within +`ediff-merge-directories', `ediff-merge-directory-revisions', and such." + (let ((merge-store-file ediff-merge-store-file) + (ediff-autostore-merges ; fake ediff-autostore-merges, if necessary + (if save-and-continue t ediff-autostore-merges))) + (if ediff-autostore-merges + (cond ((stringp merge-store-file) + ;; store, ask to delete + (ediff-write-merge-buffer-and-maybe-kill + ediff-buffer-C merge-store-file 'show-file save-and-continue)) + ((eq ediff-autostore-merges t) + ;; ask for file name + (setq merge-store-file + (read-file-name "Save the result of the merge in file: ")) + (ediff-write-merge-buffer-and-maybe-kill + ediff-buffer-C merge-store-file nil save-and-continue)) + ((and (ediff-buffer-live-p ediff-meta-buffer) + (ediff-with-current-buffer ediff-meta-buffer + (ediff-merge-metajob))) + ;; The parent metajob passed nil as the autostore file. + nil))) + )) + +;; write merge buffer. If the optional argument save-and-continue is non-nil, +;; then don't kill the merge buffer +(defun ediff-write-merge-buffer-and-maybe-kill (buf file + &optional + show-file save-and-continue) + (if (not (eq (find-buffer-visiting file) buf)) + (let ((warn-message + (format "Another buffer is visiting file %s. Too dangerous to save the merge buffer" + file))) + (beep) + (message "%s" warn-message) + (with-output-to-temp-buffer ediff-msg-buffer + (princ "\n\n") + (princ warn-message) + (princ "\n\n") + ) + (sit-for 2)) + (ediff-with-current-buffer buf + (if (or (not (file-exists-p file)) + (y-or-n-p (format "File %s exists, overwrite? " file))) + (progn + ;;(write-region nil nil file) + (ediff-with-current-buffer buf + (set-visited-file-name file) + (save-buffer)) + (if show-file + (progn + (message "Merge buffer saved in: %s" file) + (set-buffer-modified-p nil) + (sit-for 3))) + (if (and + (not save-and-continue) + (y-or-n-p "Merge buffer saved. Now kill the buffer? ")) + (ediff-kill-buffer-carefully buf))))) + )) + +;; The default way of suspending Ediff. +;; Buries Ediff buffers, kills all windows. +(defun ediff-default-suspend-function () + (let* ((buf-A ediff-buffer-A) + (buf-B ediff-buffer-B) + (buf-C ediff-buffer-C) + (buf-A-wind (ediff-get-visible-buffer-window buf-A)) + (buf-B-wind (ediff-get-visible-buffer-window buf-B)) + (buf-C-wind (ediff-get-visible-buffer-window buf-C)) + (buf-patch (if (boundp 'ediff-patchbufer) ediff-patchbufer nil)) + (buf-patch-diag (if (boundp 'ediff-patch-diagnostics) + ediff-patch-diagnostics nil)) + (buf-err ediff-error-buffer) + (buf-diff ediff-diff-buffer) + (buf-custom-diff ediff-custom-diff-buffer) + (buf-fine-diff ediff-fine-diff-buffer)) + + ;; hide the control panel + (if (and (ediff-window-display-p) (frame-live-p ediff-control-frame)) + (iconify-frame ediff-control-frame) + (bury-buffer)) + (if buf-err (bury-buffer buf-err)) + (if buf-diff (bury-buffer buf-diff)) + (if buf-custom-diff (bury-buffer buf-custom-diff)) + (if buf-fine-diff (bury-buffer buf-fine-diff)) + (if buf-patch (bury-buffer buf-patch)) + (if buf-patch-diag (bury-buffer buf-patch-diag)) + (if (window-live-p buf-A-wind) + (progn + (select-window buf-A-wind) + (delete-other-windows) + (bury-buffer)) + (if (ediff-buffer-live-p buf-A) + (progn + (set-buffer buf-A) + (bury-buffer)))) + (if (window-live-p buf-B-wind) + (progn + (select-window buf-B-wind) + (delete-other-windows) + (bury-buffer)) + (if (ediff-buffer-live-p buf-B) + (progn + (set-buffer buf-B) + (bury-buffer)))) + (if (window-live-p buf-C-wind) + (progn + (select-window buf-C-wind) + (delete-other-windows) + (bury-buffer)) + (if (ediff-buffer-live-p buf-C) + (progn + (set-buffer buf-C) + (bury-buffer)))) + )) + + +(defun ediff-suspend () + "Suspend Ediff. +To resume, switch to the appropriate `Ediff Control Panel' +buffer and then type \\[ediff-recenter]. Ediff will automatically set +up an appropriate window config." + (interactive) + (ediff-barf-if-not-control-buffer) + (run-hooks 'ediff-suspend-hook) + (message + "To resume, type M-x eregistry and select the desired Ediff session")) + +;; ediff-barf-if-not-control-buffer ensures only called from ediff. +(declare-function ediff-version "ediff" ()) + +(defun ediff-status-info () + "Show the names of the buffers or files being operated on by Ediff. +Hit \\[ediff-recenter] to reset the windows afterward." + (interactive) + (ediff-barf-if-not-control-buffer) + (save-excursion + (ediff-skip-unsuitable-frames)) + (with-output-to-temp-buffer ediff-msg-buffer + (ediff-with-current-buffer standard-output + (fundamental-mode)) + (raise-frame (selected-frame)) + (princ (ediff-version)) + (princ "\n\n") + (ediff-with-current-buffer ediff-buffer-A + (if buffer-file-name + (princ + (format "File A = %S\n" buffer-file-name)) + (princ + (format "Buffer A = %S\n" (buffer-name))))) + (ediff-with-current-buffer ediff-buffer-B + (if buffer-file-name + (princ + (format "File B = %S\n" buffer-file-name)) + (princ + (format "Buffer B = %S\n" (buffer-name))))) + (if ediff-3way-job + (ediff-with-current-buffer ediff-buffer-C + (if buffer-file-name + (princ + (format "File C = %S\n" buffer-file-name)) + (princ + (format "Buffer C = %S\n" (buffer-name)))))) + (princ (format "Customized diff output %s\n" + (if (ediff-buffer-live-p ediff-custom-diff-buffer) + (concat "\tin buffer " + (buffer-name ediff-custom-diff-buffer)) + " is not available"))) + (princ (format "Plain diff output %s\n" + (if (ediff-buffer-live-p ediff-diff-buffer) + (concat "\tin buffer " + (buffer-name ediff-diff-buffer)) + " is not available"))) + + (let* ((A-line (ediff-with-current-buffer ediff-buffer-A + (1+ (count-lines (point-min) (point))))) + (B-line (ediff-with-current-buffer ediff-buffer-B + (1+ (count-lines (point-min) (point))))) + C-line) + (princ (format "\Buffer A's point is on line %d\n" A-line)) + (princ (format "Buffer B's point is on line %d\n" B-line)) + (if ediff-3way-job + (progn + (setq C-line (ediff-with-current-buffer ediff-buffer-C + (1+ (count-lines (point-min) (point))))) + (princ (format "Buffer C's point is on line %d\n" C-line))))) + + (princ (format "\nCurrent difference number = %S\n" + (cond ((< ediff-current-difference 0) 'start) + ((>= ediff-current-difference + ediff-number-of-differences) 'end) + (t (1+ ediff-current-difference))))) + + (princ + (format "\n%s regions that differ in white space & line breaks only" + (if ediff-ignore-similar-regions + "Ignoring" "Showing"))) + (if (and ediff-merge-job ediff-show-clashes-only) + (princ + "\nFocusing on regions where both buffers differ from the ancestor")) + (if (and ediff-skip-merge-regions-that-differ-from-default ediff-merge-job) + (princ + "\nSkipping merge regions that differ from default setting")) + + (cond ((eq ediff-skip-diff-region-function 'ediff-show-all-diffs) + (princ "\nSelective browsing by regexp is off\n")) + ((eq ediff-skip-diff-region-function + ediff-hide-regexp-matches-function) + (princ + "\nIgnoring regions that match") + (princ + (format + "\n\t regexp `%s' in buffer A %S\n\t regexp `%s' in buffer B\n" + ediff-regexp-hide-A ediff-hide-regexp-connective + ediff-regexp-hide-B))) + ((eq ediff-skip-diff-region-function + ediff-focus-on-regexp-matches-function) + (princ + "\nFocusing on regions that match") + (princ + (format + "\n\t regexp `%s' in buffer A %S\n\t regexp `%s' in buffer B\n" + ediff-regexp-focus-A ediff-focus-regexp-connective + ediff-regexp-focus-B))) + (t (princ "\nSelective browsing via a user-defined method.\n"))) + + (princ + (format "\nBugs/suggestions: type `%s' while in Ediff Control Panel." + (substitute-command-keys "\\[ediff-submit-report]"))) + ) ; with output + (if (frame-live-p ediff-control-frame) + (ediff-reset-mouse ediff-control-frame)) + (if (window-live-p ediff-control-window) + (select-window ediff-control-window))) + + + + +;;; Support routines + +;; Select a difference by placing the ASCII flags around the appropriate +;; group of lines in the A, B buffers +;; This may have to be modified for buffer C, when it will be supported. +(defun ediff-select-difference (n) + (if (and (ediff-buffer-live-p ediff-buffer-A) + (ediff-buffer-live-p ediff-buffer-B) + (ediff-valid-difference-p n)) + (progn + (cond + ((and (ediff-has-face-support-p) ediff-use-faces) + (ediff-highlight-diff n)) + ((eq ediff-highlighting-style 'ascii) + (ediff-place-flags-in-buffer + 'A ediff-buffer-A ediff-control-buffer n) + (ediff-place-flags-in-buffer + 'B ediff-buffer-B ediff-control-buffer n) + (if ediff-3way-job + (ediff-place-flags-in-buffer + 'C ediff-buffer-C ediff-control-buffer n)) + (if (ediff-buffer-live-p ediff-ancestor-buffer) + (ediff-place-flags-in-buffer + 'Ancestor ediff-ancestor-buffer + ediff-control-buffer n)) + )) + + (ediff-install-fine-diff-if-necessary n) + ;; set current difference here so the hook will be able to refer to it + (setq ediff-current-difference n) + (run-hooks 'ediff-select-hook)))) + + +;; Unselect a difference by removing the ASCII flags in the buffers. +;; This may have to be modified for buffer C, when it will be supported. +(defun ediff-unselect-difference (n) + (if (ediff-valid-difference-p n) + (progn + (cond ((and (ediff-has-face-support-p) ediff-use-faces) + (ediff-unhighlight-diff)) + ((eq ediff-highlighting-style 'ascii) + (ediff-remove-flags-from-buffer + ediff-buffer-A + (ediff-get-diff-overlay n 'A)) + (ediff-remove-flags-from-buffer + ediff-buffer-B + (ediff-get-diff-overlay n 'B)) + (if ediff-3way-job + (ediff-remove-flags-from-buffer + ediff-buffer-C + (ediff-get-diff-overlay n 'C))) + (if (ediff-buffer-live-p ediff-ancestor-buffer) + (ediff-remove-flags-from-buffer + ediff-ancestor-buffer + (ediff-get-diff-overlay n 'Ancestor))) + )) + + ;; unhighlight fine diffs + (ediff-set-fine-diff-properties ediff-current-difference 'default) + (run-hooks 'ediff-unselect-hook)))) + + +;; Unselects prev diff and selects a new one, if FLAG has value other than +;; 'select-only or 'unselect-only. If FLAG is 'select-only, the +;; next difference is selected, but the current selection is not +;; unselected. If FLAG is 'unselect-only then the current selection is +;; unselected, but the next one is not selected. If NO-RECENTER is non-nil, +;; don't recenter buffers after selecting/unselecting. +(defun ediff-unselect-and-select-difference (n &optional flag no-recenter) + (let ((ediff-current-difference n)) + (or no-recenter + (ediff-recenter 'no-rehighlight))) + + (let ((control-buf ediff-control-buffer)) + (unwind-protect + (progn + (or (eq flag 'select-only) + (ediff-unselect-difference ediff-current-difference)) + + (or (eq flag 'unselect-only) + (ediff-select-difference n)) + ;; need to set current diff here even though it is also set in + ;; ediff-select-difference because ediff-select-difference might not + ;; be called if unselect-only is specified + (setq ediff-current-difference n) + ) ; end protected section + + (ediff-with-current-buffer control-buf (ediff-refresh-mode-lines))) + )) + + + +(defun ediff-highlight-diff-in-one-buffer (n buf-type) + (if (ediff-buffer-live-p (ediff-get-buffer buf-type)) + (let* ((buff (ediff-get-buffer buf-type)) + (last (ediff-with-current-buffer buff (point-max))) + (begin (ediff-get-diff-posn buf-type 'beg n)) + (end (ediff-get-diff-posn buf-type 'end n)) + (xtra (if (equal begin end) 1 0)) + (end-hilit (min last (+ end xtra))) + (current-diff-overlay + (symbol-value + (ediff-get-symbol-from-alist + buf-type ediff-current-diff-overlay-alist)))) + + (if (featurep 'xemacs) + (ediff-move-overlay current-diff-overlay begin end-hilit) + (ediff-move-overlay current-diff-overlay begin end-hilit buff)) + (ediff-overlay-put current-diff-overlay 'priority + (ediff-highest-priority begin end-hilit buff)) + (ediff-overlay-put current-diff-overlay 'ediff-diff-num n) + + ;; unhighlight the background overlay for diff n so it won't + ;; interfere with the current diff overlay + (ediff-set-overlay-face (ediff-get-diff-overlay n buf-type) nil) + ))) + + +(defun ediff-unhighlight-diff-in-one-buffer (buf-type) + (if (ediff-buffer-live-p (ediff-get-buffer buf-type)) + (let ((current-diff-overlay + (symbol-value + (ediff-get-symbol-from-alist + buf-type ediff-current-diff-overlay-alist))) + (overlay + (ediff-get-diff-overlay ediff-current-difference buf-type)) + ) + + (ediff-move-overlay current-diff-overlay 1 1) + + ;; rehighlight the overlay in the background of the + ;; current difference region + (ediff-set-overlay-face + overlay + (if (and (ediff-has-face-support-p) + ediff-use-faces ediff-highlight-all-diffs) + (ediff-background-face buf-type ediff-current-difference))) + ))) + +(defun ediff-unhighlight-diffs-totally-in-one-buffer (buf-type) + (ediff-unselect-and-select-difference -1) + (if (and (ediff-has-face-support-p) ediff-use-faces) + (let* ((inhibit-quit t) + (current-diff-overlay-var + (ediff-get-symbol-from-alist + buf-type ediff-current-diff-overlay-alist)) + (current-diff-overlay (symbol-value current-diff-overlay-var))) + (ediff-paint-background-regions 'unhighlight) + (if (ediff-overlayp current-diff-overlay) + (ediff-delete-overlay current-diff-overlay)) + (set current-diff-overlay-var nil) + ))) + + +(defun ediff-highlight-diff (n) + "Put face on diff N. Invoked for X displays only." + (ediff-highlight-diff-in-one-buffer n 'A) + (ediff-highlight-diff-in-one-buffer n 'B) + (ediff-highlight-diff-in-one-buffer n 'C) + (ediff-highlight-diff-in-one-buffer n 'Ancestor) + ) + + +(defun ediff-unhighlight-diff () + "Remove overlays from buffers A, B, and C." + (ediff-unhighlight-diff-in-one-buffer 'A) + (ediff-unhighlight-diff-in-one-buffer 'B) + (ediff-unhighlight-diff-in-one-buffer 'C) + (ediff-unhighlight-diff-in-one-buffer 'Ancestor) + ) + +;; delete highlighting overlays, restore faces to their original form +(defun ediff-unhighlight-diffs-totally () + (ediff-unhighlight-diffs-totally-in-one-buffer 'A) + (ediff-unhighlight-diffs-totally-in-one-buffer 'B) + (ediff-unhighlight-diffs-totally-in-one-buffer 'C) + (ediff-unhighlight-diffs-totally-in-one-buffer 'Ancestor) + ) + + +;; for compatibility +(defmacro ediff-minibuffer-with-setup-hook (fun &rest body) + `(if (fboundp 'minibuffer-with-setup-hook) + (minibuffer-with-setup-hook ,fun ,@body) + ,@body)) + +;; This is adapted from a similar function in `emerge.el'. +;; PROMPT should not have a trailing ': ', so that it can be modified +;; according to context. +;; If DEFAULT-FILE is set, it should be used as the default value. +;; If DEFAULT-DIR is non-nil, use it as the default directory. +;; Otherwise, use the value of Emacs' variable `default-directory.' +(defun ediff-read-file-name (prompt default-dir default-file &optional no-dirs) + ;; hack default-dir if it is not set + (setq default-dir + (file-name-as-directory + (ediff-abbreviate-file-name + (expand-file-name (or default-dir + (and default-file + (file-name-directory default-file)) + default-directory))))) + + ;; strip the directory from default-file + (if default-file + (setq default-file (file-name-nondirectory default-file))) + (if (string= default-file "") + (setq default-file nil)) + + (let ((defaults (and (fboundp 'dired-dwim-target-defaults) + (dired-dwim-target-defaults + (and default-file (list default-file)) + default-dir))) + f) + (setq f (ediff-minibuffer-with-setup-hook + (lambda () (when defaults + (setq minibuffer-default defaults))) + (read-file-name + (format "%s%s " + prompt + (cond (default-file + (concat " (default " default-file "):")) + (t (concat " (default " default-dir "):")))) + default-dir + (or default-file default-dir) + t ; must match, no-confirm + (if default-file (file-name-directory default-file))))) + (setq f (expand-file-name f default-dir)) + ;; If user entered a directory name, expand the default file in that + ;; directory. This allows the user to enter a directory name for the + ;; B-file and diff against the default-file in that directory instead + ;; of a DIRED listing! + (if (and (file-directory-p f) default-file) + (setq f (expand-file-name + (file-name-nondirectory default-file) f))) + (if (and no-dirs (file-directory-p f)) + (error "File %s is a directory" f)) + f)) + +;; If PREFIX is given, then it is used as a prefix for the temp file +;; name. Otherwise, `ediff' is used. If FILE is given, use this +;; file and don't create a new one. +;; In MS-DOS, make sure the prefix isn't too long, or else +;; `make-temp-name' isn't guaranteed to return a unique filename. +;; Also, save buffer from START to END in the file. +;; START defaults to (point-min), END to (point-max) +(defun ediff-make-temp-file (buff &optional prefix given-file start end) + (let* ((p (ediff-convert-standard-filename (or prefix "ediff"))) + (short-p p) + (coding-system-for-write ediff-coding-system-for-write) + f short-f) + (if (and (fboundp 'msdos-long-file-names) + (not (msdos-long-file-names)) + (> (length p) 2)) + (setq short-p (substring p 0 2))) + + (setq f (concat ediff-temp-file-prefix p) + short-f (concat ediff-temp-file-prefix short-p) + f (cond (given-file) + ((find-file-name-handler f 'insert-file-contents) + ;; to thwart file handlers in write-region, e.g., if file + ;; name ends with .Z or .gz + ;; This is needed so that patches produced by ediff will + ;; have more meaningful names + (ediff-make-empty-tmp-file short-f)) + (prefix + ;; Prefix is most often the same as the file name for the + ;; variant. Here we are trying to use the original file + ;; name but in the temp directory. + (ediff-make-empty-tmp-file f 'keep-name)) + (t + ;; If don't care about name, add some random stuff + ;; to proposed file name. + (ediff-make-empty-tmp-file short-f)))) + + ;; create the file + (ediff-with-current-buffer buff + (write-region (if start start (point-min)) + (if end end (point-max)) + f + nil ; don't append---erase + 'no-message) + (set-file-modes f ediff-temp-file-mode) + (expand-file-name f)))) + +;; Create a temporary file. +;; The returned file name (created by appending some random characters at the +;; end of PROPOSED-NAME is guaranteed to point to a newly created empty file. +;; This is a replacement for make-temp-name, which eliminates a security hole. +;; If KEEP-PROPOSED-NAME isn't nil, try to keep PROPOSED-NAME, unless such file +;; already exists. +;; It is a modified version of make-temp-file in emacs 20.5 +(defun ediff-make-empty-tmp-file (proposed-name &optional keep-proposed-name) + (let ((file proposed-name)) + (while (condition-case () + (progn + (if (or (file-exists-p file) (not keep-proposed-name)) + (setq file (make-temp-name proposed-name))) + ;; the with-temp-buffer thing is a workaround for an XEmacs + ;; bug: write-region complains that we are trying to visit a + ;; file in an indirect buffer, failing to notice that the + ;; VISIT flag is unset and that we are actually writing from a + ;; string and not from any buffer. + (with-temp-buffer + (write-region "" nil file nil 'silent nil 'excl)) + nil) + (file-already-exists t)) + ;; the file was somehow created by someone else between + ;; `make-temp-name' and `write-region', let's try again. + nil) + file)) + + +;; Quote metacharacters (using \) when executing diff in Unix, but not in +;; EMX OS/2 +;;(defun ediff-protect-metachars (str) +;; (or (memq system-type '(emx)) +;; (let ((limit 0)) +;; (while (string-match ediff-metachars str limit) +;; (setq str (concat (substring str 0 (match-beginning 0)) +;; "\\" +;; (substring str (match-beginning 0)))) +;; (setq limit (1+ (match-end 0)))))) +;; str) + +;; Make sure the current buffer (for a file) has the same contents as the +;; file on disk, and attempt to remedy the situation if not. +;; Signal an error if we can't make them the same, or the user doesn't want +;; to do what is necessary to make them the same. +;; Also, Ediff always offers to revert obsolete buffers, whether they +;; are modified or not. +(defun ediff-verify-file-buffer (&optional file-magic) + ;; First check if the file has been modified since the buffer visited it. + (if (verify-visited-file-modtime (current-buffer)) + (if (buffer-modified-p) + ;; If buffer is not obsolete and is modified, offer to save + (if (yes-or-no-p + (format "Buffer %s has been modified. Save it in file %s? " + (buffer-name) + buffer-file-name)) + (condition-case nil + (save-buffer) + (error + (beep) + (message "Couldn't save %s" buffer-file-name))) + (error "Buffer is out of sync for file %s" buffer-file-name)) + ;; If buffer is not obsolete and is not modified, do nothing + nil) + ;; If buffer is obsolete, offer to revert + (if (yes-or-no-p + (format "File %s was modified since visited by buffer %s. REVERT file %s? " + buffer-file-name + (buffer-name) + buffer-file-name)) + (progn + (if file-magic + (erase-buffer)) + (revert-buffer t t)) + (error "Buffer out of sync for file %s" buffer-file-name)))) + +;; if there is another buffer visiting the file of the merge buffer, offer to +;; save and delete the buffer; else bark +(defun ediff-verify-file-merge-buffer (file) + (let ((buff (if (stringp file) (find-buffer-visiting file))) + warn-message) + (or (null buff) + (progn + (setq warn-message + (format "Buffer %s is visiting %s. Save and kill the buffer? " + (buffer-name buff) file)) + (with-output-to-temp-buffer ediff-msg-buffer + (princ "\n\n") + (princ warn-message) + (princ "\n\n")) + (if (y-or-n-p + (message "%s" warn-message)) + (with-current-buffer buff + (save-buffer) + (kill-buffer (current-buffer))) + (error "Too dangerous to merge versions of a file visited by another buffer")))) + )) + + + +(defun ediff-filename-magic-p (file) + (or (ediff-file-compressed-p file) + (ediff-file-remote-p file))) + + +(defun ediff-save-buffer (arg) + "Safe way of saving buffers A, B, C, and the diff output. +`wa' saves buffer A, `wb' saves buffer B, `wc' saves buffer C, +and `wd' saves the diff output. + +With prefix argument, `wd' saves plain diff output. +Without an argument, it saves customized diff argument, if available +\(and plain output, if customized output was not generated\)." + (interactive "P") + (ediff-barf-if-not-control-buffer) + (ediff-compute-custom-diffs-maybe) + (ediff-with-current-buffer + (cond ((memq (ediff-last-command-char) '(?a ?b ?c)) + (ediff-get-buffer + (ediff-char-to-buftype (ediff-last-command-char)))) + ((eq (ediff-last-command-char) ?d) + (message "Saving diff output ...") + (sit-for 1) ; let the user see the message + (cond ((and arg (ediff-buffer-live-p ediff-diff-buffer)) + ediff-diff-buffer) + ((ediff-buffer-live-p ediff-custom-diff-buffer) + ediff-custom-diff-buffer) + ((ediff-buffer-live-p ediff-diff-buffer) + ediff-diff-buffer) + (t (error "Output from `diff' not found")))) + ) + (let ((window-min-height 2)) + (save-buffer)))) + + +;; idea suggested by Hannu Koivisto +(defun ediff-clone-buffer-for-region-comparison (buff region-name) + (let ((cloned-buff (ediff-make-cloned-buffer buff region-name)) + (pop-up-windows t) + wind + other-wind + msg-buf) + (ediff-with-current-buffer cloned-buff + (setq ediff-temp-indirect-buffer t)) + (pop-to-buffer cloned-buff) + (setq wind (ediff-get-visible-buffer-window cloned-buff)) + (select-window wind) + (delete-other-windows) + (ediff-activate-mark) + (split-window-vertically) + (ediff-select-lowest-window) + (setq other-wind (selected-window)) + (with-temp-buffer + (erase-buffer) + (insert + (format "\n ******* Mark a region in buffer %s (or confirm the existing one) *******\n" + (buffer-name cloned-buff))) + (insert + (ediff-with-current-buffer buff + (format "\n\t When done, type %s Use %s to abort\n " + (ediff-format-bindings-of 'exit-recursive-edit) + (ediff-format-bindings-of 'abort-recursive-edit)))) + (goto-char (point-min)) + (setq msg-buf (current-buffer)) + (set-window-buffer other-wind msg-buf) + (shrink-window-if-larger-than-buffer) + (if (window-live-p wind) + (select-window wind)) + (condition-case nil + (recursive-edit) + (quit + (ediff-kill-buffer-carefully cloned-buff))) + ) + cloned-buff)) + + +(defun ediff-clone-buffer-for-window-comparison (buff wind region-name) + (let ((cloned-buff (ediff-make-cloned-buffer buff region-name))) + (ediff-with-current-buffer cloned-buff + (setq ediff-temp-indirect-buffer t)) + (set-window-buffer wind cloned-buff) + cloned-buff)) + +(defun ediff-clone-buffer-for-current-diff-comparison (buff buf-type reg-name) + (let ((cloned-buff (ediff-make-cloned-buffer buff reg-name)) + (reg-start (ediff-get-diff-posn buf-type 'beg)) + (reg-end (ediff-get-diff-posn buf-type 'end))) + (ediff-with-current-buffer cloned-buff + ;; set region to be the current diff region + (goto-char reg-start) + (set-mark reg-end) + (setq ediff-temp-indirect-buffer t)) + cloned-buff)) + + + +(defun ediff-make-cloned-buffer (buff region-name) + (ediff-make-indirect-buffer + buff (generate-new-buffer-name + (concat (if (stringp buff) buff (buffer-name buff)) region-name)))) + + +(defun ediff-make-indirect-buffer (base-buf indirect-buf-name) + (if (featurep 'xemacs) + (make-indirect-buffer base-buf indirect-buf-name) + (make-indirect-buffer base-buf indirect-buf-name 'clone))) + + +;; This function operates only from an ediff control buffer +(defun ediff-compute-custom-diffs-maybe () + (let ((buf-A-file-name (buffer-file-name ediff-buffer-A)) + (buf-B-file-name (buffer-file-name ediff-buffer-B)) + file-A file-B) + (unless (and buf-A-file-name + (file-exists-p buf-A-file-name) + (not (ediff-file-remote-p buf-A-file-name))) + (setq file-A (ediff-make-temp-file ediff-buffer-A))) + (unless (and buf-B-file-name + (file-exists-p buf-B-file-name) + (not (ediff-file-remote-p buf-B-file-name))) + (setq file-B (ediff-make-temp-file ediff-buffer-B))) + (or (ediff-buffer-live-p ediff-custom-diff-buffer) + (setq ediff-custom-diff-buffer + (get-buffer-create + (ediff-unique-buffer-name "*ediff-custom-diff" "*")))) + (ediff-with-current-buffer ediff-custom-diff-buffer + (setq buffer-read-only nil) + (erase-buffer)) + (ediff-exec-process + ediff-custom-diff-program ediff-custom-diff-buffer 'synchronize + ediff-custom-diff-options + (or file-A buf-A-file-name) + (or file-B buf-B-file-name)) + ;; put the diff file in diff-mode, if it is available + (if (fboundp 'diff-mode) + (with-current-buffer ediff-custom-diff-buffer + (diff-mode))) + (and file-A (file-exists-p file-A) (delete-file file-A)) + (and file-B (file-exists-p file-B) (delete-file file-B)) + )) + +(defun ediff-show-diff-output (arg) + (interactive "P") + (ediff-barf-if-not-control-buffer) + (ediff-compute-custom-diffs-maybe) + (save-excursion + (ediff-skip-unsuitable-frames ' ok-unsplittable)) + (let ((buf (cond ((and arg (ediff-buffer-live-p ediff-diff-buffer)) + ediff-diff-buffer) + ((ediff-buffer-live-p ediff-custom-diff-buffer) + ediff-custom-diff-buffer) + ((ediff-buffer-live-p ediff-diff-buffer) + ediff-diff-buffer) + (t + (beep) + (message "Output from `diff' not found") + nil)))) + (if buf + (progn + (ediff-with-current-buffer buf + (goto-char (point-min))) + (switch-to-buffer buf) + (raise-frame (selected-frame))))) + (if (frame-live-p ediff-control-frame) + (ediff-reset-mouse ediff-control-frame)) + (if (window-live-p ediff-control-window) + (select-window ediff-control-window))) + + +(defun ediff-inferior-compare-regions () + "Compare regions in an active Ediff session. +Like ediff-regions-linewise but is called from under an active Ediff session on +the files that belong to that session. + +After quitting the session invoked via this function, type C-l to the parent +Ediff Control Panel to restore highlighting." + (interactive) + (let ((answer "") + (possibilities (list ?A ?B ?C)) + (zmacs-regions t) + use-current-diff-p + begA begB endA endB bufA bufB) + + (if (ediff-valid-difference-p ediff-current-difference) + (progn + (ediff-set-fine-diff-properties ediff-current-difference 'default) + (ediff-unhighlight-diff))) + (ediff-paint-background-regions 'unhighlight) + + (cond ((ediff-merge-job) + (setq bufB ediff-buffer-C) + ;; ask which buffer to compare to the merge buffer + (while (cond ((eq answer ?A) + (setq bufA ediff-buffer-A + possibilities '(?B)) + nil) + ((eq answer ?B) + (setq bufA ediff-buffer-B + possibilities '(?A)) + nil) + ((equal answer "")) + (t (beep 1) + (message "Valid values are A or B") + (sit-for 2) + t)) + (let ((cursor-in-echo-area t)) + (message + "Which buffer to compare to the merge buffer (A or B)? ") + (setq answer (capitalize (read-char-exclusive)))))) + + ((ediff-3way-comparison-job) + ;; ask which two buffers to compare + (while (cond ((memq answer possibilities) + (setq possibilities (delq answer possibilities)) + (setq bufA + (eval + (ediff-get-symbol-from-alist + answer ediff-buffer-alist))) + nil) + ((equal answer "")) + (t (beep 1) + (message + "Valid values are %s" + (mapconcat 'char-to-string possibilities " or ")) + (sit-for 2) + t)) + (let ((cursor-in-echo-area t)) + (message "Enter the 1st buffer you want to compare (%s): " + (mapconcat 'char-to-string possibilities " or ")) + (setq answer (capitalize (read-char-exclusive))))) + (setq answer "") ; silence error msg + (while (cond ((memq answer possibilities) + (setq possibilities (delq answer possibilities)) + (setq bufB + (eval + (ediff-get-symbol-from-alist + answer ediff-buffer-alist))) + nil) + ((equal answer "")) + (t (beep 1) + (message + "Valid values are %s" + (mapconcat 'char-to-string possibilities " or ")) + (sit-for 2) + t)) + (let ((cursor-in-echo-area t)) + (message "Enter the 2nd buffer you want to compare (%s): " + (mapconcat 'char-to-string possibilities "/")) + (setq answer (capitalize (read-char-exclusive)))))) + (t ; 2way comparison + (setq bufA ediff-buffer-A + bufB ediff-buffer-B + possibilities nil))) + + (if (and (ediff-valid-difference-p ediff-current-difference) + (y-or-n-p "Compare currently highlighted difference regions? ")) + (setq use-current-diff-p t)) + + (setq bufA (if use-current-diff-p + (ediff-clone-buffer-for-current-diff-comparison + bufA 'A "-Region.A-") + (ediff-clone-buffer-for-region-comparison bufA "-Region.A-"))) + (ediff-with-current-buffer bufA + (setq begA (region-beginning) + endA (region-end)) + (goto-char begA) + (beginning-of-line) + (setq begA (point)) + (goto-char endA) + (end-of-line) + (or (eobp) (forward-char)) ; include the newline char + (setq endA (point))) + + (setq bufB (if use-current-diff-p + (ediff-clone-buffer-for-current-diff-comparison + bufB 'B "-Region.B-") + (ediff-clone-buffer-for-region-comparison bufB "-Region.B-"))) + (ediff-with-current-buffer bufB + (setq begB (region-beginning) + endB (region-end)) + (goto-char begB) + (beginning-of-line) + (setq begB (point)) + (goto-char endB) + (end-of-line) + (or (eobp) (forward-char)) ; include the newline char + (setq endB (point))) + + + (ediff-regions-internal + bufA begA endA bufB begB endB + nil ; setup-hook + (if use-current-diff-p ; job name + 'ediff-regions-wordwise + 'ediff-regions-linewise) + (if use-current-diff-p ; word mode, if diffing current diff + t nil) + ;; setup param to pass to ediff-setup + (list (cons 'ediff-split-window-function ediff-split-window-function))) + )) + + + +(defun ediff-remove-flags-from-buffer (buffer overlay) + (ediff-with-current-buffer buffer + (let ((inhibit-read-only t)) + (if (featurep 'xemacs) + (ediff-overlay-put overlay 'begin-glyph nil) + (ediff-overlay-put overlay 'before-string nil)) + + (if (featurep 'xemacs) + (ediff-overlay-put overlay 'end-glyph nil) + (ediff-overlay-put overlay 'after-string nil)) + ))) + + + +(defun ediff-place-flags-in-buffer (buf-type buffer ctl-buffer diff) + (ediff-with-current-buffer buffer + (ediff-place-flags-in-buffer1 buf-type ctl-buffer diff))) + + +(defun ediff-place-flags-in-buffer1 (buf-type ctl-buffer diff-no) + (let* ((curr-overl (ediff-with-current-buffer ctl-buffer + (ediff-get-diff-overlay diff-no buf-type))) + (before (ediff-get-diff-posn buf-type 'beg diff-no ctl-buffer)) + after beg-of-line flag) + + ;; insert flag before the difference + (goto-char before) + (setq beg-of-line (bolp)) + + (setq flag (ediff-with-current-buffer ctl-buffer + (if (eq ediff-highlighting-style 'ascii) + (if beg-of-line + ediff-before-flag-bol ediff-before-flag-mol)))) + + ;; insert the flag itself + (if (featurep 'xemacs) + (ediff-overlay-put curr-overl 'begin-glyph flag) + (ediff-overlay-put curr-overl 'before-string flag)) + + ;; insert the flag after the difference + ;; `after' must be set here, after the before-flag was inserted + (setq after (ediff-get-diff-posn buf-type 'end diff-no ctl-buffer)) + (goto-char after) + (setq beg-of-line (bolp)) + + (setq flag (ediff-with-current-buffer ctl-buffer + (if (eq ediff-highlighting-style 'ascii) + (if beg-of-line + ediff-after-flag-eol ediff-after-flag-mol)))) + + ;; insert the flag itself + (if (featurep 'xemacs) + (ediff-overlay-put curr-overl 'end-glyph flag) + (ediff-overlay-put curr-overl 'after-string flag)) + )) + + +;;; Some diff region tests + +;; t if diff region is empty. +;; In case of buffer C, t also if it is not a 3way +;; comparison job (merging jobs return t as well). +(defun ediff-empty-diff-region-p (n buf-type) + (if (eq buf-type 'C) + (or (not ediff-3way-comparison-job) + (= (ediff-get-diff-posn 'C 'beg n) + (ediff-get-diff-posn 'C 'end n))) + (= (ediff-get-diff-posn buf-type 'beg n) + (ediff-get-diff-posn buf-type 'end n)))) + +;; Test if diff region is white space only. +;; If 2-way job and buf-type = C, then returns t. +(defun ediff-whitespace-diff-region-p (n buf-type) + (or (and (eq buf-type 'C) (not ediff-3way-job)) + (ediff-empty-diff-region-p n buf-type) + (let ((beg (ediff-get-diff-posn buf-type 'beg n)) + (end (ediff-get-diff-posn buf-type 'end n))) + (ediff-with-current-buffer (ediff-get-buffer buf-type) + (save-excursion + (goto-char beg) + (skip-chars-forward ediff-whitespace) + (>= (point) end)))))) + + +(defun ediff-get-region-contents (n buf-type ctrl-buf &optional start end) + (ediff-with-current-buffer + (ediff-with-current-buffer ctrl-buf (ediff-get-buffer buf-type)) + (buffer-substring + (or start (ediff-get-diff-posn buf-type 'beg n ctrl-buf)) + (or end (ediff-get-diff-posn buf-type 'end n ctrl-buf))))) + +;; Returns positions of difference sectors in the BUF-TYPE buffer. +;; BUF-TYPE should be a symbol -- `A', `B', or `C'. +;; POS is either `beg' or `end'--it specifies whether you want the position at +;; the beginning of a difference or at the end. +;; +;; The optional argument N says which difference (default: +;; `ediff-current-difference'). N is the internal difference number (1- what +;; the user sees). The optional argument CONTROL-BUF says +;; which control buffer is in effect in case it is not the current +;; buffer. +(defun ediff-get-diff-posn (buf-type pos &optional n control-buf) + (let (diff-overlay) + (or control-buf + (setq control-buf (current-buffer))) + + (ediff-with-current-buffer control-buf + (or n (setq n ediff-current-difference)) + (if (or (< n 0) (>= n ediff-number-of-differences)) + (if (> ediff-number-of-differences 0) + (error ediff-BAD-DIFF-NUMBER + this-command (1+ n) ediff-number-of-differences) + (error ediff-NO-DIFFERENCES))) + (setq diff-overlay (ediff-get-diff-overlay n buf-type))) + (if (not (ediff-buffer-live-p (ediff-overlay-buffer diff-overlay))) + (error ediff-KILLED-VITAL-BUFFER)) + (if (eq pos 'beg) + (ediff-overlay-start diff-overlay) + (ediff-overlay-end diff-overlay)) + )) + + +;; Restore highlighting to what it should be according to ediff-use-faces, +;; ediff-highlighting-style, and ediff-highlight-all-diffs variables. +(defun ediff-restore-highlighting (&optional ctl-buf) + (ediff-with-current-buffer (or ctl-buf (current-buffer)) + (if (and (ediff-has-face-support-p) + ediff-use-faces + ediff-highlight-all-diffs) + (ediff-paint-background-regions)) + (ediff-select-difference ediff-current-difference))) + + + +;; null out difference overlays so they won't slow down future +;; editing operations +;; VEC is either a difference vector or a fine-diff vector +(defun ediff-clear-diff-vector (vec-var &optional fine-diffs-also) + (if (vectorp (symbol-value vec-var)) + (mapc (lambda (elt) + (ediff-delete-overlay + (ediff-get-diff-overlay-from-diff-record elt)) + (if fine-diffs-also + (ediff-clear-fine-diff-vector elt)) + ) + (symbol-value vec-var))) + ;; allow them to be garbage collected + (set vec-var nil)) + + + +;;; Misc + +;; In Emacs, this just makes overlay. In the future, when Emacs will start +;; supporting sticky overlays, this function will make a sticky overlay. +;; BEG and END are expressions telling where overlay starts. +;; If they are numbers or buffers, then all is well. Otherwise, they must +;; be expressions to be evaluated in buffer BUF in order to get the overlay +;; bounds. +;; If BUFF is not a live buffer, then return nil; otherwise, return the +;; newly created overlay. +(defun ediff-make-bullet-proof-overlay (beg end buff) + (if (ediff-buffer-live-p buff) + (let (overl) + (ediff-with-current-buffer buff + (or (number-or-marker-p beg) + (setq beg (eval beg))) + (or (number-or-marker-p end) + (setq end (eval end))) + (setq overl + (if (featurep 'xemacs) + (make-extent beg end buff) + ;; advance front and rear of the overlay + (make-overlay beg end buff nil 'rear-advance))) + + ;; never detach + (ediff-overlay-put + overl (if (featurep 'emacs) 'evaporate 'detachable) nil) + ;; make overlay open-ended + ;; In emacs, it is made open ended at creation time + (when (featurep 'xemacs) + (ediff-overlay-put overl 'start-open nil) + (ediff-overlay-put overl 'end-open nil)) + (ediff-overlay-put overl 'ediff-diff-num 0) + overl)))) + + +(defun ediff-make-current-diff-overlay (type) + (if (ediff-has-face-support-p) + (let ((overlay (ediff-get-symbol-from-alist + type ediff-current-diff-overlay-alist)) + (buffer (ediff-get-buffer type)) + (face (ediff-get-symbol-from-alist + type ediff-current-diff-face-alist))) + (set overlay + (ediff-make-bullet-proof-overlay (point-max) (point-max) buffer)) + (ediff-set-overlay-face (symbol-value overlay) face) + (ediff-overlay-put (symbol-value overlay) 'ediff ediff-control-buffer)) + )) + + +;; Like other-buffer, but prefers visible buffers and ignores temporary or +;; other insignificant buffers (those beginning with "^[ *]"). +;; Gets one arg--buffer name or a list of buffer names (it won't return +;; these buffers). +;; EXCL-BUFF-LIST is an exclusion list. +(defun ediff-other-buffer (excl-buff-lst) + (or (listp excl-buff-lst) (setq excl-buff-lst (list excl-buff-lst))) + (let* ((all-buffers (nconc (ediff-get-selected-buffers) (buffer-list))) + ;; we compute this the second time because we need to do memq on it + ;; later, and nconc above will break it. Either this or use slow + ;; append instead of nconc + (selected-buffers (ediff-get-selected-buffers)) + (prefered-buffer (car all-buffers)) + visible-dired-buffers + (excl-buff-name-list + (mapcar + (lambda (b) (cond ((stringp b) b) + ((bufferp b) (buffer-name b)))) + excl-buff-lst)) + ;; if at least one buffer on the exclusion list is dired, then force + ;; all others to be dired. This is because this means that the user + ;; has already chosen a dired buffer before + (use-dired-major-mode + (cond ((null (ediff-buffer-live-p (car excl-buff-lst))) 'unknown) + ((eq (ediff-with-current-buffer (car excl-buff-lst) major-mode) + 'dired-mode) + 'yes) + (t 'no))) + ;; significant-buffers must be visible and not belong + ;; to the exclusion list `buff-list' + ;; We also exclude temporary buffers, but keep mail and gnus buffers + ;; Furthermore, we exclude dired buffers, unless they are the only + ;; ones visible (and there are at least two of them). + ;; Also, any visible window not on the exclusion list that is first in + ;; the buffer list is chosen regardless. (This is because the user + ;; clicked on it or did something to distinguish it). + (significant-buffers + (mapcar + (lambda (x) + (cond ((member (buffer-name x) excl-buff-name-list) nil) + ((memq x selected-buffers) x) + ((not (ediff-get-visible-buffer-window x)) nil) + ((eq x prefered-buffer) x) + ;; if prev selected buffer is dired, look only at + ;; dired. + ((eq use-dired-major-mode 'yes) + (if (eq (ediff-with-current-buffer x major-mode) + 'dired-mode) + x nil)) + ((eq (ediff-with-current-buffer x major-mode) + 'dired-mode) + (if (null use-dired-major-mode) + ;; don't know if we must enforce dired. + ;; Remember this buffer in case + ;; dired buffs are the only ones visible. + (setq visible-dired-buffers + (cons x visible-dired-buffers))) + ;; skip, if dired is not forced + nil) + ((memq (ediff-with-current-buffer x major-mode) + '(rmail-mode + vm-mode + gnus-article-mode + mh-show-mode)) + x) + ((string-match "^[ *]" (buffer-name x)) nil) + ((string= "*scratch*" (buffer-name x)) nil) + (t x))) + all-buffers)) + (clean-significant-buffers (delq nil significant-buffers)) + less-significant-buffers) + + (if (and (null clean-significant-buffers) + (> (length visible-dired-buffers) 0)) + (setq clean-significant-buffers visible-dired-buffers)) + + (cond (clean-significant-buffers (car clean-significant-buffers)) + ;; try also buffers that are not displayed in windows + ((setq less-significant-buffers + (delq nil + (mapcar + (lambda (x) + (cond ((member (buffer-name x) excl-buff-name-list) + nil) + ((eq use-dired-major-mode 'yes) + (if (eq (ediff-with-current-buffer + x major-mode) + 'dired-mode) + x nil)) + ((eq (ediff-with-current-buffer x major-mode) + 'dired-mode) + nil) + ((string-match "^[ *]" (buffer-name x)) nil) + ((string= "*scratch*" (buffer-name x)) nil) + (t x))) + all-buffers))) + (car less-significant-buffers)) + (t "*scratch*")) + )) + + +;; If current buffer is a Buffer-menu buffer, then take the selected buffers +;; and append the buffer at the cursor to the end. +;; This list would be the preferred list. +(defun ediff-get-selected-buffers () + (if (eq major-mode 'Buffer-menu-mode) + (let ((lis (condition-case nil + (list (Buffer-menu-buffer t)) + (error)) + )) + (save-excursion + (goto-char (point-max)) + (while (search-backward "\n>" nil t) + (forward-char 1) + (setq lis (cons (Buffer-menu-buffer t) lis))) + lis)) + )) + +;; Construct a unique buffer name. +;; The first one tried is prefixsuffix, then prefix<2>suffix, +;; prefix<3>suffix, etc. +(defun ediff-unique-buffer-name (prefix suffix) + (if (null (get-buffer (concat prefix suffix))) + (concat prefix suffix) + (let ((n 2)) + (while (get-buffer (format "%s<%d>%s" prefix n suffix)) + (setq n (1+ n))) + (format "%s<%d>%s" prefix n suffix)))) + + +(defun ediff-submit-report () + "Submit bug report on Ediff." + (interactive) + (ediff-barf-if-not-control-buffer) + (let ((reporter-prompt-for-summary-p t) + (ctl-buf ediff-control-buffer) + (ediff-device-type (ediff-device-type)) + varlist salutation buffer-name) + (setq varlist '(ediff-diff-program ediff-diff-options + ediff-diff3-program ediff-diff3-options + ediff-patch-program ediff-patch-options + ediff-shell + ediff-use-faces + ediff-auto-refine ediff-highlighting-style + ediff-buffer-A ediff-buffer-B ediff-control-buffer + ediff-forward-word-function + ediff-control-frame + ediff-control-frame-parameters + ediff-control-frame-position-function + ediff-prefer-iconified-control-frame + ediff-window-setup-function + ediff-split-window-function + ediff-job-name + ediff-word-mode + buffer-name + ediff-device-type + )) + (setq salutation " +Congratulations! You may have unearthed a bug in Ediff! + +Please make a concise and accurate summary of what happened +and mail it to the address above. +----------------------------------------------------------- +") + + (ediff-skip-unsuitable-frames) + (ediff-reset-mouse) + + (switch-to-buffer ediff-msg-buffer) + (erase-buffer) + (delete-other-windows) + (insert " +Please read this first: +---------------------- + +Some ``bugs'' may actually be no bugs at all. For instance, if you are +reporting that certain difference regions are not matched as you think they +should, this is most likely due to the way Unix diff program decides what +constitutes a difference region. Ediff is an Emacs interface to diff, and +it has nothing to do with those decisions---it only takes the output from +diff and presents it in a way that is better suited for human browsing and +manipulation. + +If Emacs happens to dump core, this is NOT an Ediff problem---it is +an Emacs bug. Report this to Emacs maintainers. + +Another popular topic for reports is compilation messages. Because Ediff +interfaces to several other packages and runs under Emacs and XEmacs, +byte-compilation may produce output like this: + + While compiling toplevel forms in file ediff.el: + ** reference to free variable pm-color-alist + ........................ + While compiling the end of the data: + ** The following functions are not known to be defined: + ediff-valid-color-p, ediff-set-face, + ........................ + +These are NOT errors, but inevitable warnings, which ought to be ignored. + +Please do not report those and similar things. However, comments and +suggestions are always welcome. + +Mail anyway? (y or n) ") + + (if (y-or-n-p "Mail anyway? ") + (progn + (if (ediff-buffer-live-p ctl-buf) + (set-buffer ctl-buf)) + (setq buffer-name (buffer-name)) + (require 'reporter) + (reporter-submit-bug-report "kifer@cs.stonybrook.edu" + (ediff-version) + varlist + nil + 'delete-other-windows + salutation)) + (bury-buffer) + (beep 1)(message "Bug report aborted") + (if (ediff-buffer-live-p ctl-buf) + (ediff-with-current-buffer ctl-buf + (ediff-recenter 'no-rehighlight)))) + )) + + +;; Find an appropriate syntax table for everyone to use +;; If buffer B is not fundamental or text mode, use its syntax table +;; Otherwise, use buffer B's. +;; The syntax mode is used in ediff-forward-word-function +;; The important thing is that every buffer should use the same syntax table +;; during the refinement operation +(defun ediff-choose-syntax-table () + (setq ediff-syntax-table + (ediff-with-current-buffer ediff-buffer-A + (if (not (memq major-mode + '(fundamental-mode text-mode indented-text-mode))) + (syntax-table)))) + (if (not ediff-syntax-table) + (setq ediff-syntax-table + (ediff-with-current-buffer ediff-buffer-B + (syntax-table)))) + ) + + +(defun ediff-deactivate-mark () + (if (featurep 'xemacs) + (zmacs-deactivate-region) + (deactivate-mark))) + +(defun ediff-activate-mark () + (if (featurep 'xemacs) + (zmacs-activate-region) + (make-local-variable 'transient-mark-mode) + (setq mark-active t transient-mark-mode t))) + +(defun ediff-nuke-selective-display () + (if (featurep 'xemacs) + (nuke-selective-display) + (save-excursion + (save-restriction + (widen) + (goto-char (point-min)) + (let ((mod-p (buffer-modified-p)) + buffer-read-only end) + (and (eq t selective-display) + (while (search-forward "\^M" nil t) + (end-of-line) + (setq end (point)) + (beginning-of-line) + (while (search-forward "\^M" end t) + (delete-char -1) + (insert "\^J")))) + (set-buffer-modified-p mod-p) + (setq selective-display nil)))))) + + +;; The next two are modified versions from emerge.el. +;; VARS must be a list of symbols +;; ediff-save-variables returns an association list: ((var . val) ...) +(defsubst ediff-save-variables (vars) + (mapcar (lambda (v) (cons v (symbol-value v))) + vars)) +;; VARS is a list of variable symbols. +(defun ediff-restore-variables (vars assoc-list) + (while vars + (set (car vars) (cdr (assoc (car vars) assoc-list))) + (setq vars (cdr vars)))) + +(defun ediff-change-saved-variable (var value buf-type) + (let* ((assoc-list + (symbol-value (ediff-get-symbol-from-alist + buf-type + ediff-buffer-values-orig-alist))) + (assoc-elt (assoc var assoc-list))) + (if assoc-elt + (setcdr assoc-elt value)))) + + +;; must execute in control buf +(defun ediff-save-protected-variables () + (setq ediff-buffer-values-orig-A + (ediff-with-current-buffer ediff-buffer-A + (ediff-save-variables ediff-protected-variables))) + (setq ediff-buffer-values-orig-B + (ediff-with-current-buffer ediff-buffer-B + (ediff-save-variables ediff-protected-variables))) + (if ediff-3way-comparison-job + (setq ediff-buffer-values-orig-C + (ediff-with-current-buffer ediff-buffer-C + (ediff-save-variables ediff-protected-variables)))) + (if (ediff-buffer-live-p ediff-ancestor-buffer) + (setq ediff-buffer-values-orig-Ancestor + (ediff-with-current-buffer ediff-ancestor-buffer + (ediff-save-variables ediff-protected-variables))))) + +;; must execute in control buf +(defun ediff-restore-protected-variables () + (let ((values-A ediff-buffer-values-orig-A) + (values-B ediff-buffer-values-orig-B) + (values-C ediff-buffer-values-orig-C) + (values-Ancestor ediff-buffer-values-orig-Ancestor)) + (ediff-with-current-buffer ediff-buffer-A + (ediff-restore-variables ediff-protected-variables values-A)) + (ediff-with-current-buffer ediff-buffer-B + (ediff-restore-variables ediff-protected-variables values-B)) + (if ediff-3way-comparison-job + (ediff-with-current-buffer ediff-buffer-C + (ediff-restore-variables ediff-protected-variables values-C))) + (if (ediff-buffer-live-p ediff-ancestor-buffer) + (ediff-with-current-buffer ediff-ancestor-buffer + (ediff-restore-variables ediff-protected-variables values-Ancestor))) + )) + +;; save BUFFER in FILE. used in hooks. +(defun ediff-save-buffer-in-file (buffer file) + (ediff-with-current-buffer buffer + (write-file file))) + + +;;; Debug + +(ediff-defvar-local ediff-command-begin-time '(0 0 0) "") + +;; calculate time used by command +(defun ediff-calc-command-time () + (let ((end (current-time)) + micro sec) + (setq micro + (if (>= (nth 2 end) (nth 2 ediff-command-begin-time)) + (- (nth 2 end) (nth 2 ediff-command-begin-time)) + (+ (nth 2 end) (- 1000000 (nth 2 ediff-command-begin-time))))) + (setq sec (- (nth 1 end) (nth 1 ediff-command-begin-time))) + (or (equal ediff-command-begin-time '(0 0 0)) + (message "Elapsed time: %d second(s) + %d microsecond(s)" sec micro)))) + +(defsubst ediff-save-time () + (setq ediff-command-begin-time (current-time))) + +(defun ediff-profile () + "Toggle profiling Ediff commands." + (interactive) + (ediff-barf-if-not-control-buffer) + + (if (featurep 'xemacs) + (make-local-hook 'post-command-hook)) + + (let ((pre-hook 'pre-command-hook) + (post-hook 'post-command-hook)) + (if (not (equal ediff-command-begin-time '(0 0 0))) + (progn (remove-hook pre-hook 'ediff-save-time) + (remove-hook post-hook 'ediff-calc-command-time) + (setq ediff-command-begin-time '(0 0 0)) + (message "Ediff profiling disabled")) + (add-hook pre-hook 'ediff-save-time t 'local) + (add-hook post-hook 'ediff-calc-command-time nil 'local) + (message "Ediff profiling enabled")))) + +(defun ediff-print-diff-vector (diff-vector-var) + (princ (format "\n*** %S ***\n" diff-vector-var)) + (mapcar (lambda (overl-vec) + (princ + (format + "Diff %d: \tOverlay: %S +\t\tFine diffs: %s +\t\tNo-fine-diff-flag: %S +\t\tState-of-diff:\t %S +\t\tState-of-merge:\t %S +" + (1+ (ediff-overlay-get (aref overl-vec 0) 'ediff-diff-num)) + (aref overl-vec 0) + ;; fine-diff-vector + (if (= (length (aref overl-vec 1)) 0) + "none\n" + (mapconcat 'prin1-to-string + (aref overl-vec 1) "\n\t\t\t ")) + (aref overl-vec 2) ; no fine diff flag + (aref overl-vec 3) ; state-of-diff + (aref overl-vec 4) ; state-of-merge + ))) + (eval diff-vector-var))) + + + +(defun ediff-debug-info () + (interactive) + (ediff-barf-if-not-control-buffer) + (with-output-to-temp-buffer ediff-debug-buffer + (ediff-with-current-buffer standard-output + (fundamental-mode)) + (princ (format "\nCtl buffer: %S\n" ediff-control-buffer)) + (ediff-print-diff-vector (intern "ediff-difference-vector-A")) + (ediff-print-diff-vector (intern "ediff-difference-vector-B")) + (ediff-print-diff-vector (intern "ediff-difference-vector-C")) + (ediff-print-diff-vector (intern "ediff-difference-vector-Ancestor")) + )) + + +;;; General utilities + +;; this uses comparison-func to decide who is a member +(defun ediff-member (elt lis comparison-func) + (while (and lis (not (funcall comparison-func (car lis) elt))) + (setq lis (cdr lis))) + lis) + +;; Make a readable representation of the invocation sequence for FUNC-DEF. +;; It would either be a key or M-x something. +(defun ediff-format-bindings-of (func-def) + (let ((desc (car (where-is-internal func-def + overriding-local-map + nil nil)))) + (if desc + (key-description desc) + (format "M-x %s" func-def)))) + +;; this uses comparison-func to decide who is a member, and this determines how +;; intersection looks like +(defun ediff-intersection (lis1 lis2 comparison-func) + (let ((result (list 'a))) + (while lis1 + (if (ediff-member (car lis1) lis2 comparison-func) + (nconc result (list (car lis1)))) + (setq lis1 (cdr lis1))) + (cdr result))) + + +;; eliminates duplicates using comparison-func +(defun ediff-union (lis1 lis2 comparison-func) + (let ((result (list 'a))) + (while lis1 + (or (ediff-member (car lis1) (cdr result) comparison-func) + (nconc result (list (car lis1)))) + (setq lis1 (cdr lis1))) + (while lis2 + (or (ediff-member (car lis2) (cdr result) comparison-func) + (nconc result (list (car lis2)))) + (setq lis2 (cdr lis2))) + (cdr result))) + +;; eliminates duplicates using comparison-func +(defun ediff-set-difference (lis1 lis2 comparison-func) + (let ((result (list 'a))) + (while lis1 + (or (ediff-member (car lis1) (cdr result) comparison-func) + (ediff-member (car lis1) lis2 comparison-func) + (nconc result (list (car lis1)))) + (setq lis1 (cdr lis1))) + (cdr result))) + +(defun ediff-add-to-history (history-var newelt) + (if (fboundp 'add-to-history) + (add-to-history history-var newelt) + (set history-var (cons newelt (symbol-value history-var))))) + +(defalias 'ediff-copy-list 'copy-sequence) + + +;; don't report error if version control package wasn't found +;;(ediff-load-version-control 'silent) + +(run-hooks 'ediff-load-hook) + + +;; 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: + +;; arch-tag: f51099b6-ef4b-470f-88a1-3a0e0b03a879 +;;; ediff-util.el ends here diff -r 681cd08dc0f7 -r e93288477c43 lisp/vc/ediff-vers.el --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/lisp/vc/ediff-vers.el Sun Jun 13 22:57:55 2010 +0000 @@ -0,0 +1,239 @@ +;;; ediff-vers.el --- version control interface to Ediff + +;; Copyright (C) 1995, 1996, 1997, 2001, 2002, 2003, 2004, +;; 2005, 2006, 2007, 2008, 2009, 2010 Free Software Foundation, Inc. + +;; Author: Michael Kifer + +;; 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 3 of the License, 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. If not, see . + +;;; Commentary: + +;;; Code: + +;; Compiler pacifier +(defvar rcs-default-co-switches) + +(and noninteractive + (eval-when-compile + (condition-case nil + ;; for compatibility with current stable version of xemacs + (progn + ;;(require 'pcvs nil 'noerror) + ;;(require 'rcs nil 'noerror) + (require 'pcvs) + (require 'rcs)) + (error nil)) + (require 'vc) + (require 'ediff-init) + )) +;; end pacifier + +(defcustom ediff-keep-tmp-versions nil + "If t, do not delete temporary previous versions for the files on which +comparison or merge operations are being performed." + :type 'boolean + :group 'ediff-vers + ) + +(defalias 'ediff-vc-revision-other-window + (if (fboundp 'vc-revision-other-window) + 'vc-revision-other-window + 'vc-version-other-window)) + +(defalias 'ediff-vc-working-revision + (if (fboundp 'vc-working-revision) + 'vc-working-revision + 'vc-workfile-version)) + +;; VC.el support + +(eval-when-compile + (require 'vc-hooks)) ;; for vc-call macro + + +(defun ediff-vc-latest-version (file) + "Return the version level of the latest version of FILE in repository." + (if (fboundp 'vc-latest-version) + (vc-latest-version file) + (or (vc-file-getprop file 'vc-latest-revision) + (cond ((vc-backend file) + (vc-call state file) + (vc-file-getprop file 'vc-latest-revision)) + (t (error "File %s is not under version control" file)))) + )) + + +(defun ediff-vc-internal (rev1 rev2 &optional startup-hooks) + ;; Run Ediff on versions of the current buffer. + ;; If REV1 is "", use the latest version of the current buffer's file. + ;; If REV2 is "" then compare current buffer with REV1. + ;; If the current buffer is named `F', the version is named `F.~REV~'. + ;; If `F.~REV~' already exists, it is used instead of being re-created. + (let (file1 file2 rev1buf rev2buf) + (if (string= rev1 "") + (setq rev1 (ediff-vc-latest-version (buffer-file-name)))) + (save-window-excursion + (save-excursion + (ediff-vc-revision-other-window rev1) + (setq rev1buf (current-buffer) + file1 (buffer-file-name))) + (save-excursion + (or (string= rev2 "") ; use current buffer + (ediff-vc-revision-other-window rev2)) + (setq rev2buf (current-buffer) + file2 (buffer-file-name))) + (setq startup-hooks + (cons `(lambda () + (ediff-delete-version-file ,file1) + (or ,(string= rev2 "") (ediff-delete-version-file ,file2))) + startup-hooks))) + (ediff-buffers + rev1buf rev2buf + startup-hooks + 'ediff-revision))) + +;; RCS.el support +(defun rcs-ediff-view-revision (&optional rev) +;; View previous RCS revision of current file. +;; With prefix argument, prompts for a revision name. + (interactive (list (if current-prefix-arg + (read-string "Revision: ")))) + (let* ((filename (buffer-file-name (current-buffer))) + (switches (append '("-p") + (if rev (list (concat "-r" rev)) nil))) + (buff (concat (file-name-nondirectory filename) ".~" rev "~"))) + (message "Working ...") + (setq filename (expand-file-name filename)) + (with-output-to-temp-buffer buff + (ediff-with-current-buffer standard-output + (fundamental-mode)) + (let ((output-buffer (ediff-rcs-get-output-buffer filename buff))) + (delete-windows-on output-buffer) + (with-current-buffer output-buffer + (apply 'call-process "co" nil t nil + ;; -q: quiet (no diagnostics) + (append switches rcs-default-co-switches + (list "-q" filename))))) + (message "") + buff))) + +(defun ediff-rcs-get-output-buffer (file name) + ;; Get a buffer for RCS output for FILE, make it writable and clean it up. + ;; Optional NAME is name to use instead of `*RCS-output*'. + ;; This is a modified version from rcs.el v1.1. I use it here to make + ;; Ediff immune to changes in rcs.el + (let ((buf (get-buffer-create name))) + (with-current-buffer buf + (setq buffer-read-only nil + default-directory (file-name-directory (expand-file-name file))) + (erase-buffer)) + buf)) + +(defun ediff-rcs-internal (rev1 rev2 &optional startup-hooks) +;; Run Ediff on versions of the current buffer. +;; If REV2 is "" then use current buffer. + (let (rev2buf rev1buf) + (save-window-excursion + (setq rev2buf (if (string= rev2 "") + (current-buffer) + (rcs-ediff-view-revision rev2)) + rev1buf (rcs-ediff-view-revision rev1))) + + ;; rcs.el doesn't create temp version files, so we don't have to delete + ;; anything in startup hooks to ediff-buffers + (ediff-buffers rev1buf rev2buf startup-hooks 'ediff-revision) + )) + +;;; Merge with Version Control + +(defun ediff-vc-merge-internal (rev1 rev2 ancestor-rev + &optional startup-hooks merge-buffer-file) +;; If ANCESTOR-REV non-nil, merge with ancestor + (let (buf1 buf2 ancestor-buf) + (save-window-excursion + (save-excursion + (ediff-vc-revision-other-window rev1) + (setq buf1 (current-buffer))) + (save-excursion + (or (string= rev2 "") + (ediff-vc-revision-other-window rev2)) + (setq buf2 (current-buffer))) + (if ancestor-rev + (save-excursion + (if (string= ancestor-rev "") + (setq ancestor-rev (ediff-vc-working-revision buffer-file-name))) + (ediff-vc-revision-other-window ancestor-rev) + (setq ancestor-buf (current-buffer)))) + (setq startup-hooks + (cons + `(lambda () + (ediff-delete-version-file ,(buffer-file-name buf1)) + (or ,(string= rev2 "") + (ediff-delete-version-file ,(buffer-file-name buf2))) + (or ,(string= ancestor-rev "") + ,(not ancestor-rev) + (ediff-delete-version-file ,(buffer-file-name ancestor-buf))) + ) + startup-hooks))) + (if ancestor-rev + (ediff-merge-buffers-with-ancestor + buf1 buf2 ancestor-buf + startup-hooks 'ediff-merge-revisions-with-ancestor merge-buffer-file) + (ediff-merge-buffers + buf1 buf2 startup-hooks 'ediff-merge-revisions merge-buffer-file)) + )) + +(defun ediff-rcs-merge-internal (rev1 rev2 ancestor-rev + &optional + startup-hooks merge-buffer-file) + ;; If ANCESTOR-REV non-nil, merge with ancestor + (let (buf1 buf2 ancestor-buf) + (save-window-excursion + (setq buf1 (rcs-ediff-view-revision rev1) + buf2 (if (string= rev2 "") + (current-buffer) + (rcs-ediff-view-revision rev2)) + ancestor-buf (if ancestor-rev + (if (string= ancestor-rev "") + (current-buffer) + (rcs-ediff-view-revision ancestor-rev))))) + ;; rcs.el doesn't create temp version files, so we don't have to delete + ;; anything in startup hooks to ediff-buffers + (if ancestor-rev + (ediff-merge-buffers-with-ancestor + buf1 buf2 ancestor-buf + startup-hooks 'ediff-merge-revisions-with-ancestor merge-buffer-file) + (ediff-merge-buffers + buf1 buf2 startup-hooks 'ediff-merge-revisions merge-buffer-file)))) + + +;; delete version file on exit unless ediff-keep-tmp-versions is true +(defun ediff-delete-version-file (file) + (or ediff-keep-tmp-versions (delete-file file))) + + +(provide 'ediff-vers) + + +;; 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: + +;; arch-tag: bbb34f0c-2a90-426a-a77a-c75f479ebbbf +;;; ediff-vers.el ends here diff -r 681cd08dc0f7 -r e93288477c43 lisp/vc/ediff-wind.el --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/lisp/vc/ediff-wind.el Sun Jun 13 22:57:55 2010 +0000 @@ -0,0 +1,1313 @@ +;;; ediff-wind.el --- window manipulation utilities + +;; Copyright (C) 1994, 1995, 1996, 1997, 2000, 2001, 2002, 2003, +;; 2004, 2005, 2006, 2007, 2008, 2009, 2010 Free Software Foundation, Inc. + +;; Author: Michael Kifer + +;; 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 3 of the License, 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. If not, see . + +;;; Commentary: + +;;; Code: + + +;; Compiler pacifier +(defvar icon-title-format) +(defvar top-toolbar-height) +(defvar bottom-toolbar-height) +(defvar left-toolbar-height) +(defvar right-toolbar-height) +(defvar left-toolbar-width) +(defvar right-toolbar-width) +(defvar default-menubar) +(defvar top-gutter) +(defvar frame-icon-title-format) +(defvar ediff-diff-status) + +;; declare-function does not exist in XEmacs +(eval-and-compile + (unless (fboundp 'declare-function) (defmacro declare-function (&rest r)))) + +(eval-when-compile + (require 'ediff-util) + (require 'ediff-help)) +;; end pacifier + +(require 'ediff-init) + +;; be careful with ediff-tbar +(if (featurep 'xemacs) + (require 'ediff-tbar) + (defun ediff-compute-toolbar-width () 0)) + +(defgroup ediff-window nil + "Ediff window manipulation." + :prefix "ediff-" + :group 'ediff + :group 'frames) + + +;; Determine which window setup function to use based on current window system. +(defun ediff-choose-window-setup-function-automatically () + (if (ediff-window-display-p) + 'ediff-setup-windows-multiframe + 'ediff-setup-windows-plain)) + +(defcustom ediff-window-setup-function (ediff-choose-window-setup-function-automatically) + "Function called to set up windows. +Ediff provides a choice of two functions: `ediff-setup-windows-plain', for +doing everything in one frame and `ediff-setup-windows-multiframe', which sets +the control panel in a separate frame. By default, the appropriate function is +chosen automatically depending on the current window system. +However, `ediff-toggle-multiframe' can be used to toggle between the multiframe +display and the single frame display. +If the multiframe function detects that one of the buffers A/B is seen in some +other frame, it will try to keep that buffer in that frame. + +If you don't like any of the two provided functions, write your own one. +The basic guidelines: + 1. It should leave the control buffer current and the control window + selected. + 2. It should set `ediff-window-A', `ediff-window-B', `ediff-window-C', + and `ediff-control-window' to contain window objects that display + the corresponding buffers. + 3. It should accept the following arguments: + buffer-A, buffer-B, buffer-C, control-buffer + Buffer C may not be used in jobs that compare only two buffers. +If you plan to do something fancy, take a close look at how the two +provided functions are written." + :type '(choice (const :tag "Multi Frame" ediff-setup-windows-multiframe) + (const :tag "Single Frame" ediff-setup-windows-plain) + (function :tag "Other function")) + :group 'ediff-window) + +;; indicates if we are in a multiframe setup +(ediff-defvar-local ediff-multiframe nil "") + +;; Share of the frame occupied by the merge window (buffer C) +(ediff-defvar-local ediff-merge-window-share 0.45 "") + +;; The control window. +(ediff-defvar-local ediff-control-window nil "") +;; Official window for buffer A +(ediff-defvar-local ediff-window-A nil "") +;; Official window for buffer B +(ediff-defvar-local ediff-window-B nil "") +;; Official window for buffer C +(ediff-defvar-local ediff-window-C nil "") +;; Ediff's window configuration. +;; Used to minimize the need to rearrange windows. +(ediff-defvar-local ediff-window-config-saved "" "") + +;; Association between buff-type and ediff-window-* +(defconst ediff-window-alist + '((A . ediff-window-A) + (?A . ediff-window-A) + (B . ediff-window-B) + (?B . ediff-window-B) + (C . ediff-window-C) + (?C . ediff-window-C))) + + +(defcustom ediff-split-window-function 'split-window-vertically + "The function used to split the main window between buffer-A and buffer-B. +You can set it to a horizontal split instead of the default vertical split +by setting this variable to `split-window-horizontally'. +You can also have your own function to do fancy splits. +This variable has no effect when buffer-A/B are shown in different frames. +In this case, Ediff will use those frames to display these buffers." + :type '(choice + (const :tag "Split vertically" split-window-vertically) + (const :tag "Split horizontally" split-window-horizontally) + function) + :group 'ediff-window) + +(defcustom ediff-merge-split-window-function 'split-window-horizontally + "The function used to split the main window between buffer-A and buffer-B. +You can set it to a vertical split instead of the default horizontal split +by setting this variable to `split-window-vertically'. +You can also have your own function to do fancy splits. +This variable has no effect when buffer-A/B/C are shown in different frames. +In this case, Ediff will use those frames to display these buffers." + :type '(choice + (const :tag "Split vertically" split-window-vertically) + (const :tag "Split horizontally" split-window-horizontally) + function) + :group 'ediff-window) + +;; Definitions hidden from the compiler by compat wrappers. +(declare-function ediff-display-pixel-width "ediff-init") +(declare-function ediff-display-pixel-height "ediff-init") + +(defconst ediff-control-frame-parameters + (list + '(name . "Ediff") + ;;'(unsplittable . t) + '(minibuffer . nil) + '(user-position . t) ; Emacs only + '(vertical-scroll-bars . nil) ; Emacs only + '(scrollbar-width . 0) ; XEmacs only + '(scrollbar-height . 0) ; XEmacs only + '(menu-bar-lines . 0) ; Emacs only + '(tool-bar-lines . 0) ; Emacs 21+ only + '(left-fringe . 0) + '(right-fringe . 0) + ;; don't lower but auto-raise + '(auto-lower . nil) + '(auto-raise . t) + '(visibility . nil) + ;; make initial frame small to avoid distraction + '(width . 1) '(height . 1) + ;; this blocks queries from window manager as to where to put + ;; ediff's control frame. we put the frame outside the display, + ;; so the initial frame won't jump all over the screen + (cons 'top (if (fboundp 'ediff-display-pixel-height) + (1+ (ediff-display-pixel-height)) + 3000)) + (cons 'left (if (fboundp 'ediff-display-pixel-width) + (1+ (ediff-display-pixel-width)) + 3000)) + ) + "Frame parameters for displaying Ediff Control Panel. +Used internally---not a user option.") + +;; position of the mouse; used to decide whether to warp the mouse into ctl +;; frame +(ediff-defvar-local ediff-mouse-pixel-position nil "") + +;; not used for now +(defvar ediff-mouse-pixel-threshold 30 + "If the user moves mouse more than this many pixels, Ediff won't warp mouse into control window.") + +(defcustom ediff-grab-mouse t + "If t, Ediff will always grab the mouse and put it in the control frame. +If 'maybe, Ediff will do it sometimes, but not after operations that require +relatively long time. If nil, the mouse will be entirely user's +responsibility." + :type 'boolean + :group 'ediff-window) + +(defcustom ediff-control-frame-position-function 'ediff-make-frame-position + "Function to call to determine the desired location for the control panel. +Expects three parameters: the control buffer, the desired width and height +of the control frame. It returns an association list +of the form \(\(top . \) \(left . \)\)" + :type 'function + :group 'ediff-window) + +(defcustom ediff-control-frame-upward-shift 42 + "The upward shift of control frame from the top of buffer A's frame. +Measured in pixels. +This is used by the default control frame positioning function, +`ediff-make-frame-position'. This variable is provided for easy +customization of the default control frame positioning." + :type 'integer + :group 'ediff-window) + +(defcustom ediff-narrow-control-frame-leftward-shift (if (featurep 'xemacs) 7 3) + "The leftward shift of control frame from the right edge of buf A's frame. +Measured in characters. +This is used by the default control frame positioning function, +`ediff-make-frame-position' to adjust the position of the control frame +when it shows the short menu. This variable is provided for easy +customization of the default." + :type 'integer + :group 'ediff-window) + +(defcustom ediff-wide-control-frame-rightward-shift 7 + "The rightward shift of control frame from the left edge of buf A's frame. +Measured in characters. +This is used by the default control frame positioning function, +`ediff-make-frame-position' to adjust the position of the control frame +when it shows the full menu. This variable is provided for easy +customization of the default." + :type 'integer + :group 'ediff-window) + + +;; Wide frame display + +;; t means Ediff is using wide display +(ediff-defvar-local ediff-wide-display-p nil "") +;; keeps frame config for toggling wide display +(ediff-defvar-local ediff-wide-display-orig-parameters nil + "Frame parameters to be restored when the user wants to toggle the wide +display off.") +(ediff-defvar-local ediff-wide-display-frame nil + "Frame to be used for wide display.") +(ediff-defvar-local ediff-make-wide-display-function 'ediff-make-wide-display + "The value is a function that is called to create a wide display. +The function is called without arguments. It should resize the frame in +which buffers A, B, and C are to be displayed, and it should save the old +frame parameters in `ediff-wide-display-orig-parameters'. +The variable `ediff-wide-display-frame' should be set to contain +the frame used for the wide display.") + +;; Frame used for the control panel in a windowing system. +(ediff-defvar-local ediff-control-frame nil "") + +(defcustom ediff-prefer-iconified-control-frame nil + "If t, keep control panel iconified when help message is off. +This has effect only on a windowing system. +If t, hitting `?' to toggle control panel off iconifies it. + +This is only useful in Emacs and only for certain kinds of window managers, +such as TWM and its derivatives, since the window manager must permit +keyboard input to go into icons. XEmacs completely ignores keyboard input +into icons, regardless of the window manager." + :type 'boolean + :group 'ediff-window) + +;;; Functions + +(defun ediff-get-window-by-clicking (wind prev-wind wind-number) + (let (event) + (message + "Select windows by clicking. Please click on Window %d " wind-number) + (while (not (ediff-mouse-event-p (setq event (ediff-read-event)))) + (if (sit-for 1) ; if sequence of events, wait till the final word + (beep 1)) + (message "Please click on Window %d " wind-number)) + (ediff-read-event) ; discard event + (setq wind (if (featurep 'xemacs) + (event-window event) + (posn-window (event-start event)))))) + + +;; Select the lowest window on the frame. +(defun ediff-select-lowest-window () + (if (featurep 'xemacs) + (select-window (frame-lowest-window)) + (let* ((lowest-window (selected-window)) + (bottom-edge (car (cdr (cdr (cdr (window-edges)))))) + (last-window (save-excursion + (other-window -1) (selected-window))) + (window-search t)) + (while window-search + (let* ((this-window (next-window)) + (next-bottom-edge + (car (cdr (cdr (cdr (window-edges this-window))))))) + (if (< bottom-edge next-bottom-edge) + (setq bottom-edge next-bottom-edge + lowest-window this-window)) + (select-window this-window) + (when (eq last-window this-window) + (select-window lowest-window) + (setq window-search nil))))))) + + +;;; Common window setup routines + +;; Set up the window configuration. If POS is given, set the points to +;; the beginnings of the buffers. +;; When 3way comparison is added, this will have to choose the appropriate +;; setup function based on ediff-job-name +(defun ediff-setup-windows (buffer-A buffer-B buffer-C control-buffer) + ;; Make sure we are not in the minibuffer window when we try to delete + ;; all other windows. + (run-hooks 'ediff-before-setup-windows-hook) + (if (eq (selected-window) (minibuffer-window)) + (other-window 1)) + + ;; in case user did a no-no on a tty + (or (ediff-window-display-p) + (setq ediff-window-setup-function 'ediff-setup-windows-plain)) + + (or (ediff-keep-window-config control-buffer) + (funcall + (ediff-with-current-buffer control-buffer ediff-window-setup-function) + buffer-A buffer-B buffer-C control-buffer)) + (run-hooks 'ediff-after-setup-windows-hook)) + +;; Just set up 3 windows. +;; Usually used without windowing systems +;; With windowing, we want to use dedicated frames. +(defun ediff-setup-windows-plain (buffer-A buffer-B buffer-C control-buffer) + (ediff-with-current-buffer control-buffer + (setq ediff-multiframe nil)) + (if ediff-merge-job + (ediff-setup-windows-plain-merge + buffer-A buffer-B buffer-C control-buffer) + (ediff-setup-windows-plain-compare + buffer-A buffer-B buffer-C control-buffer))) + +(defun ediff-setup-windows-plain-merge (buf-A buf-B buf-C control-buffer) + ;; skip dedicated and unsplittable frames + (ediff-destroy-control-frame control-buffer) + (let ((window-min-height 1) + split-window-function + merge-window-share merge-window-lines + wind-A wind-B wind-C) + (ediff-with-current-buffer control-buffer + (setq merge-window-share ediff-merge-window-share + ;; this lets us have local versions of ediff-split-window-function + split-window-function ediff-split-window-function)) + (delete-other-windows) + (set-window-dedicated-p (selected-window) nil) + (split-window-vertically) + (ediff-select-lowest-window) + (ediff-setup-control-buffer control-buffer) + + ;; go to the upper window and split it betw A, B, and possibly C + (other-window 1) + (setq merge-window-lines + (max 2 (round (* (window-height) merge-window-share)))) + (switch-to-buffer buf-A) + (setq wind-A (selected-window)) + + ;; XEmacs used to have a lot of trouble with display + ;; It did't set things right unless we tell it to sit still + ;; 19.12 seems ok. + ;;(if (featurep 'xemacs) (sit-for 0)) + + (split-window-vertically (max 2 (- (window-height) merge-window-lines))) + (if (eq (selected-window) wind-A) + (other-window 1)) + (setq wind-C (selected-window)) + (switch-to-buffer buf-C) + + (select-window wind-A) + (funcall split-window-function) + + (if (eq (selected-window) wind-A) + (other-window 1)) + (switch-to-buffer buf-B) + (setq wind-B (selected-window)) + + (ediff-with-current-buffer control-buffer + (setq ediff-window-A wind-A + ediff-window-B wind-B + ediff-window-C wind-C)) + + (ediff-select-lowest-window) + (ediff-setup-control-buffer control-buffer) + )) + + +;; This function handles all comparison jobs, including 3way jobs +(defun ediff-setup-windows-plain-compare (buf-A buf-B buf-C control-buffer) + ;; skip dedicated and unsplittable frames + (ediff-destroy-control-frame control-buffer) + (let ((window-min-height 1) + split-window-function wind-width-or-height + three-way-comparison + wind-A-start wind-B-start wind-A wind-B wind-C) + (ediff-with-current-buffer control-buffer + (setq wind-A-start (ediff-overlay-start + (ediff-get-value-according-to-buffer-type + 'A ediff-narrow-bounds)) + wind-B-start (ediff-overlay-start + (ediff-get-value-according-to-buffer-type + 'B ediff-narrow-bounds)) + ;; this lets us have local versions of ediff-split-window-function + split-window-function ediff-split-window-function + three-way-comparison ediff-3way-comparison-job)) + ;; if in minibuffer go somewhere else + (if (save-match-data + (string-match "\*Minibuf-" (buffer-name (window-buffer)))) + (select-window (next-window nil 'ignore-minibuf))) + (delete-other-windows) + (set-window-dedicated-p (selected-window) nil) + (split-window-vertically) + (ediff-select-lowest-window) + (ediff-setup-control-buffer control-buffer) + + ;; go to the upper window and split it betw A, B, and possibly C + (other-window 1) + (switch-to-buffer buf-A) + (setq wind-A (selected-window)) + (if three-way-comparison + (setq wind-width-or-height + (/ (if (eq split-window-function 'split-window-vertically) + (window-height wind-A) + (window-width wind-A)) + 3))) + + ;; XEmacs used to have a lot of trouble with display + ;; It did't set things right unless we told it to sit still + ;; 19.12 seems ok. + ;;(if (featurep 'xemacs) (sit-for 0)) + + (funcall split-window-function wind-width-or-height) + + (if (eq (selected-window) wind-A) + (other-window 1)) + (switch-to-buffer buf-B) + (setq wind-B (selected-window)) + + (if three-way-comparison + (progn + (funcall split-window-function) ; equally + (if (eq (selected-window) wind-B) + (other-window 1)) + (switch-to-buffer buf-C) + (setq wind-C (selected-window)))) + + (ediff-with-current-buffer control-buffer + (setq ediff-window-A wind-A + ediff-window-B wind-B + ediff-window-C wind-C)) + + ;; It is unlikely that we will want to implement 3way window comparison. + ;; So, only buffers A and B are used here. + (if ediff-windows-job + (progn + (set-window-start wind-A wind-A-start) + (set-window-start wind-B wind-B-start))) + + (ediff-select-lowest-window) + (ediff-setup-control-buffer control-buffer) + )) + + +;; dispatch an appropriate window setup function +(defun ediff-setup-windows-multiframe (buf-A buf-B buf-C control-buf) + (ediff-with-current-buffer control-buf + (setq ediff-multiframe t)) + (if ediff-merge-job + (ediff-setup-windows-multiframe-merge buf-A buf-B buf-C control-buf) + (ediff-setup-windows-multiframe-compare buf-A buf-B buf-C control-buf))) + +(defun ediff-setup-windows-multiframe-merge (buf-A buf-B buf-C control-buf) +;;; Algorithm: +;;; 1. Never use frames that have dedicated windows in them---it is bad to +;;; destroy dedicated windows. +;;; 2. If A and B are in the same frame but C's frame is different--- use one +;;; frame for A and B and use a separate frame for C. +;;; 3. If C's frame is non-existent, then: if the first suitable +;;; non-dedicated frame is different from A&B's, then use it for C. +;;; Otherwise, put A,B, and C in one frame. +;;; 4. If buffers A, B, C are is separate frames, use them to display these +;;; buffers. + + ;; Skip dedicated or iconified frames. + ;; Unsplittable frames are taken care of later. + (ediff-skip-unsuitable-frames 'ok-unsplittable) + + (let* ((window-min-height 1) + (wind-A (ediff-get-visible-buffer-window buf-A)) + (wind-B (ediff-get-visible-buffer-window buf-B)) + (wind-C (ediff-get-visible-buffer-window buf-C)) + (frame-A (if wind-A (window-frame wind-A))) + (frame-B (if wind-B (window-frame wind-B))) + (frame-C (if wind-C (window-frame wind-C))) + ;; on wide display, do things in one frame + (force-one-frame + (ediff-with-current-buffer control-buf ediff-wide-display-p)) + ;; this lets us have local versions of ediff-split-window-function + (split-window-function + (ediff-with-current-buffer control-buf ediff-split-window-function)) + (orig-wind (selected-window)) + (orig-frame (selected-frame)) + (use-same-frame (or force-one-frame + ;; A and C must be in one frame + (eq frame-A (or frame-C orig-frame)) + ;; B and C must be in one frame + (eq frame-B (or frame-C orig-frame)) + ;; A or B is not visible + (not (frame-live-p frame-A)) + (not (frame-live-p frame-B)) + ;; A or B is not suitable for display + (not (ediff-window-ok-for-display wind-A)) + (not (ediff-window-ok-for-display wind-B)) + ;; A and B in the same frame, and no good frame + ;; for C + (and (eq frame-A frame-B) + (not (frame-live-p frame-C))) + )) + ;; use-same-frame-for-AB implies wind A and B are ok for display + (use-same-frame-for-AB (and (not use-same-frame) + (eq frame-A frame-B))) + (merge-window-share (ediff-with-current-buffer control-buf + ediff-merge-window-share)) + merge-window-lines + designated-minibuffer-frame + done-A done-B done-C) + + ;; buf-A on its own + (if (and (window-live-p wind-A) + (null use-same-frame) ; implies wind-A is suitable + (null use-same-frame-for-AB)) + (progn ; bug A on its own + ;; buffer buf-A is seen in live wind-A + (select-window wind-A) + (delete-other-windows) + (setq wind-A (selected-window)) + (setq done-A t))) + + ;; buf-B on its own + (if (and (window-live-p wind-B) + (null use-same-frame) ; implies wind-B is suitable + (null use-same-frame-for-AB)) + (progn ; buf B on its own + ;; buffer buf-B is seen in live wind-B + (select-window wind-B) + (delete-other-windows) + (setq wind-B (selected-window)) + (setq done-B t))) + + ;; buf-C on its own + (if (and (window-live-p wind-C) + (ediff-window-ok-for-display wind-C) + (null use-same-frame)) ; buf C on its own + (progn + ;; buffer buf-C is seen in live wind-C + (select-window wind-C) + (delete-other-windows) + (setq wind-C (selected-window)) + (setq done-C t))) + + (if (and use-same-frame-for-AB ; implies wind A and B are suitable + (window-live-p wind-A)) + (progn + ;; wind-A must already be displaying buf-A + (select-window wind-A) + (delete-other-windows) + (setq wind-A (selected-window)) + + (funcall split-window-function) + (if (eq (selected-window) wind-A) + (other-window 1)) + (switch-to-buffer buf-B) + (setq wind-B (selected-window)) + + (setq done-A t + done-B t))) + + (if use-same-frame + (let ((window-min-height 1)) + (if (and (eq frame-A frame-B) + (eq frame-B frame-C) + (frame-live-p frame-A)) + (select-frame frame-A) + ;; avoid dedicated and non-splittable windows + (ediff-skip-unsuitable-frames)) + (delete-other-windows) + (setq merge-window-lines + (max 2 (round (* (window-height) merge-window-share)))) + (switch-to-buffer buf-A) + (setq wind-A (selected-window)) + + (split-window-vertically + (max 2 (- (window-height) merge-window-lines))) + (if (eq (selected-window) wind-A) + (other-window 1)) + (setq wind-C (selected-window)) + (switch-to-buffer buf-C) + + (select-window wind-A) + + (funcall split-window-function) + (if (eq (selected-window) wind-A) + (other-window 1)) + (switch-to-buffer buf-B) + (setq wind-B (selected-window)) + + (setq done-A t + done-B t + done-C t) + )) + + (or done-A ; Buf A to be set in its own frame, + ;;; or it was set before because use-same-frame = 1 + (progn + ;; Buf-A was not set up yet as it wasn't visible, + ;; and use-same-frame = nil, use-same-frame-for-AB = nil + (select-window orig-wind) + (delete-other-windows) + (switch-to-buffer buf-A) + (setq wind-A (selected-window)) + )) + (or done-B ; Buf B to be set in its own frame, + ;;; or it was set before because use-same-frame = 1 + (progn + ;; Buf-B was not set up yet as it wasn't visible + ;; and use-same-frame = nil, use-same-frame-for-AB = nil + (select-window orig-wind) + (delete-other-windows) + (switch-to-buffer buf-B) + (setq wind-B (selected-window)) + )) + + (or done-C ; Buf C to be set in its own frame, + ;;; or it was set before because use-same-frame = 1 + (progn + ;; Buf-C was not set up yet as it wasn't visible + ;; and use-same-frame = nil + (select-window orig-wind) + (delete-other-windows) + (switch-to-buffer buf-C) + (setq wind-C (selected-window)) + )) + + (ediff-with-current-buffer control-buf + (setq ediff-window-A wind-A + ediff-window-B wind-B + ediff-window-C wind-C) + (setq frame-A (window-frame ediff-window-A) + designated-minibuffer-frame + (window-frame (minibuffer-window frame-A)))) + + (ediff-setup-control-frame control-buf designated-minibuffer-frame) + )) + + +;; Window setup for all comparison jobs, including 3way comparisons +(defun ediff-setup-windows-multiframe-compare (buf-A buf-B buf-C control-buf) +;;; Algorithm: +;;; If a buffer is seen in a frame, use that frame for that buffer. +;;; If it is not seen, use the current frame. +;;; If both buffers are not seen, they share the current frame. If one +;;; of the buffers is not seen, it is placed in the current frame (where +;;; ediff started). If that frame is displaying the other buffer, it is +;;; shared between the two buffers. +;;; However, if we decide to put both buffers in one frame +;;; and the selected frame isn't splittable, we create a new frame and +;;; put both buffers there, event if one of this buffers is visible in +;;; another frame. + + ;; Skip dedicated or iconified frames. + ;; Unsplittable frames are taken care of later. + (ediff-skip-unsuitable-frames 'ok-unsplittable) + + (let* ((window-min-height 1) + (wind-A (ediff-get-visible-buffer-window buf-A)) + (wind-B (ediff-get-visible-buffer-window buf-B)) + (wind-C (ediff-get-visible-buffer-window buf-C)) + (frame-A (if wind-A (window-frame wind-A))) + (frame-B (if wind-B (window-frame wind-B))) + (frame-C (if wind-C (window-frame wind-C))) + (ctl-frame-exists-p (ediff-with-current-buffer control-buf + (frame-live-p ediff-control-frame))) + ;; on wide display, do things in one frame + (force-one-frame + (ediff-with-current-buffer control-buf ediff-wide-display-p)) + ;; this lets us have local versions of ediff-split-window-function + (split-window-function + (ediff-with-current-buffer control-buf ediff-split-window-function)) + (three-way-comparison + (ediff-with-current-buffer control-buf ediff-3way-comparison-job)) + (orig-wind (selected-window)) + (use-same-frame (or force-one-frame + (eq frame-A frame-B) + (not (ediff-window-ok-for-display wind-A)) + (not (ediff-window-ok-for-display wind-B)) + (if three-way-comparison + (or (eq frame-A frame-C) + (eq frame-B frame-C) + (not (ediff-window-ok-for-display wind-C)) + (not (frame-live-p frame-A)) + (not (frame-live-p frame-B)) + (not (frame-live-p frame-C)))) + (and (not (frame-live-p frame-B)) + (or ctl-frame-exists-p + (eq frame-A (selected-frame)))) + (and (not (frame-live-p frame-A)) + (or ctl-frame-exists-p + (eq frame-B (selected-frame)))))) + wind-A-start wind-B-start + designated-minibuffer-frame + done-A done-B done-C) + + (ediff-with-current-buffer control-buf + (setq wind-A-start (ediff-overlay-start + (ediff-get-value-according-to-buffer-type + 'A ediff-narrow-bounds)) + wind-B-start (ediff-overlay-start + (ediff-get-value-according-to-buffer-type + 'B ediff-narrow-bounds)))) + + (if (and (window-live-p wind-A) (null use-same-frame)) ; buf-A on its own + (progn + ;; buffer buf-A is seen in live wind-A + (select-window wind-A) ; must be displaying buf-A + (delete-other-windows) + (setq wind-A (selected-window)) + (setq done-A t))) + + (if (and (window-live-p wind-B) (null use-same-frame)) ; buf B on its own + (progn + ;; buffer buf-B is seen in live wind-B + (select-window wind-B) ; must be displaying buf-B + (delete-other-windows) + (setq wind-B (selected-window)) + (setq done-B t))) + + (if (and (window-live-p wind-C) (null use-same-frame)) ; buf C on its own + (progn + ;; buffer buf-C is seen in live wind-C + (select-window wind-C) ; must be displaying buf-C + (delete-other-windows) + (setq wind-C (selected-window)) + (setq done-C t))) + + (if use-same-frame + (let (wind-width-or-height) ; this affects 3way setups only + (if (and (eq frame-A frame-B) (frame-live-p frame-A)) + (select-frame frame-A) + ;; avoid dedicated and non-splittable windows + (ediff-skip-unsuitable-frames)) + (delete-other-windows) + (switch-to-buffer buf-A) + (setq wind-A (selected-window)) + + (if three-way-comparison + (setq wind-width-or-height + (/ + (if (eq split-window-function 'split-window-vertically) + (window-height wind-A) + (window-width wind-A)) + 3))) + + (funcall split-window-function wind-width-or-height) + (if (eq (selected-window) wind-A) + (other-window 1)) + (switch-to-buffer buf-B) + (setq wind-B (selected-window)) + + (if three-way-comparison + (progn + (funcall split-window-function) ; equally + (if (memq (selected-window) (list wind-A wind-B)) + (other-window 1)) + (switch-to-buffer buf-C) + (setq wind-C (selected-window)))) + (setq done-A t + done-B t + done-C t) + )) + + (or done-A ; Buf A to be set in its own frame + ;;; or it was set before because use-same-frame = 1 + (progn + ;; Buf-A was not set up yet as it wasn't visible, + ;; and use-same-frame = nil + (select-window orig-wind) + (delete-other-windows) + (switch-to-buffer buf-A) + (setq wind-A (selected-window)) + )) + (or done-B ; Buf B to be set in its own frame + ;;; or it was set before because use-same-frame = 1 + (progn + ;; Buf-B was not set up yet as it wasn't visible, + ;; and use-same-frame = nil + (select-window orig-wind) + (delete-other-windows) + (switch-to-buffer buf-B) + (setq wind-B (selected-window)) + )) + + (if three-way-comparison + (or done-C ; Buf C to be set in its own frame + ;;; or it was set before because use-same-frame = 1 + (progn + ;; Buf-C was not set up yet as it wasn't visible, + ;; and use-same-frame = nil + (select-window orig-wind) + (delete-other-windows) + (switch-to-buffer buf-C) + (setq wind-C (selected-window)) + ))) + + (ediff-with-current-buffer control-buf + (setq ediff-window-A wind-A + ediff-window-B wind-B + ediff-window-C wind-C) + + (setq frame-A (window-frame ediff-window-A) + designated-minibuffer-frame + (window-frame (minibuffer-window frame-A)))) + + ;; It is unlikely that we'll implement a version of ediff-windows that + ;; would compare 3 windows at once. So, we don't use buffer C here. + (if ediff-windows-job + (progn + (set-window-start wind-A wind-A-start) + (set-window-start wind-B wind-B-start))) + + (ediff-setup-control-frame control-buf designated-minibuffer-frame) + )) + +;; skip unsplittable frames and frames that have dedicated windows. +;; create a new splittable frame if none is found +(defun ediff-skip-unsuitable-frames (&optional ok-unsplittable) + (if (ediff-window-display-p) + (let ((wind-frame (window-frame (selected-window))) + seen-windows) + (while (and (not (memq (selected-window) seen-windows)) + (or + (ediff-frame-has-dedicated-windows wind-frame) + (ediff-frame-iconified-p wind-frame) + ;; skip small windows + (< (frame-height wind-frame) + (* 3 window-min-height)) + (if ok-unsplittable + nil + (ediff-frame-unsplittable-p wind-frame)))) + ;; remember history + (setq seen-windows (cons (selected-window) seen-windows)) + ;; try new window + (other-window 1 t) + (setq wind-frame (window-frame (selected-window))) + ) + (if (memq (selected-window) seen-windows) + ;; fed up, no appropriate frames + (setq wind-frame (make-frame '((unsplittable))))) + + (select-frame wind-frame) + ))) + +(defun ediff-frame-has-dedicated-windows (frame) + (let (ans) + (walk-windows + (lambda (wind) (if (window-dedicated-p wind) + (setq ans t))) + 'ignore-minibuffer + frame) + ans)) + +;; window is ok, if it is only one window on the frame, not counting the +;; minibuffer, or none of the frame's windows is dedicated. +;; The idea is that it is bad to destroy dedicated windows while creating an +;; ediff window setup +(defun ediff-window-ok-for-display (wind) + (and + (window-live-p wind) + (or + ;; only one window + (eq wind (next-window wind 'ignore-minibuffer (window-frame wind))) + ;; none is dedicated (in multiframe setup) + (not (ediff-frame-has-dedicated-windows (window-frame wind))) + ))) + +;; Prepare or refresh control frame +(defun ediff-setup-control-frame (ctl-buffer designated-minibuffer-frame) + (let ((window-min-height 1) + ctl-frame-iconified-p dont-iconify-ctl-frame deiconify-ctl-frame + ctl-frame old-ctl-frame lines + ;; user-grabbed-mouse + fheight fwidth adjusted-parameters) + + (ediff-with-current-buffer ctl-buffer + (if (and (featurep 'xemacs) (featurep 'menubar)) + (set-buffer-menubar nil)) + ;;(setq user-grabbed-mouse (ediff-user-grabbed-mouse)) + (run-hooks 'ediff-before-setup-control-frame-hook)) + + (setq old-ctl-frame (ediff-with-current-buffer ctl-buffer ediff-control-frame)) + (ediff-with-current-buffer ctl-buffer + (setq ctl-frame (if (frame-live-p old-ctl-frame) + old-ctl-frame + (make-frame ediff-control-frame-parameters)) + ediff-control-frame ctl-frame) + ;; protect against undefined face-attribute + (condition-case nil + (if (and (featurep 'emacs) (face-attribute 'mode-line :box)) + (set-face-attribute 'mode-line ctl-frame :box nil)) + (error))) + + (setq ctl-frame-iconified-p (ediff-frame-iconified-p ctl-frame)) + (select-frame ctl-frame) + (if (window-dedicated-p (selected-window)) + () + (delete-other-windows) + (switch-to-buffer ctl-buffer)) + + ;; must be before ediff-setup-control-buffer + ;; just a precaution--we should be in ctl-buffer already + (ediff-with-current-buffer ctl-buffer + (make-local-variable 'frame-title-format) + (make-local-variable 'frame-icon-title-format) ; XEmacs + (make-local-variable 'icon-title-format)) ; Emacs + + (ediff-setup-control-buffer ctl-buffer) + (setq dont-iconify-ctl-frame + (not (string= ediff-help-message ediff-brief-help-message))) + (setq deiconify-ctl-frame + (and (eq this-command 'ediff-toggle-help) + dont-iconify-ctl-frame)) + + ;; 1 more line for the modeline + (setq lines (1+ (count-lines (point-min) (point-max))) + fheight lines + fwidth (max (+ (ediff-help-message-line-length) 2) + (ediff-compute-toolbar-width)) + adjusted-parameters + (list + ;; possibly change surrogate minibuffer + (cons 'minibuffer + (minibuffer-window + designated-minibuffer-frame)) + (cons 'width fwidth) + (cons 'height fheight) + (cons 'user-position t) + )) + + ;; adjust autoraise + (setq adjusted-parameters + (cons (if ediff-use-long-help-message + '(auto-raise . nil) + '(auto-raise . t)) + adjusted-parameters)) + + ;; In XEmacs, buffer menubar needs to be killed before frame parameters + ;; are changed. + (if (ediff-has-toolbar-support-p) + (when (featurep 'xemacs) + (if (ediff-has-gutter-support-p) + (set-specifier top-gutter (list ctl-frame nil))) + (sit-for 0) + (set-specifier top-toolbar-height (list ctl-frame 0)) + ;;(set-specifier bottom-toolbar-height (list ctl-frame 0)) + (set-specifier left-toolbar-width (list ctl-frame 0)) + (set-specifier right-toolbar-width (list ctl-frame 0)))) + + ;; Under OS/2 (emx) we have to call modify frame parameters twice, in order + ;; to make sure that at least once we do it for non-iconified frame. If + ;; appears that in the OS/2 port of Emacs, one can't modify frame + ;; parameters of iconified frames. As a precaution, we do likewise for + ;; windows-nt. + (if (memq system-type '(emx windows-nt windows-95)) + (modify-frame-parameters ctl-frame adjusted-parameters)) + + ;; make or zap toolbar (if not requested) + (ediff-make-bottom-toolbar ctl-frame) + + (goto-char (point-min)) + + (modify-frame-parameters ctl-frame adjusted-parameters) + (make-frame-visible ctl-frame) + + ;; This works around a bug in 19.25 and earlier. There, if frame gets + ;; iconified, the current buffer changes to that of the frame that + ;; becomes exposed as a result of this iconification. + ;; So, we make sure the current buffer doesn't change. + (select-frame ctl-frame) + (ediff-refresh-control-frame) + + (cond ((and ediff-prefer-iconified-control-frame + (not ctl-frame-iconified-p) (not dont-iconify-ctl-frame)) + (iconify-frame ctl-frame)) + ((or deiconify-ctl-frame (not ctl-frame-iconified-p)) + (raise-frame ctl-frame))) + + (set-window-dedicated-p (selected-window) t) + + ;; Now move the frame. We must do it separately due to an obscure bug in + ;; XEmacs + (modify-frame-parameters + ctl-frame + (funcall ediff-control-frame-position-function ctl-buffer fwidth fheight)) + + ;; synchronize so the cursor will move to control frame + ;; per RMS suggestion + (if (ediff-window-display-p) + (let ((count 7)) + (sit-for .1) + (while (and (not (frame-visible-p ctl-frame)) (> count 0)) + (setq count (1- count)) + (sit-for .3)))) + + (or (ediff-frame-iconified-p ctl-frame) + ;; don't warp the mouse, unless ediff-grab-mouse = t + (ediff-reset-mouse ctl-frame + (or (eq this-command 'ediff-quit) + (not (eq ediff-grab-mouse t))))) + + (when (featurep 'xemacs) + (ediff-with-current-buffer ctl-buffer + (make-local-hook 'select-frame-hook) + (add-hook 'select-frame-hook + 'ediff-xemacs-select-frame-hook nil 'local))) + + (ediff-with-current-buffer ctl-buffer + (run-hooks 'ediff-after-setup-control-frame-hook)))) + + +(defun ediff-destroy-control-frame (ctl-buffer) + (ediff-with-current-buffer ctl-buffer + (if (and (ediff-window-display-p) (frame-live-p ediff-control-frame)) + (let ((ctl-frame ediff-control-frame)) + (if (and (featurep 'xemacs) (featurep 'menubar)) + (set-buffer-menubar default-menubar)) + (setq ediff-control-frame nil) + (delete-frame ctl-frame)))) + (if ediff-multiframe + (ediff-skip-unsuitable-frames)) + ;;(ediff-reset-mouse nil) + ) + + +;; finds a good place to clip control frame +(defun ediff-make-frame-position (ctl-buffer ctl-frame-width ctl-frame-height) + (ediff-with-current-buffer ctl-buffer + (let* ((frame-A (window-frame ediff-window-A)) + (frame-A-parameters (frame-parameters frame-A)) + (frame-A-top (eval (cdr (assoc 'top frame-A-parameters)))) + (frame-A-left (eval (cdr (assoc 'left frame-A-parameters)))) + (frame-A-width (frame-width frame-A)) + (ctl-frame ediff-control-frame) + horizontal-adjustment upward-adjustment + ctl-frame-top ctl-frame-left) + + ;; Multiple control frames are clipped based on the value of + ;; ediff-control-buffer-number. This is done in order not to obscure + ;; other active control panels. + (setq horizontal-adjustment (* 2 ediff-control-buffer-number) + upward-adjustment (* -14 ediff-control-buffer-number)) + + (setq ctl-frame-top + (- frame-A-top upward-adjustment ediff-control-frame-upward-shift) + ctl-frame-left + (+ frame-A-left + (if ediff-use-long-help-message + (* (ediff-frame-char-width ctl-frame) + (+ ediff-wide-control-frame-rightward-shift + horizontal-adjustment)) + (- (* frame-A-width (ediff-frame-char-width frame-A)) + (* (ediff-frame-char-width ctl-frame) + (+ ctl-frame-width + ediff-narrow-control-frame-leftward-shift + horizontal-adjustment)))))) + (setq ctl-frame-top + (min ctl-frame-top + (- (ediff-display-pixel-height) + (* 2 ctl-frame-height + (ediff-frame-char-height ctl-frame)))) + ctl-frame-left + (min ctl-frame-left + (- (ediff-display-pixel-width) + (* ctl-frame-width (ediff-frame-char-width ctl-frame))))) + ;; keep ctl frame within the visible bounds + (setq ctl-frame-top (max ctl-frame-top 1) + ctl-frame-left (max ctl-frame-left 1)) + + (list (cons 'top ctl-frame-top) + (cons 'left ctl-frame-left)) + ))) + +(defun ediff-xemacs-select-frame-hook () + (if (and (equal (selected-frame) ediff-control-frame) + (not ediff-use-long-help-message)) + (raise-frame ediff-control-frame))) + +(defun ediff-make-wide-display () + "Construct an alist of parameters for the wide display. +Saves the old frame parameters in `ediff-wide-display-orig-parameters'. +The frame to be resized is kept in `ediff-wide-display-frame'. +This function modifies only the left margin and the width of the display. +It assumes that it is called from within the control buffer." + (if (not (fboundp 'ediff-display-pixel-width)) + (error "Can't determine display width")) + (let* ((frame-A (window-frame ediff-window-A)) + (frame-A-params (frame-parameters frame-A)) + (cw (ediff-frame-char-width frame-A)) + (wd (- (/ (ediff-display-pixel-width) cw) 5))) + (setq ediff-wide-display-orig-parameters + (list (cons 'left (max 0 (eval (cdr (assoc 'left frame-A-params))))) + (cons 'width (cdr (assoc 'width frame-A-params)))) + ediff-wide-display-frame frame-A) + (modify-frame-parameters + frame-A `((left . ,cw) (width . ,wd) (user-position . t))))) + + +;; Revise the mode line to display which difference we have selected +;; Also resets modelines of buffers A/B, since they may be clobbered by +;; anothe invocations of Ediff. +(defun ediff-refresh-mode-lines () + (let (buf-A-state-diff buf-B-state-diff buf-C-state-diff buf-C-state-merge) + + (if (ediff-valid-difference-p) + (setq + buf-C-state-diff (ediff-get-state-of-diff ediff-current-difference 'C) + buf-C-state-merge (ediff-get-state-of-merge ediff-current-difference) + buf-A-state-diff (ediff-get-state-of-diff ediff-current-difference 'A) + buf-B-state-diff (ediff-get-state-of-diff ediff-current-difference 'B) + buf-A-state-diff (if buf-A-state-diff + (format "[%s] " buf-A-state-diff) + "") + buf-B-state-diff (if buf-B-state-diff + (format "[%s] " buf-B-state-diff) + "") + buf-C-state-diff (if (and (ediff-buffer-live-p ediff-buffer-C) + (or buf-C-state-diff buf-C-state-merge)) + (format "[%s%s%s] " + (or buf-C-state-diff "") + (if buf-C-state-merge + (concat " " buf-C-state-merge) + "") + (if (ediff-get-state-of-ancestor + ediff-current-difference) + " AncestorEmpty" + "") + ) + "")) + (setq buf-A-state-diff "" + buf-B-state-diff "" + buf-C-state-diff "")) + + ;; control buffer format + (setq mode-line-format + (if (ediff-narrow-control-frame-p) + (list " " mode-line-buffer-identification) + (list "-- " mode-line-buffer-identification " Quick Help"))) + ;; control buffer id + (setq mode-line-buffer-identification + (if (ediff-narrow-control-frame-p) + (ediff-make-narrow-control-buffer-id 'skip-name) + (ediff-make-wide-control-buffer-id))) + ;; Force mode-line redisplay + (force-mode-line-update) + + (if (and (ediff-window-display-p) (frame-live-p ediff-control-frame)) + (ediff-refresh-control-frame)) + + (ediff-with-current-buffer ediff-buffer-A + (setq ediff-diff-status buf-A-state-diff) + (ediff-strip-mode-line-format) + (setq mode-line-format + (list " A: " 'ediff-diff-status mode-line-format)) + (force-mode-line-update)) + (ediff-with-current-buffer ediff-buffer-B + (setq ediff-diff-status buf-B-state-diff) + (ediff-strip-mode-line-format) + (setq mode-line-format + (list " B: " 'ediff-diff-status mode-line-format)) + (force-mode-line-update)) + (if ediff-3way-job + (ediff-with-current-buffer ediff-buffer-C + (setq ediff-diff-status buf-C-state-diff) + (ediff-strip-mode-line-format) + (setq mode-line-format + (list " C: " 'ediff-diff-status mode-line-format)) + (force-mode-line-update))) + (if (ediff-buffer-live-p ediff-ancestor-buffer) + (ediff-with-current-buffer ediff-ancestor-buffer + (ediff-strip-mode-line-format) + ;; we keep the second dummy string in the mode line format of the + ;; ancestor, since for other buffers Ediff prepends 2 strings and + ;; ediff-strip-mode-line-format expects that. + (setq mode-line-format + (list " Ancestor: " + (cond ((not (stringp buf-C-state-merge)) + "") + ((string-match "prefer-A" buf-C-state-merge) + "[=diff(B)] ") + ((string-match "prefer-B" buf-C-state-merge) + "[=diff(A)] ") + (t "")) + mode-line-format)))) + )) + + +(defun ediff-refresh-control-frame () + (if (featurep 'emacs) + ;; set frame/icon titles for Emacs + (modify-frame-parameters + ediff-control-frame + (list (cons 'title (ediff-make-base-title)) + (cons 'icon-name (ediff-make-narrow-control-buffer-id)) + )) + ;; set frame/icon titles for XEmacs + (setq frame-title-format (ediff-make-base-title) + frame-icon-title-format (ediff-make-narrow-control-buffer-id)) + ;; force an update of the frame title + (modify-frame-parameters ediff-control-frame '(())))) + + +(defun ediff-make-narrow-control-buffer-id (&optional skip-name) + (concat + (if skip-name + " " + (ediff-make-base-title)) + (cond ((< ediff-current-difference 0) + (format " _/%d" ediff-number-of-differences)) + ((>= ediff-current-difference ediff-number-of-differences) + (format " $/%d" ediff-number-of-differences)) + (t + (format " %d/%d" + (1+ ediff-current-difference) + ediff-number-of-differences))))) + +(defun ediff-make-base-title () + (concat + (cdr (assoc 'name ediff-control-frame-parameters)) + ediff-control-buffer-suffix)) + +(defun ediff-make-wide-control-buffer-id () + (cond ((< ediff-current-difference 0) + (list (format "%%b At start of %d diffs" + ediff-number-of-differences))) + ((>= ediff-current-difference ediff-number-of-differences) + (list (format "%%b At end of %d diffs" + ediff-number-of-differences))) + (t + (list (format "%%b diff %d of %d" + (1+ ediff-current-difference) + ediff-number-of-differences))))) + + + +;; If buff is not live, return nil +(defun ediff-get-visible-buffer-window (buff) + (if (ediff-buffer-live-p buff) + (if (featurep 'xemacs) + (get-buffer-window buff t) + (get-buffer-window buff 'visible)))) + + +;;; Functions to decide when to redraw windows + +(defun ediff-keep-window-config (control-buf) + (and (eq control-buf (current-buffer)) + (/= (buffer-size) 0) + (ediff-with-current-buffer control-buf + (let ((ctl-wind ediff-control-window) + (A-wind ediff-window-A) + (B-wind ediff-window-B) + (C-wind ediff-window-C)) + + (and + (ediff-window-visible-p A-wind) + (ediff-window-visible-p B-wind) + ;; if buffer C is defined then take it into account + (or (not ediff-3way-job) + (ediff-window-visible-p C-wind)) + (eq (window-buffer A-wind) ediff-buffer-A) + (eq (window-buffer B-wind) ediff-buffer-B) + (or (not ediff-3way-job) + (eq (window-buffer C-wind) ediff-buffer-C)) + (string= ediff-window-config-saved + (format "%S%S%S%S%S%S%S" + ctl-wind A-wind B-wind C-wind + ediff-split-window-function + (ediff-multiframe-setup-p) + ediff-wide-display-p))))))) + + +(provide 'ediff-wind) + + +;; 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: + +;; arch-tag: 73d9a5d7-eed7-4d9c-8b4b-21d5d78eb597 +;;; ediff-wind.el ends here diff -r 681cd08dc0f7 -r e93288477c43 lisp/vc/ediff.el --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/lisp/vc/ediff.el Sun Jun 13 22:57:55 2010 +0000 @@ -0,0 +1,1565 @@ +;;; ediff.el --- a comprehensive visual interface to diff & patch + +;; Copyright (C) 1994, 1995, 1996, 1997, 1998, 1999, 2000, 2001, 2002, +;; 2003, 2004, 2005, 2006, 2007, 2008, 2009, 2010 Free Software Foundation, Inc. + +;; Author: Michael Kifer +;; Created: February 2, 1994 +;; Keywords: comparing, merging, patching, vc, tools, unix + +;; Yoni Rabkin contacted the maintainer of this +;; file on 20/3/2008, and the maintainer agreed that when a bug is +;; filed in the Emacs bug reporting system against this file, a copy +;; of the bug report be sent to the maintainer's email address. + +(defconst ediff-version "2.81.4" "The current version of Ediff") +(defconst ediff-date "December 7, 2009" "Date of last update") + + +;; 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 3 of the License, 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. If not, see . + +;;; Commentary: + +;; Never read that diff output again! +;; Apply patch interactively! +;; Merge with ease! + +;; This package provides a convenient way of simultaneous browsing through +;; the differences between a pair (or a triple) of files or buffers. The +;; files being compared, file-A, file-B, and file-C (if applicable) are +;; shown in separate windows (side by side, one above the another, or in +;; separate frames), and the differences are highlighted as you step +;; through them. You can also copy difference regions from one buffer to +;; another (and recover old differences if you change your mind). + +;; Ediff also supports merging operations on files and buffers, including +;; merging using ancestor versions. Both comparison and merging operations can +;; be performed on directories, i.e., by pairwise comparison of files in those +;; directories. + +;; In addition, Ediff can apply a patch to a file and then let you step +;; though both files, the patched and the original one, simultaneously, +;; difference-by-difference. You can even apply a patch right out of a +;; mail buffer, i.e., patches received by mail don't even have to be saved. +;; Since Ediff lets you copy differences between buffers, you can, in +;; effect, apply patches selectively (i.e., you can copy a difference +;; region from file_orig to file, thereby undoing any particular patch that +;; you don't like). + +;; Ediff is aware of version control, which lets the user compare +;; files with their older versions. Ediff can also work with remote and +;; compressed files. Details are given below. + +;; Finally, Ediff supports directory-level comparison, merging and patching. +;; See the on-line manual for details. + +;; This package builds upon the ideas borrowed from emerge.el and several +;; Ediff's functions are adaptations from emerge.el. Much of the functionality +;; Ediff provides is also influenced by emerge.el. + +;; The present version of Ediff supersedes Emerge. It provides a superior user +;; interface and has numerous major features not found in Emerge. In +;; particular, it can do patching, and 2-way and 3-way file comparison, +;; merging, and directory operations. + + + +;;; Bugs: + +;; 1. The undo command doesn't restore deleted regions well. That is, if +;; you delete all characters in a difference region and then invoke +;; `undo', the reinstated text will most likely be inserted outside of +;; what Ediff thinks is the current difference region. (This problem +;; doesn't seem to exist with XEmacs.) +;; +;; If at any point you feel that difference regions are no longer correct, +;; you can hit '!' to recompute the differences. + +;; 2. On a monochrome display, the repertoire of faces with which to +;; highlight fine differences is limited. By default, Ediff is using +;; underlining. However, if the region is already underlined by some other +;; overlays, there is no simple way to temporarily remove that residual +;; underlining. This problem occurs when a buffer is highlighted with +;; hilit19.el or font-lock.el packages. If this residual highlighting gets +;; in the way, you can do the following. Both font-lock.el and hilit19.el +;; provide commands for unhighlighting buffers. You can either place these +;; commands in `ediff-prepare-buffer-hook' (which will unhighlight every +;; buffer used by Ediff) or you can execute them interactively, at any time +;; and on any buffer. + + +;;; Acknowledgements: + +;; Ediff was inspired by Dale R. Worley's emerge.el. +;; Ediff would not have been possible without the help and encouragement of +;; its many users. See Ediff on-line Info for the full list of those who +;; helped. Improved defaults in Ediff file-name reading commands. + +;;; Code: + +(provide 'ediff) + +;; Compiler pacifier +(eval-and-compile + (unless (fboundp 'declare-function) (defmacro declare-function (&rest r)))) + + +(eval-when-compile + (require 'dired) + (require 'ediff-util) + (require 'ediff-ptch)) +;; end pacifier + +(require 'ediff-init) +(require 'ediff-mult) ; required because of the registry stuff + +(defgroup ediff nil + "A comprehensive visual interface to diff & patch." + :tag "Ediff" + :group 'tools) + + +(defcustom ediff-use-last-dir nil + "If t, Ediff will use previous directory as default when reading file name." + :type 'boolean + :group 'ediff) + +;; Last directory used by an Ediff command for file-A. +(defvar ediff-last-dir-A nil) +;; Last directory used by an Ediff command for file-B. +(defvar ediff-last-dir-B nil) +;; Last directory used by an Ediff command for file-C. +(defvar ediff-last-dir-C nil) +;; Last directory used by an Ediff command for the ancestor file. +(defvar ediff-last-dir-ancestor nil) +;; Last directory used by an Ediff command as the output directory for merge. +(defvar ediff-last-merge-autostore-dir nil) + + +;; Used as a startup hook to set `_orig' patch file read-only. +(defun ediff-set-read-only-in-buf-A () + (ediff-with-current-buffer ediff-buffer-A + (toggle-read-only 1))) + +;; Return a plausible default for ediff's first file: +;; In dired, return the file number FILENO (or 0) in the list +;; (all-selected-files, filename under the cursor), where directories are +;; ignored. Otherwise, return DEFAULT file name, if non-nil. Else, +;; if the buffer is visiting a file, return that file name. +(defun ediff-get-default-file-name (&optional default fileno) + (cond ((eq major-mode 'dired-mode) + (let ((current (dired-get-filename nil 'no-error)) + (marked (condition-case nil + (dired-get-marked-files 'no-dir) + (error nil))) + aux-list choices result) + (or (integerp fileno) (setq fileno 0)) + (if (stringp default) + (setq aux-list (cons default aux-list))) + (if (and (stringp current) (not (file-directory-p current))) + (setq aux-list (cons current aux-list))) + (setq choices (nconc marked aux-list)) + (setq result (elt choices fileno)) + (or result + default))) + ((stringp default) default) + ((buffer-file-name (current-buffer)) + (file-name-nondirectory (buffer-file-name (current-buffer)))) + )) + +;;; Compare files/buffers + +;;;###autoload +(defun ediff-files (file-A file-B &optional startup-hooks) + "Run Ediff on a pair of files, FILE-A and FILE-B." + (interactive + (let ((dir-A (if ediff-use-last-dir + ediff-last-dir-A + default-directory)) + dir-B f) + (list (setq f (ediff-read-file-name + "File A to compare" + dir-A + (ediff-get-default-file-name) + 'no-dirs)) + (ediff-read-file-name "File B to compare" + (setq dir-B + (if ediff-use-last-dir + ediff-last-dir-B + (file-name-directory f))) + (progn + (ediff-add-to-history + 'file-name-history + (ediff-abbreviate-file-name + (expand-file-name + (file-name-nondirectory f) + dir-B))) + (ediff-get-default-file-name f 1))) + ))) + (ediff-files-internal file-A + (if (file-directory-p file-B) + (expand-file-name + (file-name-nondirectory file-A) file-B) + file-B) + nil ; file-C + startup-hooks + 'ediff-files)) + +;;;###autoload +(defun ediff-files3 (file-A file-B file-C &optional startup-hooks) + "Run Ediff on three files, FILE-A, FILE-B, and FILE-C." + (interactive + (let ((dir-A (if ediff-use-last-dir + ediff-last-dir-A + default-directory)) + dir-B dir-C f ff) + (list (setq f (ediff-read-file-name + "File A to compare" + dir-A + (ediff-get-default-file-name) + 'no-dirs)) + (setq ff (ediff-read-file-name "File B to compare" + (setq dir-B + (if ediff-use-last-dir + ediff-last-dir-B + (file-name-directory f))) + (progn + (ediff-add-to-history + 'file-name-history + (ediff-abbreviate-file-name + (expand-file-name + (file-name-nondirectory f) + dir-B))) + (ediff-get-default-file-name f 1)))) + (ediff-read-file-name "File C to compare" + (setq dir-C (if ediff-use-last-dir + ediff-last-dir-C + (file-name-directory ff))) + (progn + (ediff-add-to-history + 'file-name-history + (ediff-abbreviate-file-name + (expand-file-name + (file-name-nondirectory ff) + dir-C))) + (ediff-get-default-file-name ff 2))) + ))) + (ediff-files-internal file-A + (if (file-directory-p file-B) + (expand-file-name + (file-name-nondirectory file-A) file-B) + file-B) + (if (file-directory-p file-C) + (expand-file-name + (file-name-nondirectory file-A) file-C) + file-C) + startup-hooks + 'ediff-files3)) + +;;;###autoload +(defalias 'ediff3 'ediff-files3) + + +(defun ediff-find-file (file-var buffer-name &optional last-dir hooks-var) + "Visit FILE and arrange its buffer to Ediff's liking. +FILE-VAR is actually a variable symbol whose value must contain a true +file name. +BUFFER-NAME is a variable symbol, which will get the buffer object into +which FILE is read. +LAST-DIR is the directory variable symbol where FILE's +directory name should be returned. HOOKS-VAR is a variable symbol that will +be assigned the hook to be executed after `ediff-startup' is finished. +`ediff-find-file' arranges that the temp files it might create will be +deleted." + (let* ((file (symbol-value file-var)) + (file-magic (ediff-filename-magic-p file)) + (temp-file-name-prefix (file-name-nondirectory file))) + (cond ((not (file-readable-p file)) + (error "File `%s' does not exist or is not readable" file)) + ((file-directory-p file) + (error "File `%s' is a directory" file))) + + ;; some of the commands, below, require full file name + (setq file (expand-file-name file)) + + ;; Record the directory of the file + (if last-dir + (set last-dir (expand-file-name (file-name-directory file)))) + + ;; Setup the buffer + (set buffer-name (find-file-noselect file)) + + (ediff-with-current-buffer (symbol-value buffer-name) + (widen) ; Make sure the entire file is seen + (cond (file-magic ; file has a handler, such as jka-compr-handler or + ;;; ange-ftp-hook-function--arrange for temp file + (ediff-verify-file-buffer 'magic) + (setq file + (ediff-make-temp-file + (current-buffer) temp-file-name-prefix)) + (set hooks-var (cons `(lambda () (delete-file ,file)) + (symbol-value hooks-var)))) + ;; file processed via auto-mode-alist, a la uncompress.el + ((not (equal (file-truename file) + (file-truename (buffer-file-name)))) + (setq file + (ediff-make-temp-file + (current-buffer) temp-file-name-prefix)) + (set hooks-var (cons `(lambda () (delete-file ,file)) + (symbol-value hooks-var)))) + (t ;; plain file---just check that the file matches the buffer + (ediff-verify-file-buffer)))) + (set file-var file))) + +;; MERGE-BUFFER-FILE is the file to be associated with the merge buffer +(defun ediff-files-internal (file-A file-B file-C startup-hooks job-name + &optional merge-buffer-file) + (let (buf-A buf-B buf-C) + (if (string= file-A file-B) + (error "Files A and B are the same")) + (if (stringp file-C) + (or (and (string= file-A file-C) (error "Files A and C are the same")) + (and (string= file-B file-C) (error "Files B and C are the same")))) + (message "Reading file %s ... " file-A) + ;;(sit-for 0) + (ediff-find-file 'file-A 'buf-A 'ediff-last-dir-A 'startup-hooks) + (message "Reading file %s ... " file-B) + ;;(sit-for 0) + (ediff-find-file 'file-B 'buf-B 'ediff-last-dir-B 'startup-hooks) + (if (stringp file-C) + (progn + (message "Reading file %s ... " file-C) + ;;(sit-for 0) + (ediff-find-file + 'file-C 'buf-C + (if (eq job-name 'ediff-merge-files-with-ancestor) + 'ediff-last-dir-ancestor 'ediff-last-dir-C) + 'startup-hooks))) + (ediff-setup buf-A file-A + buf-B file-B + buf-C file-C + startup-hooks + (list (cons 'ediff-job-name job-name)) + merge-buffer-file))) + +(declare-function diff-latest-backup-file "diff" (fn)) + +;;;###autoload +(defalias 'ediff 'ediff-files) + +;;;###autoload +(defun ediff-current-file () + "Start ediff between current buffer and its file on disk. +This command can be used instead of `revert-buffer'. If there is +nothing to revert then this command fails." + (interactive) + (unless (or revert-buffer-function + revert-buffer-insert-file-contents-function + (and buffer-file-number + (or (buffer-modified-p) + (not (verify-visited-file-modtime + (current-buffer)))))) + (error "Nothing to revert")) + (let* ((auto-save-p (and (recent-auto-save-p) + buffer-auto-save-file-name + (file-readable-p buffer-auto-save-file-name) + (y-or-n-p + "Buffer has been auto-saved recently. Compare with auto-save file? "))) + (file-name (if auto-save-p + buffer-auto-save-file-name + buffer-file-name)) + (revert-buf-name (concat "FILE=" file-name)) + (revert-buf (get-buffer revert-buf-name)) + (current-major major-mode)) + (unless file-name + (error "Buffer does not seem to be associated with any file")) + (when revert-buf + (kill-buffer revert-buf) + (setq revert-buf nil)) + (setq revert-buf (get-buffer-create revert-buf-name)) + (with-current-buffer revert-buf + (insert-file-contents file-name) + ;; Assume same modes: + (funcall current-major)) + (ediff-buffers revert-buf (current-buffer)))) + + +;;;###autoload +(defun ediff-backup (file) + "Run Ediff on FILE and its backup file. +Uses the latest backup, if there are several numerical backups. +If this file is a backup, `ediff' it with its original." + (interactive (list (read-file-name "Ediff (file with backup): "))) + ;; The code is taken from `diff-backup'. + (require 'diff) + (let (bak ori) + (if (backup-file-name-p file) + (setq bak file + ori (file-name-sans-versions file)) + (setq bak (or (diff-latest-backup-file file) + (error "No backup found for %s" file)) + ori file)) + (ediff-files bak ori))) + +;;;###autoload +(defun ediff-buffers (buffer-A buffer-B &optional startup-hooks job-name) + "Run Ediff on a pair of buffers, BUFFER-A and BUFFER-B." + (interactive + (let (bf) + (list (setq bf (read-buffer "Buffer A to compare: " + (ediff-other-buffer "") t)) + (read-buffer "Buffer B to compare: " + (progn + ;; realign buffers so that two visible bufs will be + ;; at the top + (save-window-excursion (other-window 1)) + (ediff-other-buffer bf)) + t)))) + (or job-name (setq job-name 'ediff-buffers)) + (ediff-buffers-internal buffer-A buffer-B nil startup-hooks job-name)) + +;;;###autoload +(defalias 'ebuffers 'ediff-buffers) + + +;;;###autoload +(defun ediff-buffers3 (buffer-A buffer-B buffer-C + &optional startup-hooks job-name) + "Run Ediff on three buffers, BUFFER-A, BUFFER-B, and BUFFER-C." + (interactive + (let (bf bff) + (list (setq bf (read-buffer "Buffer A to compare: " + (ediff-other-buffer "") t)) + (setq bff (read-buffer "Buffer B to compare: " + (progn + ;; realign buffers so that two visible + ;; bufs will be at the top + (save-window-excursion (other-window 1)) + (ediff-other-buffer bf)) + t)) + (read-buffer "Buffer C to compare: " + (progn + ;; realign buffers so that three visible + ;; bufs will be at the top + (save-window-excursion (other-window 1)) + (ediff-other-buffer (list bf bff))) + t) + ))) + (or job-name (setq job-name 'ediff-buffers3)) + (ediff-buffers-internal buffer-A buffer-B buffer-C startup-hooks job-name)) + +;;;###autoload +(defalias 'ebuffers3 'ediff-buffers3) + + + +;; MERGE-BUFFER-FILE is the file to be associated with the merge buffer +(defun ediff-buffers-internal (buf-A buf-B buf-C startup-hooks job-name + &optional merge-buffer-file) + (let* ((buf-A-file-name (buffer-file-name (get-buffer buf-A))) + (buf-B-file-name (buffer-file-name (get-buffer buf-B))) + (buf-C-is-alive (ediff-buffer-live-p buf-C)) + (buf-C-file-name (if buf-C-is-alive + (buffer-file-name (get-buffer buf-B)))) + file-A file-B file-C) + (unwind-protect + (progn + (if (not (ediff-buffer-live-p buf-A)) + (error "Buffer %S doesn't exist" buf-A)) + (if (not (ediff-buffer-live-p buf-B)) + (error "Buffer %S doesn't exist" buf-B)) + (let ((ediff-job-name job-name)) + (if (and ediff-3way-comparison-job + (not buf-C-is-alive)) + (error "Buffer %S doesn't exist" buf-C))) + (if (stringp buf-A-file-name) + (setq buf-A-file-name (file-name-nondirectory buf-A-file-name))) + (if (stringp buf-B-file-name) + (setq buf-B-file-name (file-name-nondirectory buf-B-file-name))) + (if (stringp buf-C-file-name) + (setq buf-C-file-name (file-name-nondirectory buf-C-file-name))) + + (setq file-A (ediff-make-temp-file buf-A buf-A-file-name) + file-B (ediff-make-temp-file buf-B buf-B-file-name)) + (if buf-C-is-alive + (setq file-C (ediff-make-temp-file buf-C buf-C-file-name))) + + (ediff-setup (get-buffer buf-A) file-A + (get-buffer buf-B) file-B + (if buf-C-is-alive (get-buffer buf-C)) + file-C + (cons `(lambda () + (delete-file ,file-A) + (delete-file ,file-B) + (if (stringp ,file-C) (delete-file ,file-C))) + startup-hooks) + (list (cons 'ediff-job-name job-name)) + merge-buffer-file)) + (if (and (stringp file-A) (file-exists-p file-A)) + (delete-file file-A)) + (if (and (stringp file-B) (file-exists-p file-B)) + (delete-file file-B)) + (if (and (stringp file-C) (file-exists-p file-C)) + (delete-file file-C))))) + + +;;; Directory and file group operations + +;; Get appropriate default name for directory: +;; If ediff-use-last-dir, use ediff-last-dir-A. +;; In dired mode, use the directory that is under the point (if any); +;; otherwise, use default-directory +(defun ediff-get-default-directory-name () + (cond (ediff-use-last-dir ediff-last-dir-A) + ((eq major-mode 'dired-mode) + (let ((f (dired-get-filename nil 'noerror))) + (if (and (stringp f) (file-directory-p f)) + f + default-directory))) + (t default-directory))) + + +;;;###autoload +(defun ediff-directories (dir1 dir2 regexp) + "Run Ediff on a pair of directories, DIR1 and DIR2, comparing files that have +the same name in both. The third argument, REGEXP, is nil or a regular +expression; only file names that match the regexp are considered." + (interactive + (let ((dir-A (ediff-get-default-directory-name)) + (default-regexp (eval ediff-default-filtering-regexp)) + f) + (list (setq f (read-directory-name + "Directory A to compare:" dir-A nil 'must-match)) + (read-directory-name "Directory B to compare:" + (if ediff-use-last-dir + ediff-last-dir-B + (ediff-strip-last-dir f)) + nil 'must-match) + (read-string + (if (stringp default-regexp) + (format "Filter through regular expression (default %s): " + default-regexp) + "Filter through regular expression: ") + nil + 'ediff-filtering-regexp-history + (eval ediff-default-filtering-regexp)) + ))) + (ediff-directories-internal + dir1 dir2 nil regexp 'ediff-files 'ediff-directories + )) + +;;;###autoload +(defalias 'edirs 'ediff-directories) + + +;;;###autoload +(defun ediff-directory-revisions (dir1 regexp) + "Run Ediff on a directory, DIR1, comparing its files with their revisions. +The second argument, REGEXP, is a regular expression that filters the file +names. Only the files that are under revision control are taken into account." + (interactive + (let ((dir-A (ediff-get-default-directory-name)) + (default-regexp (eval ediff-default-filtering-regexp)) + ) + (list (read-directory-name + "Directory to compare with revision:" dir-A nil 'must-match) + (read-string + (if (stringp default-regexp) + (format "Filter through regular expression (default %s): " + default-regexp) + "Filter through regular expression: ") + nil + 'ediff-filtering-regexp-history + (eval ediff-default-filtering-regexp)) + ))) + (ediff-directory-revisions-internal + dir1 regexp 'ediff-revision 'ediff-directory-revisions + )) + +;;;###autoload +(defalias 'edir-revisions 'ediff-directory-revisions) + + +;;;###autoload +(defun ediff-directories3 (dir1 dir2 dir3 regexp) + "Run Ediff on three directories, DIR1, DIR2, and DIR3, comparing files that +have the same name in all three. The last argument, REGEXP, is nil or a +regular expression; only file names that match the regexp are considered." + + (interactive + (let ((dir-A (ediff-get-default-directory-name)) + (default-regexp (eval ediff-default-filtering-regexp)) + f) + (list (setq f (read-directory-name "Directory A to compare:" dir-A nil)) + (setq f (read-directory-name "Directory B to compare:" + (if ediff-use-last-dir + ediff-last-dir-B + (ediff-strip-last-dir f)) + nil 'must-match)) + (read-directory-name "Directory C to compare:" + (if ediff-use-last-dir + ediff-last-dir-C + (ediff-strip-last-dir f)) + nil 'must-match) + (read-string + (if (stringp default-regexp) + (format "Filter through regular expression (default %s): " + default-regexp) + "Filter through regular expression: ") + nil + 'ediff-filtering-regexp-history + (eval ediff-default-filtering-regexp)) + ))) + (ediff-directories-internal + dir1 dir2 dir3 regexp 'ediff-files3 'ediff-directories3 + )) + +;;;###autoload +(defalias 'edirs3 'ediff-directories3) + +;;;###autoload +(defun ediff-merge-directories (dir1 dir2 regexp &optional merge-autostore-dir) + "Run Ediff on a pair of directories, DIR1 and DIR2, merging files that have +the same name in both. The third argument, REGEXP, is nil or a regular +expression; only file names that match the regexp are considered." + (interactive + (let ((dir-A (ediff-get-default-directory-name)) + (default-regexp (eval ediff-default-filtering-regexp)) + f) + (list (setq f (read-directory-name "Directory A to merge:" + dir-A nil 'must-match)) + (read-directory-name "Directory B to merge:" + (if ediff-use-last-dir + ediff-last-dir-B + (ediff-strip-last-dir f)) + nil 'must-match) + (read-string + (if (stringp default-regexp) + (format "Filter through regular expression (default %s): " + default-regexp) + "Filter through regular expression: ") + nil + 'ediff-filtering-regexp-history + (eval ediff-default-filtering-regexp)) + ))) + (ediff-directories-internal + dir1 dir2 nil regexp 'ediff-merge-files 'ediff-merge-directories + nil merge-autostore-dir + )) + +;;;###autoload +(defalias 'edirs-merge 'ediff-merge-directories) + +;;;###autoload +(defun ediff-merge-directories-with-ancestor (dir1 dir2 ancestor-dir regexp + &optional + merge-autostore-dir) + "Merge files in directories DIR1 and DIR2 using files in ANCESTOR-DIR as ancestors. +Ediff merges files that have identical names in DIR1, DIR2. If a pair of files +in DIR1 and DIR2 doesn't have an ancestor in ANCESTOR-DIR, Ediff will merge +without ancestor. The fourth argument, REGEXP, is nil or a regular expression; +only file names that match the regexp are considered." + (interactive + (let ((dir-A (ediff-get-default-directory-name)) + (default-regexp (eval ediff-default-filtering-regexp)) + f) + (list (setq f (read-directory-name "Directory A to merge:" dir-A nil)) + (setq f (read-directory-name "Directory B to merge:" + (if ediff-use-last-dir + ediff-last-dir-B + (ediff-strip-last-dir f)) + nil 'must-match)) + (read-directory-name "Ancestor directory:" + (if ediff-use-last-dir + ediff-last-dir-C + (ediff-strip-last-dir f)) + nil 'must-match) + (read-string + (if (stringp default-regexp) + (format "Filter through regular expression (default %s): " + default-regexp) + "Filter through regular expression: ") + nil + 'ediff-filtering-regexp-history + (eval ediff-default-filtering-regexp)) + ))) + (ediff-directories-internal + dir1 dir2 ancestor-dir regexp + 'ediff-merge-files-with-ancestor 'ediff-merge-directories-with-ancestor + nil merge-autostore-dir + )) + +;;;###autoload +(defun ediff-merge-directory-revisions (dir1 regexp + &optional merge-autostore-dir) + "Run Ediff on a directory, DIR1, merging its files with their revisions. +The second argument, REGEXP, is a regular expression that filters the file +names. Only the files that are under revision control are taken into account." + (interactive + (let ((dir-A (ediff-get-default-directory-name)) + (default-regexp (eval ediff-default-filtering-regexp)) + ) + (list (read-directory-name + "Directory to merge with revisions:" dir-A nil 'must-match) + (read-string + (if (stringp default-regexp) + (format "Filter through regular expression (default %s): " + default-regexp) + "Filter through regular expression: ") + nil + 'ediff-filtering-regexp-history + (eval ediff-default-filtering-regexp)) + ))) + (ediff-directory-revisions-internal + dir1 regexp 'ediff-merge-revisions 'ediff-merge-directory-revisions + nil merge-autostore-dir + )) + +;;;###autoload +(defalias 'edir-merge-revisions 'ediff-merge-directory-revisions) + +;;;###autoload +(defun ediff-merge-directory-revisions-with-ancestor (dir1 regexp + &optional + merge-autostore-dir) + "Run Ediff on a directory, DIR1, merging its files with their revisions and ancestors. +The second argument, REGEXP, is a regular expression that filters the file +names. Only the files that are under revision control are taken into account." + (interactive + (let ((dir-A (ediff-get-default-directory-name)) + (default-regexp (eval ediff-default-filtering-regexp)) + ) + (list (read-directory-name + "Directory to merge with revisions and ancestors:" + dir-A nil 'must-match) + (read-string + (if (stringp default-regexp) + (format "Filter through regular expression (default %s): " + default-regexp) + "Filter through regular expression: ") + nil + 'ediff-filtering-regexp-history + (eval ediff-default-filtering-regexp)) + ))) + (ediff-directory-revisions-internal + dir1 regexp 'ediff-merge-revisions-with-ancestor + 'ediff-merge-directory-revisions-with-ancestor + nil merge-autostore-dir + )) + +;;;###autoload +(defalias + 'edir-merge-revisions-with-ancestor + 'ediff-merge-directory-revisions-with-ancestor) + +;;;###autoload +(defalias 'edirs-merge-with-ancestor 'ediff-merge-directories-with-ancestor) + +;; Run ediff-action (ediff-files, ediff-merge, ediff-merge-with-ancestors) +;; on a pair of directories (three directories, in case of ancestor). +;; The third argument, REGEXP, is nil or a regular expression; +;; only file names that match the regexp are considered. +;; JOBNAME is the symbol indicating the meta-job to be performed. +;; MERGE-AUTOSTORE-DIR is the directory in which to store merged files. +(defun ediff-directories-internal (dir1 dir2 dir3 regexp action jobname + &optional startup-hooks + merge-autostore-dir) + (if (stringp dir3) + (setq dir3 (if (file-directory-p dir3) dir3 (file-name-directory dir3)))) + + (cond ((string= dir1 dir2) + (error "Directories A and B are the same: %s" dir1)) + ((and (eq jobname 'ediff-directories3) + (string= dir1 dir3)) + (error "Directories A and C are the same: %s" dir1)) + ((and (eq jobname 'ediff-directories3) + (string= dir2 dir3)) + (error "Directories B and C are the same: %s" dir1))) + + (if merge-autostore-dir + (or (stringp merge-autostore-dir) + (error "%s: Directory for storing merged files must be a string" + jobname))) + (let (;; dir-diff-struct is of the form (common-list diff-list) + ;; It is a structure where ediff-intersect-directories returns + ;; commonalities and differences among directories + dir-diff-struct + meta-buf) + (if (and ediff-autostore-merges + (ediff-merge-metajob jobname) + (not merge-autostore-dir)) + (setq merge-autostore-dir + (read-directory-name "Save merged files in directory: " + (if ediff-use-last-dir + ediff-last-merge-autostore-dir + (ediff-strip-last-dir dir1)) + nil + 'must-match))) + ;; verify we are not merging into an orig directory + (if merge-autostore-dir + (cond ((and (stringp dir1) (string= merge-autostore-dir dir1)) + (or (y-or-n-p + "Directory for saving merged files = Directory A. Sure? ") + (error "Directory merge aborted"))) + ((and (stringp dir2) (string= merge-autostore-dir dir2)) + (or (y-or-n-p + "Directory for saving merged files = Directory B. Sure? ") + (error "Directory merge aborted"))) + ((and (stringp dir3) (string= merge-autostore-dir dir3)) + (or (y-or-n-p + "Directory for saving merged files = Ancestor Directory. Sure? ") + (error "Directory merge aborted"))))) + + (setq dir-diff-struct (ediff-intersect-directories + jobname + regexp dir1 dir2 dir3 merge-autostore-dir)) + (setq startup-hooks + ;; this sets various vars in the meta buffer inside + ;; ediff-prepare-meta-buffer + (cons `(lambda () + ;; tell what to do if the user clicks on a session record + (setq ediff-session-action-function (quote ,action)) + ;; set ediff-dir-difference-list + (setq ediff-dir-difference-list + (cdr (quote ,dir-diff-struct)))) + startup-hooks)) + (setq meta-buf (ediff-prepare-meta-buffer + 'ediff-filegroup-action + (car dir-diff-struct) + "*Ediff Session Group Panel" + 'ediff-redraw-directory-group-buffer + jobname + startup-hooks)) + (ediff-show-meta-buffer meta-buf) + )) + +;; MERGE-AUTOSTORE-DIR can be given to tell ediff where to store the merged +;; files +(defun ediff-directory-revisions-internal (dir1 regexp action jobname + &optional startup-hooks + merge-autostore-dir) + (setq dir1 (if (file-directory-p dir1) dir1 (file-name-directory dir1))) + + (if merge-autostore-dir + (or (stringp merge-autostore-dir) + (error "%S: Directory for storing merged files must be a string" + jobname))) + (let (file-list meta-buf) + (if (and ediff-autostore-merges + (ediff-merge-metajob jobname) + (not merge-autostore-dir)) + (setq merge-autostore-dir + (read-directory-name "Save merged files in directory: " + (if ediff-use-last-dir + ediff-last-merge-autostore-dir + (ediff-strip-last-dir dir1)) + nil + 'must-match))) + ;; verify merge-autostore-dir != dir1 + (if (and merge-autostore-dir + (stringp dir1) + (string= merge-autostore-dir dir1)) + (or (y-or-n-p + "Directory for saving merged file = directory A. Sure? ") + (error "Merge of directory revisions aborted"))) + + (setq file-list + (ediff-get-directory-files-under-revision + jobname regexp dir1 merge-autostore-dir)) + (setq startup-hooks + ;; this sets various vars in the meta buffer inside + ;; ediff-prepare-meta-buffer + (cons `(lambda () + ;; tell what to do if the user clicks on a session record + (setq ediff-session-action-function (quote ,action))) + startup-hooks)) + (setq meta-buf (ediff-prepare-meta-buffer + 'ediff-filegroup-action + file-list + "*Ediff Session Group Panel" + 'ediff-redraw-directory-group-buffer + jobname + startup-hooks)) + (ediff-show-meta-buffer meta-buf) + )) + + +;;; Compare regions and windows + +;;;###autoload +(defun ediff-windows-wordwise (dumb-mode &optional wind-A wind-B startup-hooks) + "Compare WIND-A and WIND-B, which are selected by clicking, wordwise. +With prefix argument, DUMB-MODE, or on a non-windowing display, works as +follows: +If WIND-A is nil, use selected window. +If WIND-B is nil, use window next to WIND-A." + (interactive "P") + (ediff-windows dumb-mode wind-A wind-B + startup-hooks 'ediff-windows-wordwise 'word-mode)) + +;;;###autoload +(defun ediff-windows-linewise (dumb-mode &optional wind-A wind-B startup-hooks) + "Compare WIND-A and WIND-B, which are selected by clicking, linewise. +With prefix argument, DUMB-MODE, or on a non-windowing display, works as +follows: +If WIND-A is nil, use selected window. +If WIND-B is nil, use window next to WIND-A." + (interactive "P") + (ediff-windows dumb-mode wind-A wind-B + startup-hooks 'ediff-windows-linewise nil)) + +;; Compare WIND-A and WIND-B, which are selected by clicking. +;; With prefix argument, DUMB-MODE, or on a non-windowing display, +;; works as follows: +;; If WIND-A is nil, use selected window. +;; If WIND-B is nil, use window next to WIND-A. +(defun ediff-windows (dumb-mode wind-A wind-B startup-hooks job-name word-mode) + (if (or dumb-mode (not (ediff-window-display-p))) + (setq wind-A (ediff-get-next-window wind-A nil) + wind-B (ediff-get-next-window wind-B wind-A)) + (setq wind-A (ediff-get-window-by-clicking wind-A nil 1) + wind-B (ediff-get-window-by-clicking wind-B wind-A 2))) + + (let ((buffer-A (window-buffer wind-A)) + (buffer-B (window-buffer wind-B)) + beg-A end-A beg-B end-B) + + (save-excursion + (save-window-excursion + (sit-for 0) ; sync before using window-start/end -- a precaution + (select-window wind-A) + (setq beg-A (window-start) + end-A (window-end)) + (select-window wind-B) + (setq beg-B (window-start) + end-B (window-end)))) + (setq buffer-A + (ediff-clone-buffer-for-window-comparison + buffer-A wind-A "-Window.A-") + buffer-B + (ediff-clone-buffer-for-window-comparison + buffer-B wind-B "-Window.B-")) + (ediff-regions-internal + buffer-A beg-A end-A buffer-B beg-B end-B + startup-hooks job-name word-mode nil))) + + +;;;###autoload +(defun ediff-regions-wordwise (buffer-A buffer-B &optional startup-hooks) + "Run Ediff on a pair of regions in specified buffers. +Regions \(i.e., point and mark\) can be set in advance or marked interactively. +This function is effective only for relatively small regions, up to 200 +lines. For large regions, use `ediff-regions-linewise'." + (interactive + (let (bf) + (list (setq bf (read-buffer "Region's A buffer: " + (ediff-other-buffer "") t)) + (read-buffer "Region's B buffer: " + (progn + ;; realign buffers so that two visible bufs will be + ;; at the top + (save-window-excursion (other-window 1)) + (ediff-other-buffer bf)) + t)))) + (if (not (ediff-buffer-live-p buffer-A)) + (error "Buffer %S doesn't exist" buffer-A)) + (if (not (ediff-buffer-live-p buffer-B)) + (error "Buffer %S doesn't exist" buffer-B)) + + + (let ((buffer-A + (ediff-clone-buffer-for-region-comparison buffer-A "-Region.A-")) + (buffer-B + (ediff-clone-buffer-for-region-comparison buffer-B "-Region.B-")) + reg-A-beg reg-A-end reg-B-beg reg-B-end) + (with-current-buffer buffer-A + (setq reg-A-beg (region-beginning) + reg-A-end (region-end)) + (set-buffer buffer-B) + (setq reg-B-beg (region-beginning) + reg-B-end (region-end))) + + (ediff-regions-internal + (get-buffer buffer-A) reg-A-beg reg-A-end + (get-buffer buffer-B) reg-B-beg reg-B-end + startup-hooks 'ediff-regions-wordwise 'word-mode nil))) + +;;;###autoload +(defun ediff-regions-linewise (buffer-A buffer-B &optional startup-hooks) + "Run Ediff on a pair of regions in specified buffers. +Regions \(i.e., point and mark\) can be set in advance or marked interactively. +Each region is enlarged to contain full lines. +This function is effective for large regions, over 100-200 +lines. For small regions, use `ediff-regions-wordwise'." + (interactive + (let (bf) + (list (setq bf (read-buffer "Region A's buffer: " + (ediff-other-buffer "") t)) + (read-buffer "Region B's buffer: " + (progn + ;; realign buffers so that two visible bufs will be + ;; at the top + (save-window-excursion (other-window 1)) + (ediff-other-buffer bf)) + t)))) + (if (not (ediff-buffer-live-p buffer-A)) + (error "Buffer %S doesn't exist" buffer-A)) + (if (not (ediff-buffer-live-p buffer-B)) + (error "Buffer %S doesn't exist" buffer-B)) + + (let ((buffer-A + (ediff-clone-buffer-for-region-comparison buffer-A "-Region.A-")) + (buffer-B + (ediff-clone-buffer-for-region-comparison buffer-B "-Region.B-")) + reg-A-beg reg-A-end reg-B-beg reg-B-end) + (with-current-buffer buffer-A + (setq reg-A-beg (region-beginning) + reg-A-end (region-end)) + ;; enlarge the region to hold full lines + (goto-char reg-A-beg) + (beginning-of-line) + (setq reg-A-beg (point)) + (goto-char reg-A-end) + (end-of-line) + (or (eobp) (forward-char)) ; include the newline char + (setq reg-A-end (point)) + + (set-buffer buffer-B) + (setq reg-B-beg (region-beginning) + reg-B-end (region-end)) + ;; enlarge the region to hold full lines + (goto-char reg-B-beg) + (beginning-of-line) + (setq reg-B-beg (point)) + (goto-char reg-B-end) + (end-of-line) + (or (eobp) (forward-char)) ; include the newline char + (setq reg-B-end (point)) + ) ; save excursion + + (ediff-regions-internal + (get-buffer buffer-A) reg-A-beg reg-A-end + (get-buffer buffer-B) reg-B-beg reg-B-end + startup-hooks 'ediff-regions-linewise nil nil))) ; no word mode + +;; compare region beg-A to end-A of buffer-A +;; to regions beg-B -- end-B in buffer-B. +(defun ediff-regions-internal (buffer-A beg-A end-A buffer-B beg-B end-B + startup-hooks job-name word-mode + setup-parameters) + (let ((tmp-buffer (get-buffer-create ediff-tmp-buffer)) + overl-A overl-B + file-A file-B) + (unwind-protect + (progn + ;; in case beg/end-A/B aren't markers--make them into markers + (ediff-with-current-buffer buffer-A + (setq beg-A (move-marker (make-marker) beg-A) + end-A (move-marker (make-marker) end-A))) + (ediff-with-current-buffer buffer-B + (setq beg-B (move-marker (make-marker) beg-B) + end-B (move-marker (make-marker) end-B))) + + ;; make file-A + (if word-mode + (ediff-wordify beg-A end-A buffer-A tmp-buffer) + (ediff-copy-to-buffer beg-A end-A buffer-A tmp-buffer)) + (setq file-A (ediff-make-temp-file tmp-buffer "regA")) + + ;; make file-B + (if word-mode + (ediff-wordify beg-B end-B buffer-B tmp-buffer) + (ediff-copy-to-buffer beg-B end-B buffer-B tmp-buffer)) + (setq file-B (ediff-make-temp-file tmp-buffer "regB")) + + (setq overl-A (ediff-make-bullet-proof-overlay beg-A end-A buffer-A)) + (setq overl-B (ediff-make-bullet-proof-overlay beg-B end-B buffer-B)) + (ediff-setup buffer-A file-A + buffer-B file-B + nil nil ; buffer & file C + (cons `(lambda () + (delete-file ,file-A) + (delete-file ,file-B)) + startup-hooks) + (append + (list (cons 'ediff-word-mode word-mode) + (cons 'ediff-narrow-bounds (list overl-A overl-B)) + (cons 'ediff-job-name job-name)) + setup-parameters))) + (if (and (stringp file-A) (file-exists-p file-A)) + (delete-file file-A)) + (if (and (stringp file-B) (file-exists-p file-B)) + (delete-file file-B))) + )) + + +;;; Merge files and buffers + +;;;###autoload +(defalias 'ediff-merge 'ediff-merge-files) + +(defsubst ediff-merge-on-startup () + (ediff-do-merge 0) + ;; Can't remember why this is here, but it may cause the automatically merged + ;; buffer to be lost. So, keep the buffer modified. + ;;(ediff-with-current-buffer ediff-buffer-C + ;; (set-buffer-modified-p nil)) + ) + +;;;###autoload +(defun ediff-merge-files (file-A file-B + ;; MERGE-BUFFER-FILE is the file to be + ;; associated with the merge buffer + &optional startup-hooks merge-buffer-file) + "Merge two files without ancestor." + (interactive + (let ((dir-A (if ediff-use-last-dir + ediff-last-dir-A + default-directory)) + dir-B f) + (list (setq f (ediff-read-file-name + "File A to merge" + dir-A + (ediff-get-default-file-name) + 'no-dirs)) + (ediff-read-file-name "File B to merge" + (setq dir-B + (if ediff-use-last-dir + ediff-last-dir-B + (file-name-directory f))) + (progn + (ediff-add-to-history + 'file-name-history + (ediff-abbreviate-file-name + (expand-file-name + (file-name-nondirectory f) + dir-B))) + (ediff-get-default-file-name f 1))) + ))) + (setq startup-hooks (cons 'ediff-merge-on-startup startup-hooks)) + (ediff-files-internal file-A + (if (file-directory-p file-B) + (expand-file-name + (file-name-nondirectory file-A) file-B) + file-B) + nil ; file-C + startup-hooks + 'ediff-merge-files + merge-buffer-file)) + +;;;###autoload +(defun ediff-merge-files-with-ancestor (file-A file-B file-ancestor + &optional + startup-hooks + ;; MERGE-BUFFER-FILE is the file + ;; to be associated with the + ;; merge buffer + merge-buffer-file) + "Merge two files with ancestor." + (interactive + (let ((dir-A (if ediff-use-last-dir + ediff-last-dir-A + default-directory)) + dir-B dir-ancestor f ff) + (list (setq f (ediff-read-file-name + "File A to merge" + dir-A + (ediff-get-default-file-name) + 'no-dirs)) + (setq ff (ediff-read-file-name "File B to merge" + (setq dir-B + (if ediff-use-last-dir + ediff-last-dir-B + (file-name-directory f))) + (progn + (ediff-add-to-history + 'file-name-history + (ediff-abbreviate-file-name + (expand-file-name + (file-name-nondirectory f) + dir-B))) + (ediff-get-default-file-name f 1)))) + (ediff-read-file-name "Ancestor file" + (setq dir-ancestor + (if ediff-use-last-dir + ediff-last-dir-ancestor + (file-name-directory ff))) + (progn + (ediff-add-to-history + 'file-name-history + (ediff-abbreviate-file-name + (expand-file-name + (file-name-nondirectory ff) + dir-ancestor))) + (ediff-get-default-file-name ff 2))) + ))) + (setq startup-hooks (cons 'ediff-merge-on-startup startup-hooks)) + (ediff-files-internal file-A + (if (file-directory-p file-B) + (expand-file-name + (file-name-nondirectory file-A) file-B) + file-B) + file-ancestor + startup-hooks + 'ediff-merge-files-with-ancestor + merge-buffer-file)) + +;;;###autoload +(defalias 'ediff-merge-with-ancestor 'ediff-merge-files-with-ancestor) + +;;;###autoload +(defun ediff-merge-buffers (buffer-A buffer-B + &optional + ;; MERGE-BUFFER-FILE is the file to be + ;; associated with the merge buffer + startup-hooks job-name merge-buffer-file) + "Merge buffers without ancestor." + (interactive + (let (bf) + (list (setq bf (read-buffer "Buffer A to merge: " + (ediff-other-buffer "") t)) + (read-buffer "Buffer B to merge: " + (progn + ;; realign buffers so that two visible bufs will be + ;; at the top + (save-window-excursion (other-window 1)) + (ediff-other-buffer bf)) + t)))) + + (setq startup-hooks (cons 'ediff-merge-on-startup startup-hooks)) + (or job-name (setq job-name 'ediff-merge-buffers)) + (ediff-buffers-internal + buffer-A buffer-B nil startup-hooks job-name merge-buffer-file)) + +;;;###autoload +(defun ediff-merge-buffers-with-ancestor (buffer-A buffer-B buffer-ancestor + &optional + startup-hooks + job-name + ;; MERGE-BUFFER-FILE is the + ;; file to be associated + ;; with the merge buffer + merge-buffer-file) + "Merge buffers with ancestor." + (interactive + (let (bf bff) + (list (setq bf (read-buffer "Buffer A to merge: " + (ediff-other-buffer "") t)) + (setq bff (read-buffer "Buffer B to merge: " + (progn + ;; realign buffers so that two visible + ;; bufs will be at the top + (save-window-excursion (other-window 1)) + (ediff-other-buffer bf)) + t)) + (read-buffer "Ancestor buffer: " + (progn + ;; realign buffers so that three visible + ;; bufs will be at the top + (save-window-excursion (other-window 1)) + (ediff-other-buffer (list bf bff))) + t) + ))) + + (setq startup-hooks (cons 'ediff-merge-on-startup startup-hooks)) + (or job-name (setq job-name 'ediff-merge-buffers-with-ancestor)) + (ediff-buffers-internal + buffer-A buffer-B buffer-ancestor startup-hooks job-name merge-buffer-file)) + + +;;;###autoload +(defun ediff-merge-revisions (&optional file startup-hooks merge-buffer-file) + ;; MERGE-BUFFER-FILE is the file to be associated with the merge buffer + "Run Ediff by merging two revisions of a file. +The file is the optional FILE argument or the file visited by the current +buffer." + (interactive) + (if (stringp file) (find-file file)) + (let (rev1 rev2) + (setq rev1 + (read-string + (format + "Version 1 to merge (default %s's working version): " + (if (stringp file) + (file-name-nondirectory file) "current buffer"))) + rev2 + (read-string + (format + "Version 2 to merge (default %s): " + (if (stringp file) + (file-name-nondirectory file) "current buffer")))) + (ediff-load-version-control) + ;; ancestor-revision=nil + (funcall + (intern (format "ediff-%S-merge-internal" ediff-version-control-package)) + rev1 rev2 nil startup-hooks merge-buffer-file))) + + +;;;###autoload +(defun ediff-merge-revisions-with-ancestor (&optional + file startup-hooks + ;; MERGE-BUFFER-FILE is the file to + ;; be associated with the merge + ;; buffer + merge-buffer-file) + "Run Ediff by merging two revisions of a file with a common ancestor. +The file is the optional FILE argument or the file visited by the current +buffer." + (interactive) + (if (stringp file) (find-file file)) + (let (rev1 rev2 ancestor-rev) + (setq rev1 + (read-string + (format + "Version 1 to merge (default %s's working version): " + (if (stringp file) + (file-name-nondirectory file) "current buffer"))) + rev2 + (read-string + (format + "Version 2 to merge (default %s): " + (if (stringp file) + (file-name-nondirectory file) "current buffer"))) + ancestor-rev + (read-string + (format + "Ancestor version (default %s's base revision): " + (if (stringp file) + (file-name-nondirectory file) "current buffer")))) + (ediff-load-version-control) + (funcall + (intern (format "ediff-%S-merge-internal" ediff-version-control-package)) + rev1 rev2 ancestor-rev startup-hooks merge-buffer-file))) + +;;; Apply patch + +;;;###autoload +(defun ediff-patch-file (&optional arg patch-buf) + "Run Ediff by patching SOURCE-FILENAME. +If optional PATCH-BUF is given, use the patch in that buffer +and don't ask the user. +If prefix argument, then: if even argument, assume that the patch is in a +buffer. If odd -- assume it is in a file." + (interactive "P") + (let (source-dir source-file) + (require 'ediff-ptch) + (setq patch-buf + (ediff-get-patch-buffer + (if arg (prefix-numeric-value arg)) patch-buf)) + (setq source-dir (cond (ediff-use-last-dir ediff-last-dir-patch) + ((and (not ediff-patch-default-directory) + (buffer-file-name patch-buf)) + (file-name-directory + (expand-file-name + (buffer-file-name patch-buf)))) + (t default-directory))) + (setq source-file + (read-file-name + "File to patch (directory, if multifile patch): " + ;; use an explicit initial file + source-dir nil nil (ediff-get-default-file-name))) + (ediff-dispatch-file-patching-job patch-buf source-file))) + +;;;###autoload +(defun ediff-patch-buffer (&optional arg patch-buf) + "Run Ediff by patching the buffer specified at prompt. +Without the optional prefix ARG, asks if the patch is in some buffer and +prompts for the buffer or a file, depending on the answer. +With ARG=1, assumes the patch is in a file and prompts for the file. +With ARG=2, assumes the patch is in a buffer and prompts for the buffer. +PATCH-BUF is an optional argument, which specifies the buffer that contains the +patch. If not given, the user is prompted according to the prefix argument." + (interactive "P") + (require 'ediff-ptch) + (setq patch-buf + (ediff-get-patch-buffer + (if arg (prefix-numeric-value arg)) patch-buf)) + (ediff-patch-buffer-internal + patch-buf + (read-buffer + "Which buffer to patch? " + (ediff-other-buffer patch-buf)))) + + +;;;###autoload +(defalias 'epatch 'ediff-patch-file) +;;;###autoload +(defalias 'epatch-buffer 'ediff-patch-buffer) + + + + +;;; Versions Control functions + +;;;###autoload +(defun ediff-revision (&optional file startup-hooks) + "Run Ediff by comparing versions of a file. +The file is an optional FILE argument or the file entered at the prompt. +Default: the file visited by the current buffer. +Uses `vc.el' or `rcs.el' depending on `ediff-version-control-package'." + ;; if buffer is non-nil, use that buffer instead of the current buffer + (interactive "P") + (if (not (stringp file)) + (setq file + (ediff-read-file-name "Compare revisions for file" + (if ediff-use-last-dir + ediff-last-dir-A + default-directory) + (ediff-get-default-file-name) + 'no-dirs))) + (find-file file) + (if (and (buffer-modified-p) + (y-or-n-p (format "Buffer %s is modified. Save buffer? " + (buffer-name)))) + (save-buffer (current-buffer))) + (let (rev1 rev2) + (setq rev1 + (read-string + (format "Revision 1 to compare (default %s's latest revision): " + (file-name-nondirectory file))) + rev2 + (read-string + (format "Revision 2 to compare (default %s's current state): " + (file-name-nondirectory file)))) + (ediff-load-version-control) + (funcall + (intern (format "ediff-%S-internal" ediff-version-control-package)) + rev1 rev2 startup-hooks) + )) + + +;;;###autoload +(defalias 'erevision 'ediff-revision) + + +;; Test if version control package is loaded and load if not +;; Is SILENT is non-nil, don't report error if package is not found. +(defun ediff-load-version-control (&optional silent) + (require 'ediff-vers) + (or (featurep ediff-version-control-package) + (if (locate-library (symbol-name ediff-version-control-package)) + (progn + (message "") ; kill the message from `locate-library' + (require ediff-version-control-package)) + (or silent + (error "Version control package %S.el not found. Use vc.el instead" + ediff-version-control-package))))) + + +;;;###autoload +(defun ediff-version () + "Return string describing the version of Ediff. +When called interactively, displays the version." + (interactive) + ;; called-interactively-p - not in XEmacs + ;; (if (called-interactively-p 'interactive) + (if (interactive-p) + (message "%s" (ediff-version)) + (format "Ediff %s of %s" ediff-version ediff-date))) + +;; info is run first, and will autoload info.el. +(declare-function Info-goto-node "info" (nodename &optional fork)) + +;;;###autoload +(defun ediff-documentation (&optional node) + "Display Ediff's manual. +With optional NODE, goes to that node." + (interactive) + (let ((ctl-window ediff-control-window) + (ctl-buf ediff-control-buffer)) + + (ediff-skip-unsuitable-frames) + (condition-case nil + (progn + (pop-to-buffer (get-buffer-create "*info*")) + (info (if (featurep 'xemacs) "ediff.info" "ediff")) + (if node + (Info-goto-node node) + (message "Type `i' to search for a specific topic")) + (raise-frame (selected-frame))) + (error (beep 1) + (with-output-to-temp-buffer ediff-msg-buffer + (ediff-with-current-buffer standard-output + (fundamental-mode)) + (princ ediff-BAD-INFO)) + (if (window-live-p ctl-window) + (progn + (select-window ctl-window) + (set-window-buffer ctl-window ctl-buf))))))) + + +(dolist (mess '("^Errors in diff output. Diff output is in " + "^Hmm... I don't see an Ediff command around here...$" + "^Undocumented command! Type `G' in Ediff Control Panel to drop a note to the Ediff maintainer$" + ": This command runs in Ediff Control Buffer only!$" + ": Invalid op in ediff-check-version$" + "^ediff-shrink-window-C can be used only for merging jobs$" + "^Lost difference info on these directories$" + "^This command is inapplicable in the present context$" + "^This session group has no parent$" + "^Can't hide active session, $" + "^Ediff: something wrong--no multiple diffs buffer$" + "^Can't make context diff for Session $" + "^The patch buffer wasn't found$" + "^Aborted$" + "^This Ediff session is not part of a session group$" + "^No active Ediff sessions or corrupted session registry$" + "^No session info in this line$" + "^`.*' is not an ordinary file$" + "^Patch appears to have failed$" + "^Recomputation of differences cancelled$" + "^No fine differences in this mode$" + "^Lost connection to ancestor buffer...sorry$" + "^Not merging with ancestor$" + "^Don't know how to toggle read-only in buffer " + "Emacs is not running as a window application$" + "^This command makes sense only when merging with an ancestor$" + "^At end of the difference list$" + "^At beginning of the difference list$" + "^Nothing saved for diff .* in buffer " + "^Buffer is out of sync for file " + "^Buffer out of sync for file " + "^Output from `diff' not found$" + "^You forgot to specify a region in buffer " + "^All right. Make up your mind and come back...$" + "^Current buffer is not visiting any file$" + "^Failed to retrieve revision: $" + "^Can't determine display width.$" + "^File `.*' does not exist or is not readable$" + "^File `.*' is a directory$" + "^Buffer .* doesn't exist$" + "^Directories . and . are the same: " + "^Directory merge aborted$" + "^Merge of directory revisions aborted$" + "^Buffer .* doesn't exist$" + "^There is no file to merge$" + "^Version control package .*.el not found. Use vc.el instead$")) + (add-to-list 'debug-ignored-errors mess)) + + +(require 'ediff-util) + +(run-hooks 'ediff-load-hook) + + +;; 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: + +;; arch-tag: 97c71396-db02-4f41-8b48-6a51c3348fcc +;;; ediff.el ends here diff -r 681cd08dc0f7 -r e93288477c43 lisp/vc/emerge.el --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/lisp/vc/emerge.el Sun Jun 13 22:57:55 2010 +0000 @@ -0,0 +1,3209 @@ +;;; emerge.el --- merge diffs under Emacs control + +;;; The author has placed this file in the public domain. + +;; This file is part of GNU Emacs. + +;; Author: Dale R. Worley +;; Keywords: unix, vc, tools + +;; This software was created by Dale R. Worley and is +;; distributed free of charge. It is placed in the public domain and +;; permission is granted to anyone to use, duplicate, modify and redistribute +;; it provided that this notice is attached. + +;; Dale R. Worley provides absolutely NO WARRANTY OF ANY KIND +;; with respect to this software. The entire risk as to the quality and +;; performance of this software is with the user. IN NO EVENT WILL DALE +;; R. WORLEY BE LIABLE TO ANYONE FOR ANY DAMAGES ARISING OUT THE +;; USE OF THIS SOFTWARE, INCLUDING, WITHOUT LIMITATION, DAMAGES RESULTING FROM +;; LOST DATA OR LOST PROFITS, OR FOR ANY SPECIAL, INCIDENTAL OR CONSEQUENTIAL +;; DAMAGES. + +;;; Commentary: + +;;; Code: + +;; There aren't really global variables, just dynamic bindings +(defvar A-begin) +(defvar A-end) +(defvar B-begin) +(defvar B-end) +(defvar diff) +(defvar diff-vector) +(defvar merge-begin) +(defvar merge-end) +(defvar template) +(defvar valid-diff) + +;;; Macros + +(defmacro emerge-eval-in-buffer (buffer &rest forms) + "Macro to switch to BUFFER, evaluate FORMS, returns to original buffer. +Differs from `save-excursion' in that it doesn't save the point and mark." + `(let ((StartBuffer (current-buffer))) + (unwind-protect + (progn + (set-buffer ,buffer) + ,@forms) + (set-buffer StartBuffer)))) + +(defmacro emerge-defvar-local (var value doc) + "Defines SYMBOL as an advertised variable. +Performs a defvar, then executes `make-variable-buffer-local' on +the variable. Also sets the `preserved' property, so that +`kill-all-local-variables' (called by major-mode setting commands) +won't destroy Emerge control variables." + `(progn + (defvar ,var ,value ,doc) + (make-variable-buffer-local ',var) + (put ',var 'preserved t))) + +;; Add entries to minor-mode-alist so that emerge modes show correctly +(defvar emerge-minor-modes-list + '((emerge-mode " Emerge") + (emerge-fast-mode " F") + (emerge-edit-mode " E") + (emerge-auto-advance " A") + (emerge-skip-prefers " S"))) +(if (not (assq 'emerge-mode minor-mode-alist)) + (setq minor-mode-alist (append emerge-minor-modes-list + minor-mode-alist))) + +;; We need to define this function so describe-mode can describe Emerge mode. +(defun emerge-mode () + "Emerge mode is used by the Emerge file-merging package. +It is entered only through one of the functions: + `emerge-files' + `emerge-files-with-ancestor' + `emerge-buffers' + `emerge-buffers-with-ancestor' + `emerge-files-command' + `emerge-files-with-ancestor-command' + `emerge-files-remote' + `emerge-files-with-ancestor-remote' + +Commands: +\\{emerge-basic-keymap} +Commands must be prefixed by \\\\[emerge-basic-keymap] in `edit' mode, +but can be invoked directly in `fast' mode.") + +(define-obsolete-variable-alias 'emerge-version 'emacs-version "23.2") + +(defun emerge-version () + "Return string describing the version of Emerge. +When called interactively, displays the version." + (interactive) + (if (called-interactively-p 'interactive) + (message "Emerge version %s" emacs-version) + emacs-version)) + +(make-obsolete 'emerge-version 'emacs-version "23.2") + +;;; Emerge configuration variables + +(defgroup emerge nil + "Merge diffs under Emacs control." + :group 'tools) + +;; Commands that produce difference files +;; All that can be configured is the name of the programs to execute +;; (emerge-diff-program and emerge-diff3-program) and the options +;; to be provided (emerge-diff-options). The order in which the file names +;; are given is fixed. +;; The file names are always expanded (see expand-file-name) before being +;; passed to diff, thus they need not be invoked under a shell that +;; understands `~'. +;; The code which processes the diff/diff3 output depends on all the +;; finicky details of their output, including the somewhat strange +;; way they number lines of a file. +(defcustom emerge-diff-program "diff" + "Name of the program which compares two files." + :type 'string + :group 'emerge) +(defcustom emerge-diff3-program "diff3" + "Name of the program which compares three files. +Its arguments are the ancestor file and the two variant files." + :type 'string + :group 'emerge) +(defcustom emerge-diff-options "" + "Options to pass to `emerge-diff-program' and `emerge-diff3-program'." + :type 'string + :group 'emerge) +(defcustom emerge-match-diff-line + (let ((x "\\([0-9]+\\)\\(\\|,\\([0-9]+\\)\\)")) + (concat "^" x "\\([acd]\\)" x "$")) + "Pattern to match lines produced by diff that describe differences. +This is as opposed to lines from the source files." + :type 'regexp + :group 'emerge) +(defcustom emerge-diff-ok-lines-regexp + "^\\([0-9,]+[acd][0-9,]+$\\|[<>] \\|---\\)" + "Regexp that matches normal output lines from `emerge-diff-program'. +Lines that do not match are assumed to be error messages." + :type 'regexp + :group 'emerge) +(defcustom emerge-diff3-ok-lines-regexp + "^\\([1-3]:\\|====\\| \\)" + "Regexp that matches normal output lines from `emerge-diff3-program'. +Lines that do not match are assumed to be error messages." + :type 'regexp + :group 'emerge) + +(defcustom emerge-rcs-ci-program "ci" + "Name of the program that checks in RCS revisions." + :type 'string + :group 'emerge) +(defcustom emerge-rcs-co-program "co" + "Name of the program that checks out RCS revisions." + :type 'string + :group 'emerge) + +(defcustom emerge-process-local-variables nil + "Non-nil if Emerge should process local-variables lists in merge buffers. +\(You can explicitly request processing the local-variables +by executing `(hack-local-variables)'.)" + :type 'boolean + :group 'emerge) +(defcustom emerge-execute-line-deletions nil + "If non-nil: `emerge-execute-line' makes no output if an input was deleted. +It concludes that an input version has been deleted when an ancestor entry +is present, only one A or B entry is present, and an output entry is present. +If nil: In such circumstances, the A or B file that is present will be +copied to the designated output file." + :type 'boolean + :group 'emerge) + +(defcustom emerge-before-flag "vvvvvvvvvvvvvvvvvvvv\n" + "Flag placed above the highlighted block of code. Must end with newline. +Must be set before Emerge is loaded, or emerge-new-flags must be run +after setting." + :type 'string + :group 'emerge) +(defcustom emerge-after-flag "^^^^^^^^^^^^^^^^^^^^\n" + "Flag placed below the highlighted block of code. Must end with newline. +Must be set before Emerge is loaded, or emerge-new-flags must be run +after setting." + :type 'string + :group 'emerge) + +;; Hook variables + +(defcustom emerge-startup-hook nil + "Hook to run in the merge buffer after the merge has been set up." + :type 'hook + :group 'emerge) +(defcustom emerge-select-hook nil + "Hook to run after a difference has been selected. +The variable `n' holds the (internal) number of the difference." + :type 'hook + :group 'emerge) +(defcustom emerge-unselect-hook nil + "Hook to run after a difference has been unselected. +The variable `n' holds the (internal) number of the difference." + :type 'hook + :group 'emerge) + +;; Variables to control the default directories of the arguments to +;; Emerge commands. + +(defcustom emerge-default-last-directories nil + "If nil, default dir for filenames in emerge is `default-directory'. +If non-nil, filenames complete in the directory of the last argument of the +same type to an `emerge-files...' command." + :type 'boolean + :group 'emerge) + +(defvar emerge-last-dir-A nil + "Last directory for the first file of an `emerge-files...' command.") +(defvar emerge-last-dir-B nil + "Last directory for the second file of an `emerge-files...' command.") +(defvar emerge-last-dir-ancestor nil + "Last directory for the ancestor file of an `emerge-files...' command.") +(defvar emerge-last-dir-output nil + "Last directory for the output file of an `emerge-files...' command.") +(defvar emerge-last-revision-A nil + "Last RCS revision used for first file of an `emerge-revisions...' command.") +(defvar emerge-last-revision-B nil + "Last RCS revision used for second file of an `emerge-revisions...' command.") +(defvar emerge-last-revision-ancestor nil + "Last RCS revision used for ancestor file of an `emerge-revisions...' command.") + +(defvar emerge-before-flag-length) +(defvar emerge-before-flag-lines) +(defvar emerge-before-flag-match) +(defvar emerge-after-flag-length) +(defvar emerge-after-flag-lines) +(defvar emerge-after-flag-match) +(defvar emerge-diff-buffer) +(defvar emerge-diff-error-buffer) +(defvar emerge-prefix-argument) +(defvar emerge-file-out) +(defvar emerge-exit-func) +(defvar emerge-globalized-difference-list) +(defvar emerge-globalized-number-of-differences) + +;; The flags used to mark differences in the buffers. + +;; These function definitions need to be up here, because they are used +;; during loading. +(defun emerge-new-flags () + "Function to be called after `emerge-{before,after}-flag'. +This is called after these functions are changed to compute values that +depend on the flags." + (setq emerge-before-flag-length (length emerge-before-flag)) + (setq emerge-before-flag-lines + (emerge-count-matches-string emerge-before-flag "\n")) + (setq emerge-before-flag-match (regexp-quote emerge-before-flag)) + (setq emerge-after-flag-length (length emerge-after-flag)) + (setq emerge-after-flag-lines + (emerge-count-matches-string emerge-after-flag "\n")) + (setq emerge-after-flag-match (regexp-quote emerge-after-flag))) + +(defun emerge-count-matches-string (string regexp) + "Return the number of matches in STRING for REGEXP." + (let ((i 0) + (count 0)) + (while (string-match regexp string i) + (setq count (1+ count)) + (setq i (match-end 0))) + count)) + +;; Calculate dependent variables +(emerge-new-flags) + +(defcustom emerge-min-visible-lines 3 + "Number of lines that we want to show above and below the flags when we are +displaying a difference." + :type 'integer + :group 'emerge) + +(defcustom emerge-temp-file-prefix + (expand-file-name "emerge" temporary-file-directory) + "Prefix to put on Emerge temporary file names. +Do not start with `~/' or `~USERNAME/'." + :type 'string + :group 'emerge) + +(defcustom emerge-temp-file-mode 384 ; u=rw only + "Mode for Emerge temporary files." + :type 'integer + :group 'emerge) + +(defcustom emerge-combine-versions-template + "#ifdef NEW\n%b#else /* not NEW */\n%a#endif /* not NEW */\n" + "Template for `emerge-combine-versions' to combine the two versions. +The template is inserted as a string, with the following interpolations: + %a the A version of the difference + %b the B version of the difference + %% the character `%' +Don't forget to end the template with a newline. +Note that this variable can be made local to a particular merge buffer by +giving a prefix argument to `emerge-set-combine-versions-template'." + :type 'string + :group 'emerge) + +;; Build keymaps + +(defvar emerge-basic-keymap nil + "Keymap of Emerge commands. +Directly available in `fast' mode; +must be prefixed by \\\\[emerge-basic-keymap] in `edit' mode.") + +(defvar emerge-fast-keymap nil + "Local keymap used in Emerge `fast' mode. +Makes Emerge commands directly available.") + +(defvar emerge-options-menu + (make-sparse-keymap "Options")) + +(defvar emerge-merge-menu + (make-sparse-keymap "Merge")) + +(defvar emerge-move-menu + (make-sparse-keymap "Move")) + +(defcustom emerge-command-prefix "\C-c\C-c" + "Command prefix for Emerge commands in `edit' mode. +Must be set before Emerge is loaded." + :type 'string + :group 'emerge) + +;; This function sets up the fixed keymaps. It is executed when the first +;; Emerge is done to allow the user maximum time to set up the global keymap. +(defun emerge-setup-fixed-keymaps () + ;; Set up the basic keymap + (setq emerge-basic-keymap (make-keymap)) + (suppress-keymap emerge-basic-keymap) ; this sets 0..9 to digit-argument and + ; - to negative-argument + (define-key emerge-basic-keymap "p" 'emerge-previous-difference) + (define-key emerge-basic-keymap "n" 'emerge-next-difference) + (define-key emerge-basic-keymap "a" 'emerge-select-A) + (define-key emerge-basic-keymap "b" 'emerge-select-B) + (define-key emerge-basic-keymap "j" 'emerge-jump-to-difference) + (define-key emerge-basic-keymap "." 'emerge-find-difference) + (define-key emerge-basic-keymap "q" 'emerge-quit) + (define-key emerge-basic-keymap "\C-]" 'emerge-abort) + (define-key emerge-basic-keymap "f" 'emerge-fast-mode) + (define-key emerge-basic-keymap "e" 'emerge-edit-mode) + (define-key emerge-basic-keymap "s" nil) + (define-key emerge-basic-keymap "sa" 'emerge-auto-advance) + (define-key emerge-basic-keymap "ss" 'emerge-skip-prefers) + (define-key emerge-basic-keymap "l" 'emerge-recenter) + (define-key emerge-basic-keymap "d" nil) + (define-key emerge-basic-keymap "da" 'emerge-default-A) + (define-key emerge-basic-keymap "db" 'emerge-default-B) + (define-key emerge-basic-keymap "c" nil) + (define-key emerge-basic-keymap "ca" 'emerge-copy-as-kill-A) + (define-key emerge-basic-keymap "cb" 'emerge-copy-as-kill-B) + (define-key emerge-basic-keymap "i" nil) + (define-key emerge-basic-keymap "ia" 'emerge-insert-A) + (define-key emerge-basic-keymap "ib" 'emerge-insert-B) + (define-key emerge-basic-keymap "m" 'emerge-mark-difference) + (define-key emerge-basic-keymap "v" 'emerge-scroll-up) + (define-key emerge-basic-keymap "^" 'emerge-scroll-down) + (define-key emerge-basic-keymap "<" 'emerge-scroll-left) + (define-key emerge-basic-keymap ">" 'emerge-scroll-right) + (define-key emerge-basic-keymap "|" 'emerge-scroll-reset) + (define-key emerge-basic-keymap "x" nil) + (define-key emerge-basic-keymap "x1" 'emerge-one-line-window) + (define-key emerge-basic-keymap "xc" 'emerge-combine-versions) + (define-key emerge-basic-keymap "xC" 'emerge-combine-versions-register) + (define-key emerge-basic-keymap "xf" 'emerge-file-names) + (define-key emerge-basic-keymap "xj" 'emerge-join-differences) + (define-key emerge-basic-keymap "xl" 'emerge-line-numbers) + (define-key emerge-basic-keymap "xm" 'emerge-set-merge-mode) + (define-key emerge-basic-keymap "xs" 'emerge-split-difference) + (define-key emerge-basic-keymap "xt" 'emerge-trim-difference) + (define-key emerge-basic-keymap "xx" 'emerge-set-combine-versions-template) + ;; Allow emerge-basic-keymap to be referenced indirectly + (fset 'emerge-basic-keymap emerge-basic-keymap) + ;; Set up the fast mode keymap + (setq emerge-fast-keymap (copy-keymap emerge-basic-keymap)) + ;; Allow prefixed commands to work in fast mode + (define-key emerge-fast-keymap emerge-command-prefix 'emerge-basic-keymap) + ;; Allow emerge-fast-keymap to be referenced indirectly + (fset 'emerge-fast-keymap emerge-fast-keymap) + ;; Suppress write-file and save-buffer + (define-key emerge-fast-keymap [remap write-file] 'emerge-query-write-file) + (define-key emerge-fast-keymap [remap save-buffer] 'emerge-query-save-buffer) + + (define-key emerge-basic-keymap [menu-bar] (make-sparse-keymap)) + + (define-key emerge-fast-keymap [menu-bar emerge-options] + (cons "Merge-Options" emerge-options-menu)) + (define-key emerge-fast-keymap [menu-bar merge] + (cons "Merge" emerge-merge-menu)) + (define-key emerge-fast-keymap [menu-bar move] + (cons "Move" emerge-move-menu)) + + (define-key emerge-move-menu [emerge-scroll-reset] + '("Scroll Reset" . emerge-scroll-reset)) + (define-key emerge-move-menu [emerge-scroll-right] + '("Scroll Right" . emerge-scroll-right)) + (define-key emerge-move-menu [emerge-scroll-left] + '("Scroll Left" . emerge-scroll-left)) + (define-key emerge-move-menu [emerge-scroll-down] + '("Scroll Down" . emerge-scroll-down)) + (define-key emerge-move-menu [emerge-scroll-up] + '("Scroll Up" . emerge-scroll-up)) + (define-key emerge-move-menu [emerge-recenter] + '("Recenter" . emerge-recenter)) + (define-key emerge-move-menu [emerge-mark-difference] + '("Mark Difference" . emerge-mark-difference)) + (define-key emerge-move-menu [emerge-jump-to-difference] + '("Jump To Difference" . emerge-jump-to-difference)) + (define-key emerge-move-menu [emerge-find-difference] + '("Find Difference" . emerge-find-difference)) + (define-key emerge-move-menu [emerge-previous-difference] + '("Previous Difference" . emerge-previous-difference)) + (define-key emerge-move-menu [emerge-next-difference] + '("Next Difference" . emerge-next-difference)) + + + (define-key emerge-options-menu [emerge-one-line-window] + '("One Line Window" . emerge-one-line-window)) + (define-key emerge-options-menu [emerge-set-merge-mode] + '("Set Merge Mode..." . emerge-set-merge-mode)) + (define-key emerge-options-menu [emerge-set-combine-template] + '("Set Combine Template..." . emerge-set-combine-template)) + (define-key emerge-options-menu [emerge-default-B] + '("Default B" . emerge-default-B)) + (define-key emerge-options-menu [emerge-default-A] + '("Default A" . emerge-default-A)) + (define-key emerge-options-menu [emerge-skip-prefers] + '(menu-item "Skip Prefers" emerge-skip-prefers + :button (:toggle . emerge-skip-prefers))) + (define-key emerge-options-menu [emerge-auto-advance] + '(menu-item "Auto Advance" emerge-auto-advance + :button (:toggle . emerge-auto-advance))) + (define-key emerge-options-menu [emerge-edit-mode] + '(menu-item "Edit Mode" emerge-edit-mode :enable (not emerge-edit-mode))) + (define-key emerge-options-menu [emerge-fast-mode] + '(menu-item "Fast Mode" emerge-fast-mode :enable (not emerge-fast-mode))) + + (define-key emerge-merge-menu [emerge-abort] '("Abort" . emerge-abort)) + (define-key emerge-merge-menu [emerge-quit] '("Quit" . emerge-quit)) + (define-key emerge-merge-menu [emerge-split-difference] + '("Split Difference" . emerge-split-difference)) + (define-key emerge-merge-menu [emerge-join-differences] + '("Join Differences" . emerge-join-differences)) + (define-key emerge-merge-menu [emerge-trim-difference] + '("Trim Difference" . emerge-trim-difference)) + (define-key emerge-merge-menu [emerge-combine-versions] + '("Combine Versions" . emerge-combine-versions)) + (define-key emerge-merge-menu [emerge-copy-as-kill-B] + '("Copy B as Kill" . emerge-copy-as-kill-B)) + (define-key emerge-merge-menu [emerge-copy-as-kill-A] + '("Copy A as Kill" . emerge-copy-as-kill-A)) + (define-key emerge-merge-menu [emerge-insert-B] + '("Insert B" . emerge-insert-B)) + (define-key emerge-merge-menu [emerge-insert-A] + '("Insert A" . emerge-insert-A)) + (define-key emerge-merge-menu [emerge-select-B] + '("Select B" . emerge-select-B)) + (define-key emerge-merge-menu [emerge-select-A] + '("Select A" . emerge-select-A))) + + +;; Variables which control each merge. They are local to the merge buffer. + +;; Mode variables +(emerge-defvar-local emerge-mode nil + "Indicator for emerge-mode.") +(emerge-defvar-local emerge-fast-mode nil + "Indicator for emerge-mode fast submode.") +(emerge-defvar-local emerge-edit-mode nil + "Indicator for emerge-mode edit submode.") +(emerge-defvar-local emerge-A-buffer nil + "The buffer in which the A variant is stored.") +(emerge-defvar-local emerge-B-buffer nil + "The buffer in which the B variant is stored.") +(emerge-defvar-local emerge-merge-buffer nil + "The buffer in which the merged file is manipulated.") +(emerge-defvar-local emerge-ancestor-buffer nil + "The buffer in which the ancestor variant is stored, +or nil if there is none.") + +(defconst emerge-saved-variables + '((buffer-modified-p set-buffer-modified-p) + buffer-read-only + buffer-auto-save-file-name) + "Variables and properties of a buffer which are saved, modified and restored +during a merge.") +(defconst emerge-merging-values '(nil t nil) + "Values to be assigned to emerge-saved-variables during a merge.") + +(emerge-defvar-local emerge-A-buffer-values nil + "Remembers emerge-saved-variables for emerge-A-buffer.") +(emerge-defvar-local emerge-B-buffer-values nil + "Remembers emerge-saved-variables for emerge-B-buffer.") + +(emerge-defvar-local emerge-difference-list nil + "Vector of differences between the variants, and markers in the buffers to +show where they are. Each difference is represented by a vector of seven +elements. The first two are markers to the beginning and end of the difference +section in the A buffer, the second two are markers for the B buffer, the third +two are markers for the merge buffer, and the last element is the \"state\" of +that difference in the merge buffer. + A section of a buffer is described by two markers, one to the beginning of +the first line of the section, and one to the beginning of the first line +after the section. (If the section is empty, both markers point to the same +point.) If the section is part of the selected difference, then the markers +are moved into the flags, so the user can edit the section without disturbing +the markers. + The \"states\" are: + A the merge buffer currently contains the A variant + B the merge buffer currently contains the B variant + default-A the merge buffer contains the A variant by default, + but this difference hasn't been selected yet, so + change-default commands can alter it + default-B the merge buffer contains the B variant by default, + but this difference hasn't been selected yet, so + change-default commands can alter it + prefer-A in a three-file merge, the A variant is the preferred + choice + prefer-B in a three-file merge, the B variant is the preferred + choice") +(emerge-defvar-local emerge-current-difference -1 + "The difference that is currently selected.") +(emerge-defvar-local emerge-number-of-differences nil + "Number of differences found.") +(emerge-defvar-local emerge-edit-keymap nil + "The local keymap for the merge buffer, with the emerge commands defined in +it. Used to save the local keymap during fast mode, when the local keymap is +replaced by emerge-fast-keymap.") +(emerge-defvar-local emerge-old-keymap nil + "The original local keymap for the merge buffer.") +(emerge-defvar-local emerge-auto-advance nil + "*If non-nil, emerge-select-A and emerge-select-B automatically advance to +the next difference.") +(emerge-defvar-local emerge-skip-prefers nil + "*If non-nil, differences for which there is a preference are automatically +skipped.") +(emerge-defvar-local emerge-quit-hook nil + "Hooks to run in the merge buffer after the merge has been finished. +`emerge-prefix-argument' will hold the prefix argument of the `emerge-quit' +command. +This is *not* a user option, since Emerge uses it for its own processing.") +(emerge-defvar-local emerge-output-description nil + "Describes output destination of emerge, for `emerge-file-names'.") + +;;; Setup functions for two-file mode. + +(defun emerge-files-internal (file-A file-B &optional startup-hooks quit-hooks + output-file) + (if (not (file-readable-p file-A)) + (error "File `%s' does not exist or is not readable" file-A)) + (if (not (file-readable-p file-B)) + (error "File `%s' does not exist or is not readable" file-B)) + (let ((buffer-A (find-file-noselect file-A)) + (buffer-B (find-file-noselect file-B))) + ;; Record the directories of the files + (setq emerge-last-dir-A (file-name-directory file-A)) + (setq emerge-last-dir-B (file-name-directory file-B)) + (if output-file + (setq emerge-last-dir-output (file-name-directory output-file))) + ;; Make sure the entire files are seen, and they reflect what is on disk + (emerge-eval-in-buffer + buffer-A + (widen) + (let ((temp (file-local-copy file-A))) + (if temp + (setq file-A temp + startup-hooks + (cons `(lambda () (delete-file ,file-A)) + startup-hooks)) + ;; Verify that the file matches the buffer + (emerge-verify-file-buffer)))) + (emerge-eval-in-buffer + buffer-B + (widen) + (let ((temp (file-local-copy file-B))) + (if temp + (setq file-B temp + startup-hooks + (cons `(lambda () (delete-file ,file-B)) + startup-hooks)) + ;; Verify that the file matches the buffer + (emerge-verify-file-buffer)))) + (emerge-setup buffer-A file-A buffer-B file-B startup-hooks quit-hooks + output-file))) + +;; Start up Emerge on two files +(defun emerge-setup (buffer-A file-A buffer-B file-B startup-hooks quit-hooks + output-file) + (setq file-A (expand-file-name file-A)) + (setq file-B (expand-file-name file-B)) + (setq output-file (and output-file (expand-file-name output-file))) + (let* ((merge-buffer-name (emerge-unique-buffer-name "*merge" "*")) + ;; create the merge buffer from buffer A, so it inherits buffer A's + ;; default directory, etc. + (merge-buffer (emerge-eval-in-buffer + buffer-A + (get-buffer-create merge-buffer-name)))) + (emerge-eval-in-buffer + merge-buffer + (emerge-copy-modes buffer-A) + (setq buffer-read-only nil) + (auto-save-mode 1) + (setq emerge-mode t) + (setq emerge-A-buffer buffer-A) + (setq emerge-B-buffer buffer-B) + (setq emerge-ancestor-buffer nil) + (setq emerge-merge-buffer merge-buffer) + (setq emerge-output-description + (if output-file + (concat "Output to file: " output-file) + (concat "Output to buffer: " (buffer-name merge-buffer)))) + (save-excursion (insert-buffer-substring emerge-A-buffer)) + (emerge-set-keys) + (setq emerge-difference-list (emerge-make-diff-list file-A file-B)) + (setq emerge-number-of-differences (length emerge-difference-list)) + (setq emerge-current-difference -1) + (setq emerge-quit-hook quit-hooks) + (emerge-remember-buffer-characteristics) + (emerge-handle-local-variables)) + (emerge-setup-windows buffer-A buffer-B merge-buffer t) + (emerge-eval-in-buffer merge-buffer + (run-hooks 'startup-hooks 'emerge-startup-hook) + (setq buffer-read-only t)))) + +;; Generate the Emerge difference list between two files +(defun emerge-make-diff-list (file-A file-B) + (setq emerge-diff-buffer (get-buffer-create "*emerge-diff*")) + (emerge-eval-in-buffer + emerge-diff-buffer + (erase-buffer) + (shell-command + (format "%s %s %s %s" + emerge-diff-program emerge-diff-options + (emerge-protect-metachars file-A) + (emerge-protect-metachars file-B)) + t)) + (emerge-prepare-error-list emerge-diff-ok-lines-regexp) + (emerge-convert-diffs-to-markers + emerge-A-buffer emerge-B-buffer emerge-merge-buffer + (emerge-extract-diffs emerge-diff-buffer))) + +(defun emerge-extract-diffs (diff-buffer) + (let (list) + (emerge-eval-in-buffer + diff-buffer + (goto-char (point-min)) + (while (re-search-forward emerge-match-diff-line nil t) + (let* ((a-begin (string-to-number (buffer-substring (match-beginning 1) + (match-end 1)))) + (a-end (let ((b (match-beginning 3)) + (e (match-end 3))) + (if b + (string-to-number (buffer-substring b e)) + a-begin))) + (diff-type (buffer-substring (match-beginning 4) (match-end 4))) + (b-begin (string-to-number (buffer-substring (match-beginning 5) + (match-end 5)))) + (b-end (let ((b (match-beginning 7)) + (e (match-end 7))) + (if b + (string-to-number (buffer-substring b e)) + b-begin)))) + ;; fix the beginning and end numbers, because diff is somewhat + ;; strange about how it numbers lines + (if (string-equal diff-type "a") + (progn + (setq b-end (1+ b-end)) + (setq a-begin (1+ a-begin)) + (setq a-end a-begin)) + (if (string-equal diff-type "d") + (progn + (setq a-end (1+ a-end)) + (setq b-begin (1+ b-begin)) + (setq b-end b-begin)) + ;; (string-equal diff-type "c") + (progn + (setq a-end (1+ a-end)) + (setq b-end (1+ b-end))))) + (setq list (cons (vector a-begin a-end + b-begin b-end + 'default-A) + list))))) + (nreverse list))) + +;; Set up buffer of diff/diff3 error messages. +(defun emerge-prepare-error-list (ok-regexp) + (setq emerge-diff-error-buffer (get-buffer-create "*emerge-diff-errors*")) + (emerge-eval-in-buffer + emerge-diff-error-buffer + (erase-buffer) + (save-excursion (insert-buffer-substring emerge-diff-buffer)) + (delete-matching-lines ok-regexp))) + +;;; Top-level and setup functions for three-file mode. + +(defun emerge-files-with-ancestor-internal (file-A file-B file-ancestor + &optional startup-hooks quit-hooks + output-file) + (if (not (file-readable-p file-A)) + (error "File `%s' does not exist or is not readable" file-A)) + (if (not (file-readable-p file-B)) + (error "File `%s' does not exist or is not readable" file-B)) + (if (not (file-readable-p file-ancestor)) + (error "File `%s' does not exist or is not readable" file-ancestor)) + (let ((buffer-A (find-file-noselect file-A)) + (buffer-B (find-file-noselect file-B)) + (buffer-ancestor (find-file-noselect file-ancestor))) + ;; Record the directories of the files + (setq emerge-last-dir-A (file-name-directory file-A)) + (setq emerge-last-dir-B (file-name-directory file-B)) + (setq emerge-last-dir-ancestor (file-name-directory file-ancestor)) + (if output-file + (setq emerge-last-dir-output (file-name-directory output-file))) + ;; Make sure the entire files are seen, and they reflect what is on disk + (emerge-eval-in-buffer + buffer-A + (widen) + (let ((temp (file-local-copy file-A))) + (if temp + (setq file-A temp + startup-hooks + (cons `(lambda () (delete-file ,file-A)) + startup-hooks)) + ;; Verify that the file matches the buffer + (emerge-verify-file-buffer)))) + (emerge-eval-in-buffer + buffer-B + (widen) + (let ((temp (file-local-copy file-B))) + (if temp + (setq file-B temp + startup-hooks + (cons `(lambda () (delete-file ,file-B)) + startup-hooks)) + ;; Verify that the file matches the buffer + (emerge-verify-file-buffer)))) + (emerge-eval-in-buffer + buffer-ancestor + (widen) + (let ((temp (file-local-copy file-ancestor))) + (if temp + (setq file-ancestor temp + startup-hooks + (cons `(lambda () (delete-file ,file-ancestor)) + startup-hooks)) + ;; Verify that the file matches the buffer + (emerge-verify-file-buffer)))) + (emerge-setup-with-ancestor buffer-A file-A buffer-B file-B + buffer-ancestor file-ancestor + startup-hooks quit-hooks output-file))) + +;; Start up Emerge on two files with an ancestor +(defun emerge-setup-with-ancestor (buffer-A file-A buffer-B file-B + buffer-ancestor file-ancestor + &optional startup-hooks quit-hooks + output-file) + (setq file-A (expand-file-name file-A)) + (setq file-B (expand-file-name file-B)) + (setq file-ancestor (expand-file-name file-ancestor)) + (setq output-file (and output-file (expand-file-name output-file))) + (let* ((merge-buffer-name (emerge-unique-buffer-name "*merge" "*")) + ;; create the merge buffer from buffer A, so it inherits buffer A's + ;; default directory, etc. + (merge-buffer (emerge-eval-in-buffer + buffer-A + (get-buffer-create merge-buffer-name)))) + (emerge-eval-in-buffer + merge-buffer + (emerge-copy-modes buffer-A) + (setq buffer-read-only nil) + (auto-save-mode 1) + (setq emerge-mode t) + (setq emerge-A-buffer buffer-A) + (setq emerge-B-buffer buffer-B) + (setq emerge-ancestor-buffer buffer-ancestor) + (setq emerge-merge-buffer merge-buffer) + (setq emerge-output-description + (if output-file + (concat "Output to file: " output-file) + (concat "Output to buffer: " (buffer-name merge-buffer)))) + (save-excursion (insert-buffer-substring emerge-A-buffer)) + (emerge-set-keys) + (setq emerge-difference-list + (emerge-make-diff3-list file-A file-B file-ancestor)) + (setq emerge-number-of-differences (length emerge-difference-list)) + (setq emerge-current-difference -1) + (setq emerge-quit-hook quit-hooks) + (emerge-remember-buffer-characteristics) + (emerge-select-prefer-Bs) + (emerge-handle-local-variables)) + (emerge-setup-windows buffer-A buffer-B merge-buffer t) + (emerge-eval-in-buffer merge-buffer + (run-hooks 'startup-hooks 'emerge-startup-hook) + (setq buffer-read-only t)))) + +;; Generate the Emerge difference list between two files with an ancestor +(defun emerge-make-diff3-list (file-A file-B file-ancestor) + (setq emerge-diff-buffer (get-buffer-create "*emerge-diff*")) + (emerge-eval-in-buffer + emerge-diff-buffer + (erase-buffer) + (shell-command + (format "%s %s %s %s %s" + emerge-diff3-program emerge-diff-options + (emerge-protect-metachars file-A) + (emerge-protect-metachars file-ancestor) + (emerge-protect-metachars file-B)) + t)) + (emerge-prepare-error-list emerge-diff3-ok-lines-regexp) + (emerge-convert-diffs-to-markers + emerge-A-buffer emerge-B-buffer emerge-merge-buffer + (emerge-extract-diffs3 emerge-diff-buffer))) + +(defun emerge-extract-diffs3 (diff-buffer) + (let (list) + (emerge-eval-in-buffer + diff-buffer + (while (re-search-forward "^====\\(.?\\)$" nil t) + ;; leave point after matched line + (beginning-of-line 2) + (let ((agreement (buffer-substring (match-beginning 1) (match-end 1)))) + ;; if the A and B files are the same, ignore the difference + (if (not (string-equal agreement "2")) + (setq list + (cons + (let (group-1 group-3 pos) + (setq pos (point)) + (setq group-1 (emerge-get-diff3-group "1")) + (goto-char pos) + (setq group-3 (emerge-get-diff3-group "3")) + (vector (car group-1) (car (cdr group-1)) + (car group-3) (car (cdr group-3)) + (cond ((string-equal agreement "1") 'prefer-A) + ((string-equal agreement "3") 'prefer-B) + (t 'default-A)))) + list)))))) + (nreverse list))) + +(defun emerge-get-diff3-group (file) + ;; This save-excursion allows emerge-get-diff3-group to be called for the + ;; various groups of lines (1, 2, 3) in any order, and for the lines to + ;; appear in any order. The reason this is necessary is that Gnu diff3 + ;; can produce the groups in the order 1, 2, 3 or 1, 3, 2. + (save-excursion + (re-search-forward + (concat "^" file ":\\([0-9]+\\)\\(,\\([0-9]+\\)\\)?\\([ac]\\)$")) + (beginning-of-line 2) + ;; treatment depends on whether it is an "a" group or a "c" group + (if (string-equal (buffer-substring (match-beginning 4) (match-end 4)) "c") + ;; it is a "c" group + (if (match-beginning 2) + ;; it has two numbers + (list (string-to-number + (buffer-substring (match-beginning 1) (match-end 1))) + (1+ (string-to-number + (buffer-substring (match-beginning 3) (match-end 3))))) + ;; it has one number + (let ((x (string-to-number + (buffer-substring (match-beginning 1) (match-end 1))))) + (list x (1+ x)))) + ;; it is an "a" group + (let ((x (1+ (string-to-number + (buffer-substring (match-beginning 1) (match-end 1)))))) + (list x x))))) + +;;; Functions to start Emerge on files + +;;;###autoload +(defun emerge-files (arg file-A file-B file-out &optional startup-hooks + quit-hooks) + "Run Emerge on two files." + (interactive + (let (f) + (list current-prefix-arg + (setq f (emerge-read-file-name "File A to merge" emerge-last-dir-A + nil nil t)) + (emerge-read-file-name "File B to merge" emerge-last-dir-B nil f t) + (and current-prefix-arg + (emerge-read-file-name "Output file" emerge-last-dir-output + f f nil))))) + (if file-out + (add-hook 'quit-hooks `(lambda () (emerge-files-exit ,file-out)))) + (emerge-files-internal + file-A file-B startup-hooks + quit-hooks + file-out)) + +;;;###autoload +(defun emerge-files-with-ancestor (arg file-A file-B file-ancestor file-out + &optional startup-hooks quit-hooks) + "Run Emerge on two files, giving another file as the ancestor." + (interactive + (let (f) + (list current-prefix-arg + (setq f (emerge-read-file-name "File A to merge" emerge-last-dir-A + nil nil t)) + (emerge-read-file-name "File B to merge" emerge-last-dir-B nil f t) + (emerge-read-file-name "Ancestor file" emerge-last-dir-ancestor + nil f t) + (and current-prefix-arg + (emerge-read-file-name "Output file" emerge-last-dir-output + f f nil))))) + (if file-out + (add-hook 'quit-hooks `(lambda () (emerge-files-exit ,file-out)))) + (emerge-files-with-ancestor-internal + file-A file-B file-ancestor startup-hooks + quit-hooks + file-out)) + +;; Write the merge buffer out in place of the file the A buffer is visiting. +(defun emerge-files-exit (file-out) + ;; if merge was successful was given, save to disk + (if (not emerge-prefix-argument) + (emerge-write-and-delete file-out))) + +;;; Functions to start Emerge on buffers + +;;;###autoload +(defun emerge-buffers (buffer-A buffer-B &optional startup-hooks quit-hooks) + "Run Emerge on two buffers." + (interactive "bBuffer A to merge: \nbBuffer B to merge: ") + (let ((emerge-file-A (emerge-make-temp-file "A")) + (emerge-file-B (emerge-make-temp-file "B"))) + (emerge-eval-in-buffer + buffer-A + (write-region (point-min) (point-max) emerge-file-A nil 'no-message)) + (emerge-eval-in-buffer + buffer-B + (write-region (point-min) (point-max) emerge-file-B nil 'no-message)) + (emerge-setup (get-buffer buffer-A) emerge-file-A + (get-buffer buffer-B) emerge-file-B + (cons `(lambda () + (delete-file ,emerge-file-A) + (delete-file ,emerge-file-B)) + startup-hooks) + quit-hooks + nil))) + +;;;###autoload +(defun emerge-buffers-with-ancestor (buffer-A buffer-B buffer-ancestor + &optional startup-hooks + quit-hooks) + "Run Emerge on two buffers, giving another buffer as the ancestor." + (interactive + "bBuffer A to merge: \nbBuffer B to merge: \nbAncestor buffer: ") + (let ((emerge-file-A (emerge-make-temp-file "A")) + (emerge-file-B (emerge-make-temp-file "B")) + (emerge-file-ancestor (emerge-make-temp-file "anc"))) + (emerge-eval-in-buffer + buffer-A + (write-region (point-min) (point-max) emerge-file-A nil 'no-message)) + (emerge-eval-in-buffer + buffer-B + (write-region (point-min) (point-max) emerge-file-B nil 'no-message)) + (emerge-eval-in-buffer + buffer-ancestor + (write-region (point-min) (point-max) emerge-file-ancestor nil + 'no-message)) + (emerge-setup-with-ancestor (get-buffer buffer-A) emerge-file-A + (get-buffer buffer-B) emerge-file-B + (get-buffer buffer-ancestor) + emerge-file-ancestor + (cons `(lambda () + (delete-file ,emerge-file-A) + (delete-file ,emerge-file-B) + (delete-file + ,emerge-file-ancestor)) + startup-hooks) + quit-hooks + nil))) + +;;; Functions to start Emerge from the command line + +;;;###autoload +(defun emerge-files-command () + (let ((file-a (nth 0 command-line-args-left)) + (file-b (nth 1 command-line-args-left)) + (file-out (nth 2 command-line-args-left))) + (setq command-line-args-left (nthcdr 3 command-line-args-left)) + (emerge-files-internal + file-a file-b nil + (list `(lambda () (emerge-command-exit ,file-out)))))) + +;;;###autoload +(defun emerge-files-with-ancestor-command () + (let (file-a file-b file-anc file-out) + ;; check for a -a flag, for filemerge compatibility + (if (string= (car command-line-args-left) "-a") + ;; arguments are "-a ancestor file-a file-b file-out" + (progn + (setq file-a (nth 2 command-line-args-left)) + (setq file-b (nth 3 command-line-args-left)) + (setq file-anc (nth 1 command-line-args-left)) + (setq file-out (nth 4 command-line-args-left)) + (setq command-line-args-left (nthcdr 5 command-line-args-left))) + ;; arguments are "file-a file-b ancestor file-out" + (setq file-a (nth 0 command-line-args-left)) + (setq file-b (nth 1 command-line-args-left)) + (setq file-anc (nth 2 command-line-args-left)) + (setq file-out (nth 3 command-line-args-left)) + (setq command-line-args-left (nthcdr 4 command-line-args-left))) + (emerge-files-with-ancestor-internal + file-a file-b file-anc nil + (list `(lambda () (emerge-command-exit ,file-out)))))) + +(defun emerge-command-exit (file-out) + (emerge-write-and-delete file-out) + (kill-emacs (if emerge-prefix-argument 1 0))) + +;;; Functions to start Emerge via remote request + +;;;###autoload +(defun emerge-files-remote (file-a file-b file-out) + (setq emerge-file-out file-out) + (emerge-files-internal + file-a file-b nil + (list `(lambda () (emerge-remote-exit ,file-out ',emerge-exit-func))) + file-out) + (throw 'client-wait nil)) + +;;;###autoload +(defun emerge-files-with-ancestor-remote (file-a file-b file-anc file-out) + (setq emerge-file-out file-out) + (emerge-files-with-ancestor-internal + file-a file-b file-anc nil + (list `(lambda () (emerge-remote-exit ,file-out ',emerge-exit-func))) + file-out) + (throw 'client-wait nil)) + +(defun emerge-remote-exit (file-out emerge-exit-func) + (emerge-write-and-delete file-out) + (kill-buffer emerge-merge-buffer) + (funcall emerge-exit-func (if emerge-prefix-argument 1 0))) + +;;; Functions to start Emerge on RCS versions + +;;;###autoload +(defun emerge-revisions (arg file revision-A revision-B + &optional startup-hooks quit-hooks) + "Emerge two RCS revisions of a file." + (interactive + (list current-prefix-arg + (read-file-name "File to merge: " nil nil 'confirm) + (read-string "Revision A to merge: " emerge-last-revision-A) + (read-string "Revision B to merge: " emerge-last-revision-B))) + (setq emerge-last-revision-A revision-A + emerge-last-revision-B revision-B) + (emerge-revisions-internal + file revision-A revision-B startup-hooks + (if arg + (cons `(lambda () + (shell-command + ,(format "%s %s" emerge-rcs-ci-program file))) + quit-hooks) + quit-hooks))) + +;;;###autoload +(defun emerge-revisions-with-ancestor (arg file revision-A + revision-B ancestor + &optional + startup-hooks quit-hooks) + "Emerge two RCS revisions of a file, with another revision as ancestor." + (interactive + (list current-prefix-arg + (read-file-name "File to merge: " nil nil 'confirm) + (read-string "Revision A to merge: " emerge-last-revision-A) + (read-string "Revision B to merge: " emerge-last-revision-B) + (read-string "Ancestor: " emerge-last-revision-ancestor))) + (setq emerge-last-revision-A revision-A + emerge-last-revision-B revision-B + emerge-last-revision-ancestor ancestor) + (emerge-revision-with-ancestor-internal + file revision-A revision-B ancestor startup-hooks + (if arg + (let ((cmd )) + (cons `(lambda () + (shell-command + ,(format "%s %s" emerge-rcs-ci-program file))) + quit-hooks)) + quit-hooks))) + +(defun emerge-revisions-internal (file revision-A revision-B &optional + startup-hooks quit-hooks output-file) + (let ((buffer-A (get-buffer-create (format "%s,%s" file revision-A))) + (buffer-B (get-buffer-create (format "%s,%s" file revision-B))) + (emerge-file-A (emerge-make-temp-file "A")) + (emerge-file-B (emerge-make-temp-file "B"))) + ;; Get the revisions into buffers + (emerge-eval-in-buffer + buffer-A + (erase-buffer) + (shell-command + (format "%s -q -p%s %s" emerge-rcs-co-program revision-A file) + t) + (write-region (point-min) (point-max) emerge-file-A nil 'no-message) + (set-buffer-modified-p nil)) + (emerge-eval-in-buffer + buffer-B + (erase-buffer) + (shell-command + (format "%s -q -p%s %s" emerge-rcs-co-program revision-B file) + t) + (write-region (point-min) (point-max) emerge-file-B nil 'no-message) + (set-buffer-modified-p nil)) + ;; Do the merge + (emerge-setup buffer-A emerge-file-A + buffer-B emerge-file-B + (cons `(lambda () + (delete-file ,emerge-file-A) + (delete-file ,emerge-file-B)) + startup-hooks) + (cons `(lambda () (emerge-files-exit ,file)) + quit-hooks) + nil))) + +(defun emerge-revision-with-ancestor-internal (file revision-A revision-B + ancestor + &optional startup-hooks + quit-hooks output-file) + (let ((buffer-A (get-buffer-create (format "%s,%s" file revision-A))) + (buffer-B (get-buffer-create (format "%s,%s" file revision-B))) + (buffer-ancestor (get-buffer-create (format "%s,%s" file ancestor))) + (emerge-file-A (emerge-make-temp-file "A")) + (emerge-file-B (emerge-make-temp-file "B")) + (emerge-ancestor (emerge-make-temp-file "ancestor"))) + ;; Get the revisions into buffers + (emerge-eval-in-buffer + buffer-A + (erase-buffer) + (shell-command + (format "%s -q -p%s %s" emerge-rcs-co-program + revision-A file) + t) + (write-region (point-min) (point-max) emerge-file-A nil 'no-message) + (set-buffer-modified-p nil)) + (emerge-eval-in-buffer + buffer-B + (erase-buffer) + (shell-command + (format "%s -q -p%s %s" emerge-rcs-co-program revision-B file) + t) + (write-region (point-min) (point-max) emerge-file-B nil 'no-message) + (set-buffer-modified-p nil)) + (emerge-eval-in-buffer + buffer-ancestor + (erase-buffer) + (shell-command + (format "%s -q -p%s %s" emerge-rcs-co-program ancestor file) + t) + (write-region (point-min) (point-max) emerge-ancestor nil 'no-message) + (set-buffer-modified-p nil)) + ;; Do the merge + (emerge-setup-with-ancestor + buffer-A emerge-file-A buffer-B emerge-file-B + buffer-ancestor emerge-ancestor + (cons `(lambda () + (delete-file ,emerge-file-A) + (delete-file ,emerge-file-B) + (delete-file ,emerge-ancestor)) + startup-hooks) + (cons `(lambda () (emerge-files-exit ,file)) + quit-hooks) + output-file))) + +;;; Function to start Emerge based on a line in a file + +(defun emerge-execute-line () + "Run Emerge using files named in current text line. +Looks in that line for whitespace-separated entries of these forms: + a=file1 + b=file2 + ancestor=file3 + output=file4 +to specify the files to use in Emerge. + +In addition, if only one of `a=file' or `b=file' is present, and `output=file' +is present: +If `emerge-execute-line-deletions' is non-nil and `ancestor=file' is present, +it is assumed that the file in question has been deleted, and it is +not copied to the output file. +Otherwise, the A or B file present is copied to the output file." + (interactive) + (let (file-A file-B file-ancestor file-out + (case-fold-search t)) + ;; Stop if at end of buffer (even though we might be in a line, if + ;; the line does not end with newline) + (if (eobp) + (error "At end of buffer")) + ;; Go to the beginning of the line + (beginning-of-line) + ;; Skip any initial whitespace + (if (looking-at "[ \t]*") + (goto-char (match-end 0))) + ;; Process the entire line + (while (not (eolp)) + ;; Get the next entry + (if (looking-at "\\([a-z]+\\)=\\([^ \t\n]+\\)[ \t]*") + ;; Break apart the tab (before =) and the filename (after =) + (let ((tag (downcase + (buffer-substring (match-beginning 1) (match-end 1)))) + (file (buffer-substring (match-beginning 2) (match-end 2)))) + ;; Move point after the entry + (goto-char (match-end 0)) + ;; Store the filename in the right variable + (cond + ((string-equal tag "a") + (if file-A + (error "This line has two `A' entries")) + (setq file-A file)) + ((string-equal tag "b") + (if file-B + (error "This line has two `B' entries")) + (setq file-B file)) + ((or (string-equal tag "anc") (string-equal tag "ancestor")) + (if file-ancestor + (error "This line has two `ancestor' entries")) + (setq file-ancestor file)) + ((or (string-equal tag "out") (string-equal tag "output")) + (if file-out + (error "This line has two `output' entries")) + (setq file-out file)) + (t + (error "Unrecognized entry")))) + ;; If the match on the entry pattern failed + (error "Unparsable entry"))) + ;; Make sure that file-A and file-B are present + (if (not (or (and file-A file-B) file-out)) + (error "Must have both `A' and `B' entries")) + (if (not (or file-A file-B)) + (error "Must have `A' or `B' entry")) + ;; Go to the beginning of the next line, so next execution will use + ;; next line in buffer. + (beginning-of-line 2) + ;; Execute the correct command + (cond + ;; Merge of two files with ancestor + ((and file-A file-B file-ancestor) + (message "Merging %s and %s..." file-A file-B) + (emerge-files-with-ancestor (not (not file-out)) file-A file-B + file-ancestor file-out + nil + ;; When done, return to this buffer. + (list + `(lambda () + (switch-to-buffer ,(current-buffer)) + (message "Merge done."))))) + ;; Merge of two files without ancestor + ((and file-A file-B) + (message "Merging %s and %s..." file-A file-B) + (emerge-files (not (not file-out)) file-A file-B file-out + nil + ;; When done, return to this buffer. + (list + `(lambda () + (switch-to-buffer ,(current-buffer)) + (message "Merge done."))))) + ;; There is an output file (or there would have been an error above), + ;; but only one input file. + ;; The file appears to have been deleted in one version; do nothing. + ((and file-ancestor emerge-execute-line-deletions) + (message "No action.")) + ;; The file should be copied from the version that contains it + (t (let ((input-file (or file-A file-B))) + (message "Copying...") + (copy-file input-file file-out) + (message "%s copied to %s." input-file file-out)))))) + +;;; Sample function for creating information for emerge-execute-line + +(defcustom emerge-merge-directories-filename-regexp "[^.]" + "Regexp describing files to be processed by `emerge-merge-directories'." + :type 'regexp + :group 'emerge) + +;;;###autoload +(defun emerge-merge-directories (a-dir b-dir ancestor-dir output-dir) + (interactive + (list + (read-file-name "A directory: " nil nil 'confirm) + (read-file-name "B directory: " nil nil 'confirm) + (read-file-name "Ancestor directory (null for none): " nil nil 'confirm) + (read-file-name "Output directory (null for none): " nil nil 'confirm))) + ;; Check that we're not on a line + (if (not (and (bolp) (eolp))) + (error "There is text on this line")) + ;; Turn null strings into nil to indicate directories not used. + (if (and ancestor-dir (string-equal ancestor-dir "")) + (setq ancestor-dir nil)) + (if (and output-dir (string-equal output-dir "")) + (setq output-dir nil)) + ;; Canonicalize the directory names + (setq a-dir (expand-file-name a-dir)) + (if (not (string-equal (substring a-dir -1) "/")) + (setq a-dir (concat a-dir "/"))) + (setq b-dir (expand-file-name b-dir)) + (if (not (string-equal (substring b-dir -1) "/")) + (setq b-dir (concat b-dir "/"))) + (if ancestor-dir + (progn + (setq ancestor-dir (expand-file-name ancestor-dir)) + (if (not (string-equal (substring ancestor-dir -1) "/")) + (setq ancestor-dir (concat ancestor-dir "/"))))) + (if output-dir + (progn + (setq output-dir (expand-file-name output-dir)) + (if (not (string-equal (substring output-dir -1) "/")) + (setq output-dir (concat output-dir "/"))))) + ;; Set the mark to where we start + (push-mark) + ;; Find out what files are in the directories. + (let* ((a-dir-files + (directory-files a-dir nil emerge-merge-directories-filename-regexp)) + (b-dir-files + (directory-files b-dir nil emerge-merge-directories-filename-regexp)) + (ancestor-dir-files + (and ancestor-dir + (directory-files ancestor-dir nil + emerge-merge-directories-filename-regexp))) + (all-files (sort (nconc (copy-sequence a-dir-files) + (copy-sequence b-dir-files) + (copy-sequence ancestor-dir-files)) + (function string-lessp)))) + ;; Remove duplicates from all-files. + (let ((p all-files)) + (while p + (if (and (cdr p) (string-equal (car p) (car (cdr p)))) + (setcdr p (cdr (cdr p))) + (setq p (cdr p))))) + ;; Generate the control lines for the various files. + (while all-files + (let ((f (car all-files))) + (setq all-files (cdr all-files)) + (if (and a-dir-files (string-equal (car a-dir-files) f)) + (progn + (insert "A=" a-dir f "\t") + (setq a-dir-files (cdr a-dir-files)))) + (if (and b-dir-files (string-equal (car b-dir-files) f)) + (progn + (insert "B=" b-dir f "\t") + (setq b-dir-files (cdr b-dir-files)))) + (if (and ancestor-dir-files (string-equal (car ancestor-dir-files) f)) + (progn + (insert "ancestor=" ancestor-dir f "\t") + (setq ancestor-dir-files (cdr ancestor-dir-files)))) + (if output-dir + (insert "output=" output-dir f "\t")) + (backward-delete-char 1) + (insert "\n"))))) + +;;; Common setup routines + +;; Set up the window configuration. If POS is given, set the points to +;; the beginnings of the buffers. +(defun emerge-setup-windows (buffer-A buffer-B merge-buffer &optional pos) + ;; Make sure we are not in the minibuffer window when we try to delete + ;; all other windows. + (if (eq (selected-window) (minibuffer-window)) + (other-window 1)) + (delete-other-windows) + (switch-to-buffer merge-buffer) + (emerge-refresh-mode-line) + (split-window-vertically) + (split-window-horizontally) + (switch-to-buffer buffer-A) + (if pos + (goto-char (point-min))) + (other-window 1) + (switch-to-buffer buffer-B) + (if pos + (goto-char (point-min))) + (other-window 1) + (if pos + (goto-char (point-min))) + ;; If diff/diff3 reports errors, display them rather than the merge buffer. + (if (/= 0 (emerge-eval-in-buffer emerge-diff-error-buffer (buffer-size))) + (progn + (ding) + (message "Errors found in diff/diff3 output. Merge buffer is %s." + (buffer-name emerge-merge-buffer)) + (switch-to-buffer emerge-diff-error-buffer)))) + +;; Set up the keymap in the merge buffer +(defun emerge-set-keys () + ;; Set up fixed keymaps if necessary + (if (not emerge-basic-keymap) + (emerge-setup-fixed-keymaps)) + ;; Save the old local map + (setq emerge-old-keymap (current-local-map)) + ;; Construct the edit keymap + (setq emerge-edit-keymap (if emerge-old-keymap + (copy-keymap emerge-old-keymap) + (make-sparse-keymap))) + ;; Install the Emerge commands + (emerge-force-define-key emerge-edit-keymap emerge-command-prefix + 'emerge-basic-keymap) + (define-key emerge-edit-keymap [menu-bar] (make-sparse-keymap)) + + ;; Create the additional menu bar items. + (define-key emerge-edit-keymap [menu-bar emerge-options] + (cons "Merge-Options" emerge-options-menu)) + (define-key emerge-edit-keymap [menu-bar merge] + (cons "Merge" emerge-merge-menu)) + (define-key emerge-edit-keymap [menu-bar move] + (cons "Move" emerge-move-menu)) + + ;; Suppress write-file and save-buffer + (substitute-key-definition 'write-file + 'emerge-query-write-file + emerge-edit-keymap) + (substitute-key-definition 'save-buffer + 'emerge-query-save-buffer + emerge-edit-keymap) + (define-key emerge-edit-keymap [remap write-file] 'emerge-query-write-file) + (define-key emerge-edit-keymap [remap save-buffer] 'emerge-query-save-buffer) + (use-local-map emerge-fast-keymap) + (setq emerge-edit-mode nil) + (setq emerge-fast-mode t)) + +(defun emerge-remember-buffer-characteristics () + "Record certain properties of the buffers being merged. +Must be called in the merge buffer. Remembers read-only, modified, +auto-save, and saves them in buffer local variables. Sets the buffers +read-only and turns off `auto-save-mode'. +These characteristics are restored by `emerge-restore-buffer-characteristics'." + ;; force auto-save, because we will turn off auto-saving in buffers for the + ;; duration + (do-auto-save) + ;; remember and alter buffer characteristics + (setq emerge-A-buffer-values + (emerge-eval-in-buffer + emerge-A-buffer + (prog1 + (emerge-save-variables emerge-saved-variables) + (emerge-restore-variables emerge-saved-variables + emerge-merging-values)))) + (setq emerge-B-buffer-values + (emerge-eval-in-buffer + emerge-B-buffer + (prog1 + (emerge-save-variables emerge-saved-variables) + (emerge-restore-variables emerge-saved-variables + emerge-merging-values))))) + +(defun emerge-restore-buffer-characteristics () + "Restore characteristics saved by `emerge-remember-buffer-characteristics'." + (let ((A-values emerge-A-buffer-values) + (B-values emerge-B-buffer-values)) + (emerge-eval-in-buffer emerge-A-buffer + (emerge-restore-variables emerge-saved-variables + A-values)) + (emerge-eval-in-buffer emerge-B-buffer + (emerge-restore-variables emerge-saved-variables + B-values)))) + +;; Move to line DESIRED-LINE assuming we are at line CURRENT-LINE. +;; Return DESIRED-LINE. +(defun emerge-goto-line (desired-line current-line) + (forward-line (- desired-line current-line)) + desired-line) + +(defun emerge-convert-diffs-to-markers (A-buffer + B-buffer + merge-buffer + lineno-list) + (let* (marker-list + (A-point-min (emerge-eval-in-buffer A-buffer (point-min))) + (offset (1- A-point-min)) + (B-point-min (emerge-eval-in-buffer B-buffer (point-min))) + ;; Record current line number in each buffer + ;; so we don't have to count from the beginning. + (a-line 1) + (b-line 1)) + (emerge-eval-in-buffer A-buffer (goto-char (point-min))) + (emerge-eval-in-buffer B-buffer (goto-char (point-min))) + (while lineno-list + (let* ((list-element (car lineno-list)) + a-begin-marker + a-end-marker + b-begin-marker + b-end-marker + merge-begin-marker + merge-end-marker + (a-begin (aref list-element 0)) + (a-end (aref list-element 1)) + (b-begin (aref list-element 2)) + (b-end (aref list-element 3)) + (state (aref list-element 4))) + ;; place markers at the appropriate places in the buffers + (emerge-eval-in-buffer + A-buffer + (setq a-line (emerge-goto-line a-begin a-line)) + (setq a-begin-marker (point-marker)) + (setq a-line (emerge-goto-line a-end a-line)) + (setq a-end-marker (point-marker))) + (emerge-eval-in-buffer + B-buffer + (setq b-line (emerge-goto-line b-begin b-line)) + (setq b-begin-marker (point-marker)) + (setq b-line (emerge-goto-line b-end b-line)) + (setq b-end-marker (point-marker))) + (setq merge-begin-marker (set-marker + (make-marker) + (- (marker-position a-begin-marker) + offset) + merge-buffer)) + (setq merge-end-marker (set-marker + (make-marker) + (- (marker-position a-end-marker) + offset) + merge-buffer)) + ;; record all the markers for this difference + (setq marker-list (cons (vector a-begin-marker a-end-marker + b-begin-marker b-end-marker + merge-begin-marker merge-end-marker + state) + marker-list))) + (setq lineno-list (cdr lineno-list))) + ;; convert the list of difference information into a vector for + ;; fast access + (setq emerge-difference-list (apply 'vector (nreverse marker-list))))) + +;; If we have an ancestor, select all B variants that we prefer +(defun emerge-select-prefer-Bs () + (let ((n 0)) + (while (< n emerge-number-of-differences) + (if (eq (aref (aref emerge-difference-list n) 6) 'prefer-B) + (progn + (emerge-unselect-and-select-difference n t) + (emerge-select-B) + (aset (aref emerge-difference-list n) 6 'prefer-B))) + (setq n (1+ n)))) + (emerge-unselect-and-select-difference -1)) + +;; Process the local-variables list at the end of the merged file, if +;; requested. +(defun emerge-handle-local-variables () + (if emerge-process-local-variables + (condition-case err + (hack-local-variables) + (error (message "Local-variables error in merge buffer: %s" + (prin1-to-string err)))))) + +;;; Common exit routines + +(defun emerge-write-and-delete (file-out) + ;; clear screen format + (delete-other-windows) + ;; delete A, B, and ancestor buffers, if they haven't been changed + (if (not (buffer-modified-p emerge-A-buffer)) + (kill-buffer emerge-A-buffer)) + (if (not (buffer-modified-p emerge-B-buffer)) + (kill-buffer emerge-B-buffer)) + (if (and emerge-ancestor-buffer + (not (buffer-modified-p emerge-ancestor-buffer))) + (kill-buffer emerge-ancestor-buffer)) + ;; Write merge buffer to file + (and file-out + (write-file file-out))) + +;;; Commands + +(defun emerge-recenter (&optional arg) + "Bring the highlighted region of all three merge buffers into view. +This brings the buffers into view if they are in windows. +With an argument, reestablish the default three-window display." + (interactive "P") + ;; If there is an argument, rebuild the window structure + (if arg + (emerge-setup-windows emerge-A-buffer emerge-B-buffer + emerge-merge-buffer)) + ;; Redisplay whatever buffers are showing, if there is a selected difference + (if (and (>= emerge-current-difference 0) + (< emerge-current-difference emerge-number-of-differences)) + (let* ((merge-buffer emerge-merge-buffer) + (buffer-A emerge-A-buffer) + (buffer-B emerge-B-buffer) + (window-A (get-buffer-window buffer-A 'visible)) + (window-B (get-buffer-window buffer-B 'visible)) + (merge-window (get-buffer-window merge-buffer)) + (diff-vector + (aref emerge-difference-list emerge-current-difference))) + (if window-A (progn + (select-window window-A) + (emerge-position-region + (- (aref diff-vector 0) + (1- emerge-before-flag-length)) + (+ (aref diff-vector 1) + (1- emerge-after-flag-length)) + (1+ (aref diff-vector 0))))) + (if window-B (progn + (select-window window-B) + (emerge-position-region + (- (aref diff-vector 2) + (1- emerge-before-flag-length)) + (+ (aref diff-vector 3) + (1- emerge-after-flag-length)) + (1+ (aref diff-vector 2))))) + (if merge-window (progn + (select-window merge-window) + (emerge-position-region + (- (aref diff-vector 4) + (1- emerge-before-flag-length)) + (+ (aref diff-vector 5) + (1- emerge-after-flag-length)) + (1+ (aref diff-vector 4)))))))) + +;;; Window scrolling operations +;; These operations are designed to scroll all three windows the same amount, +;; so as to keep the text in them aligned. + +;; Perform some operation on all three windows (if they are showing). +;; Catches all errors on the operation in the A and B windows, but not +;; in the merge window. Usually, errors come from scrolling off the +;; beginning or end of the buffer, and this gives a nice error message: +;; End of buffer is reported in the merge buffer, but if the scroll was +;; possible in the A or B windows, it is performed there before the error +;; is reported. +(defun emerge-operate-on-windows (operation arg) + (let* ((merge-buffer emerge-merge-buffer) + (buffer-A emerge-A-buffer) + (buffer-B emerge-B-buffer) + (window-A (get-buffer-window buffer-A 'visible)) + (window-B (get-buffer-window buffer-B 'visible)) + (merge-window (get-buffer-window merge-buffer))) + (if window-A (progn + (select-window window-A) + (condition-case nil + (funcall operation arg) + (error)))) + (if window-B (progn + (select-window window-B) + (condition-case nil + (funcall operation arg) + (error)))) + (if merge-window (progn + (select-window merge-window) + (funcall operation arg))))) + +(defun emerge-scroll-up (&optional arg) + "Scroll up all three merge buffers, if they are in windows. +With argument N, scroll N lines; otherwise scroll by nearly +the height of the merge window. +`C-u -' alone as argument scrolls half the height of the merge window." + (interactive "P") + (emerge-operate-on-windows + 'scroll-up + ;; calculate argument to scroll-up + ;; if there is an explicit argument + (if (and arg (not (equal arg '-))) + ;; use it + (prefix-numeric-value arg) + ;; if not, see if we can determine a default amount (the window height) + (let ((merge-window (get-buffer-window emerge-merge-buffer))) + (if (null merge-window) + ;; no window, use nil + nil + (let ((default-amount + (- (window-height merge-window) 1 next-screen-context-lines))) + ;; the window was found + (if arg + ;; C-u as argument means half of default amount + (/ default-amount 2) + ;; no argument means default amount + default-amount))))))) + +(defun emerge-scroll-down (&optional arg) + "Scroll down all three merge buffers, if they are in windows. +With argument N, scroll N lines; otherwise scroll by nearly +the height of the merge window. +`C-u -' alone as argument scrolls half the height of the merge window." + (interactive "P") + (emerge-operate-on-windows + 'scroll-down + ;; calculate argument to scroll-down + ;; if there is an explicit argument + (if (and arg (not (equal arg '-))) + ;; use it + (prefix-numeric-value arg) + ;; if not, see if we can determine a default amount (the window height) + (let ((merge-window (get-buffer-window emerge-merge-buffer))) + (if (null merge-window) + ;; no window, use nil + nil + (let ((default-amount + (- (window-height merge-window) 1 next-screen-context-lines))) + ;; the window was found + (if arg + ;; C-u as argument means half of default amount + (/ default-amount 2) + ;; no argument means default amount + default-amount))))))) + +(defun emerge-scroll-left (&optional arg) + "Scroll left all three merge buffers, if they are in windows. +If an argument is given, that is how many columns are scrolled, else nearly +the width of the A and B windows. `C-u -' alone as argument scrolls half the +width of the A and B windows." + (interactive "P") + (emerge-operate-on-windows + 'scroll-left + ;; calculate argument to scroll-left + ;; if there is an explicit argument + (if (and arg (not (equal arg '-))) + ;; use it + (prefix-numeric-value arg) + ;; if not, see if we can determine a default amount + ;; (half the window width) + (let ((merge-window (get-buffer-window emerge-merge-buffer))) + (if (null merge-window) + ;; no window, use nil + nil + (let ((default-amount + (- (/ (window-width merge-window) 2) 3))) + ;; the window was found + (if arg + ;; C-u as argument means half of default amount + (/ default-amount 2) + ;; no argument means default amount + default-amount))))))) + +(defun emerge-scroll-right (&optional arg) + "Scroll right all three merge buffers, if they are in windows. +If an argument is given, that is how many columns are scrolled, else nearly +the width of the A and B windows. `C-u -' alone as argument scrolls half the +width of the A and B windows." + (interactive "P") + (emerge-operate-on-windows + 'scroll-right + ;; calculate argument to scroll-right + ;; if there is an explicit argument + (if (and arg (not (equal arg '-))) + ;; use it + (prefix-numeric-value arg) + ;; if not, see if we can determine a default amount + ;; (half the window width) + (let ((merge-window (get-buffer-window emerge-merge-buffer))) + (if (null merge-window) + ;; no window, use nil + nil + (let ((default-amount + (- (/ (window-width merge-window) 2) 3))) + ;; the window was found + (if arg + ;; C-u as argument means half of default amount + (/ default-amount 2) + ;; no argument means default amount + default-amount))))))) + +(defun emerge-scroll-reset () + "Reset horizontal scrolling in Emerge. +This resets the horizontal scrolling of all three merge buffers +to the left margin, if they are in windows." + (interactive) + (emerge-operate-on-windows + (function (lambda (x) (set-window-hscroll (selected-window) 0))) + nil)) + +;; Attempt to show the region nicely. +;; If there are min-lines lines above and below the region, then don't do +;; anything. +;; If not, recenter the region to make it so. +;; If that isn't possible, remove context lines balancedly from top and bottom +;; so the entire region shows. +;; If that isn't possible, show the top of the region. +;; BEG must be at the beginning of a line. +(defun emerge-position-region (beg end pos) + ;; First test whether the entire region is visible with + ;; emerge-min-visible-lines above and below it + (if (not (and (<= (progn + (move-to-window-line emerge-min-visible-lines) + (point)) + beg) + (<= end (progn + (move-to-window-line + (- (1+ emerge-min-visible-lines))) + (point))))) + ;; We failed that test, see if it fits at all + ;; Meanwhile positioning it correctly in case it doesn't fit + (progn + (set-window-start (selected-window) beg) + (if (pos-visible-in-window-p end) + ;; Determine the number of lines that the region occupies + (let ((lines 0)) + (while (> end (progn + (move-to-window-line lines) + (point))) + (setq lines (1+ lines))) + ;; And position the beginning on the right line + (goto-char beg) + (recenter (/ (1+ (- (1- (window-height (selected-window))) + lines)) + 2)))))) + (goto-char pos)) + +(defun emerge-next-difference () + "Advance to the next difference." + (interactive) + (if (< emerge-current-difference emerge-number-of-differences) + (let ((n (1+ emerge-current-difference))) + (while (and emerge-skip-prefers + (< n emerge-number-of-differences) + (memq (aref (aref emerge-difference-list n) 6) + '(prefer-A prefer-B))) + (setq n (1+ n))) + (let ((buffer-read-only nil)) + (emerge-unselect-and-select-difference n))) + (error "At end"))) + +(defun emerge-previous-difference () + "Go to the previous difference." + (interactive) + (if (> emerge-current-difference -1) + (let ((n (1- emerge-current-difference))) + (while (and emerge-skip-prefers + (> n -1) + (memq (aref (aref emerge-difference-list n) 6) + '(prefer-A prefer-B))) + (setq n (1- n))) + (let ((buffer-read-only nil)) + (emerge-unselect-and-select-difference n))) + (error "At beginning"))) + +(defun emerge-jump-to-difference (difference-number) + "Go to the N-th difference." + (interactive "p") + (let ((buffer-read-only nil)) + (setq difference-number (1- difference-number)) + (if (and (>= difference-number -1) + (< difference-number (1+ emerge-number-of-differences))) + (emerge-unselect-and-select-difference difference-number) + (error "Bad difference number")))) + +(defun emerge-abort () + "Abort the Emerge session." + (interactive) + (emerge-quit t)) + +(defun emerge-quit (arg) + "Finish the Emerge session and exit Emerge. +Prefix argument means to abort rather than successfully finish. +The difference depends on how the merge was started, +but usually means to not write over one of the original files, or to signal +to some process which invoked Emerge a failure code. + +Unselects the selected difference, if any, restores the read-only and modified +flags of the merged file buffers, restores the local keymap of the merge +buffer, and sets off various emerge flags. Using Emerge commands in this +buffer after this will cause serious problems." + (interactive "P") + (if (prog1 + (y-or-n-p + (if (not arg) + "Do you really want to successfully finish this merge? " + "Do you really want to abort this merge? ")) + (message "")) + (emerge-really-quit arg))) + +;; Perform the quit operations. +(defun emerge-really-quit (arg) + (setq buffer-read-only nil) + (emerge-unselect-and-select-difference -1) + (emerge-restore-buffer-characteristics) + ;; null out the difference markers so they don't slow down future editing + ;; operations + (mapc (function (lambda (d) + (set-marker (aref d 0) nil) + (set-marker (aref d 1) nil) + (set-marker (aref d 2) nil) + (set-marker (aref d 3) nil) + (set-marker (aref d 4) nil) + (set-marker (aref d 5) nil))) + emerge-difference-list) + ;; allow them to be garbage collected + (setq emerge-difference-list nil) + ;; restore the local map + (use-local-map emerge-old-keymap) + ;; turn off all the emerge modes + (setq emerge-mode nil) + (setq emerge-fast-mode nil) + (setq emerge-edit-mode nil) + (setq emerge-auto-advance nil) + (setq emerge-skip-prefers nil) + ;; restore mode line + (kill-local-variable 'mode-line-buffer-identification) + (let ((emerge-prefix-argument arg)) + (run-hooks 'emerge-quit-hook))) + +(defun emerge-select-A (&optional force) + "Select the A variant of this difference. +Refuses to function if this difference has been edited, i.e., if it +is neither the A nor the B variant. +A prefix argument forces the variant to be selected +even if the difference has been edited." + (interactive "P") + (let ((operate + (function (lambda () + (emerge-select-A-edit merge-begin merge-end A-begin A-end) + (if emerge-auto-advance + (emerge-next-difference))))) + (operate-no-change + (function (lambda () + (if emerge-auto-advance + (emerge-next-difference)))))) + (emerge-select-version force operate-no-change operate operate))) + +;; Actually select the A variant +(defun emerge-select-A-edit (merge-begin merge-end A-begin A-end) + (emerge-eval-in-buffer + emerge-merge-buffer + (delete-region merge-begin merge-end) + (goto-char merge-begin) + (insert-buffer-substring emerge-A-buffer A-begin A-end) + (goto-char merge-begin) + (aset diff-vector 6 'A) + (emerge-refresh-mode-line))) + +(defun emerge-select-B (&optional force) + "Select the B variant of this difference. +Refuses to function if this difference has been edited, i.e., if it +is neither the A nor the B variant. +A prefix argument forces the variant to be selected +even if the difference has been edited." + (interactive "P") + (let ((operate + (function (lambda () + (emerge-select-B-edit merge-begin merge-end B-begin B-end) + (if emerge-auto-advance + (emerge-next-difference))))) + (operate-no-change + (function (lambda () + (if emerge-auto-advance + (emerge-next-difference)))))) + (emerge-select-version force operate operate-no-change operate))) + +;; Actually select the B variant +(defun emerge-select-B-edit (merge-begin merge-end B-begin B-end) + (emerge-eval-in-buffer + emerge-merge-buffer + (delete-region merge-begin merge-end) + (goto-char merge-begin) + (insert-buffer-substring emerge-B-buffer B-begin B-end) + (goto-char merge-begin) + (aset diff-vector 6 'B) + (emerge-refresh-mode-line))) + +(defun emerge-default-A () + "Make the A variant the default from here down. +This selects the A variant for all differences from here down in the buffer +which are still defaulted, i.e., which the user has not selected and for +which there is no preference." + (interactive) + (let ((buffer-read-only nil)) + (let ((selected-difference emerge-current-difference) + (n (max emerge-current-difference 0))) + (while (< n emerge-number-of-differences) + (let ((diff-vector (aref emerge-difference-list n))) + (if (eq (aref diff-vector 6) 'default-B) + (progn + (emerge-unselect-and-select-difference n t) + (emerge-select-A) + (aset diff-vector 6 'default-A)))) + (setq n (1+ n)) + (if (zerop (% n 10)) + (message "Setting default to A...%d" n))) + (emerge-unselect-and-select-difference selected-difference))) + (message "Default choice is now A")) + +(defun emerge-default-B () + "Make the B variant the default from here down. +This selects the B variant for all differences from here down in the buffer +which are still defaulted, i.e., which the user has not selected and for +which there is no preference." + (interactive) + (let ((buffer-read-only nil)) + (let ((selected-difference emerge-current-difference) + (n (max emerge-current-difference 0))) + (while (< n emerge-number-of-differences) + (let ((diff-vector (aref emerge-difference-list n))) + (if (eq (aref diff-vector 6) 'default-A) + (progn + (emerge-unselect-and-select-difference n t) + (emerge-select-B) + (aset diff-vector 6 'default-B)))) + (setq n (1+ n)) + (if (zerop (% n 10)) + (message "Setting default to B...%d" n))) + (emerge-unselect-and-select-difference selected-difference))) + (message "Default choice is now B")) + +(defun emerge-fast-mode () + "Set fast mode, for Emerge. +In this mode ordinary Emacs commands are disabled, and Emerge commands +need not be prefixed with \\\\[emerge-basic-keymap]." + (interactive) + (setq buffer-read-only t) + (use-local-map emerge-fast-keymap) + (setq emerge-mode t) + (setq emerge-fast-mode t) + (setq emerge-edit-mode nil) + (message "Fast mode set") + (force-mode-line-update)) + +(defun emerge-edit-mode () + "Set edit mode, for Emerge. +In this mode ordinary Emacs commands are available, and Emerge commands +must be prefixed with \\\\[emerge-basic-keymap]." + (interactive) + (setq buffer-read-only nil) + (use-local-map emerge-edit-keymap) + (setq emerge-mode t) + (setq emerge-fast-mode nil) + (setq emerge-edit-mode t) + (message "Edit mode set") + (force-mode-line-update)) + +(defun emerge-auto-advance (arg) + "Toggle Auto-Advance mode, for Emerge. +This mode causes `emerge-select-A' and `emerge-select-B' to automatically +advance to the next difference. +With a positive argument, turn on Auto-Advance mode. +With a negative argument, turn off Auto-Advance mode." + (interactive "P") + (setq emerge-auto-advance (if (null arg) + (not emerge-auto-advance) + (> (prefix-numeric-value arg) 0))) + (message (if emerge-auto-advance + "Auto-advance set" + "Auto-advance cleared")) + (force-mode-line-update)) + +(defun emerge-skip-prefers (arg) + "Toggle Skip-Prefers mode, for Emerge. +This mode causes `emerge-next-difference' and `emerge-previous-difference' +to automatically skip over differences for which there is a preference. +With a positive argument, turn on Skip-Prefers mode. +With a negative argument, turn off Skip-Prefers mode." + (interactive "P") + (setq emerge-skip-prefers (if (null arg) + (not emerge-skip-prefers) + (> (prefix-numeric-value arg) 0))) + (message (if emerge-skip-prefers + "Skip-prefers set" + "Skip-prefers cleared")) + (force-mode-line-update)) + +(defun emerge-copy-as-kill-A () + "Put the A variant of this difference in the kill ring." + (interactive) + (emerge-validate-difference) + (let* ((diff-vector + (aref emerge-difference-list emerge-current-difference)) + (A-begin (1+ (aref diff-vector 0))) + (A-end (1- (aref diff-vector 1))) + ;; so further kills don't append + this-command) + (with-current-buffer emerge-A-buffer + (copy-region-as-kill A-begin A-end)))) + +(defun emerge-copy-as-kill-B () + "Put the B variant of this difference in the kill ring." + (interactive) + (emerge-validate-difference) + (let* ((diff-vector + (aref emerge-difference-list emerge-current-difference)) + (B-begin (1+ (aref diff-vector 2))) + (B-end (1- (aref diff-vector 3))) + ;; so further kills don't append + this-command) + (with-current-buffer emerge-B-buffer + (copy-region-as-kill B-begin B-end)))) + +(defun emerge-insert-A (arg) + "Insert the A variant of this difference at the point. +Leaves point after text, mark before. +With prefix argument, puts point before, mark after." + (interactive "P") + (emerge-validate-difference) + (let* ((diff-vector + (aref emerge-difference-list emerge-current-difference)) + (A-begin (1+ (aref diff-vector 0))) + (A-end (1- (aref diff-vector 1))) + (opoint (point)) + (buffer-read-only nil)) + (insert-buffer-substring emerge-A-buffer A-begin A-end) + (if (not arg) + (set-mark opoint) + (set-mark (point)) + (goto-char opoint)))) + +(defun emerge-insert-B (arg) + "Insert the B variant of this difference at the point. +Leaves point after text, mark before. +With prefix argument, puts point before, mark after." + (interactive "P") + (emerge-validate-difference) + (let* ((diff-vector + (aref emerge-difference-list emerge-current-difference)) + (B-begin (1+ (aref diff-vector 2))) + (B-end (1- (aref diff-vector 3))) + (opoint (point)) + (buffer-read-only nil)) + (insert-buffer-substring emerge-B-buffer B-begin B-end) + (if (not arg) + (set-mark opoint) + (set-mark (point)) + (goto-char opoint)))) + +(defun emerge-mark-difference (arg) + "Leaves the point before this difference and the mark after it. +With prefix argument, puts mark before, point after." + (interactive "P") + (emerge-validate-difference) + (let* ((diff-vector + (aref emerge-difference-list emerge-current-difference)) + (merge-begin (1+ (aref diff-vector 4))) + (merge-end (1- (aref diff-vector 5)))) + (if (not arg) + (progn + (goto-char merge-begin) + (set-mark merge-end)) + (goto-char merge-end) + (set-mark merge-begin)))) + +(defun emerge-file-names () + "Show the names of the buffers or files being operated on by Emerge. +Use C-u l to reset the windows afterward." + (interactive) + (delete-other-windows) + (let ((temp-buffer-show-function + (function (lambda (buf) + (split-window-vertically) + (switch-to-buffer buf) + (other-window 1))))) + (with-output-to-temp-buffer "*Help*" + (emerge-eval-in-buffer emerge-A-buffer + (if buffer-file-name + (progn + (princ "File A is: ") + (princ buffer-file-name)) + (progn + (princ "Buffer A is: ") + (princ (buffer-name)))) + (princ "\n")) + (emerge-eval-in-buffer emerge-B-buffer + (if buffer-file-name + (progn + (princ "File B is: ") + (princ buffer-file-name)) + (progn + (princ "Buffer B is: ") + (princ (buffer-name)))) + (princ "\n")) + (if emerge-ancestor-buffer + (emerge-eval-in-buffer emerge-ancestor-buffer + (if buffer-file-name + (progn + (princ "Ancestor file is: ") + (princ buffer-file-name)) + (progn + (princ "Ancestor buffer is: ") + (princ (buffer-name)))) + (princ "\n"))) + (princ emerge-output-description) + (with-current-buffer standard-output + (help-mode))))) + +(defun emerge-join-differences (arg) + "Join the selected difference with the following one. +With a prefix argument, join with the preceding one." + (interactive "P") + (let ((n emerge-current-difference)) + ;; adjust n to be first difference to join + (if arg + (setq n (1- n))) + ;; n and n+1 are the differences to join + ;; check that they are both differences + (if (or (< n 0) (>= n (1- emerge-number-of-differences))) + (error "Incorrect differences to join")) + ;; remove the flags + (emerge-unselect-difference emerge-current-difference) + ;; decrement total number of differences + (setq emerge-number-of-differences (1- emerge-number-of-differences)) + ;; build new differences vector + (let ((i 0) + (new-differences (make-vector emerge-number-of-differences nil))) + (while (< i emerge-number-of-differences) + (aset new-differences i + (cond + ((< i n) (aref emerge-difference-list i)) + ((> i n) (aref emerge-difference-list (1+ i))) + (t (let ((prev (aref emerge-difference-list i)) + (next (aref emerge-difference-list (1+ i)))) + (vector (aref prev 0) + (aref next 1) + (aref prev 2) + (aref next 3) + (aref prev 4) + (aref next 5) + (let ((ps (aref prev 6)) + (ns (aref next 6))) + (cond + ((eq ps ns) + ps) + ((and (or (eq ps 'B) (eq ps 'prefer-B)) + (or (eq ns 'B) (eq ns 'prefer-B))) + 'B) + (t 'A)))))))) + (setq i (1+ i))) + (setq emerge-difference-list new-differences)) + ;; set the current difference correctly + (setq emerge-current-difference n) + ;; fix the mode line + (emerge-refresh-mode-line) + ;; reinsert the flags + (emerge-select-difference emerge-current-difference) + (emerge-recenter))) + +(defun emerge-split-difference () + "Split the current difference where the points are in the three windows." + (interactive) + (let ((n emerge-current-difference)) + ;; check that this is a valid difference + (emerge-validate-difference) + ;; get the point values and old difference + (let ((A-point (emerge-eval-in-buffer emerge-A-buffer + (point-marker))) + (B-point (emerge-eval-in-buffer emerge-B-buffer + (point-marker))) + (merge-point (point-marker)) + (old-diff (aref emerge-difference-list n))) + ;; check location of the points, give error if they aren't in the + ;; differences + (if (or (< A-point (aref old-diff 0)) + (> A-point (aref old-diff 1))) + (error "Point outside of difference in A buffer")) + (if (or (< B-point (aref old-diff 2)) + (> B-point (aref old-diff 3))) + (error "Point outside of difference in B buffer")) + (if (or (< merge-point (aref old-diff 4)) + (> merge-point (aref old-diff 5))) + (error "Point outside of difference in merge buffer")) + ;; remove the flags + (emerge-unselect-difference emerge-current-difference) + ;; increment total number of differences + (setq emerge-number-of-differences (1+ emerge-number-of-differences)) + ;; build new differences vector + (let ((i 0) + (new-differences (make-vector emerge-number-of-differences nil))) + (while (< i emerge-number-of-differences) + (aset new-differences i + (cond + ((< i n) + (aref emerge-difference-list i)) + ((> i (1+ n)) + (aref emerge-difference-list (1- i))) + ((= i n) + (vector (aref old-diff 0) + A-point + (aref old-diff 2) + B-point + (aref old-diff 4) + merge-point + (aref old-diff 6))) + (t + (vector (copy-marker A-point) + (aref old-diff 1) + (copy-marker B-point) + (aref old-diff 3) + (copy-marker merge-point) + (aref old-diff 5) + (aref old-diff 6))))) + (setq i (1+ i))) + (setq emerge-difference-list new-differences)) + ;; set the current difference correctly + (setq emerge-current-difference n) + ;; fix the mode line + (emerge-refresh-mode-line) + ;; reinsert the flags + (emerge-select-difference emerge-current-difference) + (emerge-recenter)))) + +(defun emerge-trim-difference () + "Trim lines off top and bottom of difference that are the same. +If lines are the same in both the A and the B versions, strip them off. +\(This can happen when the A and B versions have common lines that the +ancestor version does not share.)" + (interactive) + ;; make sure we are in a real difference + (emerge-validate-difference) + ;; remove the flags + (emerge-unselect-difference emerge-current-difference) + (let* ((diff (aref emerge-difference-list emerge-current-difference)) + (top-a (marker-position (aref diff 0))) + (bottom-a (marker-position (aref diff 1))) + (top-b (marker-position (aref diff 2))) + (bottom-b (marker-position (aref diff 3))) + (top-m (marker-position (aref diff 4))) + (bottom-m (marker-position (aref diff 5))) + size success sa sb sm) + ;; move down the tops of the difference regions as much as possible + ;; Try advancing comparing 1000 chars at a time. + ;; When that fails, go 500 chars at a time, and so on. + (setq size 1000) + (while (> size 0) + (setq success t) + (while success + (setq size (min size (- bottom-a top-a) (- bottom-b top-b) + (- bottom-m top-m))) + (setq sa (emerge-eval-in-buffer emerge-A-buffer + (buffer-substring top-a + (+ size top-a)))) + (setq sb (emerge-eval-in-buffer emerge-B-buffer + (buffer-substring top-b + (+ size top-b)))) + (setq sm (buffer-substring top-m (+ size top-m))) + (setq success (and (> size 0) (equal sa sb) (equal sb sm))) + (if success + (setq top-a (+ top-a size) + top-b (+ top-b size) + top-m (+ top-m size)))) + (setq size (/ size 2))) + ;; move up the bottoms of the difference regions as much as possible + ;; Try advancing comparing 1000 chars at a time. + ;; When that fails, go 500 chars at a time, and so on. + (setq size 1000) + (while (> size 0) + (setq success t) + (while success + (setq size (min size (- bottom-a top-a) (- bottom-b top-b) + (- bottom-m top-m))) + (setq sa (emerge-eval-in-buffer emerge-A-buffer + (buffer-substring (- bottom-a size) + bottom-a))) + (setq sb (emerge-eval-in-buffer emerge-B-buffer + (buffer-substring (- bottom-b size) + bottom-b))) + (setq sm (buffer-substring (- bottom-m size) bottom-m)) + (setq success (and (> size 0) (equal sa sb) (equal sb sm))) + (if success + (setq bottom-a (- bottom-a size) + bottom-b (- bottom-b size) + bottom-m (- bottom-m size)))) + (setq size (/ size 2))) + ;; {top,bottom}-{a,b,m} are now set at the new beginnings and ends + ;; of the difference regions. Move them to the beginning of lines, as + ;; appropriate. + (emerge-eval-in-buffer emerge-A-buffer + (goto-char top-a) + (beginning-of-line) + (aset diff 0 (point-marker)) + (goto-char bottom-a) + (beginning-of-line 2) + (aset diff 1 (point-marker))) + (emerge-eval-in-buffer emerge-B-buffer + (goto-char top-b) + (beginning-of-line) + (aset diff 2 (point-marker)) + (goto-char bottom-b) + (beginning-of-line 2) + (aset diff 3 (point-marker))) + (goto-char top-m) + (beginning-of-line) + (aset diff 4 (point-marker)) + (goto-char bottom-m) + (beginning-of-line 2) + (aset diff 5 (point-marker)) + ;; put the flags back in, recenter the display + (emerge-select-difference emerge-current-difference) + (emerge-recenter))) + +;; FIXME the manual advertised this as working in the A or B buffers, +;; but it does not, because all the buffer locals are nil there. +;; It would work to call it from the merge buffer and specify that one +;; wants to use the value of point in the A or B buffer. +;; But with the prefix argument already in use, there is no easy way +;; to have it ask for a buffer. +(defun emerge-find-difference (arg) + "Find the difference containing the current position of the point. +If there is no containing difference and the prefix argument is positive, +it finds the nearest following difference. A negative prefix argument finds +the nearest previous difference." + (interactive "P") + (cond ((eq (current-buffer) emerge-A-buffer) + (emerge-find-difference-A arg)) + ((eq (current-buffer) emerge-B-buffer) + (emerge-find-difference-B arg)) + (t (emerge-find-difference-merge arg)))) + +(defun emerge-find-difference-merge (arg) + "Find the difference containing point, in the merge buffer. +If there is no containing difference and the prefix argument is positive, +it finds the nearest following difference. A negative prefix argument finds +the nearest previous difference." + (interactive "P") + ;; search for the point in the merge buffer, using the markers + ;; for the beginning and end of the differences in the merge buffer + (emerge-find-difference1 arg (point) 4 5)) + +(defun emerge-find-difference-A (arg) + "Find the difference containing point, in the A buffer. +This command must be executed in the merge buffer. +If there is no containing difference and the prefix argument is positive, +it finds the nearest following difference. A negative prefix argument finds +the nearest previous difference." + (interactive "P") + ;; search for the point in the A buffer, using the markers + ;; for the beginning and end of the differences in the A buffer + (emerge-find-difference1 arg + (emerge-eval-in-buffer emerge-A-buffer (point)) + 0 1)) + +(defun emerge-find-difference-B (arg) + "Find the difference containing point, in the B buffer. +This command must be executed in the merge buffer. +If there is no containing difference and the prefix argument is positive, +it finds the nearest following difference. A negative prefix argument finds +the nearest previous difference." + (interactive "P") + ;; search for the point in the B buffer, using the markers + ;; for the beginning and end of the differences in the B buffer + (emerge-find-difference1 arg + (emerge-eval-in-buffer emerge-B-buffer (point)) + 2 3)) + +(defun emerge-find-difference1 (arg location begin end) + (let* ((index + ;; find first difference containing or after the current position + (catch 'search + (let ((n 0)) + (while (< n emerge-number-of-differences) + (let ((diff-vector (aref emerge-difference-list n))) + (if (<= location (marker-position (aref diff-vector end))) + (throw 'search n))) + (setq n (1+ n)))) + emerge-number-of-differences)) + (contains + ;; whether the found difference contains the current position + (and (< index emerge-number-of-differences) + (<= (marker-position (aref (aref emerge-difference-list index) + begin)) + location))) + (arg-value + ;; numeric value of prefix argument + (prefix-numeric-value arg))) + (emerge-unselect-and-select-difference + (cond + ;; if the point is in a difference, select it + (contains index) + ;; if the arg is nil and the point is not in a difference, error + ((null arg) (error "No difference contains point")) + ;; if the arg is positive, select the following difference + ((> arg-value 0) + (if (< index emerge-number-of-differences) + index + (error "No difference contains or follows point"))) + ;; if the arg is negative, select the preceding difference + (t + (if (> index 0) + (1- index) + (error "No difference contains or precedes point"))))))) + +(defun emerge-line-numbers () + "Display the current line numbers. +This function displays the line numbers of the points in the A, B, and +merge buffers." + (interactive) + (let* ((valid-diff + (and (>= emerge-current-difference 0) + (< emerge-current-difference emerge-number-of-differences))) + (diff (and valid-diff + (aref emerge-difference-list emerge-current-difference))) + (merge-line (emerge-line-number-in-buf 4 5)) + (A-line (emerge-eval-in-buffer emerge-A-buffer + (emerge-line-number-in-buf 0 1))) + (B-line (emerge-eval-in-buffer emerge-B-buffer + (emerge-line-number-in-buf 2 3)))) + (message "At lines: merge = %d, A = %d, B = %d" + merge-line A-line B-line))) + +(defun emerge-line-number-in-buf (begin-marker end-marker) + (let (temp) + (setq temp (save-excursion + (beginning-of-line) + (1+ (count-lines 1 (point))))) + (if valid-diff + (progn + (if (> (point) (aref diff begin-marker)) + (setq temp (- temp emerge-before-flag-lines))) + (if (> (point) (aref diff end-marker)) + (setq temp (- temp emerge-after-flag-lines))))) + temp)) + +(defun emerge-set-combine-template (string &optional localize) + "Set `emerge-combine-versions-template' to STRING. +This value controls how `emerge-combine-versions' combines the two versions. +With prefix argument, `emerge-combine-versions-template' is made local to this +merge buffer. Localization is permanent for any particular merge buffer." + (interactive "s\nP") + (if localize + (make-local-variable 'emerge-combine-versions-template)) + (setq emerge-combine-versions-template string) + (message + (if (assq 'emerge-combine-versions-template (buffer-local-variables)) + "emerge-set-combine-versions-template set locally" + "emerge-set-combine-versions-template set"))) + +(defun emerge-set-combine-versions-template (start end &optional localize) + "Copy region into `emerge-combine-versions-template'. +This controls how `emerge-combine-versions' will combine the two versions. +With prefix argument, `emerge-combine-versions-template' is made local to this +merge buffer. Localization is permanent for any particular merge buffer." + (interactive "r\nP") + (if localize + (make-local-variable 'emerge-combine-versions-template)) + (setq emerge-combine-versions-template (buffer-substring start end)) + (message + (if (assq 'emerge-combine-versions-template (buffer-local-variables)) + "emerge-set-combine-versions-template set locally." + "emerge-set-combine-versions-template set."))) + +(defun emerge-combine-versions (&optional force) + "Combine versions using the template in `emerge-combine-versions-template'. +Refuses to function if this difference has been edited, i.e., if it is +neither the A nor the B variant. +An argument forces the variant to be selected even if the difference has +been edited." + (interactive "P") + (emerge-combine-versions-internal emerge-combine-versions-template force)) + +(defun emerge-combine-versions-register (char &optional force) + "Combine the two versions using the template in register REG. +See documentation of the variable `emerge-combine-versions-template' +for how the template is interpreted. +Refuses to function if this difference has been edited, i.e., if it is +neither the A nor the B variant. +An argument forces the variant to be selected even if the difference has +been edited." + (interactive "cRegister containing template: \nP") + (let ((template (get-register char))) + (if (not (stringp template)) + (error "Register does not contain text")) + (emerge-combine-versions-internal template force))) + +(defun emerge-combine-versions-internal (template force) + (let ((operate + (function (lambda () + (emerge-combine-versions-edit merge-begin merge-end + A-begin A-end B-begin B-end) + (if emerge-auto-advance + (emerge-next-difference)))))) + (emerge-select-version force operate operate operate))) + +(defun emerge-combine-versions-edit (merge-begin merge-end + A-begin A-end B-begin B-end) + (emerge-eval-in-buffer + emerge-merge-buffer + (delete-region merge-begin merge-end) + (goto-char merge-begin) + (let ((i 0)) + (while (< i (length template)) + (let ((c (aref template i))) + (if (= c ?%) + (progn + (setq i (1+ i)) + (setq c + (condition-case nil + (aref template i) + (error ?%))) + (cond ((= c ?a) + (insert-buffer-substring emerge-A-buffer A-begin A-end)) + ((= c ?b) + (insert-buffer-substring emerge-B-buffer B-begin B-end)) + ((= c ?%) + (insert ?%)) + (t + (insert c)))) + (insert c))) + (setq i (1+ i)))) + (goto-char merge-begin) + (aset diff-vector 6 'combined) + (emerge-refresh-mode-line))) + +(defun emerge-set-merge-mode (mode) + "Set the major mode in a merge buffer. +Overrides any change that the mode might make to the mode line or local +keymap. Leaves merge in fast mode." + (interactive + (list (intern (completing-read "New major mode for merge buffer: " + obarray 'commandp t nil)))) + (funcall mode) + (emerge-refresh-mode-line) + (if emerge-fast-mode + (emerge-fast-mode) + (emerge-edit-mode))) + +(defun emerge-one-line-window () + (interactive) + (let ((window-min-height 1)) + (shrink-window (- (window-height) 2)))) + +;;; Support routines + +;; Select a difference by placing the visual flags around the appropriate +;; group of lines in the A, B, and merge buffers +(defun emerge-select-difference (n) + (let ((emerge-globalized-difference-list emerge-difference-list) + (emerge-globalized-number-of-differences emerge-number-of-differences)) + (emerge-place-flags-in-buffer emerge-A-buffer n 0 1) + (emerge-place-flags-in-buffer emerge-B-buffer n 2 3) + (emerge-place-flags-in-buffer nil n 4 5)) + (run-hooks 'emerge-select-hook)) + +(defun emerge-place-flags-in-buffer (buffer difference before-index + after-index) + (if buffer + (emerge-eval-in-buffer + buffer + (emerge-place-flags-in-buffer1 difference before-index after-index)) + (emerge-place-flags-in-buffer1 difference before-index after-index))) + +(defun emerge-place-flags-in-buffer1 (difference before-index after-index) + (let ((buffer-read-only nil)) + ;; insert the flag before the difference + (let ((before (aref (aref emerge-globalized-difference-list difference) + before-index)) + here) + (goto-char before) + ;; insert the flag itself + (insert-before-markers emerge-before-flag) + (setq here (point)) + ;; Put the marker(s) referring to this position 1 character before the + ;; end of the flag, so it won't be damaged by the user. + ;; This gets a bit tricky, as there could be a number of markers + ;; that have to be moved. + (set-marker before (1- before)) + (let ((n (1- difference)) after-marker before-marker diff-list) + (while (and + (>= n 0) + (progn + (setq diff-list (aref emerge-globalized-difference-list n) + after-marker (aref diff-list after-index)) + (= after-marker here))) + (set-marker after-marker (1- after-marker)) + (setq before-marker (aref diff-list before-index)) + (if (= before-marker here) + (setq before-marker (1- before-marker))) + (setq n (1- n))))) + ;; insert the flag after the difference + (let* ((after (aref (aref emerge-globalized-difference-list difference) + after-index)) + (here (marker-position after))) + (goto-char here) + ;; insert the flag itself + (insert emerge-after-flag) + ;; Put the marker(s) referring to this position 1 character after the + ;; beginning of the flag, so it won't be damaged by the user. + ;; This gets a bit tricky, as there could be a number of markers + ;; that have to be moved. + (set-marker after (1+ after)) + (let ((n (1+ difference)) before-marker after-marker diff-list) + (while (and + (< n emerge-globalized-number-of-differences) + (progn + (setq diff-list (aref emerge-globalized-difference-list n) + before-marker (aref diff-list before-index)) + (= before-marker here))) + (set-marker before-marker (1+ before-marker)) + (setq after-marker (aref diff-list after-index)) + (if (= after-marker here) + (setq after-marker (1+ after-marker))) + (setq n (1+ n))))))) + +;; Unselect a difference by removing the visual flags in the buffers. +(defun emerge-unselect-difference (n) + (let ((diff-vector (aref emerge-difference-list n))) + (emerge-remove-flags-in-buffer emerge-A-buffer + (aref diff-vector 0) (aref diff-vector 1)) + (emerge-remove-flags-in-buffer emerge-B-buffer + (aref diff-vector 2) (aref diff-vector 3)) + (emerge-remove-flags-in-buffer emerge-merge-buffer + (aref diff-vector 4) (aref diff-vector 5))) + (run-hooks 'emerge-unselect-hook)) + +(defun emerge-remove-flags-in-buffer (buffer before after) + (emerge-eval-in-buffer + buffer + (let ((buffer-read-only nil)) + ;; remove the flags, if they're there + (goto-char (- before (1- emerge-before-flag-length))) + (if (looking-at emerge-before-flag-match) + (delete-char emerge-before-flag-length) + ;; the flag isn't there + (ding) + (message "Trouble removing flag")) + (goto-char (1- after)) + (if (looking-at emerge-after-flag-match) + (delete-char emerge-after-flag-length) + ;; the flag isn't there + (ding) + (message "Trouble removing flag"))))) + +;; Select a difference, removing any flags that exist now. +(defun emerge-unselect-and-select-difference (n &optional suppress-display) + (if (and (>= emerge-current-difference 0) + (< emerge-current-difference emerge-number-of-differences)) + (emerge-unselect-difference emerge-current-difference)) + (if (and (>= n 0) (< n emerge-number-of-differences)) + (progn + (emerge-select-difference n) + (let* ((diff-vector (aref emerge-difference-list n)) + (selection-type (aref diff-vector 6))) + (if (eq selection-type 'default-A) + (aset diff-vector 6 'A) + (if (eq selection-type 'default-B) + (aset diff-vector 6 'B)))))) + (setq emerge-current-difference n) + (if (not suppress-display) + (progn + (emerge-recenter) + (emerge-refresh-mode-line)))) + +;; Perform tests to see whether user should be allowed to select a version +;; of this difference: +;; a valid difference has been selected; and +;; the difference text in the merge buffer is: +;; the A version (execute a-version), or +;; the B version (execute b-version), or +;; empty (execute neither-version), or +;; argument FORCE is true (execute neither-version) +;; Otherwise, signal an error. +(defun emerge-select-version (force a-version b-version neither-version) + (emerge-validate-difference) + (let ((buffer-read-only nil)) + (let* ((diff-vector + (aref emerge-difference-list emerge-current-difference)) + (A-begin (1+ (aref diff-vector 0))) + (A-end (1- (aref diff-vector 1))) + (B-begin (1+ (aref diff-vector 2))) + (B-end (1- (aref diff-vector 3))) + (merge-begin (1+ (aref diff-vector 4))) + (merge-end (1- (aref diff-vector 5)))) + (if (emerge-compare-buffers emerge-A-buffer A-begin A-end + emerge-merge-buffer merge-begin + merge-end) + (funcall a-version) + (if (emerge-compare-buffers emerge-B-buffer B-begin B-end + emerge-merge-buffer merge-begin + merge-end) + (funcall b-version) + (if (or force (= merge-begin merge-end)) + (funcall neither-version) + (error "This difference region has been edited"))))))) + +;; Read a file name, handling all of the various defaulting rules. + +(defun emerge-read-file-name (prompt alternative-default-dir default-file + A-file must-match) + ;; `prompt' should not have trailing ": ", so that it can be modified + ;; according to context. + ;; If alternative-default-dir is non-nil, it should be used as the default + ;; directory instead if default-directory, if emerge-default-last-directories + ;; is set. + ;; If default-file is set, it should be used as the default value. + ;; If A-file is set, and its directory is different from + ;; alternative-default-dir, and if emerge-default-last-directories is set, + ;; the default file should be the last part of A-file in the default + ;; directory. (Overriding default-file.) + (cond + ;; If this is not the A-file argument (shown by non-nil A-file), and + ;; if emerge-default-last-directories is set, and + ;; the default directory exists but is not the same as the directory of the + ;; A-file, + ;; then make the default file have the same name as the A-file, but in + ;; the default directory. + ((and emerge-default-last-directories + A-file + alternative-default-dir + (not (string-equal alternative-default-dir + (file-name-directory A-file)))) + (read-file-name (format "%s (default %s): " + prompt (file-name-nondirectory A-file)) + alternative-default-dir + (concat alternative-default-dir + (file-name-nondirectory A-file)) + (and must-match 'confirm))) + ;; If there is a default file, use it. + (default-file + (read-file-name (format "%s (default %s): " prompt default-file) + ;; If emerge-default-last-directories is set, use the + ;; directory from the same argument of the last call of + ;; Emerge as the default for this argument. + (and emerge-default-last-directories + alternative-default-dir) + default-file (and must-match 'confirm))) + (t + (read-file-name (concat prompt ": ") + ;; If emerge-default-last-directories is set, use the + ;; directory from the same argument of the last call of + ;; Emerge as the default for this argument. + (and emerge-default-last-directories + alternative-default-dir) + nil (and must-match 'confirm))))) + +;; Revise the mode line to display which difference we have selected + +(defun emerge-refresh-mode-line () + (setq mode-line-buffer-identification + (list (format "Emerge: %%b diff %d of %d%s" + (1+ emerge-current-difference) + emerge-number-of-differences + (if (and (>= emerge-current-difference 0) + (< emerge-current-difference + emerge-number-of-differences)) + (cdr (assq (aref (aref emerge-difference-list + emerge-current-difference) + 6) + '((A . " - A") + (B . " - B") + (prefer-A . " - A*") + (prefer-B . " - B*") + (combined . " - comb")))) + "")))) + (force-mode-line-update)) + +;; compare two regions in two buffers for containing the same text +(defun emerge-compare-buffers (buffer-x x-begin x-end buffer-y y-begin y-end) + ;; first check that the two regions are the same length + (if (not (and (= (- x-end x-begin) (- y-end y-begin)))) + nil + (catch 'exit + (while (< x-begin x-end) + ;; bite off and compare no more than 1000 characters at a time + (let* ((compare-length (min (- x-end x-begin) 1000)) + (x-string (emerge-eval-in-buffer + buffer-x + (buffer-substring x-begin + (+ x-begin compare-length)))) + (y-string (emerge-eval-in-buffer + buffer-y + (buffer-substring y-begin + (+ y-begin compare-length))))) + (if (not (string-equal x-string y-string)) + (throw 'exit nil) + (setq x-begin (+ x-begin compare-length)) + (setq y-begin (+ y-begin compare-length))))) + t))) + +;; Construct a unique buffer name. +;; The first one tried is prefixsuffix, then prefix<2>suffix, +;; prefix<3>suffix, etc. +(defun emerge-unique-buffer-name (prefix suffix) + (if (null (get-buffer (concat prefix suffix))) + (concat prefix suffix) + (let ((n 2)) + (while (get-buffer (format "%s<%d>%s" prefix n suffix)) + (setq n (1+ n))) + (format "%s<%d>%s" prefix n suffix)))) + +;; Verify that we have a difference selected. +(defun emerge-validate-difference () + (if (not (and (>= emerge-current-difference 0) + (< emerge-current-difference emerge-number-of-differences))) + (error "No difference selected"))) + +;;; Functions for saving and restoring a batch of variables + +;; These functions save (get the values of) and restore (set the values of) +;; a list of variables. The argument is a list of symbols (the names of +;; the variables). A list element can also be a list of two functions, +;; the first of which (when called with no arguments) gets the value, and +;; the second (when called with a value as an argument) sets the value. +;; A "function" is anything that funcall can handle as an argument. + +(defun emerge-save-variables (vars) + (mapcar (function (lambda (v) (if (symbolp v) + (symbol-value v) + (funcall (car v))))) + vars)) + +(defun emerge-restore-variables (vars values) + (while vars + (let ((var (car vars)) + (value (car values))) + (if (symbolp var) + (set var value) + (funcall (car (cdr var)) value))) + (setq vars (cdr vars)) + (setq values (cdr values)))) + +;; Make a temporary file that only we have access to. +;; PREFIX is appended to emerge-temp-file-prefix to make the filename prefix. +(defun emerge-make-temp-file (prefix) + (let (f (old-modes (default-file-modes))) + (unwind-protect + (progn + (set-default-file-modes emerge-temp-file-mode) + (setq f (make-temp-file (concat emerge-temp-file-prefix prefix)))) + (set-default-file-modes old-modes)) + f)) + +;;; Functions that query the user before he can write out the current buffer. + +(defun emerge-query-write-file () + "Ask the user whether to write out an incomplete merge. +If answer is yes, call `write-file' to do so. See `emerge-query-and-call' +for details of the querying process." + (interactive) + (emerge-query-and-call 'write-file)) + +(defun emerge-query-save-buffer () + "Ask the user whether to save an incomplete merge. +If answer is yes, call `save-buffer' to do so. See `emerge-query-and-call' +for details of the querying process." + (interactive) + (emerge-query-and-call 'save-buffer)) + +(defun emerge-query-and-call (command) + "Ask the user whether to save or write out the incomplete merge. +If answer is yes, call COMMAND interactively. During the call, the flags +around the current difference are removed." + (if (yes-or-no-p "Do you really write to write out this unfinished merge? ") + ;; He really wants to do it -- unselect the difference for the duration + (progn + (if (and (>= emerge-current-difference 0) + (< emerge-current-difference emerge-number-of-differences)) + (emerge-unselect-difference emerge-current-difference)) + ;; call-interactively takes the value of current-prefix-arg as the + ;; prefix argument value to be passed to the command. Thus, we have + ;; to do nothing special to make sure the prefix argument is + ;; transmitted to the command. + (call-interactively command) + (if (and (>= emerge-current-difference 0) + (< emerge-current-difference emerge-number-of-differences)) + (progn + (emerge-select-difference emerge-current-difference) + (emerge-recenter)))) + ;; He's being smart and not doing it + (message "Not written"))) + +;; Make sure the current buffer (for a file) has the same contents as the +;; file on disk, and attempt to remedy the situation if not. +;; Signal an error if we can't make them the same, or the user doesn't want +;; to do what is necessary to make them the same. +(defun emerge-verify-file-buffer () + ;; First check if the file has been modified since the buffer visited it. + (if (verify-visited-file-modtime (current-buffer)) + (if (buffer-modified-p) + ;; If buffer is not obsolete and is modified, offer to save + (if (yes-or-no-p (format "Save file %s? " buffer-file-name)) + (save-buffer) + (error "Buffer out of sync for file %s" buffer-file-name)) + ;; If buffer is not obsolete and is not modified, do nothing + nil) + (if (buffer-modified-p) + ;; If buffer is obsolete and is modified, give error + (error "Buffer out of sync for file %s" buffer-file-name) + ;; If buffer is obsolete and is not modified, offer to revert + (if (yes-or-no-p (format "Revert file %s? " buffer-file-name)) + (revert-buffer t t) + (error "Buffer out of sync for file %s" buffer-file-name))))) + +;; Utilities that might have value outside of Emerge. + +;; Set up the mode in the current buffer to duplicate the mode in another +;; buffer. +(defun emerge-copy-modes (buffer) + ;; Set the major mode + (funcall (emerge-eval-in-buffer buffer major-mode))) + +;; Define a key, even if a prefix of it is defined +(defun emerge-force-define-key (keymap key definition) + "Like `define-key', but forcibly creates prefix characters as needed. +If some prefix of KEY has a non-prefix definition, it is redefined." + ;; Find out if a prefix of key is defined + (let ((v (lookup-key keymap key))) + ;; If so, undefine it + (if (integerp v) + (define-key keymap (substring key 0 v) nil))) + ;; Now define the key + (define-key keymap key definition)) + +;;;;; Improvements to describe-mode, so that it describes minor modes as well +;;;;; as the major mode +;;(defun describe-mode (&optional minor) +;; "Display documentation of current major mode. +;;If optional arg MINOR is non-nil (or prefix argument is given if interactive), +;;display documentation of active minor modes as well. +;;For this to work correctly for a minor mode, the mode's indicator variable +;;\(listed in `minor-mode-alist') must also be a function whose documentation +;;describes the minor mode." +;; (interactive) +;; (with-output-to-temp-buffer "*Help*" +;; (princ mode-name) +;; (princ " Mode:\n") +;; (princ (documentation major-mode)) +;; (let ((minor-modes minor-mode-alist) +;; (locals (buffer-local-variables))) +;; (while minor-modes +;; (let* ((minor-mode (car (car minor-modes))) +;; (indicator (car (cdr (car minor-modes)))) +;; (local-binding (assq minor-mode locals))) +;; ;; Document a minor mode if it is listed in minor-mode-alist, +;; ;; bound locally in this buffer, non-nil, and has a function +;; ;; definition. +;; (if (and local-binding +;; (cdr local-binding) +;; (fboundp minor-mode)) +;; (progn +;; (princ (format "\n\n\n%s minor mode (indicator%s):\n" +;; minor-mode indicator)) +;; (princ (documentation minor-mode))))) +;; (setq minor-modes (cdr minor-modes)))) +;; (with-current-buffer standard-output +;; (help-mode)) +;; (help-print-return-message))) + +;; This goes with the redefinition of describe-mode. +;;;; Adjust things so that keyboard macro definitions are documented correctly. +;;(fset 'defining-kbd-macro (symbol-function 'start-kbd-macro)) + +;; substitute-key-definition should work now. +;;;; Function to shadow a definition in a keymap with definitions in another. +;;(defun emerge-shadow-key-definition (olddef newdef keymap shadowmap) +;; "Shadow OLDDEF with NEWDEF for any keys in KEYMAP with entries in SHADOWMAP. +;;In other words, SHADOWMAP will now shadow all definitions of OLDDEF in KEYMAP +;;with NEWDEF. Does not affect keys that are already defined in SHADOWMAP, +;;including those whose definition is OLDDEF." +;; ;; loop through all keymaps accessible from keymap +;; (let ((maps (accessible-keymaps keymap))) +;; (while maps +;; (let ((prefix (car (car maps))) +;; (map (cdr (car maps)))) +;; ;; examine a keymap +;; (if (arrayp map) +;; ;; array keymap +;; (let ((len (length map)) +;; (i 0)) +;; (while (< i len) +;; (if (eq (aref map i) olddef) +;; ;; set the shadowing definition +;; (let ((key (concat prefix (char-to-string i)))) +;; (emerge-define-key-if-possible shadowmap key newdef))) +;; (setq i (1+ i)))) +;; ;; sparse keymap +;; (while map +;; (if (eq (cdr-safe (car-safe map)) olddef) +;; ;; set the shadowing definition +;; (let ((key +;; (concat prefix (char-to-string (car (car map)))))) +;; (emerge-define-key-if-possible shadowmap key newdef))) +;; (setq map (cdr map))))) +;; (setq maps (cdr maps))))) + +;; Define a key if it (or a prefix) is not already defined in the map. +(defun emerge-define-key-if-possible (keymap key definition) + ;; look up the present definition of the key + (let ((present (lookup-key keymap key))) + (if (integerp present) + ;; if it is "too long", look up the valid prefix + (if (not (lookup-key keymap (substring key 0 present))) + ;; if the prefix isn't defined, define it + (define-key keymap key definition)) + ;; if there is no present definition, define it + (if (not present) + (define-key keymap key definition))))) + +;; Ordinary substitute-key-definition should do this now. +;;(defun emerge-recursively-substitute-key-definition (olddef newdef keymap) +;; "Like `substitute-key-definition', but act recursively on subkeymaps. +;;Make sure that subordinate keymaps aren't shared with other keymaps! +;;\(`copy-keymap' will suffice.)" +;; ;; Loop through all keymaps accessible from keymap +;; (let ((maps (accessible-keymaps keymap))) +;; (while maps +;; ;; Substitute in this keymap +;; (substitute-key-definition olddef newdef (cdr (car maps))) +;; (setq maps (cdr maps))))) + +;; Show the name of the file in the buffer. +(defun emerge-show-file-name () + "Displays the name of the file loaded into the current buffer. +If the name won't fit on one line, the minibuffer is expanded to hold it, +and the command waits for a keystroke from the user. If the keystroke is +SPC, it is ignored; if it is anything else, it is processed as a command." + (interactive) + (let ((name (buffer-file-name))) + (or name + (setq name "Buffer has no file name.")) + (save-window-excursion + (select-window (minibuffer-window)) + (unwind-protect + (progn + (erase-buffer) + (insert name) + (while (and (not (pos-visible-in-window-p)) + (not (window-full-height-p))) + (enlarge-window 1)) + (let* ((echo-keystrokes 0) + (c (read-event))) + (if (not (eq c 32)) + (setq unread-command-events (list c))))) + (erase-buffer))))) + +;; Improved auto-save file names. +;; This function fixes many problems with the standard auto-save file names: +;; Auto-save files for non-file buffers get put in the default directory +;; for the buffer, whether that makes sense or not. +;; Auto-save files for file buffers get put in the directory of the file, +;; regardless of whether we can write into it or not. +;; Auto-save files for non-file buffers don't use the process id, so if a +;; user runs more than on Emacs, they can make auto-save files that overwrite +;; each other. +;; To use this function, do: +;; (fset 'make-auto-save-file-name +;; (symbol-function 'emerge-make-auto-save-file-name)) +(defun emerge-make-auto-save-file-name () + "Return file name to use for auto-saves of current buffer. +Does not consider `auto-save-visited-file-name'; +that is checked before calling this function. +You can redefine this for customization. +See also `auto-save-file-name-p'." + (if buffer-file-name + ;; if buffer has a file, try the format /## + (let ((f (concat (file-name-directory buffer-file-name) + "#" + (file-name-nondirectory buffer-file-name) + "#"))) + (if (file-writable-p f) + ;; the file is writable, so use it + f + ;; the file isn't writable, so use the format + ;; ~/#&&# + (concat (getenv "HOME") + "/#&" + (file-name-nondirectory buffer-file-name) + "&" + (emerge-hash-string-into-string + (file-name-directory buffer-file-name)) + "#"))) + ;; if buffer has no file, use the format ~/#%%# + (expand-file-name (concat (getenv "HOME") + "/#%" + ;; quote / into \! and \ into \\ + (emerge-unslashify-name (buffer-name)) + "%" + (make-temp-name "") + "#")))) + +;; Hash a string into five characters more-or-less suitable for use in a file +;; name. (Allowed characters are ! through ~, except /.) +(defun emerge-hash-string-into-string (s) + (let ((bins (vector 0 0 0 0 0)) + (i 0)) + (while (< i (length s)) + (aset bins (% i 5) (% (+ (* (aref bins (% i 5)) 35) + (aref s i)) + 65536)) + (setq i (1+ i))) + (mapconcat (function (lambda (b) + (setq b (+ (% b 93) ?!)) + (if (>= b ?/) + (setq b (1+ b))) + (char-to-string b))) + bins ""))) + +;; Quote any /s in a string by replacing them with \!. +;; Also, replace any \s by \\, to make it one-to-one. +(defun emerge-unslashify-name (s) + (let ((limit 0)) + (while (string-match "[/\\]" s limit) + (setq s (concat (substring s 0 (match-beginning 0)) + (if (string= (substring s (match-beginning 0) + (match-end 0)) + "/") + "\\!" + "\\\\") + (substring s (match-end 0)))) + (setq limit (1+ (match-end 0))))) + s) + +;; Metacharacters that have to be protected from the shell when executing +;; a diff/diff3 command. +(defcustom emerge-metachars "[ \t\n!\"#$&'()*;<=>?[\\^`{|~]" + "Characters that must be quoted with \\ when used in a shell command line. +More precisely, a [...] regexp to match any one such character." + :type 'regexp + :group 'emerge) + +;; Quote metacharacters (using \) when executing a diff/diff3 command. +(defun emerge-protect-metachars (s) + (let ((limit 0)) + (while (string-match emerge-metachars s limit) + (setq s (concat (substring s 0 (match-beginning 0)) + "\\" + (substring s (match-beginning 0)))) + (setq limit (1+ (match-end 0))))) + s) + +(provide 'emerge) + +;; arch-tag: a575f092-6e44-400e-b8a2-4124e9377585 +;;; emerge.el ends here diff -r 681cd08dc0f7 -r e93288477c43 lisp/vc/log-edit.el --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/lisp/vc/log-edit.el Sun Jun 13 22:57:55 2010 +0000 @@ -0,0 +1,835 @@ +;;; log-edit.el --- Major mode for editing CVS commit messages + +;; Copyright (C) 1999, 2000, 2001, 2002, 2003, 2004, 2005, 2006, 2007, +;; 2008, 2009, 2010 Free Software Foundation, Inc. + +;; Author: Stefan Monnier +;; Keywords: pcl-cvs cvs commit log vc + +;; 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 3 of the License, 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. If not, see . + +;;; Commentary: + +;; Todo: + +;; - Move in VC's code +;; - Add compatibility for VC's hook variables + +;;; Code: + +(eval-when-compile (require 'cl)) +(require 'add-log) ; for all the ChangeLog goodies +(require 'pcvs-util) +(require 'ring) + +;;;; +;;;; Global Variables +;;;; + +(defgroup log-edit nil + "Major mode for editing RCS and CVS commit messages." + :group 'pcl-cvs + :group 'vc ; It's used by VC. + :version "21.1" + :prefix "log-edit-") + +;; compiler pacifiers +(defvar cvs-buffer) + + +;; The main keymap + +(easy-mmode-defmap log-edit-mode-map + `(("\C-c\C-c" . log-edit-done) + ("\C-c\C-a" . log-edit-insert-changelog) + ("\C-c\C-d" . log-edit-show-diff) + ("\C-c\C-f" . log-edit-show-files) + ("\M-n" . log-edit-next-comment) + ("\M-p" . log-edit-previous-comment) + ("\M-r" . log-edit-comment-search-backward) + ("\M-s" . log-edit-comment-search-forward) + ("\C-c?" . log-edit-mode-help)) + "Keymap for the `log-edit-mode' (to edit version control log messages)." + :group 'log-edit) + +;; Compatibility with old names. Should we bother ? +(defvar vc-log-mode-map log-edit-mode-map) +(defvar vc-log-entry-mode vc-log-mode-map) + +(easy-menu-define log-edit-menu log-edit-mode-map + "Menu used for `log-edit-mode'." + '("Log-Edit" + ["Done" log-edit-done + :help "Exit log-edit and proceed with the actual action."] + "--" + ["Insert ChangeLog" log-edit-insert-changelog + :help "Insert a log message by looking at the ChangeLog"] + ["Add to ChangeLog" log-edit-add-to-changelog + :help "Insert this log message into the appropriate ChangeLog file"] + "--" + ["Show diff" log-edit-show-diff + :help "Show the diff for the files to be committed."] + ["List files" log-edit-show-files + :help "Show the list of relevant files."] + "--" + ["Previous comment" log-edit-previous-comment + :help "Cycle backwards through comment history"] + ["Next comment" log-edit-next-comment + :help "Cycle forwards through comment history."] + ["Search comment forward" log-edit-comment-search-forward + :help "Search forwards through comment history for a substring match of str"] + ["Search comment backward" log-edit-comment-search-backward + :help "Search backwards through comment history for substring match of str"])) + +(defcustom log-edit-confirm 'changed + "If non-nil, `log-edit-done' will request confirmation. +If 'changed, only request confirmation if the list of files has + changed since the beginning of the log-edit session." + :group 'log-edit + :type '(choice (const changed) (const t) (const nil))) + +(defcustom log-edit-keep-buffer nil + "If non-nil, don't hide the buffer after `log-edit-done'." + :group 'log-edit + :type 'boolean) + +(defvar cvs-commit-buffer-require-final-newline t) +(make-obsolete-variable 'cvs-commit-buffer-require-final-newline + 'log-edit-require-final-newline + "21.1") + +(defcustom log-edit-require-final-newline + cvs-commit-buffer-require-final-newline + "Enforce a newline at the end of commit log messages. +Enforce it silently if t, query if non-nil and don't do anything if nil." + :group 'log-edit + :type '(choice (const ask) (const t) (const nil))) + +(defcustom log-edit-setup-invert nil + "Non-nil means `log-edit' should invert the meaning of its SETUP arg. +If SETUP is 'force, this variable has no effect." + :group 'log-edit + :type 'boolean) + +(defcustom log-edit-hook '(log-edit-insert-cvs-template + log-edit-show-files + log-edit-insert-changelog) + "Hook run at the end of `log-edit'." + :group 'log-edit + :type '(hook :options (log-edit-insert-changelog + log-edit-insert-cvs-rcstemplate + log-edit-insert-cvs-template + log-edit-insert-filenames))) + +(defcustom log-edit-mode-hook (if (boundp 'vc-log-mode-hook) vc-log-mode-hook) + "Hook run when entering `log-edit-mode'." + :group 'log-edit + :type 'hook) + +(defcustom log-edit-done-hook nil + "Hook run before doing the actual commit. +This hook can be used to cleanup the message, enforce various +conventions, or to allow recording the message in some other database, +such as a bug-tracking system. The list of files about to be committed +can be obtained from `log-edit-files'." + :group 'log-edit + :type '(hook :options (log-edit-set-common-indentation + log-edit-add-to-changelog))) + +(defcustom log-edit-strip-single-file-name nil + "If non-nil, remove file name from single-file log entries." + :type 'boolean + :safe 'booleanp + :group 'log-edit + :version "24.1") + +(defvar cvs-changelog-full-paragraphs t) +(make-obsolete-variable 'cvs-changelog-full-paragraphs + 'log-edit-changelog-full-paragraphs + "21.1") + +(defvar log-edit-changelog-full-paragraphs cvs-changelog-full-paragraphs + "*If non-nil, include full ChangeLog paragraphs in the log. +This may be set in the ``local variables'' section of a ChangeLog, to +indicate the policy for that ChangeLog. + +A ChangeLog paragraph is a bunch of log text containing no blank lines; +a paragraph usually describes a set of changes with a single purpose, +but perhaps spanning several functions in several files. Changes in +different paragraphs are unrelated. + +You could argue that the log entry for a file should contain the +full ChangeLog paragraph mentioning the change to the file, even though +it may mention other files, because that gives you the full context you +need to understand the change. This is the behavior you get when this +variable is set to t. + +On the other hand, you could argue that the log entry for a change +should contain only the text for the changes which occurred in that +file, because the log is per-file. This is the behavior you get +when this variable is set to nil.") + +;;;; Internal global or buffer-local vars + +(defconst log-edit-files-buf "*log-edit-files*") +(defvar log-edit-initial-files nil) +(defvar log-edit-callback nil) +(defvar log-edit-diff-function nil) +(defvar log-edit-listfun nil) + +(defvar log-edit-parent-buffer nil) + +;;; Originally taken from VC-Log mode + +(defconst log-edit-maximum-comment-ring-size 32 + "Maximum number of saved comments in the comment ring.") +(defvar log-edit-comment-ring (make-ring log-edit-maximum-comment-ring-size)) +(defvar log-edit-comment-ring-index nil) +(defvar log-edit-last-comment-match "") + +(defun log-edit-new-comment-index (stride len) + "Return the comment index STRIDE elements from the current one. +LEN is the length of `log-edit-comment-ring'." + (mod (cond + (log-edit-comment-ring-index (+ log-edit-comment-ring-index stride)) + ;; Initialize the index on the first use of this command + ;; so that the first M-p gets index 0, and the first M-n gets + ;; index -1. + ((> stride 0) (1- stride)) + (t stride)) + len)) + +(defun log-edit-previous-comment (arg) + "Cycle backwards through comment history. +With a numeric prefix ARG, go back ARG comments." + (interactive "*p") + (let ((len (ring-length log-edit-comment-ring))) + (if (<= len 0) + (progn (message "Empty comment ring") (ding)) + ;; Don't use `erase-buffer' because we don't want to `widen'. + (delete-region (point-min) (point-max)) + (setq log-edit-comment-ring-index (log-edit-new-comment-index arg len)) + (message "Comment %d" (1+ log-edit-comment-ring-index)) + (insert (ring-ref log-edit-comment-ring log-edit-comment-ring-index))))) + +(defun log-edit-next-comment (arg) + "Cycle forwards through comment history. +With a numeric prefix ARG, go forward ARG comments." + (interactive "*p") + (log-edit-previous-comment (- arg))) + +(defun log-edit-comment-search-backward (str &optional stride) + "Search backwards through comment history for substring match of STR. +If the optional argument STRIDE is present, that is a step-width to use +when going through the comment ring." + ;; Why substring rather than regexp ? -sm + (interactive + (list (read-string "Comment substring: " nil nil log-edit-last-comment-match))) + (unless stride (setq stride 1)) + (if (string= str "") + (setq str log-edit-last-comment-match) + (setq log-edit-last-comment-match str)) + (let* ((str (regexp-quote str)) + (len (ring-length log-edit-comment-ring)) + (n (log-edit-new-comment-index stride len))) + (while (progn (when (or (>= n len) (< n 0)) (error "Not found")) + (not (string-match str (ring-ref log-edit-comment-ring n)))) + (setq n (+ n stride))) + (setq log-edit-comment-ring-index n) + (log-edit-previous-comment 0))) + +(defun log-edit-comment-search-forward (str) + "Search forwards through comment history for a substring match of STR." + (interactive + (list (read-string "Comment substring: " nil nil log-edit-last-comment-match))) + (log-edit-comment-search-backward str -1)) + +(defun log-edit-comment-to-change-log (&optional whoami file-name) + "Enter last VC comment into the change log for the current file. +WHOAMI (interactive prefix) non-nil means prompt for user name +and site. FILE-NAME is the name of the change log; if nil, use +`change-log-default-name'. + +This may be useful as a `log-edit-checkin-hook' to update change logs +automatically." + (interactive (if current-prefix-arg + (list current-prefix-arg + (prompt-for-change-log-name)))) + (let (;; Extract the comment first so we get any error before doing anything. + (comment (ring-ref log-edit-comment-ring 0)) + ;; Don't let add-change-log-entry insert a defun name. + (add-log-current-defun-function 'ignore) + end) + ;; Call add-log to do half the work. + (add-change-log-entry whoami file-name t t) + ;; Insert the VC comment, leaving point before it. + (setq end (save-excursion (insert comment) (point-marker))) + (if (looking-at "\\s *\\s(") + ;; It starts with an open-paren, as in "(foo): Frobbed." + ;; So remove the ": " add-log inserted. + (delete-char -2)) + ;; Canonicalize the white space between the file name and comment. + (just-one-space) + ;; Indent rest of the text the same way add-log indented the first line. + (let ((indentation (current-indentation))) + (save-excursion + (while (< (point) end) + (forward-line 1) + (indent-to indentation)) + (setq end (point)))) + ;; Fill the inserted text, preserving open-parens at bol. + (let ((paragraph-start (concat paragraph-start "\\|\\s *\\s("))) + (beginning-of-line) + (fill-region (point) end)) + ;; Canonicalize the white space at the end of the entry so it is + ;; separated from the next entry by a single blank line. + (skip-syntax-forward " " end) + (delete-char (- (skip-syntax-backward " "))) + (or (eobp) (looking-at "\n\n") + (insert "\n")))) + +;; Compatibility with old names. +(define-obsolete-variable-alias 'vc-comment-ring 'log-edit-comment-ring "22.1") +(define-obsolete-variable-alias 'vc-comment-ring-index 'log-edit-comment-ring-index "22.1") +(define-obsolete-function-alias 'vc-previous-comment 'log-edit-previous-comment "22.1") +(define-obsolete-function-alias 'vc-next-comment 'log-edit-next-comment "22.1") +(define-obsolete-function-alias 'vc-comment-search-reverse 'log-edit-comment-search-backward "22.1") +(define-obsolete-function-alias 'vc-comment-search-forward 'log-edit-comment-search-forward "22.1") +(define-obsolete-function-alias 'vc-comment-to-change-log 'log-edit-comment-to-change-log "22.1") + +;;; +;;; Actual code +;;; + +(defface log-edit-summary '((t :inherit font-lock-function-name-face)) + "Face for the summary in `log-edit-mode' buffers.") + +(defface log-edit-header '((t :inherit font-lock-keyword-face)) + "Face for the headers in `log-edit-mode' buffers.") + +(defface log-edit-unknown-header '((t :inherit font-lock-comment-face)) + "Face for unknown headers in `log-edit-mode' buffers.") + +(defvar log-edit-headers-alist '(("Summary" . log-edit-summary) + ("Fixes") ("Author")) + "AList of known headers and the face to use to highlight them.") + +(defconst log-edit-header-contents-regexp + "[ \t]*\\(.*\\(\n[ \t].*\\)*\\)\n?") + +(defun log-edit-match-to-eoh (limit) + ;; FIXME: copied from message-match-to-eoh. + (let ((start (point))) + (rfc822-goto-eoh) + ;; Typical situation: some temporary change causes the header to be + ;; incorrect, so EOH comes earlier than intended: the last lines of the + ;; intended headers are now not considered part of the header any more, + ;; so they don't have the multiline property set. When the change is + ;; completed and the header has its correct shape again, the lack of the + ;; multiline property means we won't rehighlight the last lines of + ;; the header. + (if (< (point) start) + nil ;No header within start..limit. + ;; Here we disregard LIMIT so that we may extend the area again. + (set-match-data (list start (point))) + (point)))) + +(defvar log-edit-font-lock-keywords + ;; Copied/inspired by message-font-lock-keywords. + `((log-edit-match-to-eoh + (,(concat "^\\(\\([a-z]+\\):\\)" log-edit-header-contents-regexp + "\\|\\(.*\\)") + (progn (goto-char (match-beginning 0)) (match-end 0)) nil + (1 (if (assoc (match-string 2) log-edit-headers-alist) + 'log-edit-header + 'log-edit-unknown-header) + nil lax) + (3 (or (cdr (assoc (match-string 2) log-edit-headers-alist)) + 'log-edit-header) + nil lax) + (4 font-lock-warning-face))))) + +;;;###autoload +(defun log-edit (callback &optional setup params buffer mode &rest ignore) + "Setup a buffer to enter a log message. +\\The buffer will be put in mode MODE or `log-edit-mode' +if MODE is nil. +If SETUP is non-nil, the buffer is then erased and `log-edit-hook' is run. +Mark and point will be set around the entire contents of the buffer so +that it is easy to kill the contents of the buffer with \\[kill-region]. +Once you're done editing the message, pressing \\[log-edit-done] will call +`log-edit-done' which will end up calling CALLBACK to do the actual commit. + +PARAMS if non-nil is an alist. Possible keys and associated values: + `log-edit-listfun' -- function taking no arguments that returns the list of + files that are concerned by the current operation (using relative names); + `log-edit-diff-function' -- function taking no arguments that + displays a diff of the files concerned by the current operation. + +If BUFFER is non-nil `log-edit' will jump to that buffer, use it to edit the +log message and go back to the current buffer when done. Otherwise, it +uses the current buffer." + (let ((parent (current-buffer))) + (if buffer (pop-to-buffer buffer)) + (when (and log-edit-setup-invert (not (eq setup 'force))) + (setq setup (not setup))) + (when setup + (erase-buffer) + (insert "Summary: ") + (save-excursion (insert "\n\n"))) + (if mode + (funcall mode) + (log-edit-mode)) + (set (make-local-variable 'log-edit-callback) callback) + (if (listp params) + (dolist (crt params) + (set (make-local-variable (car crt)) (cdr crt))) + ;; For backward compatibility with log-edit up to version 22.2 + ;; accept non-list PARAMS to mean `log-edit-list'. + (set (make-local-variable 'log-edit-listfun) params)) + + (if buffer (set (make-local-variable 'log-edit-parent-buffer) parent)) + (set (make-local-variable 'log-edit-initial-files) (log-edit-files)) + (when setup (run-hooks 'log-edit-hook)) + (goto-char (point-min)) (push-mark (point-max)) + (message "%s" (substitute-command-keys + "Press \\[log-edit-done] when you are done editing.")))) + +(define-derived-mode log-edit-mode text-mode "Log-Edit" + "Major mode for editing version-control log messages. +When done editing the log entry, just type \\[log-edit-done] which +will trigger the actual commit of the file(s). +Several other handy support commands are provided of course and +the package from which this is used might also provide additional +commands (under C-x v for VC, for example). + +\\{log-edit-mode-map}" + (set (make-local-variable 'font-lock-defaults) + '(log-edit-font-lock-keywords t t)) + (make-local-variable 'log-edit-comment-ring-index) + (hack-dir-local-variables-non-file-buffer)) + +(defun log-edit-hide-buf (&optional buf where) + (when (setq buf (get-buffer (or buf log-edit-files-buf))) + (let ((win (get-buffer-window buf where))) + (if win (ignore-errors (delete-window win)))) + (bury-buffer buf))) + +(defun log-edit-done () + "Finish editing the log message and commit the files. +If you want to abort the commit, simply delete the buffer." + (interactive) + ;; Clean up empty headers. + (goto-char (point-min)) + (while (looking-at (concat "^[a-z]*:" log-edit-header-contents-regexp)) + (let ((beg (match-beginning 0))) + (goto-char (match-end 0)) + (if (string-match "\\`[ \n\t]*\\'" (match-string 1)) + (delete-region beg (point))))) + ;; Get rid of leading empty lines. + (goto-char (point-min)) + (when (looking-at "\\([ \t]*\n\\)+") + (delete-region (match-beginning 0) (match-end 0))) + ;; Get rid of trailing empty lines + (goto-char (point-max)) + (skip-syntax-backward " ") + (when (equal (char-after) ?\n) (forward-char 1)) + (delete-region (point) (point-max)) + ;; Check for final newline + (if (and (> (point-max) (point-min)) + (/= (char-before (point-max)) ?\n) + (or (eq log-edit-require-final-newline t) + (and log-edit-require-final-newline + (y-or-n-p + (format "Buffer %s does not end in newline. Add one? " + (buffer-name)))))) + (save-excursion + (goto-char (point-max)) + (insert ?\n))) + (let ((comment (buffer-string))) + (when (or (ring-empty-p log-edit-comment-ring) + (not (equal comment (ring-ref log-edit-comment-ring 0)))) + (ring-insert log-edit-comment-ring comment))) + (let ((win (get-buffer-window log-edit-files-buf))) + (if (and log-edit-confirm + (not (and (eq log-edit-confirm 'changed) + (equal (log-edit-files) log-edit-initial-files))) + (progn + (log-edit-show-files) + (not (y-or-n-p "Really commit? ")))) + (progn (when (not win) (log-edit-hide-buf)) + (message "Oh, well! Later maybe?")) + (run-hooks 'log-edit-done-hook) + (log-edit-hide-buf) + (unless (or log-edit-keep-buffer (not log-edit-parent-buffer)) + (cvs-bury-buffer (current-buffer) log-edit-parent-buffer)) + (call-interactively log-edit-callback)))) + +(defun log-edit-files () + "Return the list of files that are about to be committed." + (ignore-errors (funcall log-edit-listfun))) + +(defun log-edit-mode-help () + "Provide help for the `log-edit-mode-map'." + (interactive) + (if (eq last-command 'log-edit-mode-help) + (describe-function major-mode) + (message "%s" + (substitute-command-keys + "Type `\\[log-edit-done]' to finish commit. Try `\\[describe-function] log-edit-done' for more help.")))) + +(defcustom log-edit-common-indent 0 + "Minimum indentation to use in `log-edit-set-common-indentation'." + :group 'log-edit + :type 'integer) + +(defun log-edit-set-common-indentation () + "(Un)Indent the current buffer rigidly to `log-edit-common-indent'." + (save-excursion + (let ((common (point-max))) + (rfc822-goto-eoh) + (while (< (point) (point-max)) + (if (not (looking-at "^[ \t]*$")) + (setq common (min common (current-indentation)))) + (forward-line 1)) + (rfc822-goto-eoh) + (indent-rigidly (point) (point-max) + (- log-edit-common-indent common))))) + +(defun log-edit-show-diff () + "Show the diff for the files to be committed." + (interactive) + (if (functionp log-edit-diff-function) + (funcall log-edit-diff-function) + (error "Diff functionality has not been setup"))) + +(defun log-edit-show-files () + "Show the list of files to be committed." + (interactive) + (let* ((files (log-edit-files)) + (buf (get-buffer-create log-edit-files-buf))) + (with-current-buffer buf + (log-edit-hide-buf buf 'all) + (setq buffer-read-only nil) + (erase-buffer) + (cvs-insert-strings files) + (setq buffer-read-only t) + (goto-char (point-min)) + (save-selected-window + (cvs-pop-to-buffer-same-frame buf) + (shrink-window-if-larger-than-buffer) + (selected-window))))) + +(defun log-edit-insert-cvs-template () + "Insert the template specified by the CVS administrator, if any. +This simply uses the local CVS/Template file." + (interactive) + (when (or (called-interactively-p 'interactive) + (= (point-min) (point-max))) + (when (file-readable-p "CVS/Template") + (insert-file-contents "CVS/Template")))) + +(defun log-edit-insert-cvs-rcstemplate () + "Insert the rcstemplate from the CVS repository. +This contacts the repository to get the rcstemplate file and +can thus take some time." + (interactive) + (when (or (called-interactively-p 'interactive) + (= (point-min) (point-max))) + (when (file-readable-p "CVS/Root") + ;; Ignore the stderr stuff, even if it's an error. + (call-process "cvs" nil '(t nil) nil + "checkout" "-p" "CVSROOT/rcstemplate")))) + +(defun log-edit-insert-filenames () + "Insert the list of files that are to be committed." + (interactive) + (insert "Affected files: \n" + (mapconcat 'identity (log-edit-files) " \n"))) + +(defun log-edit-add-to-changelog () + "Insert this log message into the appropriate ChangeLog file." + (interactive) + ;; Yuck! + (unless (string= (buffer-string) (ring-ref log-edit-comment-ring 0)) + (ring-insert log-edit-comment-ring (buffer-string))) + (dolist (f (log-edit-files)) + (let ((buffer-file-name (expand-file-name f))) + (save-excursion + (log-edit-comment-to-change-log))))) + +(defvar log-edit-changelog-use-first nil) +(defun log-edit-insert-changelog (&optional use-first) + "Insert a log message by looking at the ChangeLog. +The idea is to write your ChangeLog entries first, and then use this +command to commit your changes. + +To select default log text, we: +- find the ChangeLog entries for the files to be checked in, +- verify that the top entry in the ChangeLog is on the current date + and by the current user; if not, we don't provide any default text, +- search the ChangeLog entry for paragraphs containing the names of + the files we're checking in, and finally +- use those paragraphs as the log text. + +If the optional prefix arg USE-FIRST is given (via \\[universal-argument]), +or if the command is repeated a second time in a row, use the first log entry +regardless of user name or time." + (interactive "P") + (let ((eoh (save-excursion (rfc822-goto-eoh) (point)))) + (when (<= (point) eoh) + (goto-char eoh) + (if (looking-at "\n") (forward-char 1)))) + (let ((log-edit-changelog-use-first + (or use-first (eq last-command 'log-edit-insert-changelog)))) + (log-edit-insert-changelog-entries (log-edit-files))) + (log-edit-set-common-indentation) + (goto-char (point-min)) + (when (and log-edit-strip-single-file-name (looking-at "\\*\\s-+")) + (forward-line 1) + (when (not (re-search-forward "^\\*\\s-+" nil t)) + (goto-char (point-min)) + (skip-chars-forward "^():") + (skip-chars-forward ": ") + (delete-region (point-min) (point))))) + +;;;; +;;;; functions for getting commit message from ChangeLog a file... +;;;; Courtesy Jim Blandy +;;;; + +(defun log-edit-narrow-changelog () + "Narrow to the top page of the current buffer, a ChangeLog file. +Actually, the narrowed region doesn't include the date line. +A \"page\" in a ChangeLog file is the area between two dates." + (or (eq major-mode 'change-log-mode) + (error "log-edit-narrow-changelog: current buffer isn't a ChangeLog")) + + (goto-char (point-min)) + + ;; Skip date line and subsequent blank lines. + (forward-line 1) + (if (looking-at "[ \t\n]*\n") + (goto-char (match-end 0))) + + (let ((start (point))) + (forward-page 1) + (narrow-to-region start (point)) + (goto-char (point-min)))) + +(defun log-edit-changelog-paragraph () + "Return the bounds of the ChangeLog paragraph containing point. +If we are between paragraphs, return the previous paragraph." + (beginning-of-line) + (if (looking-at "^[ \t]*$") + (skip-chars-backward " \t\n" (point-min))) + (list (progn + (if (re-search-backward "^[ \t]*\n" nil 'or-to-limit) + (goto-char (match-end 0))) + (point)) + (if (re-search-forward "^[ \t\n]*$" nil t) + (match-beginning 0) + (point-max)))) + +(defun log-edit-changelog-subparagraph () + "Return the bounds of the ChangeLog subparagraph containing point. +A subparagraph is a block of non-blank lines beginning with an asterisk. +If we are between sub-paragraphs, return the previous subparagraph." + (end-of-line) + (if (search-backward "*" nil t) + (list (progn (beginning-of-line) (point)) + (progn + (forward-line 1) + (if (re-search-forward "^[ \t]*[\n*]" nil t) + (match-beginning 0) + (point-max)))) + (list (point) (point)))) + +(defun log-edit-changelog-entry () + "Return the bounds of the ChangeLog entry containing point. +The variable `log-edit-changelog-full-paragraphs' decides whether an +\"entry\" is a paragraph or a subparagraph; see its documentation string +for more details." + (save-excursion + (if log-edit-changelog-full-paragraphs + (log-edit-changelog-paragraph) + (log-edit-changelog-subparagraph)))) + +(defvar user-full-name) +(defvar user-mail-address) +(defun log-edit-changelog-ours-p () + "See if ChangeLog entry at point is for the current user, today. +Return non-nil if it is." + ;; Code adapted from add-change-log-entry. + (let ((name (or (and (boundp 'add-log-full-name) add-log-full-name) + (and (fboundp 'user-full-name) (user-full-name)) + (and (boundp 'user-full-name) user-full-name))) + (mail (or (and (boundp 'add-log-mailing-address) add-log-mailing-address) + ;;(and (fboundp 'user-mail-address) (user-mail-address)) + (and (boundp 'user-mail-address) user-mail-address))) + (time (or (and (boundp 'add-log-time-format) + (functionp add-log-time-format) + (funcall add-log-time-format)) + (format-time-string "%Y-%m-%d")))) + (looking-at (if log-edit-changelog-use-first + "[^ \t]" + (regexp-quote (format "%s %s <%s>" time name mail)))))) + +(defun log-edit-changelog-entries (file) + "Return the ChangeLog entries for FILE, and the ChangeLog they came from. +The return value looks like this: + (LOGBUFFER (ENTRYSTART ENTRYEND) ...) +where LOGBUFFER is the name of the ChangeLog buffer, and each +\(ENTRYSTART . ENTRYEND\) pair is a buffer region." + (let ((changelog-file-name + (let ((default-directory + (file-name-directory (expand-file-name file))) + (visiting-buffer (find-buffer-visiting file))) + ;; If there is a buffer visiting FILE, and it has a local + ;; value for `change-log-default-name', use that. + (if (and visiting-buffer + (local-variable-p 'change-log-default-name + visiting-buffer)) + (with-current-buffer visiting-buffer + change-log-default-name) + ;; `find-change-log' uses `change-log-default-name' if set + ;; and sets it before exiting, so we need to work around + ;; that memoizing which is undesired here + (setq change-log-default-name nil) + (find-change-log))))) + (with-current-buffer (find-file-noselect changelog-file-name) + (unless (eq major-mode 'change-log-mode) (change-log-mode)) + (goto-char (point-min)) + (if (looking-at "\\s-*\n") (goto-char (match-end 0))) + (if (not (log-edit-changelog-ours-p)) + (list (current-buffer)) + (save-restriction + (log-edit-narrow-changelog) + (goto-char (point-min)) + + ;; Search for the name of FILE relative to the ChangeLog. If that + ;; doesn't occur anywhere, they're not using full relative + ;; filenames in the ChangeLog, so just look for FILE; we'll accept + ;; some false positives. + (let ((pattern (file-relative-name + file (file-name-directory changelog-file-name)))) + (if (or (string= pattern "") + (not (save-excursion + (search-forward pattern nil t)))) + (setq pattern (file-name-nondirectory file))) + + (setq pattern (concat "\\(^\\|[^[:alnum:]]\\)" + pattern + "\\($\\|[^[:alnum:]]\\)")) + + (let (texts + (pos (point))) + (while (and (not (eobp)) (re-search-forward pattern nil t)) + (let ((entry (log-edit-changelog-entry))) + (if (< (elt entry 1) (max (1+ pos) (point))) + ;; This is not relevant, actually. + nil + (push entry texts)) + ;; Make sure we make progress. + (setq pos (max (1+ pos) (elt entry 1))) + (goto-char pos))) + + (cons (current-buffer) texts)))))))) + +(defun log-edit-changelog-insert-entries (buffer beg end &rest files) + "Insert the text from BUFFER between BEG and END. +Rename relative filenames in the ChangeLog entry as FILES." + (let ((opoint (point)) + (log-name (buffer-file-name buffer)) + (case-fold-search nil) + bound) + (insert-buffer-substring buffer beg end) + (setq bound (point-marker)) + (when log-name + (dolist (f files) + (save-excursion + (goto-char opoint) + (when (re-search-forward + (concat "\\(^\\|[ \t]\\)\\(" + (file-relative-name f (file-name-directory log-name)) + "\\)[, :\n]") + bound t) + (replace-match f t t nil 2))))) + ;; Eliminate tabs at the beginning of the line. + (save-excursion + (goto-char opoint) + (while (re-search-forward "^\\(\t+\\)" bound t) + (replace-match ""))))) + +(defun log-edit-insert-changelog-entries (files) + "Given a list of files FILES, insert the ChangeLog entries for them." + (let ((log-entries nil)) + ;; Note that any ChangeLog entry can apply to more than one file. + ;; Here we construct a log-entries list with elements of the form + ;; ((LOGBUFFER ENTRYSTART ENTRYEND) FILE1 FILE2...) + (dolist (file files) + (let* ((entries (log-edit-changelog-entries file)) + (buf (car entries)) + key entry) + (dolist (region (cdr entries)) + (setq key (cons buf region)) + (if (setq entry (assoc key log-entries)) + (setcdr entry (append (cdr entry) (list file))) + (push (list key file) log-entries))))) + ;; Now map over log-entries, and extract the strings. + (dolist (log-entry (nreverse log-entries)) + (apply 'log-edit-changelog-insert-entries + (append (car log-entry) (cdr log-entry))) + (insert "\n")))) + +(defun log-edit-extract-headers (headers comment) + "Extract headers from COMMENT to form command line arguments. +HEADERS should be an alist with elements of the form (HEADER . CMDARG) +associating header names to the corresponding cmdline option name and the +result is then a list of the form (MSG CMDARG1 HDRTEXT1 CMDARG2 HDRTEXT2...). +where MSG is the remaining text from STRING. +If \"Summary\" is not in HEADERS, then the \"Summary\" header is extracted +anyway and put back as the first line of MSG." + (with-temp-buffer + (insert comment) + (rfc822-goto-eoh) + (narrow-to-region (point-min) (point)) + (let ((case-fold-search t) + (summary ()) + (res ())) + (dolist (header (if (assoc "Summary" headers) headers + (cons '("Summary" . t) headers))) + (goto-char (point-min)) + (while (re-search-forward (concat "^" (car header) + ":" log-edit-header-contents-regexp) + nil t) + (if (eq t (cdr header)) + (setq summary (match-string 1)) + (push (match-string 1) res) + (push (or (cdr header) (car header)) res)) + (replace-match "" t t))) + ;; Remove header separator if the header is empty. + (widen) + (goto-char (point-min)) + (when (looking-at "\\([ \t]*\n\\)+") + (delete-region (match-beginning 0) (match-end 0))) + (if summary (insert summary "\n")) + (cons (buffer-string) res)))) + +(provide 'log-edit) + +;; arch-tag: 8089b39c-983b-4e83-93cd-ed0a64c7fdcc +;;; log-edit.el ends here diff -r 681cd08dc0f7 -r e93288477c43 lisp/vc/log-view.el --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/lisp/vc/log-view.el Sun Jun 13 22:57:55 2010 +0000 @@ -0,0 +1,545 @@ +;;; log-view.el --- Major mode for browsing RCS/CVS/SCCS log output + +;; Copyright (C) 1999, 2000, 2001, 2002, 2003, 2004, 2005, 2006, 2007, +;; 2008, 2009, 2010 Free Software Foundation, Inc. + +;; Author: Stefan Monnier +;; Keywords: rcs, sccs, cvs, log, vc, tools + +;; 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 3 of the License, 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. If not, see . + +;;; Commentary: + +;; Major mode to browse revision log histories. +;; Currently supports the format output by: +;; RCS, SCCS, CVS, Subversion, and DaRCS. + +;; Examples of log output: + +;;;; RCS/CVS: + +;; ---------------------------- +;; revision 1.35 locked by: turlutut +;; date: 2005-03-22 18:48:38 +0000; author: monnier; state: Exp; lines: +6 -8 +;; (gnus-display-time-event-handler): +;; Check display-time-timer at runtime rather than only at load time +;; in case display-time-mode is turned off in the mean time. +;; ---------------------------- +;; revision 1.34 +;; date: 2005-02-09 15:50:38 +0000; author: kfstorm; state: Exp; lines: +7 -7 +;; branches: 1.34.2; +;; Change release version from 21.4 to 22.1 throughout. +;; Change development version from 21.3.50 to 22.0.50. + +;;;; SCCS: + +;;;; Subversion: + +;; ------------------------------------------------------------------------ +;; r4622 | ckuethe | 2007-12-23 18:18:01 -0500 (Sun, 23 Dec 2007) | 2 lines +;; +;; uBlox AEK-4T in binary mode. Added to unstable because it breaks gpsfake +;; +;; ------------------------------------------------------------------------ +;; r4621 | ckuethe | 2007-12-23 16:48:11 -0500 (Sun, 23 Dec 2007) | 3 lines +;; +;; Add a note about requiring usbfs to use the garmin gps18 (usb) +;; Mention firmware testing the AC12 with firmware BQ00 and BQ04 +;; +;; ------------------------------------------------------------------------ +;; r4620 | ckuethe | 2007-12-23 15:52:34 -0500 (Sun, 23 Dec 2007) | 1 line +;; +;; add link to latest hardware reference +;; ------------------------------------------------------------------------ +;; r4619 | ckuethe | 2007-12-23 14:37:31 -0500 (Sun, 23 Dec 2007) | 1 line +;; +;; there is now a regression test for AC12 without raw data output + +;;;; Darcs: + +;; Changes to darcsum.el: +;; +;; Mon Nov 28 15:19:38 GMT 2005 Dave Love +;; * Abstract process startup into darcsum-start-process. Use TERM=dumb. +;; TERM=dumb avoids escape characters, at least, for any old darcs that +;; doesn't understand DARCS_DONT_COLOR & al. +;; +;; Thu Nov 24 15:20:45 GMT 2005 Dave Love +;; * darcsum-mode-related changes. +;; Don't call font-lock-mode (unnecessary) or use-local-map (redundant). +;; Use mode-class 'special. Add :group. +;; Add trailing-whitespace option to mode hook and fix +;; darcsum-display-changeset not to use trailing whitespace. + +;;;; Mercurial + +;; changeset: 11:8ff1a4166444 +;; tag: tip +;; user: Eric S. Raymond +;; date: Wed Dec 26 12:18:58 2007 -0500 +;; summary: Explain keywords. Add markup fixes. +;; +;; changeset: 10:20abc7ab09c3 +;; user: Eric S. Raymond +;; date: Wed Dec 26 11:37:28 2007 -0500 +;; summary: Typo fixes. +;; +;; changeset: 9:ada9f4da88aa +;; user: Eric S. Raymond +;; date: Wed Dec 26 11:23:00 2007 -0500 +;; summary: Add RCS example session. + +;;; Todo: + +;; - add ability to modify a log-entry (via cvs-mode-admin ;-) +;; - remove references to cvs-* +;; - make it easier to add support for new backends without changing the code. + +;;; Code: + +(eval-when-compile (require 'cl)) +(require 'pcvs-util) +(autoload 'vc-find-revision "vc") +(autoload 'vc-diff-internal "vc") + +(defvar cvs-minor-wrap-function) + +(defgroup log-view nil + "Major mode for browsing log output of RCS/CVS/SCCS." + :group 'pcl-cvs + :prefix "log-view-") + +;; Needed because log-view-mode-map inherits from widget-keymap. (Bug#5311) +(require 'wid-edit) + +(easy-mmode-defmap log-view-mode-map + '(("z" . kill-this-buffer) + ("q" . quit-window) + ("m" . log-view-toggle-mark-entry) + ("e" . log-view-modify-change-comment) + ("d" . log-view-diff) + ("=" . log-view-diff) + ("D" . log-view-diff-changeset) + ("a" . log-view-annotate-version) + ("f" . log-view-find-revision) + ("n" . log-view-msg-next) + ("p" . log-view-msg-prev) + ("\t" . log-view-msg-next) + ([backtab] . log-view-msg-prev) + ("N" . log-view-file-next) + ("P" . log-view-file-prev) + ("\M-n" . log-view-file-next) + ("\M-p" . log-view-file-prev)) + "Log-View's keymap." + :inherit widget-keymap + :group 'log-view) + +(easy-menu-define log-view-mode-menu log-view-mode-map + "Log-View Display Menu" + `("Log-View" + ;; XXX Do we need menu entries for these? + ;; ["Quit" quit-window] + ;; ["Kill This Buffer" kill-this-buffer] + ["Mark Log Entry for Diff" set-mark-command + :help ""] + ["Diff Revisions" log-view-diff + :help "Get the diff between two revisions"] + ["Changeset Diff" log-view-diff-changeset + :help "Get the changeset diff between two revisions"] + ["Visit Version" log-view-find-revision + :help "Visit the version at point"] + ["Annotate Version" log-view-annotate-version + :help "Annotate the version at point"] + ["Modify Log Comment" log-view-modify-change-comment + :help "Edit the change comment displayed at point"] + "-----" + ["Next Log Entry" log-view-msg-next + :help "Go to the next count'th log message"] + ["Previous Log Entry" log-view-msg-prev + :help "Go to the previous count'th log message"] + ["Next File" log-view-file-next + :help "Go to the next count'th file"] + ["Previous File" log-view-file-prev + :help "Go to the previous count'th file"])) + +(defvar log-view-mode-hook nil + "Hook run at the end of `log-view-mode'.") + +(defface log-view-file + '((((class color) (background light)) + (:background "grey70" :weight bold)) + (t (:weight bold))) + "Face for the file header line in `log-view-mode'." + :group 'log-view) +(define-obsolete-face-alias 'log-view-file-face 'log-view-file "22.1") +(defvar log-view-file-face 'log-view-file) + +(defface log-view-message + '((((class color) (background light)) + (:background "grey85")) + (t (:weight bold))) + "Face for the message header line in `log-view-mode'." + :group 'log-view) +;; backward-compatibility alias +(define-obsolete-face-alias 'log-view-message-face 'log-view-message "22.1") +(defvar log-view-message-face 'log-view-message) + +(defvar log-view-file-re + (concat "^\\(?:Working file: \\(?1:.+\\)" ;RCS and CVS. + ;; Subversion has no such thing?? + "\\|\\(?:SCCS/s\\.\\|Changes to \\)\\(?1:.+\\):" ;SCCS and Darcs. + "\\)\n") ;Include the \n for font-lock reasons. + "Regexp matching the text identifying the file. +The match group number 1 should match the file name itself.") + +(defvar log-view-per-file-logs t + "Set if to t if the logs are shown one file at a time.") + +(defvar log-view-message-re + (concat "^\\(?:revision \\(?1:[.0-9]+\\)\\(?:\t.*\\)?" ; RCS and CVS. + "\\|r\\(?1:[0-9]+\\) | .* | .*" ; Subversion. + "\\|D \\(?1:[.0-9]+\\) .*" ; SCCS. + ;; Darcs doesn't have revision names. VC-darcs uses patch names + ;; instead. Darcs patch names are hashcodes, which do not appear + ;; in the log output :-(, but darcs accepts any prefix of the log + ;; message as a patch name, so we match the first line of the log + ;; message. + ;; First loosely match the date format. + (concat "\\|[^ \n].*[^0-9\n][0-9][0-9]:[0-9][0-9][^0-9\n].*[^ \n]" + ;;Email of user and finally Msg, used as revision name. + " .*@.*\n\\(?: \\* \\(?1:.*\\)\\)?") + "\\)$") + "Regexp matching the text identifying a revision. +The match group number 1 should match the revision number itself.") + +(defvar log-view-font-lock-keywords + ;; We use `eval' so as to use the buffer-local value of log-view-file-re + ;; and log-view-message-re, if applicable. + '((eval . `(,log-view-file-re + (1 (if (boundp 'cvs-filename-face) cvs-filename-face)) + (0 log-view-file-face append))) + (eval . `(,log-view-message-re . log-view-message-face)))) + +(defconst log-view-font-lock-defaults + '(log-view-font-lock-keywords t nil nil nil)) + +(defvar log-view-vc-fileset nil + "Set this to the fileset corresponding to the current log.") + +(defvar log-view-vc-backend nil + "Set this to the VC backend that created the current log.") + +;;;; +;;;; Actual code +;;;; + +;;;###autoload +(define-derived-mode log-view-mode special-mode "Log-View" + "Major mode for browsing CVS log output." + (setq buffer-read-only t) + (set (make-local-variable 'font-lock-defaults) log-view-font-lock-defaults) + (set (make-local-variable 'beginning-of-defun-function) + 'log-view-beginning-of-defun) + (set (make-local-variable 'end-of-defun-function) + 'log-view-end-of-defun) + (set (make-local-variable 'cvs-minor-wrap-function) 'log-view-minor-wrap) + (hack-dir-local-variables-non-file-buffer)) + +;;;; +;;;; Navigation +;;;; + +;; define log-view-{msg,file}-{next,prev} +(easy-mmode-define-navigation log-view-msg log-view-message-re "log message") +(easy-mmode-define-navigation log-view-file log-view-file-re "file") + +(defun log-view-goto-rev (rev) + (goto-char (point-min)) + (ignore-errors + (while (not (equal rev (log-view-current-tag))) + (log-view-msg-next)) + t)) + +;;;; +;;;; Linkage to PCL-CVS (mostly copied from cvs-status.el) +;;;; + +(defconst log-view-dir-re "^cvs[.ex]* [a-z]+: Logging \\(.+\\)$") + +(defun log-view-current-file () + (save-excursion + (forward-line 1) + (or (re-search-backward log-view-file-re nil t) + (re-search-forward log-view-file-re nil t) + (error "Unable to determine the current file")) + (let* ((file (match-string 1)) + (cvsdir (and (re-search-backward log-view-dir-re nil t) + (match-string 1))) + (pcldir (and (boundp 'cvs-pcl-cvs-dirchange-re) + (re-search-backward cvs-pcl-cvs-dirchange-re nil t) + (match-string 1))) + (dir "")) + (let ((default-directory "")) + (when pcldir (setq dir (expand-file-name pcldir dir))) + (when cvsdir (setq dir (expand-file-name cvsdir dir)))) + (expand-file-name file dir)))) + +(defun log-view-current-tag (&optional where) + (save-excursion + (when where (goto-char where)) + (forward-line 1) + (let ((pt (point))) + (when (re-search-backward log-view-message-re nil t) + (let ((rev (match-string-no-properties 1))) + (unless (re-search-forward log-view-file-re pt t) + rev)))))) + +(defun log-view-toggle-mark-entry () + "Toggle the marked state for the log entry at point. +Individual log entries can be marked and unmarked. The marked +entries are denoted by changing their background color. +`log-view-get-marked' returns the list of tags for the marked +log entries." + (interactive) + (save-excursion + (forward-line 1) + (let ((pt (point))) + (when (re-search-backward log-view-message-re nil t) + (let ((beg (match-beginning 0)) + end ov ovlist found tag) + (unless (re-search-forward log-view-file-re pt t) + ;; Look to see if the current entry is marked. + (setq found (get-char-property (point) 'log-view-self)) + (if found + (delete-overlay found) + ;; Create an overlay that covers this entry and change + ;; its color. + (setq tag (log-view-current-tag (point))) + (forward-line 1) + (setq end + (if (re-search-forward log-view-message-re nil t) + (match-beginning 0) + (point-max))) + (setq ov (make-overlay beg end)) + (overlay-put ov 'face 'log-view-file) + ;; This is used to check if the overlay is present. + (overlay-put ov 'log-view-self ov) + (overlay-put ov 'log-view-marked tag)))))))) + +(defun log-view-get-marked () + "Return the list of tags for the marked log entries." + (save-excursion + (let ((pos (point-min)) + marked-list ov) + (while (setq pos (next-single-property-change pos 'face)) + (when (setq ov (get-char-property pos 'log-view-self)) + (push (overlay-get ov 'log-view-marked) marked-list) + (setq pos (overlay-end ov)))) + marked-list))) + +(defun log-view-beginning-of-defun () + ;; This assumes that a log entry starts with a line matching + ;; `log-view-message-re'. Modes that derive from `log-view-mode' + ;; for which this assumption is not valid will have to provide + ;; another implementation of this function. `log-view-msg-prev' + ;; does a similar job to this function, we can't use it here + ;; directly because it prints messages that are not appropriate in + ;; this context and it does not move to the beginning of the buffer + ;; when the point is before the first log entry. + + ;; `log-view-beginning-of-defun' and `log-view-end-of-defun' have + ;; been checked to work with logs produced by RCS, CVS, git, + ;; mercurial and subversion. + + (re-search-backward log-view-message-re nil 'move)) + +(defun log-view-end-of-defun () + ;; The idea in this function is to search for the beginning of the + ;; next log entry using `log-view-message-re' and then go back one + ;; line when finding it. Modes that derive from `log-view-mode' for + ;; which this assumption is not valid will have to provide another + ;; implementation of this function. + + ;; Look back and if there is no entry there it means we are before + ;; the first log entry, so go forward until finding one. + (unless (save-excursion (re-search-backward log-view-message-re nil t)) + (re-search-forward log-view-message-re nil t)) + + ;; In case we are at the end of log entry going forward a line will + ;; make us find the next entry when searching. If we are inside of + ;; an entry going forward a line will still keep the point inside + ;; the same entry. + (forward-line 1) + + ;; In case we are at the beginning of an entry, move past it. + (when (looking-at log-view-message-re) + (goto-char (match-end 0)) + (forward-line 1)) + + ;; Search for the start of the next log entry. Go to the end of the + ;; buffer if we could not find a next entry. + (when (re-search-forward log-view-message-re nil 'move) + (goto-char (match-beginning 0)) + (forward-line -1))) + +(defvar cvs-minor-current-files) +(defvar cvs-branch-prefix) +(defvar cvs-secondary-branch-prefix) + +(defun log-view-minor-wrap (buf f) + (let ((data (with-current-buffer buf + (let* ((beg (point)) + (end (if mark-active (mark) (point))) + (fr (log-view-current-tag beg)) + (to (log-view-current-tag end))) + (when (string-equal fr to) + (save-excursion + (goto-char end) + (log-view-msg-next) + (setq to (log-view-current-tag)))) + (cons + ;; The first revision has to be the one at point, for + ;; operations that only take one revision + ;; (e.g. cvs-mode-edit). + (cons (log-view-current-file) fr) + (cons (log-view-current-file) to)))))) + (let ((cvs-branch-prefix (cdar data)) + (cvs-secondary-branch-prefix (and (cdar data) (cddr data))) + (cvs-minor-current-files + (cons (caar data) + (when (and (cadr data) (not (equal (caar data) (cadr data)))) + (list (cadr data))))) + ;; FIXME: I need to force because the fileinfos are UNKNOWN + (cvs-force-command "/F")) + (funcall f)))) + +(defun log-view-find-revision (pos) + "Visit the version at point." + (interactive "d") + (unless log-view-per-file-logs + (when (> (length log-view-vc-fileset) 1) + (error "Multiple files shown in this buffer, cannot use this command here"))) + (save-excursion + (goto-char pos) + (switch-to-buffer (vc-find-revision (if log-view-per-file-logs + (log-view-current-file) + (car log-view-vc-fileset)) + (log-view-current-tag))))) + + +(defun log-view-extract-comment () + "Parse comment from around the current point in the log." + (save-excursion + (let (st en (backend (vc-backend (log-view-current-file)))) + (log-view-end-of-defun) + (cond ((eq backend 'SVN) + (forward-line -1))) + (setq en (point)) + (log-view-beginning-of-defun) + (cond ((memq backend '(SCCS RCS CVS MCVS SVN)) + (forward-line 2)) + ((eq backend 'Hg) + (forward-line 4) + (re-search-forward "summary: *" nil t))) + (setq st (point)) + (buffer-substring st en)))) + +(declare-function vc-modify-change-comment "vc" (files rev oldcomment)) + +(defun log-view-modify-change-comment () + "Edit the change comment displayed at point." + (interactive) + (vc-modify-change-comment (list (if log-view-per-file-logs + (log-view-current-file) + (car log-view-vc-fileset))) + (log-view-current-tag) + (log-view-extract-comment))) + +(defun log-view-annotate-version (pos) + "Annotate the version at point." + (interactive "d") + (unless log-view-per-file-logs + (when (> (length log-view-vc-fileset) 1) + (error "Multiple files shown in this buffer, cannot use this command here"))) + (save-excursion + (goto-char pos) + (vc-annotate (if log-view-per-file-logs + (log-view-current-file) + (car log-view-vc-fileset)) + (log-view-current-tag)))) + +;; +;; diff +;; + +(defun log-view-diff (beg end) + "Get the diff between two revisions. +If the mark is not active or the mark is on the revision at point, +get the diff between the revision at point and its previous revision. +Otherwise, get the diff between the revisions where the region starts +and ends. +Contrary to `log-view-diff-changeset', it will only show the part of the +changeset that affected the currently considered file(s)." + (interactive + (list (if mark-active (region-beginning) (point)) + (if mark-active (region-end) (point)))) + (let ((fr (log-view-current-tag beg)) + (to (log-view-current-tag end))) + (when (string-equal fr to) + (save-excursion + (goto-char end) + (log-view-msg-next) + (setq to (log-view-current-tag)))) + (vc-diff-internal + t (list log-view-vc-backend + (if log-view-per-file-logs + (list (log-view-current-file)) + log-view-vc-fileset)) + to fr))) + +(declare-function vc-diff-internal "vc" + (async vc-fileset rev1 rev2 &optional verbose)) + +(defun log-view-diff-changeset (beg end) + "Get the diff between two revisions. +If the mark is not active or the mark is on the revision at point, +get the diff between the revision at point and its previous revision. +Otherwise, get the diff between the revisions where the region starts +and ends. +Contrary to `log-view-diff', it will show the whole changeset including +the changes that affected other files than the currently considered file(s)." + (interactive + (list (if mark-active (region-beginning) (point)) + (if mark-active (region-end) (point)))) + (when (eq (vc-call-backend log-view-vc-backend 'revision-granularity) 'file) + (error "The %s backend does not support changeset diffs" log-view-vc-backend)) + (let ((fr (log-view-current-tag beg)) + (to (log-view-current-tag end))) + (when (string-equal fr to) + ;; TO and FR are the same, look at the previous revision. + (setq to (vc-call-backend log-view-vc-backend 'previous-revision nil fr))) + (vc-diff-internal + t + ;; We want to see the diff for all the files in the changeset, so + ;; pass NIL for the file list. The value passed here should + ;; follow what `vc-deduce-fileset' returns. + (list log-view-vc-backend nil) + to fr))) + +(provide 'log-view) + +;; arch-tag: 0d64220b-ce7e-4f62-9c2a-6b04c2f81f4f +;;; log-view.el ends here diff -r 681cd08dc0f7 -r e93288477c43 lisp/vc/pcvs-defs.el --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/lisp/vc/pcvs-defs.el Sun Jun 13 22:57:55 2010 +0000 @@ -0,0 +1,528 @@ +;;; pcvs-defs.el --- variable definitions for PCL-CVS + +;; Copyright (C) 1991, 1992, 1993, 1994, 1995, 1996, 1997, 1998, 1999, +;; 2000, 2001, 2002, 2003, 2004, 2005, 2006, 2007, 2008, 2009, 2010 +;; Free Software Foundation, Inc. + +;; Author: Stefan Monnier +;; Keywords: pcl-cvs + +;; 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 3 of the License, 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. If not, see . + +;;; Commentary: + + +;;; Code: + +(eval-when-compile (require 'cl)) +(require 'pcvs-util) + +;;;; ------------------------------------------------------- +;;;; START OF THINGS TO CHECK WHEN INSTALLING + +(defvar cvs-program "cvs" + "*Name or full path of the cvs executable.") + +(defvar cvs-version + ;; With the divergence of the CVSNT codebase and version numbers, this is + ;; not really good any more. + (ignore-errors + (with-temp-buffer + (call-process cvs-program nil t nil "-v") + (goto-char (point-min)) + (when (re-search-forward "(CVS\\(NT\\)?) \\([0-9]+\\)\\.\\([0-9]+\\)" + nil t) + (cons (string-to-number (match-string 1)) + (string-to-number (match-string 2)))))) + "*Version of `cvs' installed on your system. +It must be in the (MAJOR . MINOR) format.") + +;; FIXME: this is only used by cvs-mode-diff-backup +(defvar cvs-diff-program (or (and (boundp 'diff-command) diff-command) "diff") + "*Name or full path of the best diff program you've got. +NOTE: there are some nasty bugs in the context diff variants of some vendor +versions, such as the one in SunOS-4.") + +;;;; END OF THINGS TO CHECK WHEN INSTALLING +;;;; -------------------------------------------------------- + +;;;; +;;;; User configuration variables: +;;;; +;;;; NOTE: these should be set in your ~/.emacs (or site-lisp/default.el) file. +;;;; + +(defgroup pcl-cvs nil + "Special support for the CVS versioning system." + :version "21.1" + :group 'tools + :prefix "cvs-") + +;; +;; cvsrc options +;; + +(defcustom cvs-cvsrc-file (convert-standard-filename "~/.cvsrc") + "Path to your cvsrc file." + :group 'pcl-cvs + :type '(file)) + +(defvar cvs-shared-start 4 + "Index of the first shared flag. +If set to 4, for instance, a numeric argument smaller than 4 will +select a non-shared flag, while a numeric argument greater than 3 +will select a shared-flag.") + +(defvar cvs-shared-flags (make-list cvs-shared-start nil) + "List of flags whose settings is shared among several commands.") + +(defvar cvs-cvsroot nil + "*Specifies where the (current) cvs master repository is. +Overrides the environment variable $CVSROOT by sending \" -d dir\" to +all CVS commands. This switch is useful if you have multiple CVS +repositories. It can be set interactively with \\[cvs-change-cvsroot.] +There is no need to set this if $CVSROOT is set to a correct value.") + +(defcustom cvs-auto-remove-handled nil + "If up-to-date files should be acknowledged automatically. +If T, they will be removed from the *cvs* buffer after every command. +If DELAYED, they will be removed from the *cvs* buffer before every command. +If STATUS, they will only be removed after a `cvs-mode-status' command. +Else, they will never be automatically removed from the *cvs* buffer." + :group 'pcl-cvs + :type '(choice (const nil) (const status) (const delayed) (const t))) + +(defcustom cvs-auto-remove-directories 'handled + "If ALL, directory entries will never be shown. +If HANDLED, only non-handled directories will be shown. +If EMPTY, only non-empty directories will be shown." + :group 'pcl-cvs + :type '(choice (const :tag "No" nil) (const all) (const handled) (const empty))) + +(defcustom cvs-auto-revert t + "Non-nil if changed files should automatically be reverted." + :group 'pcl-cvs + :type '(boolean)) + +(defcustom cvs-sort-ignore-file t + "Non-nil if `cvs-mode-ignore' should sort the .cvsignore automatically." + :group 'pcl-cvs + :type '(boolean)) + +(defcustom cvs-force-dir-tag t + "If non-nil, tagging can only be applied to directories. +Tagging should generally be applied a directory at a time, but sometimes it is +useful to be able to tag a single file. The normal way to do that is to use +`cvs-mode-force-command' so as to temporarily override the restrictions," + :group 'pcl-cvs + :type '(boolean)) + +(defcustom cvs-default-ignore-marks nil + "Non-nil if cvs mode commands should ignore any marked files. +Normally they run on the files that are marked (with `cvs-mode-mark'), +or the file under the cursor if no files are marked. If this variable +is set to a non-nil value they will by default run on the file on the +current line. See also `cvs-invert-ignore-marks'" + :group 'pcl-cvs + :type '(boolean)) + +(defvar cvs-diff-ignore-marks t) +(make-obsolete-variable 'cvs-diff-ignore-marks + 'cvs-invert-ignore-marks + "21.1") + +(defcustom cvs-invert-ignore-marks + (let ((l ())) + (unless (equal cvs-diff-ignore-marks cvs-default-ignore-marks) + (push "diff" l)) + (when (and cvs-force-dir-tag (not cvs-default-ignore-marks)) + (push "tag" l)) + l) + "List of cvs commands that invert the default ignore-mark behavior. +Commands in this set will use the opposite default from the one set +in `cvs-default-ignore-marks'." + :group 'pcl-cvs + :type '(set (const "diff") + (const "tag") + (const "ignore"))) + +(defcustom cvs-confirm-removals t + "Ask for confirmation before removing files. +Non-nil means that PCL-CVS will ask confirmation before removing files +except for files whose content can readily be recovered from the repository. +A value of `list' means that the list of files to be deleted will be +displayed when asking for confirmation." + :group 'pcl-cvs + :type '(choice (const list) + (const t) + (const nil))) + +(defcustom cvs-add-default-message nil + "Default message to use when adding files. +If set to nil, `cvs-mode-add' will always prompt for a message." + :group 'pcl-cvs + :type '(choice (const :tag "Prompt" nil) + (string))) + +(defvar cvs-diff-buffer-name "*cvs-diff*") +(make-obsolete-variable 'cvs-diff-buffer-name + 'cvs-buffer-name-alist + "21.1") + +(defcustom cvs-find-file-and-jump nil + "Jump to the modified area when finding a file. +If non-nil, `cvs-mode-file-file' will place the cursor at the beginning of +the modified area. If the file is not locally modified, this will obviously +have no effect." + :group 'pcl-cvs + :type '(boolean)) + +(defcustom cvs-buffer-name-alist + '(("diff" cvs-diff-buffer-name diff-mode) + ("status" "*cvs-info*" cvs-status-mode) + ("tree" "*cvs-info*" cvs-status-mode) + ("message" "*cvs-commit*" nil log-edit) + ("log" "*cvs-info*" log-view-mode)) + "Buffer name and mode to be used for each command. +This is a list of elements of the form + + (CMD BUFNAME MODE &optional POSTPROC) + +CMD is the name of the command. +BUFNAME is an expression that should evaluate to a string used as + a buffer name. It can use the variable CMD if it wants to. +MODE is the command to use to setup the buffer. +POSTPROC is a function that should be executed when the command terminates + +The CMD used for `cvs-mode-commit' is \"message\". For that special + case, POSTPROC is called just after MODE with special arguments." + :group 'pcl-cvs + :type '(repeat + (list (choice (const "diff") + (const "status") + (const "tree") + (const "message") + (const "log") + (string)) + (choice (const "*vc-diff*") + (const "*cvs-info*") + (const "*cvs-commit*") + (const (expand-file-name "*cvs-commit*")) + (const (format "*cvs-%s*" cmd)) + (const (expand-file-name (format "*cvs-%s*" cmd))) + (sexp :value "my-cvs-info-buffer") + (const nil)) + (choice (function-item diff-mode) + (function-item cvs-edit-mode) + (function-item cvs-status-mode) + function + (const nil)) + (set :inline t + (choice (function-item cvs-status-cvstrees) + (function-item cvs-status-trees) + function))))) + +(defvar cvs-buffer-name '(expand-file-name "*cvs*" dir) ;; "*cvs*" + "Name of the cvs buffer. +This expression will be evaluated in an environment where DIR is set to +the directory name of the cvs buffer.") + +(defvar cvs-temp-buffer-name + ;; Was '(expand-file-name " *cvs-tmp*" dir), but that causes them to + ;; become non-hidden if uniquification is done `forward'. + " *cvs-tmp*" + "*Name of the cvs temporary buffer. +Output from cvs is placed here for asynchronous commands.") + +(defcustom cvs-idiff-imerge-handlers + (if (fboundp 'ediff) + '(cvs-ediff-diff . cvs-ediff-merge) + '(cvs-emerge-diff . cvs-emerge-merge)) + "Pair of functions to be used for resp. diff'ing and merg'ing interactively." + :group 'pcl-cvs + :type '(choice (const :tag "Ediff" (cvs-ediff-diff . cvs-ediff-merge)) + (const :tag "Emerge" (cvs-emerge-diff . cvs-emerge-merge)))) + +(defvar cvs-mode-hook nil + "Run after `cvs-mode' was setup.") + + +;;;; +;;;; Internal variables, used in the process buffer. +;;;; + +(defvar cvs-postprocess nil + "(Buffer local) what to do once the process exits.") + +;;;; +;;;; Internal variables for the *cvs* buffer. +;;;; + +(defcustom cvs-reuse-cvs-buffer 'subdir + "When to reuse an existing cvs buffer. +Alternatives are: + CURRENT: just reuse the current buffer if it is a cvs buffer + SAMEDIR: reuse any cvs buffer displaying the same directory + SUBDIR: or reuse any cvs buffer displaying any sub- or super- directory + ALWAYS: reuse any cvs buffer." + :group 'pcl-cvs + :type '(choice (const always) (const subdir) (const samedir) (const current))) + +(defvar cvs-temp-buffer nil + "(Buffer local) The temporary buffer associated with this *cvs* buffer.") + +(defvar cvs-lock-file nil + "Full path to a lock file that CVS is waiting for (or was waiting for). +This variable is buffer local and only used in the *cvs* buffer.") + +(defvar cvs-lock-file-regexp "^#cvs\\.\\([trw]fl\\.[-.a-z0-9]+\\|lock\\)\\'" + "Regexp matching the possible names of locks in the CVS repository.") + +(defconst cvs-cursor-column 22 + "Column to position cursor in in `cvs-mode'.") + +;;;; +;;;; Global internal variables +;;;; + +(defconst cvs-vendor-branch "1.1.1" + "The default branch used by CVS for vendor code.") + +(easy-mmode-defmap cvs-mode-diff-map + '(("E" "imerge" . cvs-mode-imerge) + ("=" . cvs-mode-diff) + ("e" "idiff" . cvs-mode-idiff) + ("2" "other" . cvs-mode-idiff-other) + ("d" "diff" . cvs-mode-diff) + ("b" "backup" . cvs-mode-diff-backup) + ("h" "head" . cvs-mode-diff-head) + ("r" "repository" . cvs-mode-diff-repository) + ("y" "yesterday" . cvs-mode-diff-yesterday) + ("v" "vendor" . cvs-mode-diff-vendor)) + "Keymap for diff-related operations in `cvs-mode'." + :name "Diff") +;; This is necessary to allow correct handling of \\[cvs-mode-diff-map] +;; in substitute-command-keys. +(fset 'cvs-mode-diff-map cvs-mode-diff-map) + +(easy-mmode-defmap cvs-mode-map + ;;(define-prefix-command 'cvs-mode-map-diff-prefix) + ;;(define-prefix-command 'cvs-mode-map-control-c-prefix) + '(;; various + ;; (undo . cvs-mode-undo) + ("?" . cvs-help) + ("h" . cvs-help) + ("q" . cvs-bury-buffer) + ("z" . kill-this-buffer) + ("F" . cvs-mode-set-flags) + ;; ("\M-f" . cvs-mode-force-command) + ("!" . cvs-mode-force-command) + ("\C-c\C-c" . cvs-mode-kill-process) + ;; marking + ("m" . cvs-mode-mark) + ("M" . cvs-mode-mark-all-files) + ("S" . cvs-mode-mark-on-state) + ("u" . cvs-mode-unmark) + ("\C-?". cvs-mode-unmark-up) + ("%" . cvs-mode-mark-matching-files) + ("T" . cvs-mode-toggle-marks) + ("\M-\C-?" . cvs-mode-unmark-all-files) + ;; navigation keys + (" " . cvs-mode-next-line) + ("n" . cvs-mode-next-line) + ("p" . cvs-mode-previous-line) + ("\t" . cvs-mode-next-line) + ([backtab] . cvs-mode-previous-line) + ;; M- keys are usually those that operate on modules + ;;("\M-C". cvs-mode-rcs2log) ; i.e. "Create a ChangeLog" + ;;("\M-t". cvs-rtag) + ;;("\M-l". cvs-rlog) + ("\M-c". cvs-checkout) + ("\M-e". cvs-examine) + ("g" . cvs-mode-revert-buffer) + ("\M-u". cvs-update) + ("\M-s". cvs-status) + ;; diff commands + ("=" . cvs-mode-diff) + ("d" . cvs-mode-diff-map) + ;; keys that operate on individual files + ("\C-k" . cvs-mode-acknowledge) + ("A" . cvs-mode-add-change-log-entry-other-window) + ;;("B" . cvs-mode-byte-compile-files) + ("C" . cvs-mode-commit-setup) + ("O" . cvs-mode-update) + ("U" . cvs-mode-undo) + ("I" . cvs-mode-insert) + ("a" . cvs-mode-add) + ("b" . cvs-set-branch-prefix) + ("B" . cvs-set-secondary-branch-prefix) + ("c" . cvs-mode-commit) + ("e" . cvs-mode-examine) + ("f" . cvs-mode-find-file) + ("\C-m" . cvs-mode-find-file) + ("i" . cvs-mode-ignore) + ("l" . cvs-mode-log) + ("o" . cvs-mode-find-file-other-window) + ("r" . cvs-mode-remove) + ("s" . cvs-mode-status) + ("t" . cvs-mode-tag) + ("v" . cvs-mode-view-file) + ("x" . cvs-mode-remove-handled) + ;; cvstree bindings + ("+" . cvs-mode-tree) + ;; mouse bindings + ([mouse-2] . cvs-mode-find-file) + ([follow-link] . (lambda (pos) + (if (eq (get-char-property pos 'face) 'cvs-filename) t))) + ([(down-mouse-3)] . cvs-menu) + ;; dired-like bindings + ("\C-o" . cvs-mode-display-file) + ;; Emacs-21 toolbar + ;;([tool-bar item1] . (menu-item "Examine" cvs-examine :image (image :file "/usr/share/icons/xpaint.xpm" :type xpm))) + ;;([tool-bar item2] . (menu-item "Update" cvs-update :image (image :file "/usr/share/icons/mail1.xpm" :type xpm))) + ) + "Keymap for `cvs-mode'." + :dense t + :suppress t) + +(fset 'cvs-mode-map cvs-mode-map) + +(easy-menu-define cvs-menu cvs-mode-map "Menu used in `cvs-mode'." + '("CVS" + ["Open file" cvs-mode-find-file t] + ["Open in other window" cvs-mode-find-file-other-window t] + ["Display in other window" cvs-mode-display-file t] + ["Interactive merge" cvs-mode-imerge t] + ("View diff" + ["Interactive diff" cvs-mode-idiff t] + ["Current diff" cvs-mode-diff t] + ["Diff with head" cvs-mode-diff-head t] + ["Diff with vendor" cvs-mode-diff-vendor t] + ["Diff against yesterday" cvs-mode-diff-yesterday t] + ["Diff with backup" cvs-mode-diff-backup t]) + ["View log" cvs-mode-log t] + ["View status" cvs-mode-status t] + ["View tag tree" cvs-mode-tree t] + "----" + ["Insert" cvs-mode-insert] + ["Update" cvs-mode-update (cvs-enabledp 'update)] + ["Re-examine" cvs-mode-examine t] + ["Commit" cvs-mode-commit-setup (cvs-enabledp 'commit)] + ["Tag" cvs-mode-tag (cvs-enabledp (when cvs-force-dir-tag 'tag))] + ["Undo changes" cvs-mode-undo (cvs-enabledp 'undo)] + ["Add" cvs-mode-add (cvs-enabledp 'add)] + ["Remove" cvs-mode-remove (cvs-enabledp 'remove)] + ["Ignore" cvs-mode-ignore (cvs-enabledp 'ignore)] + ["Add ChangeLog" cvs-mode-add-change-log-entry-other-window t] + "----" + ["Mark" cvs-mode-mark t] + ["Mark all" cvs-mode-mark-all-files t] + ["Mark by regexp..." cvs-mode-mark-matching-files t] + ["Mark by state..." cvs-mode-mark-on-state t] + ["Unmark" cvs-mode-unmark t] + ["Unmark all" cvs-mode-unmark-all-files t] + ["Hide handled" cvs-mode-remove-handled t] + "----" + ["PCL-CVS Manual" (lambda () (interactive) + (info "(pcl-cvs)Top")) t] + "----" + ["Quit" cvs-mode-quit t])) + +;;;; +;;;; CVS-Minor mode +;;;; + +(defcustom cvs-minor-mode-prefix "\C-xc" + "Prefix key for the `cvs-mode' bindings in `cvs-minor-mode'." + :group 'pcl-cvs) + +(easy-mmode-defmap cvs-minor-mode-map + `((,cvs-minor-mode-prefix . cvs-mode-map) + ("e" . (menu-item nil cvs-mode-edit-log + :filter (lambda (x) (if (derived-mode-p 'log-view-mode) x))))) + "Keymap for `cvs-minor-mode', used in buffers related to PCL-CVS.") + +(defvar cvs-buffer nil + "(Buffer local) The *cvs* buffer associated with this buffer.") +(put 'cvs-buffer 'permanent-local t) +;;(make-variable-buffer-local 'cvs-buffer) + +(defvar cvs-minor-wrap-function nil + "Function to call when switching to the *cvs* buffer. +Takes two arguments: +- a *cvs* buffer. +- a zero-arg function which is guaranteed not to switch buffer. +It is expected to call the function.") +;;(make-variable-buffer-local 'cvs-minor-wrap-function) + +(defvar cvs-minor-current-files) +;;"Current files in a `cvs-minor-mode' buffer." +;; This should stay `void' because we want to be able to tell the difference +;; between an empty list and no list at all. + +(defconst cvs-pcl-cvs-dirchange-re "^pcl-cvs: descending directory \\(.*\\)$") + +;;;; +;;;; autoload the global menu +;;;; + +;;;###autoload +(defvar cvs-global-menu + (let ((m (make-sparse-keymap "PCL-CVS"))) + (define-key m [status] + `(menu-item ,(purecopy "Directory Status") cvs-status + :help ,(purecopy "A more verbose status of a workarea"))) + (define-key m [checkout] + `(menu-item ,(purecopy "Checkout Module") cvs-checkout + :help ,(purecopy "Check out a module from the repository"))) + (define-key m [update] + `(menu-item ,(purecopy "Update Directory") cvs-update + :help ,(purecopy "Fetch updates from the repository"))) + (define-key m [examine] + `(menu-item ,(purecopy "Examine Directory") cvs-examine + :help ,(purecopy "Examine the current state of a workarea"))) + (fset 'cvs-global-menu m))) + + +;; cvs-1.10 and above can take file arguments in other directories +;; while others need to be executed once per directory +(defvar cvs-execute-single-dir + (if (or (null cvs-version) + (or (>= (cdr cvs-version) 10) (> (car cvs-version) 1))) + ;; Supposedly some recent versions of CVS output some directory info + ;; as they recurse downthe tree, but it's not good enough in the case + ;; where we run "cvs status foo bar/foo". + '("status") + t) + "Whether cvs commands should be executed a directory at a time. +If a list, specifies for which commands the single-dir mode should be used. +If T, single-dir mode should be used for all operations. + +CVS versions before 1.10 did not allow passing them arguments in different +directories, so pcl-cvs checks what version you're using to determine +whether to use the new feature or not. +Sadly, even with a new cvs executable, if you connect to an older cvs server +\(typically a cvs-1.9 on the server), the old restriction applies. In such +a case the sanity check made by pcl-cvs fails and you will have to manually +set this variable to t (until the cvs server is upgraded). +When the above problem occurs, pcl-cvs should (hopefully) catch cvs' error +message and replace it with a message telling you to change this variable.") + +;; +(provide 'pcvs-defs) + +;; arch-tag: c7c701d0-d1d4-4aa9-a302-007bb03aca5e +;;; pcvs-defs.el ends here diff -r 681cd08dc0f7 -r e93288477c43 lisp/vc/pcvs-info.el --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/lisp/vc/pcvs-info.el Sun Jun 13 22:57:55 2010 +0000 @@ -0,0 +1,489 @@ +;;; pcvs-info.el --- internal representation of a fileinfo entry + +;; Copyright (C) 1991, 1992, 1993, 1994, 1995, 1996, 1997, 1998, 1999, +;; 2000, 2001, 2002, 2003, 2004, 2005, 2006, 2007, 2008, 2009, 2010 +;; Free Software Foundation, Inc. + +;; Author: Stefan Monnier +;; Keywords: pcl-cvs + +;; 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 3 of the License, 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. If not, see . + +;;; Commentary: + +;; The cvs-fileinfo data structure: +;; +;; When the `cvs update' is ready we parse the output. Every file +;; that is affected in some way is added to the cookie collection as +;; a "fileinfo" (as defined below in cvs-create-fileinfo). + +;;; Code: + +(eval-when-compile (require 'cl)) +(require 'pcvs-util) +;;(require 'pcvs-defs) + +;;;; +;;;; config variables +;;;; + +(define-obsolete-variable-alias 'cvs-display-full-path + 'cvs-display-full-name "22.1") + +(defcustom cvs-display-full-name t + "Specifies how the filenames should be displayed in the listing. +If non-nil, their full filename name will be displayed, else only the +non-directory part." + :group 'pcl-cvs + :type '(boolean)) + +(defcustom cvs-allow-dir-commit nil + "Allow `cvs-mode-commit' on directories. +If you commit without any marked file and with the cursor positioned +on a directory entry, cvs would commit the whole directory. This seems +to confuse some users sometimes." + :group 'pcl-cvs + :type '(boolean)) + +;;;; +;;;; Faces for fontification +;;;; + +(defface cvs-header + '((((class color) (background dark)) + (:foreground "lightyellow" :weight bold)) + (((class color) (background light)) + (:foreground "blue4" :weight bold)) + (t (:weight bold))) + "PCL-CVS face used to highlight directory changes." + :group 'pcl-cvs) +(define-obsolete-face-alias 'cvs-header-face 'cvs-header "22.1") + +(defface cvs-filename + '((((class color) (background dark)) + (:foreground "lightblue")) + (((class color) (background light)) + (:foreground "blue4")) + (t ())) + "PCL-CVS face used to highlight file names." + :group 'pcl-cvs) +(define-obsolete-face-alias 'cvs-filename-face 'cvs-filename "22.1") + +(defface cvs-unknown + '((((class color) (background dark)) + (:foreground "red1")) + (((class color) (background light)) + (:foreground "red1")) + (t (:slant italic))) + "PCL-CVS face used to highlight unknown file status." + :group 'pcl-cvs) +(define-obsolete-face-alias 'cvs-unknown-face 'cvs-unknown "22.1") + +(defface cvs-handled + '((((class color) (background dark)) + (:foreground "pink")) + (((class color) (background light)) + (:foreground "pink")) + (t ())) + "PCL-CVS face used to highlight handled file status." + :group 'pcl-cvs) +(define-obsolete-face-alias 'cvs-handled-face 'cvs-handled "22.1") + +(defface cvs-need-action + '((((class color) (background dark)) + (:foreground "orange")) + (((class color) (background light)) + (:foreground "orange")) + (t (:slant italic))) + "PCL-CVS face used to highlight status of files needing action." + :group 'pcl-cvs) +(define-obsolete-face-alias 'cvs-need-action-face 'cvs-need-action "22.1") + +(defface cvs-marked + '((((min-colors 88) (class color) (background dark)) + (:foreground "green1" :weight bold)) + (((class color) (background dark)) + (:foreground "green" :weight bold)) + (((class color) (background light)) + (:foreground "green3" :weight bold)) + (t (:weight bold))) + "PCL-CVS face used to highlight marked file indicator." + :group 'pcl-cvs) +(define-obsolete-face-alias 'cvs-marked-face 'cvs-marked "22.1") + +(defface cvs-msg + '((t (:slant italic))) + "PCL-CVS face used to highlight CVS messages." + :group 'pcl-cvs) +(define-obsolete-face-alias 'cvs-msg-face 'cvs-msg "22.1") + +(defvar cvs-fi-up-to-date-face 'cvs-handled) +(defvar cvs-fi-unknown-face 'cvs-unknown) +(defvar cvs-fi-conflict-face 'font-lock-warning-face) + +;; There is normally no need to alter the following variable, but if +;; your site has installed CVS in a non-standard way you might have +;; to change it. + +(defvar cvs-bakprefix ".#" + "The prefix that CVS prepends to files when rcsmerge'ing.") + +(easy-mmode-defmap cvs-status-map + '(([(mouse-2)] . cvs-mode-toggle-mark)) + "Local keymap for text properties of status") + +;; Constructor: + +(defstruct (cvs-fileinfo + (:constructor nil) + (:copier nil) + (:constructor -cvs-create-fileinfo (type dir file full-log + &key marked subtype + merge + base-rev + head-rev)) + (:conc-name cvs-fileinfo->)) + marked ;; t/nil. + type ;; See below + subtype ;; See below + dir ;; Relative directory the file resides in. + ;; (concat dir file) should give a valid path. + file ;; The file name sans the directory. + base-rev ;; During status: This is the revision that the + ;; working file is based on. + head-rev ;; During status: This is the highest revision in + ;; the repository. + merge ;; A cons cell containing the (ancestor . head) revisions + ;; of the merge that resulted in the current file. + ;;removed ;; t if the file no longer exists. + full-log ;; The output from cvs, unparsed. + ;;mod-time ;; Not used. + + ;; In addition to the above, the following values can be extracted: + + ;; handled ;; t if this file doesn't require further action. + ;; full-name ;; The complete relative filename. + ;; pp-name ;; The printed file name + ;; backup-file;; For MERGED and CONFLICT files after a \"cvs update\", + ;; this is a full path to the backup file where the + ;; untouched version resides. + + ;; The meaning of the type field: + + ;; Value ---Used by--- Explanation + ;; update status + ;; NEED-UPDATE x file needs update + ;; MODIFIED x x modified by you, unchanged in repository + ;; MERGED x x successful merge + ;; ADDED x x added by you, not yet committed + ;; MISSING x rm'd, but not yet `cvs remove'd + ;; REMOVED x x removed by you, not yet committed + ;; NEED-MERGE x need merge + ;; CONFLICT x conflict when merging + ;; ;;MOD-CONFLICT x removed locally, changed in repository. + ;; DIRCHANGE x x A change of directory. + ;; UNKNOWN x An unknown file. + ;; UP-TO-DATE x The file is up-to-date. + ;; UPDATED x x file copied from repository + ;; PATCHED x x diff applied from repository + ;; COMMITTED x x cvs commit'd + ;; DEAD An entry that should be removed + ;; MESSAGE x x This is a special fileinfo that is used + ;; to display a text that should be in + ;; full-log." + ;; TEMP A temporary message that should be removed + ) +(defun cvs-create-fileinfo (type dir file msg &rest keys) + (cvs-check-fileinfo (apply #'-cvs-create-fileinfo type dir file msg keys))) + +;; Fake selectors: + +(defun cvs-fileinfo->full-name (fileinfo) + "Return the full path for the file that is described in FILEINFO." + (let ((dir (cvs-fileinfo->dir fileinfo))) + (if (eq (cvs-fileinfo->type fileinfo) 'DIRCHANGE) + (if (string= dir "") "." (directory-file-name dir)) + ;; Here, I use `concat' rather than `expand-file-name' because I want + ;; the resulting path to stay relative if `dir' is relative. + (concat dir (cvs-fileinfo->file fileinfo))))) +(define-obsolete-function-alias 'cvs-fileinfo->full-path + 'cvs-fileinfo->full-name "22.1") + +(defun cvs-fileinfo->pp-name (fi) + "Return the filename of FI as it should be displayed." + (if cvs-display-full-name + (cvs-fileinfo->full-name fi) + (cvs-fileinfo->file fi))) + +(defun cvs-fileinfo->backup-file (fileinfo) + "Construct the file name of the backup file for FILEINFO." + (let* ((dir (cvs-fileinfo->dir fileinfo)) + (file (cvs-fileinfo->file fileinfo)) + (default-directory (file-name-as-directory (expand-file-name dir))) + (files (directory-files "." nil + (concat "\\`" (regexp-quote cvs-bakprefix) + (regexp-quote file) "\\(\\.[0-9]+\\.[0-9]+\\)+\\'"))) + bf) + (dolist (f files) + (when (and (file-readable-p f) + (or (null bf) (file-newer-than-file-p f bf))) + (setq bf f))) + (concat dir bf))) + +;; (defun cvs-fileinfo->handled (fileinfo) +;; "Tell if this requires further action" +;; (memq (cvs-fileinfo->type fileinfo) '(UP-TO-DATE DEAD))) + + +;; Predicate: + +(defun cvs-check-fileinfo (fi) + "Check FI's conformance to some conventions." + (let ((check 'none) + (type (cvs-fileinfo->type fi)) + (subtype (cvs-fileinfo->subtype fi)) + (marked (cvs-fileinfo->marked fi)) + (dir (cvs-fileinfo->dir fi)) + (file (cvs-fileinfo->file fi)) + (base-rev (cvs-fileinfo->base-rev fi)) + (head-rev (cvs-fileinfo->head-rev fi)) + (full-log (cvs-fileinfo->full-log fi))) + (if (and (setq check 'marked) (memq marked '(t nil)) + (setq check 'base-rev) (or (null base-rev) (stringp base-rev)) + (setq check 'head-rev) (or (null head-rev) (stringp head-rev)) + (setq check 'full-log) (stringp full-log) + (setq check 'dir) + (and (stringp dir) + (not (file-name-absolute-p dir)) + (or (string= dir "") + (string= dir (file-name-as-directory dir)))) + (setq check 'file) + (and (stringp file) + (string= file (file-name-nondirectory file))) + (setq check 'type) (symbolp type) + (setq check 'consistency) + (case type + (DIRCHANGE (and (null subtype) (string= "." file))) + ((NEED-UPDATE ADDED MISSING DEAD MODIFIED MESSAGE UP-TO-DATE + REMOVED NEED-MERGE CONFLICT UNKNOWN MESSAGE) + t))) + fi + (error "Invalid :%s in cvs-fileinfo %s" check fi)))) + + +;;;; +;;;; State table to indicate what you can do when. +;;;; + +(defconst cvs-states + `((NEED-UPDATE update diff ignore) + (UP-TO-DATE update nil remove diff safe-rm revert) + (MODIFIED update commit undo remove diff merge diff-base) + (ADDED update commit remove) + (MISSING remove undo update safe-rm revert) + (REMOVED commit add undo safe-rm) + (NEED-MERGE update undo diff diff-base) + (CONFLICT merge remove undo commit diff diff-base) + (DIRCHANGE remove update diff ,(if cvs-allow-dir-commit 'commit) tag) + (UNKNOWN ignore add remove) + (DEAD ) + (MESSAGE)) + "Fileinfo state descriptions for pcl-cvs. +This is an assoc list. Each element consists of (STATE . FUNS) +- STATE (described in `cvs-create-fileinfo') is the key +- FUNS is the list of applicable operations. + The first one (if any) should be the \"default\" action. +Most of the actions have the obvious meaning. +`safe-rm' indicates that the file can be removed without losing + any information.") + +;;;; +;;;; Utility functions +;;;; + +(defun cvs-applicable-p (fi-or-type func) + "Check if FUNC is applicable to FI-OR-TYPE. +If FUNC is nil, always return t. +FI-OR-TYPE can either be a symbol (a fileinfo-type) or a fileinfo." + (let ((type (if (symbolp fi-or-type) fi-or-type + (cvs-fileinfo->type fi-or-type)))) + (and (not (eq type 'MESSAGE)) + (eq (car (memq func (cdr (assq type cvs-states)))) func)))) + +(defun cvs-add-face (str face &optional keymap &rest props) + (when keymap + (when (keymapp keymap) + (setq props (list* 'keymap keymap props))) + (setq props (list* 'mouse-face 'highlight props))) + (add-text-properties 0 (length str) (list* 'font-lock-face face props) str) + str) + +(defun cvs-fileinfo-pp (fileinfo) + "Pretty print FILEINFO. Insert a printed representation in current buffer. +For use by the cookie package." + (cvs-check-fileinfo fileinfo) + (let ((type (cvs-fileinfo->type fileinfo)) + (subtype (cvs-fileinfo->subtype fileinfo))) + (insert + (case type + (DIRCHANGE (concat "In directory " + (cvs-add-face (cvs-fileinfo->full-name fileinfo) + 'cvs-header t 'cvs-goal-column t) + ":")) + (MESSAGE + (cvs-add-face (format "Message: %s" (cvs-fileinfo->full-log fileinfo)) + 'cvs-msg)) + (t + (let* ((status (if (cvs-fileinfo->marked fileinfo) + (cvs-add-face "*" 'cvs-marked) + " ")) + (file (cvs-add-face (cvs-fileinfo->pp-name fileinfo) + 'cvs-filename t 'cvs-goal-column t)) + (base (or (cvs-fileinfo->base-rev fileinfo) "")) + (head (cvs-fileinfo->head-rev fileinfo)) + (type + (let ((str (case type + ;;(MOD-CONFLICT "Not Removed") + (DEAD "") + (t (capitalize (symbol-name type))))) + (face (let ((sym (intern + (concat "cvs-fi-" + (downcase (symbol-name type)) + "-face")))) + (or (and (boundp sym) (symbol-value sym)) + 'cvs-need-action)))) + (cvs-add-face str face cvs-status-map))) + (side (or + ;; maybe a subtype + (when subtype (downcase (symbol-name subtype))) + ;; or the head-rev + (when (and head (not (string= head base))) head) + ;; or nothing + ""))) + (format "%-11s %s %-11s %-11s %s" + side status type base file)))) + "\n"))) + + +(defun cvs-fileinfo-update (fi fi-new) + "Update FI with the information provided in FI-NEW." + (let ((type (cvs-fileinfo->type fi-new)) + (merge (cvs-fileinfo->merge fi-new))) + (setf (cvs-fileinfo->type fi) type) + (setf (cvs-fileinfo->subtype fi) (cvs-fileinfo->subtype fi-new)) + (setf (cvs-fileinfo->full-log fi) (cvs-fileinfo->full-log fi-new)) + (setf (cvs-fileinfo->base-rev fi) (cvs-fileinfo->base-rev fi-new)) + (setf (cvs-fileinfo->head-rev fi) (cvs-fileinfo->head-rev fi-new)) + (cond + (merge (setf (cvs-fileinfo->merge fi) merge)) + ((memq type '(UP-TO-DATE NEED-UPDATE)) + (setf (cvs-fileinfo->merge fi) nil))))) + +(defun cvs-fileinfo< (a b) + "Compare fileinfo A with fileinfo B and return t if A is `less'. +The ordering defined by this function is such that directories are +sorted alphabetically, and inside every directory the DIRCHANGE +fileinfo will appear first, followed by all files (alphabetically)." + (let ((subtypea (cvs-fileinfo->subtype a)) + (subtypeb (cvs-fileinfo->subtype b))) + (cond + ;; Sort according to directories. + ((string< (cvs-fileinfo->dir a) (cvs-fileinfo->dir b)) t) + ((not (string= (cvs-fileinfo->dir a) (cvs-fileinfo->dir b))) nil) + + ;; The DIRCHANGE entry is always first within the directory. + ((eq (cvs-fileinfo->type b) 'DIRCHANGE) nil) + ((eq (cvs-fileinfo->type a) 'DIRCHANGE) t) + + ;; All files are sorted by file name. + ((string< (cvs-fileinfo->file a) (cvs-fileinfo->file b)))))) + +;;; +;;; Look at CVS/Entries to quickly find a first approximation of the status +;;; + +(defun cvs-fileinfo-from-entries (dir &optional all) + "List of fileinfos for DIR, extracted from CVS/Entries. +Unless ALL is optional, returns only the files that are not up-to-date. +DIR can also be a file." + (let* ((singlefile + (cond + ((equal dir "") nil) + ((file-directory-p dir) (setq dir (file-name-as-directory dir)) nil) + (t (prog1 (file-name-nondirectory dir) + (setq dir (or (file-name-directory dir) "")))))) + (file (expand-file-name "CVS/Entries" dir)) + (fis nil)) + (if (not (file-readable-p file)) + (push (cvs-create-fileinfo (if singlefile 'UNKNOWN 'DIRCHANGE) + dir (or singlefile ".") "") fis) + (with-temp-buffer + (insert-file-contents file) + (goto-char (point-min)) + ;; Select the single file entry in case we're only interested in a file. + (cond + ((not singlefile) + (push (cvs-create-fileinfo 'DIRCHANGE dir "." "") fis)) + ((re-search-forward + (concat "^[^/]*/" (regexp-quote singlefile) "/.*") nil t) + (setq all t) + (goto-char (match-beginning 0)) + (narrow-to-region (point) (match-end 0))) + (t + (push (cvs-create-fileinfo 'UNKNOWN dir singlefile "") fis) + (narrow-to-region (point-min) (point-min)))) + (while (looking-at "\\([^/]*\\)/\\([^/]*\\)/\\([^/]*\\)/\\([^/]*\\)/") + (if (/= (match-beginning 1) (match-end 1)) + (setq fis (append (cvs-fileinfo-from-entries + (concat dir (file-name-as-directory + (match-string 2))) + all) + fis)) + (let ((f (match-string 2)) + (rev (match-string 3)) + (date (match-string 4)) + timestamp + (type 'MODIFIED) + (subtype nil)) + (cond + ((equal (substring rev 0 1) "-") + (setq type 'REMOVED rev (substring rev 1))) + ((not (file-exists-p (concat dir f))) (setq type 'MISSING)) + ((equal rev "0") (setq type 'ADDED rev nil)) + ((equal date "Result of merge") (setq subtype 'MERGED)) + ((let ((mtime (nth 5 (file-attributes (concat dir f)))) + (system-time-locale "C")) + (setq timestamp (format-time-string "%c" mtime 'utc)) + ;; Solaris sometimes uses "Wed Sep 05", not "Wed Sep 5". + ;; See "grep '[^a-z_]ctime' cvs/src/*.c" for reference. + (if (= (aref timestamp 8) ?0) + (setq timestamp (concat (substring timestamp 0 8) + " " (substring timestamp 9)))) + (equal timestamp date)) + (setq type (if all 'UP-TO-DATE))) + ((equal date (concat "Result of merge+" timestamp)) + (setq type 'CONFLICT))) + (when type + (push (cvs-create-fileinfo type dir f "" + :base-rev rev :subtype subtype) + fis)))) + (forward-line 1)))) + fis)) + +(provide 'pcvs-info) + +;; arch-tag: d85dde07-bdc2-400a-882f-92f398c7b0ba +;;; pcvs-info.el ends here diff -r 681cd08dc0f7 -r e93288477c43 lisp/vc/pcvs-parse.el --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/lisp/vc/pcvs-parse.el Sun Jun 13 22:57:55 2010 +0000 @@ -0,0 +1,538 @@ +;;; pcvs-parse.el --- the CVS output parser + +;; Copyright (C) 1991, 1992, 1993, 1994, 1995, 1996, 1997, 1998, 1999, +;; 2000, 2001, 2002, 2003, 2004, 2005, 2006, 2007, 2008, 2009, 2010 Free Software Foundation, Inc. + +;; Author: Stefan Monnier +;; Keywords: pcl-cvs + +;; 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 3 of the License, 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. If not, see . + +;;; Commentary: + +;;; Bugs: + +;; - when merging a modified file, if the merge says that the file already +;; contained in the changes, it marks the file as `up-to-date' although +;; it might still contain further changes. +;; Example: merging a zero-change commit. + +;;; Code: + +(eval-when-compile (require 'cl)) + +(require 'pcvs-util) +(require 'pcvs-info) + +;; imported from pcvs.el +(defvar cvs-execute-single-dir) + +;; parse vars + +(defcustom cvs-update-prog-output-skip-regexp "$" + "A regexp that matches the end of the output from all cvs update programs. +That is, output from any programs that are run by CVS (by the flag -u +in the `modules' file - see cvs(5)) when `cvs update' is performed should +terminate with a line that this regexp matches. It is enough that +some part of the line is matched. + +The default (a single $) fits programs without output." + :group 'pcl-cvs + :type '(regexp :value "$")) + +(defcustom cvs-parse-ignored-messages + '("Executing ssh-askpass to query the password.*$" + ".*Remote host denied X11 forwarding.*$") + "A list of regexps matching messages that should be ignored by the parser. +Each regexp should match a whole set of lines and should hence be terminated +by `$'." + :group 'pcl-cvs + :type '(repeat regexp)) + +;; a few more defvars just to shut up the compiler +(defvar cvs-start) +(defvar cvs-current-dir) +(defvar cvs-current-subdir) +(defvar dont-change-disc) + +;;;; The parser + +(defconst cvs-parse-known-commands + '("status" "add" "commit" "update" "remove" "checkout" "ci") + "List of CVS commands whose output is understood by the parser.") + +(defun cvs-parse-buffer (parse-spec dont-change-disc &optional subdir) + "Parse current buffer according to PARSE-SPEC. +PARSE-SPEC is a function of no argument advancing the point and returning + either a fileinfo or t (if the matched text should be ignored) or + nil if it didn't match anything. +DONT-CHANGE-DISC just indicates whether the command was changing the disc + or not (useful to tell the difference between `cvs-examine' and `cvs-update' + output. +The path names should be interpreted as relative to SUBDIR (defaults + to the `default-directory'). +Return a list of collected entries, or t if an error occurred." + (goto-char (point-min)) + (let ((fileinfos ()) + (cvs-current-dir "") + (case-fold-search nil) + (cvs-current-subdir (or subdir ""))) + (while (not (or (eobp) (eq fileinfos t))) + (let ((ret (cvs-parse-run-table parse-spec))) + (cond + ;; it matched a known information message + ((cvs-fileinfo-p ret) (push ret fileinfos)) + ;; it didn't match anything at all (impossible) + ((and (consp ret) (cvs-fileinfo-p (car ret))) + (setq fileinfos (append ret fileinfos))) + ((null ret) (setq fileinfos t)) + ;; it matched something that should be ignored + (t nil)))) + (nreverse fileinfos))) + + +;; All those parsing macros/functions should return a success indicator +(defsubst cvs-parse-msg () (buffer-substring cvs-start (1- (point)))) + +;;(defsubst COLLECT (exp) (push exp *result*)) +;;(defsubst PROG (e) t) +;;(defmacro SEQ (&rest seqs) (cons 'and seqs)) + +(defmacro cvs-match (re &rest matches) + "Try to match RE and extract submatches. +If RE matches, advance the point until the line after the match and +then assign the variables as specified in MATCHES (via `setq')." + (cons 'cvs-do-match + (cons re (mapcar (lambda (match) + `(cons ',(first match) ,(second match))) + matches)))) + +(defun cvs-do-match (re &rest matches) + "Internal function for the `cvs-match' macro. +Match RE and if successful, execute MATCHES." + ;; Is it a match? + (when (looking-at re) + (goto-char (match-end 0)) + ;; Skip the newline (unless we already are at the end of the buffer). + (when (and (eolp) (< (point) (point-max))) (forward-char)) + ;; assign the matches + (dolist (match matches t) + (let ((val (cdr match))) + (set (car match) (if (integerp val) (match-string val) val)))))) + +(defmacro cvs-or (&rest alts) + "Try each one of the ALTS alternatives until one matches." + `(let ((-cvs-parse-point (point))) + ,(cons 'or + (mapcar (lambda (es) + `(or ,es (ignore (goto-char -cvs-parse-point)))) + alts)))) +(def-edebug-spec cvs-or t) + +;; This is how parser tables should be executed +(defun cvs-parse-run-table (parse-spec) + "Run PARSE-SPEC and provide sensible default behavior." + (unless (bolp) (forward-line 1)) ;this should never be needed + (let ((cvs-start (point))) + (cvs-or + (funcall parse-spec) + + (dolist (re cvs-parse-ignored-messages) + (when (cvs-match re) (return t))) + + ;; This is a parse error. Create a message-type fileinfo. + (and + (cvs-match ".*$") + (cvs-create-fileinfo 'MESSAGE cvs-current-dir " " + ;; (concat " Unknown msg: '" + (cvs-parse-msg) ;; "'") + :subtype 'ERROR))))) + + +(defun cvs-parsed-fileinfo (type path &optional directory &rest keys) + "Create a fileinfo. +TYPE can either be a type symbol or a cons of the form (TYPE . SUBTYPE). +PATH is the filename. +DIRECTORY influences the way PATH is interpreted: +- if it's a string, it denotes the directory in which PATH (which should then be + a plain file name with no directory component) resides. +- if it's nil, the PATH should not be trusted: if it has a directory + component, use it, else, assume it is relative to the current directory. +- else, the PATH should be trusted to be relative to the root + directory (i.e. if there is no directory component, it means the file + is inside the main directory). +The remaining KEYS are passed directly to `cvs-create-fileinfo'." + (let ((dir directory) + (file path)) + ;; only trust the directory if it's a string + (unless (stringp directory) + ;; else, if the directory is true, the path should be trusted + (setq dir (or (file-name-directory path) (if directory ""))) + (setq file (file-name-nondirectory path))) + + (let ((type (if (consp type) (car type) type)) + (subtype (if (consp type) (cdr type)))) + (when dir (setq cvs-current-dir dir)) + (apply 'cvs-create-fileinfo type + (concat cvs-current-subdir (or dir cvs-current-dir)) + file (cvs-parse-msg) :subtype subtype keys)))) + +;;;; CVS Process Parser Tables: +;;;; +;;;; The table for status and update could actually be merged since they +;;;; don't conflict. But they don't overlap much either. + +(defun cvs-parse-table () + "Table of message objects for `cvs-parse-process'." + (let (c file dir path base-rev subtype) + (cvs-or + + (cvs-parse-status) + (cvs-parse-merge) + (cvs-parse-commit) + + ;; this is not necessary because the fileinfo merging will remove + ;; such duplicate info and luckily the second info is the one we want. + ;; (and (cvs-match "M \\(.*\\)$" (path 1)) + ;; (cvs-parse-merge path)) + + ;; Normal file state indicator. + (and + (cvs-match "\\([MARCUPNJ?]\\) \\(.*\\)$" (c 1) (path 2)) + ;; M: The file is modified by the user, and untouched in the repository. + ;; A: The file is "cvs add"ed, but not "cvs ci"ed. + ;; R: The file is "cvs remove"ed, but not "cvs ci"ed. + ;; C: Conflict + ;; U: The file is copied from the repository. + ;; P: The file was patched from the repository. + ;; ?: Unknown file. + (let ((code (aref c 0))) + (cvs-parsed-fileinfo + (case code + (?M 'MODIFIED) + (?A 'ADDED) + (?R 'REMOVED) + (?? 'UNKNOWN) + (?C + (if (not dont-change-disc) 'CONFLICT + ;; This is ambiguous. We should look for conflict markers in the + ;; file to decide between CONFLICT and NEED-MERGE. With CVS-1.10 + ;; servers, this should not be necessary, because they return + ;; a complete merge output. + (with-temp-buffer + (ignore-errors (insert-file-contents path)) + (goto-char (point-min)) + (if (re-search-forward "^<<<<<<< " nil t) + 'CONFLICT 'NEED-MERGE)))) + (?J 'NEED-MERGE) ;not supported by standard CVS + ((?U ?P) + (if dont-change-disc 'NEED-UPDATE + (cons 'UP-TO-DATE (if (eq code ?U) 'UPDATED 'PATCHED))))) + path 'trust))) + + (and + (cvs-match "pcl-cvs: descending directory \\(.*\\)$" (dir 1)) + (setq cvs-current-subdir dir)) + + ;; A special cvs message + (and + (let ((case-fold-search t)) + (cvs-match "cvs[.a-z]* [a-z]+: ")) + (cvs-or + + ;; CVS is descending a subdirectory + ;; (status says `examining' while update says `updating') + (and + (cvs-match "\\(Examining\\|Updating\\) \\(.*\\)$" (dir 2)) + (let ((dir (if (string= "." dir) "" (file-name-as-directory dir)))) + (cvs-parsed-fileinfo 'DIRCHANGE "." dir))) + + ;; [-n update] A new (or pruned) directory appeared but isn't traversed + (and + (cvs-match "New directory `\\(.*\\)' -- ignored$" (dir 1)) + ;; (cvs-parsed-fileinfo 'MESSAGE " " (file-name-as-directory dir)) + ;; These messages either correspond to a true new directory + ;; that an update will bring in, or to a directory that's empty + ;; on the current branch (either because it only exists in other + ;; branches, or because it's been removed). + (if (ignore-errors + (with-temp-buffer + (ignore-errors + (insert-file-contents + (expand-file-name ".cvsignore" (file-name-directory dir)))) + (goto-char (point-min)) + (re-search-forward + (concat "^" (regexp-quote (file-name-nondirectory dir)) "/$") + nil t))) + t ;The user requested to ignore those messages. + (cvs-parsed-fileinfo '(NEED-UPDATE . NEW-DIR) dir t))) + + ;; File removed, since it is removed (by third party) in repository. + (and + (cvs-or + ;; some cvs versions output quotes around these files + (cvs-match "warning: `\\(.*\\)' is not (any longer) pertinent$" (file 1)) + (cvs-match "warning: \\(.*\\) is not (any longer) pertinent$" (file 1)) + (cvs-match "`\\(.*\\)' is no longer in the repository$" (file 1)) + (cvs-match "\\(.*\\) is no longer in the repository$" (file 1))) + (cvs-parsed-fileinfo + (if dont-change-disc '(NEED-UPDATE . REMOVED) 'DEAD) file)) + + ;; [add] + (and + (cvs-or + (cvs-match "scheduling file `\\(.*\\)' for addition.*$" (path 1)) + (cvs-match "re-adding file \\(.*\\) (in place of .*)$" (path 1))) + (cvs-parsed-fileinfo 'ADDED path)) + + ;; [add] this will also show up as a `U ' + (and + (cvs-match "`?\\(.*?\\)'?, version \\(.*\\), resurrected$" + (path 1) (base-rev 2)) + ;; FIXME: resurrection only brings back the original version, + ;; not the latest on the branch, so `up-to-date' is not always + ;; what we want. + (cvs-parsed-fileinfo '(UP-TO-DATE . RESURRECTED) path nil + :base-rev base-rev)) + + ;; [remove] + (and + (cvs-match "removed `\\(.*\\)'$" (path 1)) + (cvs-parsed-fileinfo 'DEAD path)) + + ;; [remove,merge] + (and + (cvs-match "scheduling `\\(.*\\)' for removal$" (file 1)) + (cvs-parsed-fileinfo 'REMOVED file)) + + ;; [update] File removed by you, but not cvs rm'd + (and + (cvs-match "warning: \\(.*\\) was lost$" (path 1)) + (cvs-match (concat "U " (regexp-quote path) "$")) + (cvs-parsed-fileinfo (if dont-change-disc + 'MISSING + '(UP-TO-DATE . UPDATED)) + path)) + + ;; Mode conflicts (rather than contents) + (and + (cvs-match "conflict: ") + (cvs-or + (cvs-match "removed \\(.*\\) was modified by second party$" + (path 1) (subtype 'REMOVED)) + (cvs-match "\\(.*\\) created independently by second party$" + (path 1) (subtype 'ADDED)) + (cvs-match "\\(.*\\) is modified but no longer in the repository$" + (path 1) (subtype 'MODIFIED))) + (cvs-match (concat "C " (regexp-quote path))) + (cvs-parsed-fileinfo (cons 'CONFLICT subtype) path)) + + ;; Messages that should be shown to the user + (and + (cvs-or + (cvs-match "move away \\(.*\\); it is in the way$" (file 1)) + (cvs-match "warning: new-born \\(.*\\) has disappeared$" (file 1)) + (cvs-match "sticky tag .* for file `\\(.*\\)' is not a branch$" + (file 1))) + (cvs-parsed-fileinfo 'MESSAGE file)) + + ;; File unknown. + (and (cvs-match "use `.+ add' to create an entry for \\(.*\\)$" (path 1)) + (cvs-parsed-fileinfo 'UNKNOWN path)) + + ;; [commit] + (and (cvs-match "Up-to-date check failed for `\\(.+\\)'$" (file 1)) + (cvs-parsed-fileinfo 'NEED-MERGE file)) + + ;; We use cvs-execute-multi-dir but cvs can't handle it + ;; Probably because the cvs-client can but the cvs-server can't + (and (cvs-match ".* files with '?/'? in their name.*$") + (not cvs-execute-single-dir) + (setq cvs-execute-single-dir t) + (cvs-create-fileinfo + 'MESSAGE "" " " + "*** Add (setq cvs-execute-single-dir t) to your .emacs *** + See the FAQ file or the variable's documentation for more info.")) + + ;; Cvs waits for a lock. Ignored: already handled by the process filter + (cvs-match "\\[..:..:..\\] \\(waiting for\\|obtained\\) .*lock in .*$") + ;; File you removed still exists. Ignore (will be noted as removed). + (cvs-match ".* should be removed and is still there$") + ;; just a note + (cvs-match "use ['`].+ commit' to \\sw+ th\\sw+ files? permanently$") + ;; [add,status] followed by a more complete status description anyway + (and (cvs-match "nothing known about \\(.*\\)$" (path 1)) + (cvs-parsed-fileinfo 'DEAD path 'trust)) + ;; [update] problem with patch + (cvs-match "checksum failure after patch to .*; will refetch$") + (cvs-match "refetching unpatchable files$") + ;; [commit] + (cvs-match "Rebuilding administrative file database$") + ;; ??? + (cvs-match "--> Using per-directory sticky tag `.*'") + + ;; CVS is running a *info program. + (and + (cvs-match "Executing.*$") + ;; Skip by any output the program may generate to stdout. + ;; Note that pcl-cvs will get seriously confused if the + ;; program prints anything to stderr. + (re-search-forward cvs-update-prog-output-skip-regexp)))) + + (and + (cvs-match "cvs[.ex]* \\[[a-z]+ aborted\\]:.*$") + (cvs-parsed-fileinfo 'MESSAGE "")) + + ;; sadly you can't do much with these since the path is in the repository + (cvs-match "Directory .* added to the repository$") + ))) + + +(defun cvs-parse-merge () + (let (path base-rev head-rev type) + ;; A merge (maybe with a conflict). + (and + (cvs-match "RCS file: .*$") + ;; Squirrel away info about the files that were retrieved for merging + (cvs-match "retrieving revision \\([0-9.]+\\)$" (base-rev 1)) + (cvs-match "retrieving revision \\([0-9.]+\\)$" (head-rev 1)) + (cvs-match "Merging differences between [0-9.]+ and [0-9.]+ into \\(.*\\)$" + (path 1)) + + ;; eat up potential conflict warnings + (cvs-or (cvs-match "\\(rcs\\)?merge:?\\( warning\\)?: \\(overlaps\\|conflicts\\) \\(or other problems \\)?during merge$" (type 'CONFLICT)) t) + (cvs-or + (and + (cvs-match "cvs[.ex]* [a-z]+: ") + (cvs-or + (cvs-match "conflicts found in \\(.*\\)$" (path 1) (type 'CONFLICT)) + (cvs-match "could not merge .*$") + (cvs-match "restoring \\(.*\\) from backup file .*$" (path 1)))) + t) + + ;; Is it a succesful merge? + ;; Figure out result of merging (ie, was there a conflict?) + (let ((qfile (regexp-quote path))) + (cvs-or + ;; Conflict + (and + (cvs-match (concat "C \\(.*" qfile "\\)$") (path 1) (type 'CONFLICT)) + ;; C might be followed by a "suprious" U for non-mergeable files + (cvs-or (cvs-match (concat "U \\(.*" qfile "\\)$")) t)) + ;; Successful merge + (cvs-match (concat "M \\(.*" qfile "\\)$") (path 1)) + ;; The file already contained the modifications + (cvs-match (concat "^\\(.*" qfile + "\\) already contains the differences between .*$") + (path 1) (type '(UP-TO-DATE . MERGED))) + t) + ;; FIXME: PATH might not be set yet. Sometimes the only path + ;; information is in `RCS file: ...' (yuck!!). + (cvs-parsed-fileinfo (if dont-change-disc 'NEED-MERGE + (or type '(MODIFIED . MERGED))) path nil + :merge (cons base-rev head-rev)))))) + +(defun cvs-parse-status () + (let (nofile path base-rev head-rev type) + (and + (cvs-match + "===================================================================$") + (cvs-match "File: \\(no file \\)?\\(.*[^ \t]\\)[ \t]+Status: " + (nofile 1) (path 2)) + (cvs-or + (cvs-match "Needs \\(Checkout\\|Patch\\)$" + (type (if nofile 'MISSING 'NEED-UPDATE))) + (cvs-match "Up-to-date$" + (type (if nofile '(UP-TO-DATE . REMOVED) 'UP-TO-DATE))) + (cvs-match "File had conflicts on merge$" (type 'MODIFIED)) + (cvs-match ".*[Cc]onflict.*$" (type 'CONFLICT)) + (cvs-match "Locally Added$" (type 'ADDED)) + (cvs-match "Locally Removed$" (type 'REMOVED)) + (cvs-match "Locally Modified$" (type 'MODIFIED)) + (cvs-match "Needs Merge$" (type 'NEED-MERGE)) + (cvs-match "Entry Invalid" (type '(NEED-MERGE . REMOVED))) + (cvs-match ".*$" (type 'UNKNOWN))) + (cvs-match "$") + (cvs-or + (cvs-match " *Version:[ \t]*\\([0-9.]+\\).*$" (base-rev 1)) + ;; NOTE: there's no date on the end of the following for server mode... + (cvs-match " *Working revision:[ \t]*-?\\([0-9.]+\\).*$" (base-rev 1)) + ;; Let's not get all worked up if the format changes a bit + (cvs-match " *Working revision:.*$")) + (cvs-or + (cvs-match " *RCS Version:[ \t]*\\([0-9.]+\\)[ \t]*.*$" (head-rev 1)) + (cvs-match " *Repository revision:[ \t]*\\([0-9.]+\\)[ \t]*\\(.*\\)$" + (head-rev 1)) + (cvs-match " *Repository revision:.*")) + (cvs-or (cvs-match " *Expansion option:.*") t) ;Optional CVSNT thingie. + (cvs-or (cvs-match " *Commit Identifier:.*") t) ;Optional CVSNT thingie. + (cvs-or + (and ;; Sometimes those fields are missing. + (cvs-match " *Sticky Tag:[ \t]*\\(.*\\)$") ; FIXME: use it. + (cvs-match " *Sticky Date:[ \t]*\\(.*\\)$") ; FIXME: use it. + (cvs-match " *Sticky Options:[ \t]*\\(.*\\)$")) ; FIXME: use it. + t) + (cvs-or (cvs-match " *Merge From:.*") t) ;Optional CVSNT thingie. + (cvs-match "$") + ;; ignore the tags-listing in the case of `status -v' + (cvs-or (cvs-match " *Existing Tags:\n\\(\t.*\n\\)*$") t) + (cvs-parsed-fileinfo type path nil + :base-rev base-rev + :head-rev head-rev)))) + +(defun cvs-parse-commit () + (let (path file base-rev subtype) + (cvs-or + + (and + (cvs-or + (cvs-match "\\(Checking in\\|Removing\\) \\(.*\\);$" (path 2)) + t) + (cvs-match ".*,v <-- \\(.*\\)$" (file 1)) + (cvs-or + ;; deletion + (cvs-match "new revision: delete; previous revision: \\([0-9.]*\\)$" + (subtype 'REMOVED) (base-rev 1)) + ;; addition + (cvs-match "initial revision: \\([0-9.]*\\)$" + (subtype 'ADDED) (base-rev 1)) + ;; update + (cvs-match "new revision: \\([0-9.]*\\); previous revision: .*$" + (subtype 'COMMITTED) (base-rev 1))) + (cvs-or (cvs-match "done$") t) + ;; In cvs-1.12.9 commit messages have been changed and became + ;; ambiguous. More specifically, the `path' above is not given. + ;; We assume here that in future releases the corresponding info will + ;; be put into `file'. + (progn + ;; Try to remove the temp files used by VC. + (vc-delete-automatic-version-backups (expand-file-name (or path file))) + ;; it's important here not to rely on the default directory management + ;; because `cvs commit' might begin by a series of Examining messages + ;; so the processing of the actual checkin messages might begin with + ;; a `current-dir' set to something different from "" + (cvs-parsed-fileinfo (cons 'UP-TO-DATE subtype) + (or path file) 'trust + :base-rev base-rev))) + + ;; useless message added before the actual addition: ignored + (cvs-match "RCS file: .*\ndone$")))) + + +(provide 'pcvs-parse) + +;; arch-tag: 35418375-1a23-40a0-957d-96b0262f91d6 +;;; pcvs-parse.el ends here diff -r 681cd08dc0f7 -r e93288477c43 lisp/vc/pcvs-util.el --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/lisp/vc/pcvs-util.el Sun Jun 13 22:57:55 2010 +0000 @@ -0,0 +1,371 @@ +;;; pcvs-util.el --- utility functions for PCL-CVS -*- byte-compile-dynamic: t -*- + +;; Copyright (C) 1991, 1992, 1993, 1994, 1995, 1996, 1997, 1998, 1999, +;; 2000, 2001, 2002, 2003, 2004, 2005, 2006, 2007, 2008, 2009, 2010 Free Software Foundation, Inc. + +;; Author: Stefan Monnier +;; Keywords: pcl-cvs + +;; 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 3 of the License, 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. If not, see . + +;;; Commentary: + + +;;; Code: + +(eval-when-compile (require 'cl)) + +;;;; +;;;; list processing +;;;; + +(defsubst cvs-car (x) (if (consp x) (car x) x)) +(defalias 'cvs-cdr 'cdr-safe) +(defsubst cvs-append (&rest xs) + (apply 'append (mapcar (lambda (x) (if (listp x) x (list x))) xs))) + +(defsubst cvs-every (-cvs-every-f -cvs-every-l) + (while (consp -cvs-every-l) + (unless (funcall -cvs-every-f (pop -cvs-every-l)) + (setq -cvs-every-l t))) + (not -cvs-every-l)) + +(defun cvs-union (xs ys) + (let ((zs ys)) + (dolist (x xs zs) + (unless (member x ys) (push x zs))))) + +(defun cvs-map (-cvs-map-f &rest -cvs-map-ls) + (let ((accum ())) + (while (not (cvs-every 'null -cvs-map-ls)) + (push (apply -cvs-map-f (mapcar 'car -cvs-map-ls)) accum) + (setq -cvs-map-ls (mapcar 'cdr -cvs-map-ls))) + (nreverse accum))) + +(defun cvs-first (l &optional n) + (if (null n) (car l) + (when l + (let* ((nl (list (pop l))) + (ret nl)) + (while (and l (> n 1)) + (setcdr nl (list (pop l))) + (setq nl (cdr nl)) + (decf n)) + ret)))) + +(defun cvs-partition (p l) + "Partition a list L into two lists based on predicate P. +The function returns a `cons' cell where the `car' contains +elements of L for which P is true while the `cdr' contains +the other elements. The ordering among elements is maintained." + (let (car cdr) + (dolist (x l) + (if (funcall p x) (push x car) (push x cdr))) + (cons (nreverse car) (nreverse cdr)))) + +;;; +;;; frame, window, buffer handling +;;; + +(defun cvs-pop-to-buffer-same-frame (buf) + "Pop to BUF like `pop-to-buffer' but staying on the same frame. +If `pop-to-buffer' would have opened a new frame, this function would +try to split a new window instead." + (let ((pop-up-windows (or pop-up-windows pop-up-frames)) + (pop-up-frames nil)) + (or (let ((buf (get-buffer-window buf))) (and buf (select-window buf))) + (and pop-up-windows + (ignore-errors (select-window (split-window-vertically))) + (switch-to-buffer buf)) + (pop-to-buffer (current-buffer))))) + +(defun cvs-bury-buffer (buf &optional mainbuf) + "Hide the buffer BUF that was temporarily popped up. +BUF is assumed to be a temporary buffer used from the buffer MAINBUF." + (interactive (list (current-buffer))) + (save-current-buffer + (let ((win (if (eq buf (window-buffer (selected-window))) (selected-window) + (get-buffer-window buf t)))) + (when win + (if (window-dedicated-p win) + (condition-case () + (delete-window win) + (error (iconify-frame (window-frame win)))) +;;; (if (and mainbuf (get-buffer-window mainbuf)) +;;; ;; FIXME: if the buffer popped into a pre-existing window, +;;; ;; we don't want to delete that window. +;;; t ;;(delete-window win) +;;; ) + ))) + (with-current-buffer buf + (bury-buffer (unless (and (eq buf (window-buffer (selected-window))) + (not (window-dedicated-p (selected-window)))) + buf))) + (when mainbuf + (let ((mainwin (or (get-buffer-window mainbuf) + (get-buffer-window mainbuf 'visible)))) + (when mainwin (select-window mainwin)))))) + +(defun cvs-get-buffer-create (name &optional noreuse) + "Create a buffer NAME unless such a buffer already exists. +If the NAME looks like an absolute file name, the buffer will be created +with `create-file-buffer' and will probably get another name than NAME. +In such a case, the search for another buffer with the same name doesn't +use the buffer name but the buffer's `list-buffers-directory' variable. +If NOREUSE is non-nil, always return a new buffer." + (or (and (not (file-name-absolute-p name)) + (if noreuse (generate-new-buffer name) + (get-buffer-create name))) + (unless noreuse + (dolist (buf (buffer-list)) + (with-current-buffer buf + (when (equal name list-buffers-directory) + (return buf))))) + (with-current-buffer (create-file-buffer name) + (setq list-buffers-directory name) + (current-buffer)))) + +;;;; +;;;; string processing +;;;; + +(defun cvs-insert-strings (strings) + "Insert a list of STRINGS into the current buffer. +Uses columns to keep the listing readable but compact." + (when (consp strings) + (let* ((length (apply 'max (mapcar 'length strings))) + (wwidth (1- (window-width))) + (columns (min + ;; At least 2 columns; at least 2 spaces between columns. + (max 2 (/ wwidth (+ 2 length))) + ;; Don't allocate more columns than we can fill. + ;; Windows can't show less than 3 lines anyway. + (max 1 (/ (length strings) 2)))) + (colwidth (/ wwidth columns))) + ;; Use tab-width rather than indent-to. + (setq tab-width colwidth) + ;; The insertion should be "sensible" no matter what choices were made. + (dolist (str strings) + (unless (bolp) + (insert " \t") + (when (< wwidth (+ (max colwidth (length str)) (current-column))) + (delete-char -2) (insert "\n"))) + (insert str))))) + + +(defun cvs-file-to-string (file &optional oneline args) + "Read the content of FILE and return it as a string. +If ONELINE is t, only the first line (no \\n) will be returned. +If ARGS is non-nil, the file will be executed with ARGS as its +arguments. If ARGS is not a list, no argument will be passed." + (condition-case nil + (with-temp-buffer + (if args + (apply 'call-process + file nil t nil (when (listp args) args)) + (insert-file-contents file)) + (goto-char (point-min)) + (buffer-substring (point) + (if oneline (line-end-position) (point-max)))) + (file-error nil))) + +(defun cvs-string-prefix-p (str1 str2) + "Tell whether STR1 is a prefix of STR2." + (eq t (compare-strings str2 nil (length str1) str1 nil nil))) + +;;;; +;;;; file names +;;;; + +(defsubst cvs-expand-dir-name (d) + (file-name-as-directory (expand-file-name d))) + +;;;; +;;;; (interactive ) support function +;;;; + +(defstruct (cvs-qtypedesc + (:constructor nil) (:copier nil) + (:constructor cvs-qtypedesc-create + (str2obj obj2str &optional complete hist-sym require))) + str2obj + obj2str + hist-sym + complete + require) + + +(defconst cvs-qtypedesc-string1 (cvs-qtypedesc-create 'identity 'identity t)) +(defconst cvs-qtypedesc-string (cvs-qtypedesc-create 'identity 'identity)) +(defconst cvs-qtypedesc-strings + (cvs-qtypedesc-create 'split-string-and-unquote + 'combine-and-quote-strings nil)) + +(defun cvs-query-read (default prompt qtypedesc &optional hist-sym) + (let* ((qtypedesc (or qtypedesc cvs-qtypedesc-strings)) + (hist-sym (or hist-sym (cvs-qtypedesc-hist-sym qtypedesc))) + (complete (cvs-qtypedesc-complete qtypedesc)) + (completions (and (functionp complete) (funcall complete))) + (initval (funcall (cvs-qtypedesc-obj2str qtypedesc) default))) + (funcall (cvs-qtypedesc-str2obj qtypedesc) + (cond + ((null complete) (read-string prompt initval hist-sym)) + ((functionp complete) + (completing-read prompt completions + nil (cvs-qtypedesc-require qtypedesc) + initval hist-sym)) + (t initval))))) + +;;;; +;;;; Flags handling +;;;; + +(defstruct (cvs-flags + (:constructor nil) + (:constructor -cvs-flags-make + (desc defaults &optional qtypedesc hist-sym))) + defaults persist desc qtypedesc hist-sym) + +(defmacro cvs-flags-define (sym defaults + &optional desc qtypedesc hist-sym docstring) + `(defconst ,sym + (let ((bound (boundp ',sym))) + (if (and bound (cvs-flags-p ,sym)) ,sym + (let ((defaults ,defaults)) + (-cvs-flags-make ,desc + (if bound (cons ,sym (cdr defaults)) defaults) + ,qtypedesc ,hist-sym)))) + ,docstring)) + +(defun cvs-flags-query (sym &optional desc arg) + "Query flags based on SYM. +Optional argument DESC will be used for the prompt. +If ARG (or a prefix argument) is nil, just use the 0th default. +If it is a non-negative integer, use the corresponding default. +If it is a negative integer query for a new value of the corresponding + default and return that new value. +If it is \\[universal-argument], just query and return a value without + altering the defaults. +If it is \\[universal-argument] \\[universal-argument], behave just + as if a negative zero was provided." + (let* ((flags (symbol-value sym)) + (desc (or desc (cvs-flags-desc flags))) + (qtypedesc (cvs-flags-qtypedesc flags)) + (hist-sym (cvs-flags-hist-sym flags)) + (arg (if (eq arg 'noquery) 0 (or arg current-prefix-arg 0))) + (numarg (prefix-numeric-value arg)) + (defaults (cvs-flags-defaults flags)) + (permstr (if (< numarg 0) (format " (%sth default)" (- numarg))))) + ;; special case for universal-argument + (when (consp arg) + (setq permstr (if (> numarg 4) " (permanent)" "")) + (setq numarg 0)) + + ;; sanity check + (unless (< (abs numarg) (length defaults)) + (error "There is no %sth default" (abs numarg))) + + (if permstr + (let* ((prompt (format "%s%s: " desc permstr)) + (fs (cvs-query-read (nth (- numarg) (cvs-flags-defaults flags)) + prompt qtypedesc hist-sym))) + (when (not (equal permstr "")) + (setf (nth (- numarg) (cvs-flags-defaults flags)) fs)) + fs) + (nth numarg defaults)))) + +(defsubst cvs-flags-set (sym index value) + "Set SYM's INDEX'th setting to VALUE." + (setf (nth index (cvs-flags-defaults (symbol-value sym))) value)) + +;;;; +;;;; Prefix keys +;;;; + +(defconst cvs-prefix-number 10) + +(defsubst cvs-prefix-sym (sym) (intern (concat (symbol-name sym) "-cps"))) + +(defmacro cvs-prefix-define (sym docstring desc defaults + &optional qtypedesc hist-sym) + (let ((cps (cvs-prefix-sym sym))) + `(progn + (defvar ,sym nil ,(concat (or docstring "") " +See `cvs-prefix-set' for further description of the behavior.")) + (defvar ,cps + (let ((defaults ,defaults)) + ;; sanity ensurance + (unless (>= (length defaults) cvs-prefix-number) + (setq defaults (append defaults + (make-list (1- cvs-prefix-number) + (nth 0 defaults))))) + (-cvs-flags-make ,desc defaults ,qtypedesc ,hist-sym)))))) + +(defun cvs-prefix-make-local (sym) + (let ((cps (cvs-prefix-sym sym))) + (make-local-variable sym) + (set (make-local-variable cps) (copy-cvs-flags (symbol-value cps))))) + +(defun cvs-prefix-set (sym arg) + ;; we could distinguish between numeric and non-numeric prefix args instead of + ;; relying on that magic `4'. + "Set the cvs-prefix contained in SYM. +If ARG is between 0 and 9, it selects the corresponding default. +If ARG is negative (or \\[universal-argument] which corresponds to negative 0), + it queries the user and sets the -ARG'th default. +If ARG is greater than 9 (or \\[universal-argument] \\[universal-argument]), + the (ARG mod 10)'th prefix is made persistent. +If ARG is nil toggle the PREFIX's value between its 0th default and nil + and reset the persistence." + (let* ((prefix (symbol-value (cvs-prefix-sym sym))) + (numarg (if (integerp arg) arg 0)) + ;; (defs (cvs-flags-defaults prefix)) + ) + + ;; set persistence if requested + (when (> (prefix-numeric-value arg) 9) + (setf (cvs-flags-persist prefix) t) + (setq numarg (mod numarg 10))) + + ;; set the value + (set sym + (cond + ((null arg) + (setf (cvs-flags-persist prefix) nil) + (unless (symbol-value sym) (nth 0 (cvs-flags-defaults prefix)))) + + ((or (consp arg) (< numarg 0)) + (setf (nth (- numarg) (cvs-flags-defaults prefix)) + (cvs-query-read (nth (- numarg) (cvs-flags-defaults prefix)) + (format "%s: " (cvs-flags-desc prefix)) + (cvs-flags-qtypedesc prefix) + (cvs-flags-hist-sym prefix)))) + (t (nth numarg (cvs-flags-defaults prefix))))) + (force-mode-line-update))) + +(defun cvs-prefix-get (sym &optional read-only) + "Return the current value of the prefix SYM. +And reset it unless READ-ONLY is non-nil." + (prog1 (symbol-value sym) + (unless (or read-only + (cvs-flags-persist (symbol-value (cvs-prefix-sym sym)))) + (set sym nil) + (force-mode-line-update)))) + +(provide 'pcvs-util) + +;; arch-tag: 3b2588bb-2ae3-4f1f-bf5b-dea91b1f8a59 +;;; pcvs-util.el ends here diff -r 681cd08dc0f7 -r e93288477c43 lisp/vc/pcvs.el --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/lisp/vc/pcvs.el Sun Jun 13 22:57:55 2010 +0000 @@ -0,0 +1,2443 @@ +;;; pcvs.el --- a front-end to CVS + +;; Copyright (C) 1991, 1992, 1993, 1994, 1995, 1996, 1997, 1998, 1999, +;; 2000, 2001, 2002, 2003, 2004, 2005, 2006, 2007, 2008, 2009, 2010 Free Software Foundation, Inc. + +;; Author: (The PCL-CVS Trust) pcl-cvs@cyclic.com +;; (Per Cederqvist) ceder@lysator.liu.se +;; (Greg A. Woods) woods@weird.com +;; (Jim Blandy) jimb@cyclic.com +;; (Karl Fogel) kfogel@floss.red-bean.com +;; (Jim Kingdon) kingdon@cyclic.com +;; (Stefan Monnier) monnier@cs.yale.edu +;; (Greg Klanderman) greg@alphatech.com +;; (Jari Aalto+mail.emacs) jari.aalto@poboxes.com +;; Maintainer: (Stefan Monnier) monnier@gnu.org +;; Keywords: CVS, vc, release management + +;; 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 3 of the License, 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. If not, see . + +;;; Commentary: + +;; PCL-CVS is a front-end to the CVS version control system. For people +;; familiar with VC, it is somewhat like VC-dired: it presents the status of +;; all the files in your working area and allows you to commit/update several +;; of them at a time. Compared to VC-dired, it is considerably better and +;; faster (but only for CVS). + +;; PCL-CVS was originally written by Per Cederqvist many years ago. This +;; version derives from the XEmacs-21 version, itself based on the 2.0b2 +;; version (last release from Per). It is a thorough rework. + +;; Contrary to what you'd expect, PCL-CVS is not a replacement for VC but only +;; for VC-dired. As such, I've tried to make PCL-CVS and VC interoperate +;; seamlessly (I also use VC). + +;; To use PCL-CVS just use `M-x cvs-examine RET RET'. +;; There is a TeXinfo manual, which can be helpful to get started. + +;;; Bugs: + +;; - Extracting an old version seems not to recognize encoding correctly. +;; That's probably because it's done via a process rather than a file. + +;;; Todo: + +;; ******** FIX THE DOCUMENTATION ********* +;; +;; - rework the displaying of error messages. +;; - allow to flush messages only +;; - allow to protect files like ChangeLog from flushing +;; - automatically cvs-mode-insert files from find-file-hook +;; (and don't flush them as long as they are visited) +;; - query the user for cvs-get-marked (for some cmds or if nothing's selected) +;; - don't return the first (resp last) FI if the cursor is before +;; (resp after) it. +;; - allow cvs-confirm-removals to force always confirmation. +;; - cvs-checkout should ask for a revision (with completion). +;; - removal confirmation should allow specifying another file name. +;; +;; - hide fileinfos without getting rid of them (will require ewok work). +;; - add toolbar entries +;; - marking +;; marking directories should jump to just after the dir. +;; allow (un)marking directories at a time with the mouse. +;; allow cvs-cmd-do to either clear the marks or not. +;; add a "marks active" notion, like transient-mark-mode does. +;; - liveness indicator +;; - indicate in docstring if the cmd understands the `b' prefix(es). +;; - call smerge-mode when opening CONFLICT files. +;; - have vc-checkin delegate to cvs-mode-commit when applicable +;; - higher-level CVS operations +;; cvs-mode-rename +;; cvs-mode-branch +;; - module-level commands +;; add support for parsing 'modules' file ("cvs co -c") +;; cvs-mode-rcs2log +;; cvs-rdiff +;; cvs-release +;; cvs-import +;; C-u M-x cvs-checkout should ask for a cvsroot +;; cvs-mode-handle-new-vendor-version +;; - checks out module, or alternately does update join +;; - does "cvs -n tag LAST_VENDOR" to find old files into *cvs* +;; cvs-export +;; (with completion on tag names and hooks to help generate full releases) +;; - display stickiness information. And current CVS/Tag as well. +;; - write 'cvs-mode-admin' to do arbitrary 'cvs admin' commands +;; Most interesting would be version removal and log message replacement. +;; The last one would be neat when called from log-view-mode. +;; - cvs-mode-incorporate +;; It would merge in the status from one *cvs* buffer into another. +;; This would be used to populate such a buffer that had been created with +;; a `cvs {update,status,checkout} -l'. +;; - cvs-mode-(i)diff-other-{file,buffer,cvs-buffer} +;; - offer the choice to kill the process when the user kills the cvs buffer. +;; right now, it's killed without further ado. +;; - make `cvs-mode-ignore' allow manually entering a pattern. +;; to which dir should it apply ? +;; - cvs-mode-ignore should try to remove duplicate entries. +;; - maybe poll/check CVS/Entries files to react to external `cvs' commands ? +;; - some kind of `cvs annotate' support ? +;; but vc-annotate can be used instead. +;; - proper `g' that passes safe args and uses either cvs-status or cvs-examine +;; maybe also use cvs-update depending on I-don't-know-what. +;; - add message-levels so that we can hide some levels of messages + +;;; Code: + +(eval-when-compile (require 'cl)) +(require 'ewoc) ;Ewoc was once cookie +(require 'pcvs-defs) +(require 'pcvs-util) +(require 'pcvs-parse) +(require 'pcvs-info) + + +;;;; +;;;; global vars +;;;; + +(defvar cvs-cookies) ;;nil + ;;"Handle for the cookie structure that is displayed in the *cvs* buffer.") +;;(make-variable-buffer-local 'cvs-cookies) + +;;;; +;;;; Dynamically scoped variables +;;;; + +(defvar cvs-from-vc nil "Bound to t inside VC advice.") + +;;;; +;;;; flags variables +;;;; + +(defun cvs-defaults (&rest defs) + (let ((defs (cvs-first defs cvs-shared-start))) + (append defs + (make-list (- cvs-shared-start (length defs)) (car defs)) + cvs-shared-flags))) + +;; For cvs flags, we need to add "-f" to override the cvsrc settings +;; we also want to evict the annoying -q and -Q options that hide useful +;; information from pcl-cvs. +(cvs-flags-define cvs-cvs-flags '(("-f"))) + +(cvs-flags-define cvs-checkout-flags (cvs-defaults '("-P"))) +(cvs-flags-define cvs-status-flags (cvs-defaults '("-v") nil)) +(cvs-flags-define cvs-log-flags (cvs-defaults nil)) +(cvs-flags-define cvs-diff-flags (cvs-defaults '("-u" "-N") '("-c" "-N") '("-u" "-b"))) +(cvs-flags-define cvs-tag-flags (cvs-defaults nil)) +(cvs-flags-define cvs-add-flags (cvs-defaults nil)) +(cvs-flags-define cvs-commit-flags (cvs-defaults nil)) +(cvs-flags-define cvs-remove-flags (cvs-defaults nil)) +;;(cvs-flags-define cvs-undo-flags (cvs-defaults nil)) +(cvs-flags-define cvs-update-flags (cvs-defaults '("-d" "-P"))) + +(defun cvs-reread-cvsrc () + "Reset the default arguments to those in the `cvs-cvsrc-file'." + (interactive) + (condition-case nil + (with-temp-buffer + (insert-file-contents cvs-cvsrc-file) + ;; fetch the values + (dolist (cmd '("cvs" "checkout" "status" "log" "diff" "tag" + "add" "commit" "remove" "update")) + (goto-char (point-min)) + (when (re-search-forward + (concat "^" cmd "\\(\\s-+\\(.*\\)\\)?$") nil t) + (let* ((sym (intern (concat "cvs-" cmd "-flags"))) + (val (split-string-and-unquote (or (match-string 2) "")))) + (cvs-flags-set sym 0 val)))) + ;; ensure that cvs doesn't have -q or -Q + (cvs-flags-set 'cvs-cvs-flags 0 + (cons "-f" + (cdr (cvs-partition + (lambda (x) (member x '("-q" "-Q" "-f"))) + (cvs-flags-query 'cvs-cvs-flags + nil 'noquery)))))) + (file-error nil))) + +;; initialize to cvsrc's default values +(cvs-reread-cvsrc) + + +;;;; +;;;; Mouse bindings and mode motion +;;;; + +(defvar cvs-minor-current-files) + +(defun cvs-menu (e) + "Popup the CVS menu." + (interactive "e") + (let ((cvs-minor-current-files + (list (ewoc-data (ewoc-locate + cvs-cookies (posn-point (event-end e))))))) + (popup-menu cvs-menu e))) + +(defvar cvs-mode-line-process nil + "Mode-line control for displaying info on cvs process status.") + + +;;;; +;;;; Query-Type-Descriptor for Tags +;;;; + +(autoload 'cvs-status-get-tags "cvs-status") +(defun cvs-tags-list () + "Return a list of acceptable tags, ready for completions." + (assert (cvs-buffer-p)) + (let ((marked (cvs-get-marked))) + (list* '("BASE") '("HEAD") + (when marked + (with-temp-buffer + (process-file cvs-program + nil ;no input + t ;output to current-buffer + nil ;don't update display while running + "status" + "-v" + (cvs-fileinfo->full-name (car marked))) + (goto-char (point-min)) + (let ((tags (cvs-status-get-tags))) + (when (listp tags) tags))))))) + +(defvar cvs-tag-history nil) +(defconst cvs-qtypedesc-tag + (cvs-qtypedesc-create 'identity 'identity 'cvs-tags-list 'cvs-tag-history)) + +;;;; + +(defun cvs-mode! (&optional -cvs-mode!-fun) + "Switch to the *cvs* buffer. +If -CVS-MODE!-FUN is provided, it is executed *cvs* being the current buffer + and with its window selected. Else, the *cvs* buffer is simply selected. +-CVS-MODE!-FUN is called interactively if applicable and else with no argument." + (let* ((-cvs-mode!-buf (current-buffer)) + (cvsbuf (cond ((cvs-buffer-p) (current-buffer)) + ((and cvs-buffer (cvs-buffer-p cvs-buffer)) cvs-buffer) + (t (error "can't find the *cvs* buffer")))) + (-cvs-mode!-wrapper cvs-minor-wrap-function) + (-cvs-mode!-cont (lambda () + (save-current-buffer + (if (commandp -cvs-mode!-fun) + (call-interactively -cvs-mode!-fun) + (funcall -cvs-mode!-fun)))))) + (if (not -cvs-mode!-fun) (set-buffer cvsbuf) + (let ((cvs-mode!-buf (current-buffer)) + (cvs-mode!-owin (selected-window)) + (cvs-mode!-nwin (get-buffer-window cvsbuf 'visible))) + (unwind-protect + (progn + (set-buffer cvsbuf) + (when cvs-mode!-nwin (select-window cvs-mode!-nwin)) + (if -cvs-mode!-wrapper + (funcall -cvs-mode!-wrapper -cvs-mode!-buf -cvs-mode!-cont) + (funcall -cvs-mode!-cont))) + (set-buffer cvs-mode!-buf) + (when (and cvs-mode!-nwin (eq cvs-mode!-nwin (selected-window))) + ;; the selected window has not been changed by FUN + (select-window cvs-mode!-owin))))))) + +;;;; +;;;; Prefixes +;;;; + +(defvar cvs-branches (list cvs-vendor-branch "HEAD" "HEAD")) +(cvs-prefix-define cvs-branch-prefix + "Current selected branch." + "version" + (cons cvs-vendor-branch cvs-branches) + cvs-qtypedesc-tag) + +(defun cvs-set-branch-prefix (arg) + "Set the branch prefix to take action at the next command. +See `cvs-prefix-set' for a further the description of the behavior. +\\[universal-argument] 1 selects the vendor branch +and \\[universal-argument] 2 selects the HEAD." + (interactive "P") + (cvs-mode!) + (cvs-prefix-set 'cvs-branch-prefix arg)) + +(defun cvs-add-branch-prefix (flags &optional arg) + "Add branch selection argument if the branch prefix was set. +The argument is added (or not) to the list of FLAGS and is constructed +by appending the branch to ARG which defaults to \"-r\"." + (let ((branch (cvs-prefix-get 'cvs-branch-prefix))) + ;; deactivate the secondary prefix, even if not used. + (cvs-prefix-get 'cvs-secondary-branch-prefix) + (if branch (cons (concat (or arg "-r") branch) flags) flags))) + +(cvs-prefix-define cvs-secondary-branch-prefix + "Current secondary selected branch." + "version" + (cons cvs-vendor-branch cvs-branches) + cvs-qtypedesc-tag) + +(defun cvs-set-secondary-branch-prefix (arg) + "Set the branch prefix to take action at the next command. +See `cvs-prefix-set' for a further the description of the behavior. +\\[universal-argument] 1 selects the vendor branch +and \\[universal-argument] 2 selects the HEAD." + (interactive "P") + (cvs-mode!) + (cvs-prefix-set 'cvs-secondary-branch-prefix arg)) + +(defun cvs-add-secondary-branch-prefix (flags &optional arg) + "Add branch selection argument if the secondary branch prefix was set. +The argument is added (or not) to the list of FLAGS and is constructed +by appending the branch to ARG which defaults to \"-r\". +Since the `cvs-secondary-branch-prefix' is only active if the primary +prefix is active, it is important to read the secondary prefix before +the primay since reading the primary can deactivate it." + (let ((branch (and (cvs-prefix-get 'cvs-branch-prefix 'read-only) + (cvs-prefix-get 'cvs-secondary-branch-prefix)))) + (if branch (cons (concat (or arg "-r") branch) flags) flags))) + +;;;; + +(define-minor-mode cvs-minor-mode + "This mode is used for buffers related to a main *cvs* buffer. +All the `cvs-mode' buffer operations are simply rebound under +the \\[cvs-mode-map] prefix." + nil " CVS" + :group 'pcl-cvs) +(put 'cvs-minor-mode 'permanent-local t) + + +(defvar cvs-temp-buffers nil) +(defun cvs-temp-buffer (&optional cmd normal nosetup) + "Create a temporary buffer to run CMD in. +If CMD is a string, use it to lookup `cvs-buffer-name-alist' to find +the buffer name to be used and its `major-mode'. + +The selected window will not be changed. The new buffer will not maintain undo +information and will be read-only unless NORMAL is non-nil. It will be emptied +\(unless NOSETUP is non-nil\) and its `default-directory' will be inherited +from the current buffer." + (let* ((cvs-buf (current-buffer)) + (info (cdr (assoc cmd cvs-buffer-name-alist))) + (name (eval (nth 0 info))) + (mode (nth 1 info)) + (dir default-directory) + (buf (cond + (name (cvs-get-buffer-create name)) + ((and (bufferp cvs-temp-buffer) (buffer-live-p cvs-temp-buffer)) + cvs-temp-buffer) + (t + (set (make-local-variable 'cvs-temp-buffer) + (cvs-get-buffer-create + (eval cvs-temp-buffer-name) 'noreuse)))))) + + ;; handle the potential pre-existing process + (let ((proc (get-buffer-process buf))) + (when (and (not normal) (processp proc) + (memq (process-status proc) '(run stop))) + (if cmd + ;; When CMD is specified, the buffer is normally shown to the + ;; user, so interrupting the process is not harmful. + ;; Use `delete-process' rather than `kill-process' otherwise + ;; the pending output of the process will still get inserted + ;; after we erase the buffer. + (delete-process proc) + (error "Can not run two cvs processes simultaneously")))) + + (if (not name) (kill-local-variable 'other-window-scroll-buffer) + ;; Strangely, if no window is created, `display-buffer' ends up + ;; doing a `switch-to-buffer' which does a `set-buffer', hence + ;; the need for `save-excursion'. + (unless nosetup (save-excursion (display-buffer buf))) + ;; FIXME: this doesn't do the right thing if the user later on + ;; does a `find-file-other-window' and `scroll-other-window' + (set (make-local-variable 'other-window-scroll-buffer) buf)) + + (add-to-list 'cvs-temp-buffers buf) + + (with-current-buffer buf + (setq buffer-read-only nil) + (setq default-directory dir) + (unless nosetup + ;; Disable undo before calling erase-buffer since it may generate + ;; a very large and unwanted undo record. + (buffer-disable-undo) + (erase-buffer)) + (set (make-local-variable 'cvs-buffer) cvs-buf) + ;;(cvs-minor-mode 1) + (let ((lbd list-buffers-directory)) + (if (fboundp mode) (funcall mode) (fundamental-mode)) + (when lbd (setq list-buffers-directory lbd))) + (cvs-minor-mode 1) + ;;(set (make-local-variable 'cvs-buffer) cvs-buf) + (if normal + (buffer-enable-undo) + (setq buffer-read-only t) + (buffer-disable-undo)) + buf))) + +(defun cvs-mode-kill-buffers () + "Kill all the \"temporary\" buffers created by the *cvs* buffer." + (interactive) + (dolist (buf cvs-temp-buffers) (ignore-errors (kill-buffer buf)))) + +(defun cvs-make-cvs-buffer (dir &optional new) + "Create the *cvs* buffer for directory DIR. +If non-nil, NEW means to create a new buffer no matter what." + ;; the real cvs-buffer creation + (setq dir (cvs-expand-dir-name dir)) + (let* ((buffer-name (eval cvs-buffer-name)) + (buffer + (or (and (not new) + (eq cvs-reuse-cvs-buffer 'current) + (cvs-buffer-p) ;reuse the current buffer if possible + (current-buffer)) + ;; look for another cvs buffer visiting the same directory + (save-excursion + (unless new + (dolist (buffer (cons (current-buffer) (buffer-list))) + (set-buffer buffer) + (and (cvs-buffer-p) + (case cvs-reuse-cvs-buffer + (always t) + (subdir + (or (cvs-string-prefix-p default-directory dir) + (cvs-string-prefix-p dir default-directory))) + (samedir (string= default-directory dir))) + (return buffer))))) + ;; we really have to create a new buffer: + ;; we temporarily bind cwd to "" to prevent + ;; create-file-buffer from using directory info + ;; unless it is explicitly in the cvs-buffer-name. + (cvs-get-buffer-create buffer-name new)))) + (with-current-buffer buffer + (or + (and (string= dir default-directory) (cvs-buffer-p) + ;; just a refresh + (ignore-errors + (cvs-cleanup-collection cvs-cookies nil nil t) + (current-buffer))) + ;; setup from scratch + (progn + (setq default-directory dir) + (setq buffer-read-only nil) + (erase-buffer) + (insert "Repository : " (directory-file-name (cvs-get-cvsroot)) + "\nModule : " (cvs-get-module) + "\nWorking dir: " (abbreviate-file-name dir) + (if (not (file-readable-p "CVS/Tag")) "\n" + (let ((tag (cvs-file-to-string "CVS/Tag"))) + (cond + ((string-match "\\`T" tag) + (concat "\nTag : " (substring tag 1))) + ((string-match "\\`D" tag) + (concat "\nDate : " (substring tag 1))) + ("\n")))) + "\n") + (setq buffer-read-only t) + (cvs-mode) + (set (make-local-variable 'list-buffers-directory) buffer-name) + ;;(set (make-local-variable 'cvs-temp-buffer) (cvs-temp-buffer)) + (let ((cookies (ewoc-create 'cvs-fileinfo-pp "\n\n" "\n" t))) + (set (make-local-variable 'cvs-cookies) cookies) + (add-hook 'kill-buffer-hook + (lambda () + (ignore-errors (kill-buffer cvs-temp-buffer))) + nil t) + ;;(set-buffer buf) + buffer)))))) + +(defun* cvs-cmd-do (cmd dir flags fis new + &key cvsargs noexist dont-change-disc noshow) + (let* ((dir (file-name-as-directory + (abbreviate-file-name (expand-file-name dir)))) + (cvsbuf (cvs-make-cvs-buffer dir new))) + ;; Check that dir is under CVS control. + (unless (file-directory-p dir) + (error "%s is not a directory" dir)) + (unless (or noexist (file-directory-p (expand-file-name "CVS" dir)) + (file-expand-wildcards (expand-file-name "*/CVS" dir))) + (error "%s does not contain CVS controlled files" dir)) + + (set-buffer cvsbuf) + (cvs-mode-run cmd flags fis + :cvsargs cvsargs :dont-change-disc dont-change-disc) + + (if noshow cvsbuf + (let ((pop-up-windows nil)) (pop-to-buffer cvsbuf))))) +;; (funcall (if (and (boundp 'pop-up-frames) pop-up-frames) +;; 'pop-to-buffer 'switch-to-buffer) +;; cvsbuf)))) + +(defun cvs-run-process (args fis postprocess &optional single-dir) + (assert (cvs-buffer-p cvs-buffer)) + (save-current-buffer + (let ((procbuf (current-buffer)) + (cvsbuf cvs-buffer) + (single-dir (or single-dir (eq cvs-execute-single-dir t)))) + + (set-buffer procbuf) + (goto-char (point-max)) + (unless (bolp) (let ((inhibit-read-only t)) (insert "\n"))) + ;; find the set of files we'll process in this round + (let* ((dir+files+rest + (if (or (null fis) (not single-dir)) + ;; not single-dir mode: just process the whole thing + (list "" (mapcar 'cvs-fileinfo->full-name fis) nil) + ;; single-dir mode: extract the same-dir-elements + (let ((dir (cvs-fileinfo->dir (car fis)))) + ;; output the concerned dir so the parser can translate paths + (let ((inhibit-read-only t)) + (insert "pcl-cvs: descending directory " dir "\n")) + ;; loop to find the same-dir-elems + (do* ((files () (cons (cvs-fileinfo->file fi) files)) + (fis fis (cdr fis)) + (fi (car fis) (car fis))) + ((not (and fis (string= dir (cvs-fileinfo->dir fi)))) + (list dir files fis)))))) + (dir (nth 0 dir+files+rest)) + (files (nth 1 dir+files+rest)) + (rest (nth 2 dir+files+rest))) + + (add-hook 'kill-buffer-hook + (lambda () + (let ((proc (get-buffer-process (current-buffer)))) + (when (processp proc) + (set-process-filter proc nil) + ;; Abort postprocessing but leave the sentinel so it + ;; will update the list of running procs. + (process-put proc 'cvs-postprocess nil) + (interrupt-process proc)))) + nil t) + + ;; create the new process and setup the procbuffer correspondingly + (let* ((msg (cvs-header-msg args fis)) + (args (append (cvs-flags-query 'cvs-cvs-flags nil 'noquery) + (if cvs-cvsroot (list "-d" cvs-cvsroot)) + args + files)) + ;; If process-connection-type is nil and the repository + ;; is accessed via SSH, a bad interaction between libc, + ;; CVS and SSH can lead to garbled output. + ;; It might be a glibc-specific problem (but it can also happens + ;; under Mac OS X, it seems). + ;; It seems that using a pty can help circumvent the problem, + ;; but at the cost of screwing up when the process thinks it + ;; can ask for user input (such as password or host-key + ;; confirmation). A better workaround is to set CVS_RSH to + ;; an appropriate script, or to use a later version of CVS. + (process-connection-type nil) ; Use a pipe, not a pty. + (process + ;; the process will be run in the selected dir + (let ((default-directory (cvs-expand-dir-name dir))) + (apply 'start-file-process "cvs" procbuf cvs-program args)))) + ;; setup the process. + (process-put process 'cvs-buffer cvs-buffer) + (with-current-buffer cvs-buffer (cvs-update-header msg 'add)) + (process-put process 'cvs-header msg) + (process-put + process 'cvs-postprocess + (if (null rest) + ;; this is the last invocation + postprocess + ;; else, we have to register ourselves to be rerun on the rest + `(cvs-run-process ',args ',rest ',postprocess ',single-dir))) + (set-process-sentinel process 'cvs-sentinel) + (set-process-filter process 'cvs-update-filter) + (set-marker (process-mark process) (point-max)) + (ignore-errors (process-send-eof process)) ;close its stdin to avoid hangs + + ;; now finish setting up the cvs-buffer + (set-buffer cvsbuf) + (setq cvs-mode-line-process (symbol-name (process-status process))) + (force-mode-line-update))))) + + ;; The following line is said to improve display updates on some + ;; emacsen. It shouldn't be needed, but it does no harm. + (sit-for 0)) + +(defun cvs-header-msg (args fis) + (let* ((lastarg nil) + (args (mapcar (lambda (arg) + (cond + ;; filter out the largish commit message + ((and (eq lastarg nil) (string= arg "commit")) + (setq lastarg 'commit) arg) + ((and (eq lastarg 'commit) (string= arg "-m")) + (setq lastarg '-m) arg) + ((eq lastarg '-m) + (setq lastarg 'done) "") + ;; filter out the largish `admin -mrev:msg' message + ((and (eq lastarg nil) (string= arg "admin")) + (setq lastarg 'admin) arg) + ((and (eq lastarg 'admin) + (string-match "\\`-m[^:]*:" arg)) + (setq lastarg 'done) + (concat (match-string 0 arg) "")) + ;; Keep the rest as is. + (t arg))) + args))) + (concat cvs-program " " + (combine-and-quote-strings + (append (cvs-flags-query 'cvs-cvs-flags nil 'noquery) + (if cvs-cvsroot (list "-d" cvs-cvsroot)) + args + (mapcar 'cvs-fileinfo->full-name fis)))))) + +(defun cvs-update-header (cmd add) + (let* ((hf (ewoc-get-hf cvs-cookies)) + (str (car hf)) + (done "") + (tin (ewoc-nth cvs-cookies 0))) + ;; look for the first *real* fileinfo (to determine emptyness) + (while + (and tin + (memq (cvs-fileinfo->type (ewoc-data tin)) + '(MESSAGE DIRCHANGE))) + (setq tin (ewoc-next cvs-cookies tin))) + (if add + (progn + ;; Remove the default empty line, if applicable. + (if (not (string-match "." str)) (setq str "\n")) + (setq str (concat "-- Running " cmd " ...\n" str))) + (if (not (string-match + ;; FIXME: If `cmd' is large, this will bump into the + ;; compiled-regexp size limit. We could drop the "^" anchor + ;; and use search-forward to circumvent the problem. + (concat "^-- Running " (regexp-quote cmd) " \\.\\.\\.\n") str)) + (error "Internal PCL-CVS error while removing message") + (setq str (replace-match "" t t str)) + ;; Re-add the default empty line, if applicable. + (if (not (string-match "." str)) (setq str "\n\n")) + (setq done (concat "-- last cmd: " cmd " --\n")))) + ;; set the new header and footer + (ewoc-set-hf cvs-cookies + str (concat "\n--------------------- " + (if tin "End" "Empty") + " ---------------------\n" + done)))) + + +(defun cvs-sentinel (proc msg) + "Sentinel for the cvs update process. +This is responsible for parsing the output from the cvs update when +it is finished." + (when (memq (process-status proc) '(signal exit)) + (let ((cvs-postproc (process-get proc 'cvs-postprocess)) + (cvs-buf (process-get proc 'cvs-buffer)) + (procbuf (process-buffer proc))) + (unless (buffer-live-p cvs-buf) (setq cvs-buf nil)) + (unless (buffer-live-p procbuf) (setq procbuf nil)) + ;; Since the buffer and mode line will show that the + ;; process is dead, we can delete it now. Otherwise it + ;; will stay around until M-x list-processes. + (process-put proc 'postprocess nil) + (delete-process proc) + ;; Don't do anything if the main buffer doesn't exist any more. + (when cvs-buf + (with-current-buffer cvs-buf + (cvs-update-header (process-get proc 'cvs-header) nil) + (setq cvs-mode-line-process (symbol-name (process-status proc))) + (force-mode-line-update) + (when cvs-postproc + (if (null procbuf) + ;;(set-process-buffer proc nil) + (error "cvs' process buffer was killed") + (with-current-buffer procbuf + ;; Do the postprocessing like parsing and such. + (save-excursion (eval cvs-postproc))))))) + ;; Check whether something is left. + (when (and procbuf (not (get-buffer-process procbuf))) + (with-current-buffer procbuf + ;; IIRC, we enable undo again once the process is finished + ;; for cases where the output was inserted in *vc-diff* or + ;; in a file-like buffer. --Stef + (buffer-enable-undo) + (with-current-buffer (or cvs-buf (current-buffer)) + (message "CVS process has completed in %s" + (buffer-name)))))))) + +(defun cvs-parse-process (dcd &optional subdir old-fis) + "Parse the output of a cvs process. +DCD is the `dont-change-disc' flag to use when parsing that output. +SUBDIR is the subdirectory (if any) where this command was run. +OLD-FIS is the list of fileinfos on which the cvs command was applied and + which should be considered up-to-date if they are missing from the output." + (when (eq system-type 'darwin) + ;; Fixup the ^D^H^H inserted at beginning of buffer sometimes on MacOSX + ;; because of the call to `process-send-eof'. + (save-excursion + (goto-char (point-min)) + (while (re-search-forward "^\\^D+" nil t) + (let ((inhibit-read-only t)) + (delete-region (match-beginning 0) (match-end 0)))))) + (let* ((fileinfos (cvs-parse-buffer 'cvs-parse-table dcd subdir)) + last) + (with-current-buffer cvs-buffer + ;; Expand OLD-FIS to actual files. + (let ((fis nil)) + (dolist (fi old-fis) + (setq fis (if (eq (cvs-fileinfo->type fi) 'DIRCHANGE) + (nconc (ewoc-collect cvs-cookies 'cvs-dir-member-p + (cvs-fileinfo->dir fi)) + fis) + (cons fi fis)))) + (setq old-fis fis)) + ;; Drop OLD-FIS which were already up-to-date. + (let ((fis nil)) + (dolist (fi old-fis) + (unless (eq (cvs-fileinfo->type fi) 'UP-TO-DATE) (push fi fis))) + (setq old-fis fis)) + ;; Add the new fileinfos to the ewoc. + (dolist (fi fileinfos) + (setq last (cvs-addto-collection cvs-cookies fi last)) + ;; This FI was in the output, so remove it from OLD-FIS. + (setq old-fis (delq (ewoc-data last) old-fis))) + ;; Process the "silent output" (i.e. absence means up-to-date). + (dolist (fi old-fis) + (setf (cvs-fileinfo->type fi) 'UP-TO-DATE) + (setq last (cvs-addto-collection cvs-cookies fi last))) + (setq fileinfos (nconc old-fis fileinfos)) + ;; Clean up the ewoc as requested by the user. + (cvs-cleanup-collection cvs-cookies + (eq cvs-auto-remove-handled t) + cvs-auto-remove-directories + nil) + ;; Revert buffers if necessary. + (when (and cvs-auto-revert (not dcd) (not cvs-from-vc)) + (cvs-revert-if-needed fileinfos))))) + +(defmacro defun-cvs-mode (fun args docstring interact &rest body) + "Define a function to be used in a *cvs* buffer. +This will look for a *cvs* buffer and execute BODY in it. +Since the interactive arguments might need to be queried after +switching to the *cvs* buffer, the generic code is rather ugly, +but luckily we can often use simpler alternatives. + +FUN can be either a symbol (i.e. STYLE is nil) or a cons (FUN . STYLE). +ARGS and DOCSTRING are the normal argument list. +INTERACT is the interactive specification or nil for non-commands. + +STYLE can be either SIMPLE, NOARGS or DOUBLE. It's an error for it +to have any other value, unless other details of the function make it +clear what alternative to use. +- SIMPLE will get all the interactive arguments from the original buffer. +- NOARGS will get all the arguments from the *cvs* buffer and will + always behave as if called interactively. +- DOUBLE is the generic case." + (declare (debug (&define sexp lambda-list stringp ("interactive" interactive) def-body)) + (doc-string 3)) + (let ((style (cvs-cdr fun)) + (fun (cvs-car fun))) + (cond + ;; a trivial interaction, no need to move it + ((or (eq style 'SIMPLE) + (null (nth 1 interact)) + (stringp (nth 1 interact))) + `(defun ,fun ,args ,docstring ,interact + (cvs-mode! (lambda () ,@body)))) + + ;; fun is only called interactively: move all the args to the inner fun + ((eq style 'NOARGS) + `(defun ,fun () ,docstring (interactive) + (cvs-mode! (lambda ,args ,interact ,@body)))) + + ;; bad case + ((eq style 'DOUBLE) + (string-match ".*" docstring) + (let ((line1 (match-string 0 docstring)) + (fun-1 (intern (concat (symbol-name fun) "-1")))) + `(progn + (defun ,fun-1 ,args + ,(concat docstring "\nThis function only works within a *cvs* buffer. +For interactive use, use `" (symbol-name fun) "' instead.") + ,interact + ,@body) + (put ',fun-1 'definition-name ',fun) + (defun ,fun () + ,(concat line1 "\nWrapper function that switches to a *cvs* buffer +before calling the real function `" (symbol-name fun-1) "'.\n") + (interactive) + (cvs-mode! ',fun-1))))) + + (t (error "Unknown style %s in `defun-cvs-mode'" style))))) + +(defun-cvs-mode cvs-mode-kill-process () + "Kill the temporary buffer and associated process." + (interactive) + (when (and (bufferp cvs-temp-buffer) (buffer-live-p cvs-temp-buffer)) + (let ((proc (get-buffer-process cvs-temp-buffer))) + (when proc (delete-process proc))))) + +;; +;; Maintaining the collection in the face of updates +;; + +(defun cvs-addto-collection (c fi &optional tin) + "Add FI to C and return FI's corresponding tin. +FI is inserted in its proper place or maybe even merged with a preexisting + fileinfo if applicable. +TIN specifies an optional starting point." + (unless tin (setq tin (ewoc-nth c 0))) + (while (and tin (cvs-fileinfo< fi (ewoc-data tin))) + (setq tin (ewoc-prev c tin))) + (if (null tin) (ewoc-enter-first c fi) ;empty collection + (assert (not (cvs-fileinfo< fi (ewoc-data tin)))) + (let ((next-tin (ewoc-next c tin))) + (while (not (or (null next-tin) + (cvs-fileinfo< fi (ewoc-data next-tin)))) + (setq tin next-tin next-tin (ewoc-next c next-tin))) + (if (or (cvs-fileinfo< (ewoc-data tin) fi) + (eq (cvs-fileinfo->type fi) 'MESSAGE)) + ;; tin < fi < next-tin + (ewoc-enter-after c tin fi) + ;; fi == tin + (cvs-fileinfo-update (ewoc-data tin) fi) + (ewoc-invalidate c tin) + ;; Move cursor back to where it belongs. + (when (bolp) (cvs-move-to-goal-column)) + tin)))) + +(defcustom cvs-cleanup-functions nil + "Functions to tweak the cleanup process. +The functions are called with a single argument (a FILEINFO) and should +return a non-nil value if that fileinfo should be removed." + :group 'pcl-cvs + :type '(hook :options (cvs-cleanup-removed))) + +(defun cvs-cleanup-removed (fi) + "Non-nil if FI has been cvs-removed but still exists. +This is intended for use on `cvs-cleanup-functions' when you have cvs-removed +automatically generated files (which should hence not be under CVS control) +but can't commit the removal because the repository's owner doesn't understand +the problem." + (and (or (eq (cvs-fileinfo->type fi) 'REMOVED) + (and (eq (cvs-fileinfo->type fi) 'CONFLICT) + (eq (cvs-fileinfo->subtype fi) 'REMOVED))) + (file-exists-p (cvs-fileinfo->full-name fi)))) + +;; called at the following times: +;; - postparse ((eq cvs-auto-remove-handled t) cvs-auto-remove-directories nil) +;; - pre-run ((eq cvs-auto-remove-handled 'delayed) nil t) +;; - remove-handled (t (or cvs-auto-remove-directories 'handled) t) +;; - cvs-cmd-do (nil nil t) +;; - post-ignore (nil nil nil) +;; - acknowledge (nil nil nil) +;; - remove (nil nil nil) +(defun cvs-cleanup-collection (c rm-handled rm-dirs rm-msgs) + "Remove undesired entries. +C is the collection +RM-HANDLED if non-nil means remove handled entries. +RM-DIRS behaves like `cvs-auto-remove-directories'. +RM-MSGS if non-nil means remove messages." + (let (last-fi first-dir (rerun t)) + (while rerun + (setq rerun nil) + (setq first-dir t) + (setq last-fi (cvs-create-fileinfo 'DEAD "../" "" "")) ;place-holder + (ewoc-filter + c (lambda (fi) + (let* ((type (cvs-fileinfo->type fi)) + (subtype (cvs-fileinfo->subtype fi)) + (keep + (case type + ;; remove temp messages and keep the others + (MESSAGE (not (or rm-msgs (eq subtype 'TEMP)))) + ;; remove entries + (DEAD nil) + ;; handled also? + (UP-TO-DATE (not rm-handled)) + ;; keep the rest + (t (not (run-hook-with-args-until-success + 'cvs-cleanup-functions fi)))))) + + ;; mark dirs for removal + (when (and keep rm-dirs + (eq (cvs-fileinfo->type last-fi) 'DIRCHANGE) + (not (when first-dir (setq first-dir nil) t)) + (or (eq rm-dirs 'all) + (not (cvs-string-prefix-p + (cvs-fileinfo->dir last-fi) + (cvs-fileinfo->dir fi))) + (and (eq type 'DIRCHANGE) (eq rm-dirs 'empty)) + (eq subtype 'FOOTER))) + (setf (cvs-fileinfo->type last-fi) 'DEAD) + (setq rerun t)) + (when keep (setq last-fi fi))))) + ;; remove empty last dir + (when (and rm-dirs + (not first-dir) + (eq (cvs-fileinfo->type last-fi) 'DIRCHANGE)) + (setf (cvs-fileinfo->type last-fi) 'DEAD) + (setq rerun t))))) + +(defun cvs-get-cvsroot () + "Gets the CVSROOT for DIR." + (let ((cvs-cvsroot-file (expand-file-name "Root" "CVS"))) + (or (cvs-file-to-string cvs-cvsroot-file t) + cvs-cvsroot + (getenv "CVSROOT") + "?????"))) + +(defun cvs-get-module () + "Return the current CVS module. +This usually doesn't really work but is a handy initval in a prompt." + (let* ((repfile (expand-file-name "Repository" "CVS")) + (rep (cvs-file-to-string repfile t))) + (cond + ((null rep) "") + ((not (file-name-absolute-p rep)) rep) + (t + (let* ((root (cvs-get-cvsroot)) + (str (concat (file-name-as-directory (or root "/")) " || " rep))) + (if (and root (string-match "\\(.*\\) || \\1\\(.*\\)\\'" str)) + (match-string 2 str) + (file-name-nondirectory rep))))))) + + + +;;;; +;;;; running a "cvs checkout". +;;;; + +;;;###autoload +(defun cvs-checkout (modules dir flags &optional root) + "Run a 'cvs checkout MODULES' in DIR. +Feed the output to a *cvs* buffer, display it in the current window, +and run `cvs-mode' on it. + +With a prefix argument, prompt for cvs FLAGS to use." + (interactive + (let ((root (cvs-get-cvsroot))) + (if (or (null root) current-prefix-arg) + (setq root (read-string "CVS Root: "))) + (list (split-string-and-unquote + (read-string "Module(s): " (cvs-get-module))) + (read-directory-name "CVS Checkout Directory: " + nil default-directory nil) + (cvs-add-branch-prefix + (cvs-flags-query 'cvs-checkout-flags "cvs checkout flags")) + root))) + (when (eq flags t) + (setf flags (cvs-flags-query 'cvs-checkout-flags nil 'noquery))) + (let ((cvs-cvsroot root)) + (cvs-cmd-do "checkout" (or dir default-directory) + (append flags modules) nil 'new + :noexist t))) + +(defun-cvs-mode (cvs-mode-checkout . NOARGS) (dir) + "Run cvs checkout against the current branch. +The files are stored to DIR." + (interactive + (let* ((branch (cvs-prefix-get 'cvs-branch-prefix)) + (prompt (format "CVS Checkout Directory for `%s%s': " + (cvs-get-module) + (if branch (format " (branch: %s)" branch) + "")))) + (list (read-directory-name prompt nil default-directory nil)))) + (let ((modules (split-string-and-unquote (cvs-get-module))) + (flags (cvs-add-branch-prefix + (cvs-flags-query 'cvs-checkout-flags "cvs checkout flags"))) + (cvs-cvsroot (cvs-get-cvsroot))) + (cvs-checkout modules dir flags))) + +;;;; +;;;; The code for running a "cvs update" and friends in various ways. +;;;; + +(defun-cvs-mode (cvs-mode-revert-buffer . SIMPLE) + (&optional ignore-auto noconfirm) + "Rerun `cvs-examine' on the current directory with the default flags." + (interactive) + (cvs-examine default-directory t)) + +(defun cvs-query-directory (prompt) + "Read directory name, prompting with PROMPT. +If in a *cvs* buffer, don't prompt unless a prefix argument is given." + (if (and (cvs-buffer-p) + (not current-prefix-arg)) + default-directory + (read-directory-name prompt nil default-directory nil))) + +;;;###autoload +(defun cvs-quickdir (dir &optional flags noshow) + "Open a *cvs* buffer on DIR without running cvs. +With a prefix argument, prompt for a directory to use. +A prefix arg >8 (ex: \\[universal-argument] \\[universal-argument]), + prevents reuse of an existing *cvs* buffer. +Optional argument NOSHOW if non-nil means not to display the buffer. +FLAGS is ignored." + (interactive (list (cvs-query-directory "CVS quickdir (directory): "))) + ;; FIXME: code duplication with cvs-cmd-do and cvs-parse-process + (let* ((dir (file-name-as-directory + (abbreviate-file-name (expand-file-name dir)))) + (new (> (prefix-numeric-value current-prefix-arg) 8)) + (cvsbuf (cvs-make-cvs-buffer dir new)) + last) + ;; Check that dir is under CVS control. + (unless (file-directory-p dir) + (error "%s is not a directory" dir)) + (unless (file-directory-p (expand-file-name "CVS" dir)) + (error "%s does not contain CVS controlled files" dir)) + (set-buffer cvsbuf) + (dolist (fi (cvs-fileinfo-from-entries "")) + (setq last (cvs-addto-collection cvs-cookies fi last))) + (cvs-cleanup-collection cvs-cookies + (eq cvs-auto-remove-handled t) + cvs-auto-remove-directories + nil) + (if noshow cvsbuf + (let ((pop-up-windows nil)) (pop-to-buffer cvsbuf))))) + +;;;###autoload +(defun cvs-examine (directory flags &optional noshow) + "Run a `cvs -n update' in the specified DIRECTORY. +That is, check what needs to be done, but don't change the disc. +Feed the output to a *cvs* buffer and run `cvs-mode' on it. +With a prefix argument, prompt for a directory and cvs FLAGS to use. +A prefix arg >8 (ex: \\[universal-argument] \\[universal-argument]), + prevents reuse of an existing *cvs* buffer. +Optional argument NOSHOW if non-nil means not to display the buffer." + (interactive (list (cvs-query-directory "CVS Examine (directory): ") + (cvs-flags-query 'cvs-update-flags "cvs -n update flags"))) + (when (eq flags t) + (setf flags (cvs-flags-query 'cvs-update-flags nil 'noquery))) + (when find-file-visit-truename (setq directory (file-truename directory))) + (cvs-cmd-do "update" directory flags nil + (> (prefix-numeric-value current-prefix-arg) 8) + :cvsargs '("-n") + :noshow noshow + :dont-change-disc t)) + + +;;;###autoload +(defun cvs-update (directory flags) + "Run a `cvs update' in the current working DIRECTORY. +Feed the output to a *cvs* buffer and run `cvs-mode' on it. +With a \\[universal-argument] prefix argument, prompt for a directory to use. +A prefix arg >8 (ex: \\[universal-argument] \\[universal-argument]), + prevents reuse of an existing *cvs* buffer. +The prefix is also passed to `cvs-flags-query' to select the FLAGS + passed to cvs." + (interactive (list (cvs-query-directory "CVS Update (directory): ") + (cvs-flags-query 'cvs-update-flags "cvs update flags"))) + (when (eq flags t) + (setf flags (cvs-flags-query 'cvs-update-flags nil 'noquery))) + (cvs-cmd-do "update" directory flags nil + (> (prefix-numeric-value current-prefix-arg) 8))) + + +;;;###autoload +(defun cvs-status (directory flags &optional noshow) + "Run a `cvs status' in the current working DIRECTORY. +Feed the output to a *cvs* buffer and run `cvs-mode' on it. +With a prefix argument, prompt for a directory and cvs FLAGS to use. +A prefix arg >8 (ex: \\[universal-argument] \\[universal-argument]), + prevents reuse of an existing *cvs* buffer. +Optional argument NOSHOW if non-nil means not to display the buffer." + (interactive (list (cvs-query-directory "CVS Status (directory): ") + (cvs-flags-query 'cvs-status-flags "cvs status flags"))) + (when (eq flags t) + (setf flags (cvs-flags-query 'cvs-status-flags nil 'noquery))) + (cvs-cmd-do "status" directory flags nil + (> (prefix-numeric-value current-prefix-arg) 8) + :noshow noshow :dont-change-disc t)) + +(defun cvs-update-filter (proc string) + "Filter function for pcl-cvs. +This function gets the output that CVS sends to stdout. It inserts +the STRING into (process-buffer PROC) but it also checks if CVS is waiting +for a lock file. If so, it inserts a message cookie in the *cvs* buffer." + (save-match-data + (with-current-buffer (process-buffer proc) + (let ((inhibit-read-only t)) + (save-excursion + ;; Insert the text, moving the process-marker. + (goto-char (process-mark proc)) + (insert string) + (set-marker (process-mark proc) (point)) + ;; FIXME: Delete any old lock message + ;;(if (tin-nth cookies 1) + ;; (tin-delete cookies + ;; (tin-nth cookies 1))) + ;; Check if CVS is waiting for a lock. + (beginning-of-line 0) ;Move to beginning of last complete line. + (when (looking-at "^[ a-z]+: \\(.*waiting for .*lock in \\(.*\\)\\)$") + (let ((msg (match-string 1)) + (lock (match-string 2))) + (with-current-buffer cvs-buffer + (set (make-local-variable 'cvs-lock-file) lock) + ;; display the lock situation in the *cvs* buffer: + (ewoc-enter-last + cvs-cookies + (cvs-create-fileinfo + 'MESSAGE "" " " + (concat msg + (when (file-exists-p lock) + (substitute-command-keys + "\n\t(type \\[cvs-mode-delete-lock] to delete it)"))) + :subtype 'TEMP)) + (pop-to-buffer (current-buffer)) + (goto-char (point-max)) + (beep))))))))) + + +;;;; +;;;; The cvs-mode and its associated commands. +;;;; + +(cvs-prefix-define cvs-force-command "" "" '("/F") cvs-qtypedesc-string1) +(defun-cvs-mode cvs-mode-force-command (arg) + "Force the next cvs command to operate on all the selected files. +By default, cvs commands only operate on files on which the command +\"makes sense\". This overrides the safety feature on the next cvs command. +It actually behaves as a toggle. If prefixed by \\[universal-argument] \\[universal-argument], +the override will persist until the next toggle." + (interactive "P") + (cvs-prefix-set 'cvs-force-command arg)) + +(put 'cvs-mode 'mode-class 'special) +(define-derived-mode cvs-mode nil "CVS" + "Mode used for PCL-CVS, a frontend to CVS. +Full documentation is in the Texinfo file." + (setq mode-line-process + '("" cvs-force-command cvs-ignore-marks-modif + ":" (cvs-branch-prefix + ("" cvs-branch-prefix (cvs-secondary-branch-prefix + ("->" cvs-secondary-branch-prefix)))) + " " cvs-mode-line-process)) + (if buffer-file-name + (error "Use M-x cvs-quickdir to get a *cvs* buffer")) + (buffer-disable-undo) + ;;(set (make-local-variable 'goal-column) cvs-cursor-column) + (set (make-local-variable 'revert-buffer-function) 'cvs-mode-revert-buffer) + (setq truncate-lines t) + (cvs-prefix-make-local 'cvs-branch-prefix) + (cvs-prefix-make-local 'cvs-secondary-branch-prefix) + (cvs-prefix-make-local 'cvs-force-command) + (cvs-prefix-make-local 'cvs-ignore-marks-modif) + (make-local-variable 'cvs-mode-line-process) + (make-local-variable 'cvs-temp-buffers)) + + +(defun cvs-buffer-p (&optional buffer) + "Return whether the (by default current) BUFFER is a `cvs-mode' buffer." + (save-excursion + (if buffer (set-buffer buffer)) + (and (eq major-mode 'cvs-mode)))) + +(defun cvs-buffer-check () + "Check that the current buffer follows cvs-buffer's conventions." + (let ((buf (current-buffer)) + (check 'none)) + (or (and (setq check 'collection) + (eq (ewoc-buffer cvs-cookies) buf) + (setq check 'cvs-temp-buffer) + (or (null cvs-temp-buffer) + (null (buffer-live-p cvs-temp-buffer)) + (and (eq (with-current-buffer cvs-temp-buffer cvs-buffer) buf) + (equal (with-current-buffer cvs-temp-buffer + default-directory) + default-directory))) + t) + (error "Inconsistent %s in buffer %s" check (buffer-name buf))))) + + +(defun cvs-mode-quit () + "Quit PCL-CVS, killing the *cvs* buffer." + (interactive) + (and (y-or-n-p "Quit pcl-cvs? ") (kill-buffer (current-buffer)))) + +;; Give help.... + +(defun cvs-help () + "Display help for various PCL-CVS commands." + (interactive) + (if (eq last-command 'cvs-help) + (describe-function 'cvs-mode) ; would need minor-mode for log-edit-mode + (message "%s" + (substitute-command-keys + "`\\[cvs-help]':help `\\[cvs-mode-add]':add `\\[cvs-mode-commit]':commit \ +`\\[cvs-mode-diff-map]':diff* `\\[cvs-mode-log]':log \ +`\\[cvs-mode-remove]':remove `\\[cvs-mode-status]':status \ +`\\[cvs-mode-undo]':undo")))) + +;; Move around in the buffer + +(defun cvs-move-to-goal-column () + (let* ((eol (line-end-position)) + (fpos (next-single-property-change (point) 'cvs-goal-column nil eol))) + (when (< fpos eol) + (goto-char fpos)))) + +(defun-cvs-mode cvs-mode-previous-line (arg) + "Go to the previous line. +If a prefix argument is given, move by that many lines." + (interactive "p") + (ewoc-goto-prev cvs-cookies arg) + (cvs-move-to-goal-column)) + +(defun-cvs-mode cvs-mode-next-line (arg) + "Go to the next line. +If a prefix argument is given, move by that many lines." + (interactive "p") + (ewoc-goto-next cvs-cookies arg) + (cvs-move-to-goal-column)) + +;;;; +;;;; Mark handling +;;;; + +(defun-cvs-mode cvs-mode-mark (&optional arg) + "Mark the fileinfo on the current line. +If the fileinfo is a directory, all the contents of that directory are +marked instead. A directory can never be marked." + (interactive) + (let* ((tin (ewoc-locate cvs-cookies)) + (fi (ewoc-data tin))) + (if (eq (cvs-fileinfo->type fi) 'DIRCHANGE) + ;; it's a directory: let's mark all files inside + (ewoc-map + (lambda (f dir) + (when (cvs-dir-member-p f dir) + (setf (cvs-fileinfo->marked f) + (not (if (eq arg 'toggle) (cvs-fileinfo->marked f) arg))) + t)) ;Tell cookie to redisplay this cookie. + cvs-cookies + (cvs-fileinfo->dir fi)) + ;; not a directory: just do the obvious + (setf (cvs-fileinfo->marked fi) + (not (if (eq arg 'toggle) (cvs-fileinfo->marked fi) arg))) + (ewoc-invalidate cvs-cookies tin) + (cvs-mode-next-line 1)))) + +(defalias 'cvs-mouse-toggle-mark 'cvs-mode-toggle-mark) +(defun cvs-mode-toggle-mark (e) + "Toggle the mark of the entry at point." + (interactive (list last-input-event)) + (save-excursion + (posn-set-point (event-end e)) + (cvs-mode-mark 'toggle))) + +(defun-cvs-mode cvs-mode-unmark () + "Unmark the fileinfo on the current line." + (interactive) + (cvs-mode-mark t)) + +(defun-cvs-mode cvs-mode-mark-all-files () + "Mark all files." + (interactive) + (ewoc-map (lambda (cookie) + (unless (eq (cvs-fileinfo->type cookie) 'DIRCHANGE) + (setf (cvs-fileinfo->marked cookie) t))) + cvs-cookies)) + +(defun-cvs-mode (cvs-mode-mark-on-state . SIMPLE) (state) + "Mark all files in state STATE." + (interactive + (list + (let ((default + (condition-case nil + (downcase + (symbol-name + (cvs-fileinfo->type + (cvs-mode-marked nil nil :read-only t :one t :noquery t)))) + (error nil)))) + (intern + (upcase + (completing-read + (concat + "Mark files in state" (if default (concat " [" default "]")) ": ") + (mapcar (lambda (x) + (list (downcase (symbol-name (car x))))) + cvs-states) + nil t nil nil default)))))) + (ewoc-map (lambda (fi) + (when (eq (cvs-fileinfo->type fi) state) + (setf (cvs-fileinfo->marked fi) t))) + cvs-cookies)) + +(defun-cvs-mode cvs-mode-mark-matching-files (regex) + "Mark all files matching REGEX." + (interactive "sMark files matching: ") + (ewoc-map (lambda (cookie) + (when (and (not (eq (cvs-fileinfo->type cookie) 'DIRCHANGE)) + (string-match regex (cvs-fileinfo->file cookie))) + (setf (cvs-fileinfo->marked cookie) t))) + cvs-cookies)) + +(defun-cvs-mode cvs-mode-unmark-all-files () + "Unmark all files. +Directories are also unmarked, but that doesn't matter, since +they should always be unmarked." + (interactive) + (ewoc-map (lambda (cookie) + (setf (cvs-fileinfo->marked cookie) nil) + t) + cvs-cookies)) + +(defun-cvs-mode cvs-mode-unmark-up () + "Unmark the file on the previous line." + (interactive) + (let ((tin (ewoc-goto-prev cvs-cookies 1))) + (when tin + (setf (cvs-fileinfo->marked (ewoc-data tin)) nil) + (ewoc-invalidate cvs-cookies tin))) + (cvs-move-to-goal-column)) + +(defconst cvs-ignore-marks-alternatives + '(("toggle-marks" . "/TM") + ("force-marks" . "/FM") + ("ignore-marks" . "/IM"))) + +(cvs-prefix-define cvs-ignore-marks-modif + "Prefix to decide whether to ignore marks or not." + "active" + (mapcar 'cdr cvs-ignore-marks-alternatives) + (cvs-qtypedesc-create + (lambda (str) (cdr (assoc str cvs-ignore-marks-alternatives))) + (lambda (obj) (car (rassoc obj cvs-ignore-marks-alternatives))) + (lambda () cvs-ignore-marks-alternatives) + nil t)) + +(defun-cvs-mode cvs-mode-toggle-marks (arg) + "Toggle whether the next CVS command uses marks. +See `cvs-prefix-set' for further description of the behavior. +\\[universal-argument] 1 selects `force-marks', +\\[universal-argument] 2 selects `ignore-marks', +\\[universal-argument] 3 selects `toggle-marks'." + (interactive "P") + (cvs-prefix-set 'cvs-ignore-marks-modif arg)) + +(defun cvs-ignore-marks-p (cmd &optional read-only) + (let ((default (if (member cmd cvs-invert-ignore-marks) + (not cvs-default-ignore-marks) + cvs-default-ignore-marks)) + (modif (cvs-prefix-get 'cvs-ignore-marks-modif read-only))) + (cond + ((equal modif "/IM") t) + ((equal modif "/TM") (not default)) + ((equal modif "/FM") nil) + (t default)))) + +(defun cvs-mode-mark-get-modif (cmd) + (if (cvs-ignore-marks-p cmd 'read-only) "/IM" "/FM")) + +(defun cvs-get-marked (&optional ignore-marks ignore-contents) + "Return a list of all selected fileinfos. +If there are any marked tins, and IGNORE-MARKS is nil, return them. +Otherwise, if the cursor selects a directory, and IGNORE-CONTENTS is +nil, return all files in it, else return just the directory. +Otherwise return (a list containing) the file the cursor points to, or +an empty list if it doesn't point to a file at all." + (let ((fis nil)) + (dolist (fi (if (and (boundp 'cvs-minor-current-files) + (consp cvs-minor-current-files)) + (mapcar + (lambda (f) + (if (cvs-fileinfo-p f) f + (let ((f (file-relative-name f))) + (if (file-directory-p f) + (cvs-create-fileinfo + 'DIRCHANGE (file-name-as-directory f) "." "") + (let ((dir (file-name-directory f)) + (file (file-name-nondirectory f))) + (cvs-create-fileinfo + 'UNKNOWN (or dir "") file "")))))) + cvs-minor-current-files) + (or (and (not ignore-marks) + (ewoc-collect cvs-cookies 'cvs-fileinfo->marked)) + (list (ewoc-data (ewoc-locate cvs-cookies)))))) + + (if (or ignore-contents (not (eq (cvs-fileinfo->type fi) 'DIRCHANGE))) + (push fi fis) + ;; If a directory is selected, return members, if any. + (setq fis + (append (ewoc-collect + cvs-cookies 'cvs-dir-member-p (cvs-fileinfo->dir fi)) + fis)))) + (nreverse fis))) + +(defun* cvs-mode-marked (filter &optional cmd + &key read-only one file noquery) + "Get the list of marked FIS. +CMD is used to determine whether to use the marks or not. +Only files for which FILTER is applicable are returned. +If READ-ONLY is non-nil, the current toggling is left intact. +If ONE is non-nil, marks are ignored and a single FI is returned. +If FILE is non-nil, directory entries won't be selected." + (unless cmd (setq cmd (symbol-name filter))) + (let* ((fis (cvs-get-marked (or one (cvs-ignore-marks-p cmd read-only)) + (and (not file) + (cvs-applicable-p 'DIRCHANGE filter)))) + (force (cvs-prefix-get 'cvs-force-command)) + (fis (car (cvs-partition + (lambda (fi) (cvs-applicable-p fi (and (not force) filter))) + fis)))) + (when (and (or (null fis) (and one (cdr fis))) (not noquery)) + (message (if (null fis) + "`%s' is not applicable to any of the selected files." + "`%s' is only applicable to a single file.") cmd) + (sit-for 1) + (setq fis (list (cvs-insert-file + (read-file-name (format "File to %s: " cmd)))))) + (if one (car fis) fis))) + +(defun cvs-enabledp (filter) + "Determine whether FILTER applies to at least one of the selected files." + (ignore-errors (cvs-mode-marked filter nil :read-only t :noquery t))) + +(defun cvs-mode-files (&rest -cvs-mode-files-args) + (cvs-mode! + (lambda () + (mapcar 'cvs-fileinfo->full-name + (apply 'cvs-mode-marked -cvs-mode-files-args))))) + +;; +;; Interface between Log-Edit and PCL-CVS +;; + +(defun cvs-mode-commit-setup () + "Run `cvs-mode-commit' with setup." + (interactive) + (cvs-mode-commit 'force)) + +(defcustom cvs-mode-commit-hook nil + "Hook run after setting up the commit buffer." + :type 'hook + :options '(cvs-mode-diff) + :group 'pcl-cvs) + +(defun cvs-mode-commit (setup) + "Check in all marked files, or the current file. +The user will be asked for a log message in a buffer. +The buffer's mode and name is determined by the \"message\" setting + of `cvs-buffer-name-alist'. +The POSTPROC specified there (typically `log-edit') is then called, + passing it the SETUP argument." + (interactive "P") + ;; It seems that the save-excursion that happens if I use the better + ;; form of `(cvs-mode! (lambda ...))' screws up a couple things which + ;; end up being rather annoying (like log-edit-mode's message being + ;; displayed in the wrong minibuffer). + (cvs-mode!) + (let ((buf (cvs-temp-buffer "message" 'normal 'nosetup)) + (setupfun (or (nth 2 (cdr (assoc "message" cvs-buffer-name-alist))) + 'log-edit))) + (funcall setupfun 'cvs-do-commit setup + '((log-edit-listfun . cvs-commit-filelist) + (log-edit-diff-function . cvs-mode-diff)) buf) + (set (make-local-variable 'cvs-minor-wrap-function) 'cvs-commit-minor-wrap) + (run-hooks 'cvs-mode-commit-hook))) + +(defun cvs-commit-minor-wrap (buf f) + (let ((cvs-ignore-marks-modif (cvs-mode-mark-get-modif "commit"))) + (funcall f))) + +(defun cvs-commit-filelist () + (cvs-mode-files 'commit nil :read-only t :file t :noquery t)) + +(defun cvs-do-commit (flags) + "Do the actual commit, using the current buffer as the log message." + (interactive (list (cvs-flags-query 'cvs-commit-flags "cvs commit flags"))) + (let ((msg (buffer-substring-no-properties (point-min) (point-max)))) + (cvs-mode!) + ;;(pop-to-buffer cvs-buffer) + (cvs-mode-do "commit" (list* "-m" msg flags) 'commit))) + + +;;;; Editing existing commit log messages. + +(defun cvs-edit-log-text-at-point () + (save-excursion + (end-of-line) + (when (re-search-backward "^revision " nil t) + (forward-line 1) + (if (looking-at "date:") (forward-line 1)) + (if (looking-at "branches:") (forward-line 1)) + (buffer-substring + (point) + (if (re-search-forward + "^\\(-\\{28\\}\\|=\\{77\\}\\|revision [.0-9]+\\)$" + nil t) + (match-beginning 0) + (point)))))) + +(defvar cvs-edit-log-revision) +(defvar cvs-edit-log-files) (put 'cvs-edit-log-files 'permanent-local t) +(defun cvs-mode-edit-log (file rev &optional text) + "Edit the log message at point. +This is best called from a `log-view-mode' buffer." + (interactive + (list + (or (cvs-mode! (lambda () + (car (cvs-mode-files nil nil + :read-only t :file t :noquery t)))) + (read-string "File name: ")) + (or (cvs-mode! (lambda () (cvs-prefix-get 'cvs-branch-prefix))) + (read-string "Revision to edit: ")) + (cvs-edit-log-text-at-point))) + ;; It seems that the save-excursion that happens if I use the better + ;; form of `(cvs-mode! (lambda ...))' screws up a couple things which + ;; end up being rather annoying (like log-edit-mode's message being + ;; displayed in the wrong minibuffer). + (cvs-mode!) + (let ((buf (cvs-temp-buffer "message" 'normal 'nosetup)) + (setupfun (or (nth 2 (cdr (assoc "message" cvs-buffer-name-alist))) + 'log-edit))) + (with-current-buffer buf + ;; Set the filename before, so log-edit can correctly setup its + ;; log-edit-initial-files variable. + (set (make-local-variable 'cvs-edit-log-files) (list file))) + (funcall setupfun 'cvs-do-edit-log nil + '((log-edit-listfun . cvs-edit-log-filelist) + (log-edit-diff-function . cvs-mode-diff)) + buf) + (when text (erase-buffer) (insert text)) + (set (make-local-variable 'cvs-edit-log-revision) rev) + (set (make-local-variable 'cvs-minor-wrap-function) + 'cvs-edit-log-minor-wrap) + ;; (run-hooks 'cvs-mode-commit-hook) + )) + +(defun cvs-edit-log-minor-wrap (buf f) + (let ((cvs-branch-prefix (with-current-buffer buf cvs-edit-log-revision)) + (cvs-minor-current-files + (with-current-buffer buf cvs-edit-log-files)) + ;; FIXME: I need to force because the fileinfos are UNKNOWN + (cvs-force-command "/F")) + (funcall f))) + +(defun cvs-edit-log-filelist () + (if cvs-minor-wrap-function + (cvs-mode-files nil nil :read-only t :file t :noquery t) + cvs-edit-log-files)) + +(defun cvs-do-edit-log (rev) + "Do the actual commit, using the current buffer as the log message." + (interactive (list cvs-edit-log-revision)) + (let ((msg (buffer-substring-no-properties (point-min) (point-max)))) + (cvs-mode! + (lambda () + (cvs-mode-do "admin" (list (concat "-m" rev ":" msg)) nil))))) + + +;;;; +;;;; CVS Mode commands +;;;; + +(defun-cvs-mode (cvs-mode-insert . NOARGS) (file) + "Insert an entry for a specific file into the current listing. +This is typically used if the file is up-to-date (or has been added +outside of PCL-CVS) and one wants to do some operation on it." + (interactive + (list (read-file-name + "File to insert: " + ;; Can't use ignore-errors here because interactive + ;; specs aren't byte-compiled. + (condition-case nil + (file-name-as-directory + (expand-file-name + (cvs-fileinfo->dir + (cvs-mode-marked nil nil :read-only t :one t :noquery t)))) + (error nil))))) + (cvs-insert-file file)) + +(defun cvs-insert-file (file) + "Insert FILE (and its contents if it's a dir) and return its FI." + (let ((file (file-relative-name (directory-file-name file))) last) + (dolist (fi (cvs-fileinfo-from-entries file)) + (setq last (cvs-addto-collection cvs-cookies fi last))) + ;; There should have been at least one entry. + (goto-char (ewoc-location last)) + (ewoc-data last))) + +(defun cvs-mark-fis-dead (fis) + ;; Helper function, introduced because of the need for macro-expansion. + (dolist (fi fis) + (setf (cvs-fileinfo->type fi) 'DEAD))) + +(defun-cvs-mode (cvs-mode-add . SIMPLE) (flags) + "Add marked files to the cvs repository. +With prefix argument, prompt for cvs flags." + (interactive (list (cvs-flags-query 'cvs-add-flags "cvs add flags"))) + (let ((fis (cvs-mode-marked 'add)) + (needdesc nil) (dirs nil)) + ;; find directories and look for fis needing a description + (dolist (fi fis) + (cond + ((file-directory-p (cvs-fileinfo->full-name fi)) (push fi dirs)) + ((eq (cvs-fileinfo->type fi) 'UNKNOWN) (setq needdesc t)))) + ;; prompt for description if necessary + (let* ((msg (if (and needdesc + (or current-prefix-arg (not cvs-add-default-message))) + (read-from-minibuffer "Enter description: ") + (or cvs-add-default-message ""))) + (flags (list* "-m" msg flags)) + (postproc + ;; setup postprocessing for the directory entries + (when dirs + `((cvs-run-process (list "-n" "update") + ',dirs + '(cvs-parse-process t)) + (cvs-mark-fis-dead ',dirs))))) + (cvs-mode-run "add" flags fis :postproc postproc)))) + +(defun-cvs-mode (cvs-mode-diff . DOUBLE) (flags) + "Diff the selected files against the repository. +This command compares the files in your working area against the +revision which they are based upon." + (interactive + (list (cvs-add-branch-prefix + (cvs-add-secondary-branch-prefix + (cvs-flags-query 'cvs-diff-flags "cvs diff flags"))))) + (cvs-mode-do "diff" flags 'diff + :show t)) ;; :ignore-exit t + +(defun-cvs-mode (cvs-mode-diff-head . SIMPLE) (flags) + "Diff the selected files against the head of the current branch. +See ``cvs-mode-diff'' for more info." + (interactive (list (cvs-flags-query 'cvs-diff-flags "cvs diff flags"))) + (cvs-mode-diff-1 (cons "-rHEAD" flags))) + +(defun-cvs-mode (cvs-mode-diff-repository . SIMPLE) (flags) + "Diff the files for changes in the repository since last co/update/commit. +See ``cvs-mode-diff'' for more info." + (interactive (list (cvs-flags-query 'cvs-diff-flags "cvs diff flags"))) + (cvs-mode-diff-1 (cons "-rBASE" (cons "-rHEAD" flags)))) + +(defun-cvs-mode (cvs-mode-diff-yesterday . SIMPLE) (flags) + "Diff the selected files against yesterday's head of the current branch. +See ``cvs-mode-diff'' for more info." + (interactive (list (cvs-flags-query 'cvs-diff-flags "cvs diff flags"))) + (cvs-mode-diff-1 (cons "-Dyesterday" flags))) + +(defun-cvs-mode (cvs-mode-diff-vendor . SIMPLE) (flags) + "Diff the selected files against the head of the vendor branch. +See ``cvs-mode-diff'' for more info." + (interactive (list (cvs-flags-query 'cvs-diff-flags "cvs diff flags"))) + (cvs-mode-diff-1 (cons (concat "-r" cvs-vendor-branch) flags))) + +;; sadly, this is not provided by cvs, so we have to roll our own +(defun-cvs-mode (cvs-mode-diff-backup . SIMPLE) (flags) + "Diff the files against the backup file. +This command can be used on files that are marked with \"Merged\" +or \"Conflict\" in the *cvs* buffer." + (interactive (list (cvs-flags-query 'cvs-diff-flags "diff flags"))) + (unless (listp flags) (error "flags should be a list of strings")) + (save-some-buffers) + (let* ((marked (cvs-get-marked (cvs-ignore-marks-p "diff"))) + (fis (car (cvs-partition 'cvs-fileinfo->backup-file marked)))) + (unless (consp fis) + (error "No files with a backup file selected!")) + ;; let's extract some info into the environment for `buffer-name' + (let* ((dir (cvs-fileinfo->dir (car fis))) + (file (cvs-fileinfo->file (car fis)))) + (set-buffer (cvs-temp-buffer "diff"))) + (message "cvs diff backup...") + (cvs-execute-single-file-list fis 'cvs-diff-backup-extractor + cvs-diff-program flags)) + (message "cvs diff backup... Done.")) + +(defun cvs-diff-backup-extractor (fileinfo) + "Return the filename and the name of the backup file as a list. +Signal an error if there is no backup file." + (let ((backup-file (cvs-fileinfo->backup-file fileinfo))) + (unless backup-file + (error "%s has no backup file" (cvs-fileinfo->full-name fileinfo))) + (list backup-file (cvs-fileinfo->full-name fileinfo)))) + +;; +;; Emerge support +;; +(defun cvs-emerge-diff (b1 b2) (emerge-buffers b1 b2 b1)) +(defun cvs-emerge-merge (b1 b2 base out) + (emerge-buffers-with-ancestor b1 b2 base (find-file-noselect out))) + +;; +;; Ediff support +;; + +(defvar ediff-after-quit-destination-buffer) +(defvar ediff-after-quit-hook-internal) +(defvar cvs-transient-buffers) +(defun cvs-ediff-startup-hook () + (add-hook 'ediff-after-quit-hook-internal + `(lambda () + (cvs-ediff-exit-hook + ',ediff-after-quit-destination-buffer ',cvs-transient-buffers)) + nil 'local)) + +(defun cvs-ediff-exit-hook (cvs-buf tmp-bufs) + ;; kill the temp buffers (and their associated windows) + (dolist (tb tmp-bufs) + (when (and tb (buffer-live-p tb) (not (buffer-modified-p tb))) + (let ((win (get-buffer-window tb t))) + (kill-buffer tb) + (when (window-live-p win) (ignore-errors (delete-window win)))))) + ;; switch back to the *cvs* buffer + (when (and cvs-buf (buffer-live-p cvs-buf) + (not (get-buffer-window cvs-buf t))) + (ignore-errors (switch-to-buffer cvs-buf)))) + +(defun cvs-ediff-diff (b1 b2) + (let ((ediff-after-quit-destination-buffer (current-buffer)) + (startup-hook '(cvs-ediff-startup-hook))) + (ediff-buffers b1 b2 startup-hook 'ediff-revision))) + +(defun cvs-ediff-merge (b1 b2 base out) + (let ((ediff-after-quit-destination-buffer (current-buffer)) + (startup-hook '(cvs-ediff-startup-hook))) + (ediff-merge-buffers-with-ancestor + b1 b2 base startup-hook + 'ediff-merge-revisions-with-ancestor + out))) + +;; +;; Interactive merge/diff support. +;; + +(defun cvs-retrieve-revision (fileinfo rev) + "Retrieve the given REVision of the file in FILEINFO into a new buffer." + (let* ((file (cvs-fileinfo->full-name fileinfo)) + (buffile (concat file "." rev))) + (or (find-buffer-visiting buffile) + (with-current-buffer (create-file-buffer buffile) + (message "Retrieving revision %s..." rev) + ;; Discard stderr output to work around the CVS+SSH+libc + ;; problem when stdout and stderr are the same. + (let ((res + (let ((coding-system-for-read 'binary)) + (apply 'process-file cvs-program nil '(t nil) nil + "-q" "update" "-p" + ;; If `rev' is HEAD, don't pass it at all: + ;; the default behavior is to get the head + ;; of the current branch whereas "-r HEAD" + ;; stupidly gives you the head of the trunk. + (append (unless (equal rev "HEAD") (list "-r" rev)) + (list file)))))) + (when (and res (not (and (equal 0 res)))) + (error "Something went wrong retrieving revision %s: %s" rev res)) + ;; Figure out the encoding used and decode the byte-sequence + ;; into a sequence of chars. + (decode-coding-inserted-region + (point-min) (point-max) file t nil nil t) + ;; Set buffer-file-coding-system. + (after-insert-file-set-coding (buffer-size) t) + (set-buffer-modified-p nil) + (let ((buffer-file-name (expand-file-name file))) + (after-find-file)) + (toggle-read-only 1) + (message "Retrieving revision %s... Done" rev) + (current-buffer)))))) + +;; FIXME: The user should be able to specify ancestor/head/backup and we should +;; provide sensible defaults when merge info is unavailable (rather than rely +;; on smerge-ediff). Also provide sane defaults for need-merge files. +(defun-cvs-mode cvs-mode-imerge () + "Merge interactively appropriate revisions of the selected file." + (interactive) + (let ((fi (cvs-mode-marked 'merge nil :one t :file t))) + (let ((merge (cvs-fileinfo->merge fi)) + (file (cvs-fileinfo->full-name fi)) + (backup-file (cvs-fileinfo->backup-file fi))) + (if (not (and merge backup-file)) + (let ((buf (find-file-noselect file))) + (message "Missing merge info or backup file, using VC.") + (with-current-buffer buf + (smerge-ediff))) + (let* ((ancestor-buf (cvs-retrieve-revision fi (car merge))) + (head-buf (cvs-retrieve-revision fi (cdr merge))) + (backup-buf (let ((auto-mode-alist nil)) + (find-file-noselect backup-file))) + ;; this binding is used by cvs-ediff-startup-hook + (cvs-transient-buffers (list ancestor-buf backup-buf head-buf))) + (with-current-buffer backup-buf + (let ((buffer-file-name (expand-file-name file))) + (after-find-file))) + (funcall (cdr cvs-idiff-imerge-handlers) + backup-buf head-buf ancestor-buf file)))))) + +(cvs-flags-define cvs-idiff-version + (list "BASE" cvs-vendor-branch cvs-vendor-branch "BASE" "BASE") + "version: " cvs-qtypedesc-tag) + +(defun-cvs-mode (cvs-mode-idiff . NOARGS) (&optional rev1 rev2) + "Diff interactively current file to revisions." + (interactive + (let* ((rev1 (cvs-prefix-get 'cvs-branch-prefix)) + (rev2 (and rev1 (cvs-prefix-get 'cvs-secondary-branch-prefix)))) + (list (or rev1 (cvs-flags-query 'cvs-idiff-version)) + rev2))) + (let ((fi (cvs-mode-marked 'diff "idiff" :one t :file t))) + (let* ((file (cvs-fileinfo->full-name fi)) + (rev1-buf (cvs-retrieve-revision fi (or rev1 "BASE"))) + (rev2-buf (if rev2 (cvs-retrieve-revision fi rev2))) + ;; this binding is used by cvs-ediff-startup-hook + (cvs-transient-buffers (list rev1-buf rev2-buf))) + (funcall (car cvs-idiff-imerge-handlers) + rev1-buf (or rev2-buf (find-file-noselect file)))))) + +(defun-cvs-mode (cvs-mode-idiff-other . NOARGS) () + "Diff interactively current file to revisions." + (interactive) + (let* ((rev1 (cvs-prefix-get 'cvs-branch-prefix)) + (rev2 (and rev1 (cvs-prefix-get 'cvs-secondary-branch-prefix))) + (fis (cvs-mode-marked 'diff "idiff" :file t))) + (when (> (length fis) 2) + (error "idiff-other cannot be applied to more than 2 files at a time")) + (let* ((fi1 (car fis)) + (rev1-buf (if rev1 (cvs-retrieve-revision fi1 rev1) + (find-file-noselect (cvs-fileinfo->full-name fi1)))) + rev2-buf) + (if (cdr fis) + (let ((fi2 (nth 1 fis))) + (setq rev2-buf + (if rev2 (cvs-retrieve-revision fi2 rev2) + (find-file-noselect (cvs-fileinfo->full-name fi2))))) + (error "idiff-other doesn't know what other file/buffer to use")) + (let* (;; this binding is used by cvs-ediff-startup-hook + (cvs-transient-buffers (list rev1-buf rev2-buf))) + (funcall (car cvs-idiff-imerge-handlers) + rev1-buf rev2-buf))))) + + +(defun cvs-is-within-p (fis dir) + "Non-nil if buffer is inside one of FIS (in DIR)." + (when (stringp buffer-file-name) + (setq buffer-file-name (expand-file-name buffer-file-name)) + (let (ret) + (dolist (fi (or fis (list (cvs-create-fileinfo 'DIRCHANGE "" "." "")))) + (when (cvs-string-prefix-p + (expand-file-name (cvs-fileinfo->full-name fi) dir) + buffer-file-name) + (setq ret t))) + ret))) + +(defun* cvs-mode-run (cmd flags fis + &key (buf (cvs-temp-buffer)) + dont-change-disc cvsargs postproc) + "Generic cvs-mode- function. +Executes `cvs CVSARGS CMD FLAGS FIS'. +BUF is the buffer to be used for cvs' output. +DONT-CHANGE-DISC non-nil indicates that the command will not change the + contents of files. This is only used by the parser. +POSTPROC is a list of expressions to be evaluated at the very end (after + parsing if applicable). It will be prepended with `progn' if necessary." + (let ((def-dir default-directory)) + ;; Save the relevant buffers + (save-some-buffers nil (lambda () (cvs-is-within-p fis def-dir)))) + (unless (listp flags) (error "flags should be a list of strings")) + ;; Some w32 versions of CVS don't like an explicit . too much. + (when (and (car fis) (null (cdr fis)) + (eq (cvs-fileinfo->type (car fis)) 'DIRCHANGE) + ;; (equal (cvs-fileinfo->file (car fis)) ".") + (equal (cvs-fileinfo->dir (car fis)) "")) + (setq fis nil)) + (let* ((single-dir (or (not (listp cvs-execute-single-dir)) + (member cmd cvs-execute-single-dir))) + (parse (member cmd cvs-parse-known-commands)) + (args (append cvsargs (list cmd) flags)) + (after-mode (nth 2 (cdr (assoc cmd cvs-buffer-name-alist))))) + (cvs-cleanup-collection cvs-cookies ;cleanup remaining messages + (eq cvs-auto-remove-handled 'delayed) nil t) + (when (fboundp after-mode) + (setq postproc (append postproc `((,after-mode))))) + (when parse + (let ((old-fis + (when (member cmd '("status" "update")) ;FIXME: Yuck!! + ;; absence of `cvs update' output has a specific meaning. + (or fis (list (cvs-create-fileinfo 'DIRCHANGE "" "." "")))))) + (push `(cvs-parse-process ',dont-change-disc nil ',old-fis) postproc))) + (setq postproc (if (cdr postproc) (cons 'progn postproc) (car postproc))) + (with-current-buffer buf + (let ((inhibit-read-only t)) (erase-buffer)) + (message "Running cvs %s ..." cmd) + (cvs-run-process args fis postproc single-dir)))) + + +(defun* cvs-mode-do (cmd flags filter + &key show dont-change-disc cvsargs postproc) + "Generic cvs-mode- function. +Executes `cvs CVSARGS CMD FLAGS' on the selected files. +FILTER is passed to `cvs-applicable-p' to only apply the command to + files for which it makes sense. +SHOW indicates that CMD should be not be run in the default temp buffer and + should be shown to the user. The buffer and mode to be used is determined + by `cvs-buffer-name-alist'. +DONT-CHANGE-DISC non-nil indicates that the command will not change the + contents of files. This is only used by the parser." + (cvs-mode-run cmd flags (cvs-mode-marked filter cmd) + :buf (cvs-temp-buffer (when show cmd)) + :dont-change-disc dont-change-disc + :cvsargs cvsargs + :postproc postproc)) + +(defun-cvs-mode (cvs-mode-status . SIMPLE) (flags) + "Show cvs status for all marked files. +With prefix argument, prompt for cvs flags." + (interactive (list (cvs-flags-query 'cvs-status-flags "cvs status flags"))) + (cvs-mode-do "status" flags nil :dont-change-disc t :show t + :postproc (when (eq cvs-auto-remove-handled 'status) + `((with-current-buffer ,(current-buffer) + (cvs-mode-remove-handled)))))) + +(defun-cvs-mode (cvs-mode-tree . SIMPLE) (flags) + "Call cvstree using the file under the point as a keyfile." + (interactive (list (cvs-flags-query 'cvs-status-flags "cvs status flags"))) + (cvs-mode-run "status" (cons "-v" flags) (cvs-mode-marked nil "status") + :buf (cvs-temp-buffer "tree") + :dont-change-disc t + :postproc '((cvs-status-cvstrees)))) + +;; cvs log + +(defun-cvs-mode (cvs-mode-log . NOARGS) (flags) + "Display the cvs log of all selected files. +With prefix argument, prompt for cvs flags." + (interactive (list (cvs-add-branch-prefix + (cvs-flags-query 'cvs-log-flags "cvs log flags")))) + (cvs-mode-do "log" flags nil :show t)) + + +(defun-cvs-mode (cvs-mode-update . NOARGS) (flags) + "Update all marked files. +With a prefix argument, prompt for cvs flags." + (interactive + (list (cvs-add-branch-prefix + (cvs-add-secondary-branch-prefix + (cvs-flags-query 'cvs-update-flags "cvs update flags") + "-j") "-j"))) + (cvs-mode-do "update" flags 'update)) + + +(defun-cvs-mode (cvs-mode-examine . NOARGS) (flags) + "Re-examine all marked files. +With a prefix argument, prompt for cvs flags." + (interactive + (list (cvs-add-branch-prefix + (cvs-add-secondary-branch-prefix + (cvs-flags-query 'cvs-update-flags "cvs -n update flags") + "-j") "-j"))) + (cvs-mode-do "update" flags nil :cvsargs '("-n") :dont-change-disc t)) + + +(defun-cvs-mode cvs-mode-ignore (&optional pattern) + "Arrange so that CVS ignores the selected files. +This command ignores files that are not flagged as `Unknown'." + (interactive) + (dolist (fi (cvs-mode-marked 'ignore)) + (cvs-append-to-ignore (cvs-fileinfo->dir fi) (cvs-fileinfo->file fi) + (eq (cvs-fileinfo->subtype fi) 'NEW-DIR)) + (setf (cvs-fileinfo->type fi) 'DEAD)) + (cvs-cleanup-collection cvs-cookies nil nil nil)) + +(declare-function vc-editable-p "vc" (file)) +(declare-function vc-checkout "vc" (file &optional writable rev)) + +(defun cvs-append-to-ignore (dir str &optional old-dir) + "Add STR to the .cvsignore file in DIR. +If OLD-DIR is non-nil, then this is a directory that we don't want +to hear about anymore." + (with-current-buffer + (find-file-noselect (expand-file-name ".cvsignore" dir)) + (when (ignore-errors + (and buffer-read-only + (eq 'CVS (vc-backend buffer-file-name)) + (not (vc-editable-p buffer-file-name)))) + ;; CVSREAD=on special case + (vc-checkout buffer-file-name t)) + (goto-char (point-max)) + (unless (bolp) (insert "\n")) + (insert str (if old-dir "/\n" "\n")) + (if cvs-sort-ignore-file (sort-lines nil (point-min) (point-max))) + (save-buffer))) + + +(defun cvs-mode-find-file-other-window (e) + "Select a buffer containing the file in another window." + (interactive (list last-input-event)) + (cvs-mode-find-file e t)) + + +(defun cvs-mode-display-file (e) + "Show a buffer containing the file in another window." + (interactive (list last-input-event)) + (cvs-mode-find-file e 'dont-select)) + + +(defun cvs-mode-view-file (e) + "View the file." + (interactive (list last-input-event)) + (cvs-mode-find-file e nil t)) + + +(defun cvs-mode-view-file-other-window (e) + "View the file." + (interactive (list last-input-event)) + (cvs-mode-find-file e t t)) + + +(defun cvs-find-modif (fi) + (with-temp-buffer + (process-file cvs-program nil (current-buffer) nil + "-f" "diff" (cvs-fileinfo->file fi)) + (goto-char (point-min)) + (if (re-search-forward "^\\([0-9]+\\)" nil t) + (string-to-number (match-string 1)) + 1))) + + +(defun cvs-mode-find-file (e &optional other view) + "Select a buffer containing the file. +With a prefix, opens the buffer in an OTHER window." + (interactive (list last-input-event current-prefix-arg)) + ;; If the event moves point, check that it moves it to a valid location. + (when (and (/= (point) (progn (posn-set-point (event-end e)) (point))) + (not (memq (get-text-property (1- (line-end-position)) + 'font-lock-face) + '(cvs-header cvs-filename)))) + (error "Not a file name")) + (cvs-mode! + (lambda (&optional rev) + (interactive (list (cvs-prefix-get 'cvs-branch-prefix))) + (let* ((cvs-buf (current-buffer)) + (fi (cvs-mode-marked nil nil :one t))) + (if (eq (cvs-fileinfo->type fi) 'DIRCHANGE) + (let ((odir default-directory)) + (setq default-directory + (cvs-expand-dir-name (cvs-fileinfo->dir fi))) + (cond ((eq other 'dont-select) + (display-buffer (find-file-noselect default-directory))) + (other (dired-other-window default-directory)) + (t (dired default-directory))) + (set-buffer cvs-buf) + (setq default-directory odir)) + (let ((buf (if rev (cvs-retrieve-revision fi rev) + (find-file-noselect (cvs-fileinfo->full-name fi))))) + (funcall (cond ((eq other 'dont-select) 'display-buffer) + (other + (if view 'view-buffer-other-window + 'switch-to-buffer-other-window)) + (t (if view 'view-buffer 'switch-to-buffer))) + buf) + (when (and cvs-find-file-and-jump (cvs-applicable-p fi 'diff-base)) + (save-restriction + (widen) + (goto-char (point-min)) + (forward-line (1- (cvs-find-modif fi))))) + buf)))))) + + +(defun-cvs-mode (cvs-mode-undo . SIMPLE) (flags) + "Undo local changes to all marked files. +The file is removed and `cvs update FILE' is run." + ;;"With prefix argument, prompt for cvs FLAGS." + (interactive (list nil));; (cvs-flags-query 'cvs-undo-flags "undo flags") + (if current-prefix-arg (call-interactively 'cvs-mode-revert-to-rev) + (let* ((fis (cvs-do-removal 'undo "update" 'all)) + (removedp (lambda (fi) + (or (eq (cvs-fileinfo->type fi) 'REMOVED) + (and (eq (cvs-fileinfo->type fi) 'CONFLICT) + (eq (cvs-fileinfo->subtype fi) 'REMOVED))))) + (fis-split (cvs-partition removedp fis)) + (fis-removed (car fis-split)) + (fis-other (cdr fis-split))) + (if (null fis-other) + (when fis-removed (cvs-mode-run "add" nil fis-removed)) + (cvs-mode-run "update" flags fis-other + :postproc + (when fis-removed + `((with-current-buffer ,(current-buffer) + (cvs-mode-run "add" nil ',fis-removed))))))))) + + +(defun-cvs-mode (cvs-mode-revert-to-rev . NOARGS) (rev) + "Revert the selected files to an old revision." + (interactive + (list (or (cvs-prefix-get 'cvs-branch-prefix) + (let ((current-prefix-arg '(4))) + (cvs-flags-query 'cvs-idiff-version))))) + (let* ((fis (cvs-mode-marked 'revert "revert" :file t)) + (tag (concat "tmp_pcl_tag_" (make-temp-name ""))) + (untag `((with-current-buffer ,(current-buffer) + (cvs-mode-run "tag" (list "-d" ',tag) ',fis)))) + (update `((with-current-buffer ,(current-buffer) + (cvs-mode-run "update" (list "-j" ',tag "-j" ',rev) ',fis + :postproc ',untag))))) + (cvs-mode-run "tag" (list tag) fis :postproc update))) + + +(defun-cvs-mode cvs-mode-delete-lock () + "Delete the lock file that CVS is waiting for. +Note that this can be dangerous. You should only do this +if you are convinced that the process that created the lock is dead." + (interactive) + (let* ((default-directory (cvs-expand-dir-name cvs-lock-file)) + (locks (directory-files default-directory nil cvs-lock-file-regexp))) + (cond + ((not locks) (error "No lock files found")) + ((yes-or-no-p (concat "Really delete locks in " cvs-lock-file "? ")) + (dolist (lock locks) + (cond ((file-directory-p lock) (delete-directory lock)) + ((file-exists-p lock) (delete-file lock)))))))) + + +(defun-cvs-mode cvs-mode-remove-handled () + "Remove all lines that are handled. +Empty directories are removed." + (interactive) + (cvs-cleanup-collection cvs-cookies + t (or cvs-auto-remove-directories 'handled) t)) + + +(defun-cvs-mode cvs-mode-acknowledge () + "Remove all marked files from the buffer." + (interactive) + (dolist (fi (cvs-get-marked (cvs-ignore-marks-p "acknowledge") t)) + (setf (cvs-fileinfo->type fi) 'DEAD)) + (cvs-cleanup-collection cvs-cookies nil nil nil)) + +(defun cvs-do-removal (filter &optional cmd all) + "Remove files. +Returns a list of FIS that should be `cvs remove'd." + (let* ((files (cvs-mode-marked filter cmd :file t :read-only t)) + (fis (cdr (cvs-partition (lambda (fi) + (eq (cvs-fileinfo->type fi) 'UNKNOWN)) + (cvs-mode-marked filter cmd)))) + (silent (or (not cvs-confirm-removals) + (cvs-every (lambda (fi) + (or (not (file-exists-p + (cvs-fileinfo->full-name fi))) + (cvs-applicable-p fi 'safe-rm))) + files))) + (tmpbuf (cvs-temp-buffer))) + (when (and (not silent) (equal cvs-confirm-removals 'list)) + (with-current-buffer tmpbuf + (let ((inhibit-read-only t)) + (cvs-insert-strings (mapcar 'cvs-fileinfo->full-name fis)) + (cvs-pop-to-buffer-same-frame (current-buffer)) + (shrink-window-if-larger-than-buffer)))) + (if (not (or silent + (unwind-protect + (yes-or-no-p + (let ((nfiles (length files)) + (verb (if (eq filter 'undo) "Undo" "Delete"))) + (if (= 1 nfiles) + (format "%s file: \"%s\" ? " + verb + (cvs-fileinfo->file (car files))) + (format "%s %d files? " + verb + nfiles)))) + (cvs-bury-buffer tmpbuf cvs-buffer)))) + (progn (message "Aborting") nil) + (dolist (fi files) + (let* ((type (cvs-fileinfo->type fi)) + (file (cvs-fileinfo->full-name fi))) + (when (or all (eq type 'UNKNOWN)) + (when (file-exists-p file) (delete-file file)) + (unless all (setf (cvs-fileinfo->type fi) 'DEAD) t)))) + fis))) + +(defun-cvs-mode (cvs-mode-remove . SIMPLE) (flags) + "Remove all marked files. +With prefix argument, prompt for cvs flags." + (interactive (list (cvs-flags-query 'cvs-remove-flags "cvs remove flags"))) + (let ((fis (cvs-do-removal 'remove))) + (if fis (cvs-mode-run "remove" (cons "-f" flags) fis) + (cvs-cleanup-collection cvs-cookies nil nil nil)))) + + +(defvar cvs-tag-name "") +(defun-cvs-mode (cvs-mode-tag . SIMPLE) (tag &optional flags) + "Run `cvs tag TAG' on all selected files. +With prefix argument, prompt for cvs flags. +By default this can only be used on directories. +Use \\[cvs-mode-force-command] or change `cvs-force-dir-tag' if you need +to use it on individual files." + (interactive + (list (setq cvs-tag-name + (cvs-query-read cvs-tag-name "Tag name: " cvs-qtypedesc-tag)) + (cvs-flags-query 'cvs-tag-flags "tag flags"))) + (cvs-mode-do "tag" (append flags (list tag)) + (when cvs-force-dir-tag 'tag))) + +(defun-cvs-mode (cvs-mode-untag . SIMPLE) (tag &optional flags) + "Run `cvs tag -d TAG' on all selected files. +With prefix argument, prompt for cvs flags." + (interactive + (list (setq cvs-tag-name + (cvs-query-read cvs-tag-name "Tag to delete: " cvs-qtypedesc-tag)) + (cvs-flags-query 'cvs-tag-flags "tag flags"))) + (cvs-mode-do "tag" (append '("-d") flags (list tag)) + (when cvs-force-dir-tag 'tag))) + + +;; Byte compile files. + +(defun-cvs-mode cvs-mode-byte-compile-files () + "Run byte-compile-file on all selected files that end in '.el'." + (interactive) + (let ((marked (cvs-get-marked (cvs-ignore-marks-p "byte-compile")))) + (dolist (fi marked) + (let ((filename (cvs-fileinfo->full-name fi))) + (when (string-match "\\.el\\'" filename) + (byte-compile-file filename)))))) + +;; ChangeLog support. + +(defun-cvs-mode cvs-mode-add-change-log-entry-other-window () + "Add a ChangeLog entry in the ChangeLog of the current directory." + (interactive) + ;; Require `add-log' explicitly, because if it gets autoloaded when we call + ;; add-change-log-entry-other-window below, the + ;; add-log-buffer-file-name-function ends up unbound when we leave the `let'. + (require 'add-log) + (dolist (fi (cvs-mode-marked nil nil)) + (let* ((default-directory (cvs-expand-dir-name (cvs-fileinfo->dir fi))) + (add-log-buffer-file-name-function + (lambda () + (let ((file (expand-file-name (cvs-fileinfo->file fi)))) + (if (file-directory-p file) + ;; Be careful to use a directory name, otherwise add-log + ;; starts looking for a ChangeLog file in the + ;; parent dir. + (file-name-as-directory file) + file))))) + (kill-local-variable 'change-log-default-name) + (save-excursion (add-change-log-entry-other-window))))) + +;; interactive commands to set optional flags + +(defun cvs-mode-set-flags (flag) + "Ask for new setting of cvs-FLAG-flags." + (interactive + (list (completing-read + "Which flag: " + '("cvs" "diff" "update" "status" "log" "tag" ;"rtag" + "commit" "remove" "undo" "checkout") + nil t))) + (let* ((sym (intern (concat "cvs-" flag "-flags")))) + (let ((current-prefix-arg '(16))) + (cvs-flags-query sym (concat flag " flags"))))) + + +;;;; +;;;; Utilities for the *cvs* buffer +;;;; + +(defun cvs-dir-member-p (fileinfo dir) + "Return true if FILEINFO represents a file in directory DIR." + (and (not (eq (cvs-fileinfo->type fileinfo) 'DIRCHANGE)) + (cvs-string-prefix-p dir (cvs-fileinfo->dir fileinfo)))) + +(defun cvs-execute-single-file (fi extractor program constant-args) + "Internal function for `cvs-execute-single-file-list'." + (let* ((arg-list (funcall extractor fi)) + (inhibit-read-only t)) + + ;; Execute the command unless extractor returned t. + (when (listp arg-list) + (let* ((args (append constant-args arg-list))) + + (insert (format "=== %s %s\n\n" + program (split-string-and-unquote args))) + + ;; FIXME: return the exit status? + (apply 'process-file program nil t t args) + (goto-char (point-max)))))) + +;; FIXME: make this run in the background ala cvs-run-process... +(defun cvs-execute-single-file-list (fis extractor program constant-args) + "Run PROGRAM on all elements on FIS. +CONSTANT-ARGS is a list of strings to pass as arguments to PROGRAM. +The arguments given to the program will be CONSTANT-ARGS followed by +the list that EXTRACTOR returns. + +EXTRACTOR will be called once for each file on FIS. It is given +one argument, the cvs-fileinfo. It can return t, which means ignore +this file, or a list of arguments to send to the program." + (dolist (fi fis) + (cvs-execute-single-file fi extractor program constant-args))) + + +(defun cvs-revert-if-needed (fis) + (dolist (fileinfo fis) + (let* ((file (cvs-fileinfo->full-name fileinfo)) + (buffer (find-buffer-visiting file))) + ;; For a revert to happen the user must be editing the file... + (unless (or (null buffer) + (memq (cvs-fileinfo->type fileinfo) '(MESSAGE UNKNOWN)) + ;; FIXME: check whether revert is really needed. + ;; `(verify-visited-file-modtime buffer)' doesn't cut it + ;; because it only looks at the time stamp (it ignores + ;; read-write changes) which is not changed by `commit'. + (buffer-modified-p buffer)) + (with-current-buffer buffer + (ignore-errors + (revert-buffer 'ignore-auto 'dont-ask 'preserve-modes) + ;; `preserve-modes' avoids changing the (minor) modes. But we + ;; do want to reset the mode for VC, so we do it explicitly. + (vc-find-file-hook) + (when (eq (cvs-fileinfo->type fileinfo) 'CONFLICT) + (smerge-start-session)))))))) + + +(defun cvs-change-cvsroot (newroot) + "Change the cvsroot." + (interactive "DNew repository: ") + (if (or (file-directory-p (expand-file-name "CVSROOT" newroot)) + (y-or-n-p (concat "Warning: no CVSROOT found inside repository." + " Change cvs-cvsroot anyhow? "))) + (setq cvs-cvsroot newroot))) + +;;;; +;;;; useful global settings +;;;; + +;; +;; Hook to allow calling PCL-CVS by visiting the /CVS subdirectory +;; + +;;;###autoload +(defcustom cvs-dired-action 'cvs-quickdir + "The action to be performed when opening a CVS directory. +Sensible values are `cvs-examine', `cvs-status' and `cvs-quickdir'." + :group 'pcl-cvs + :type '(choice (const cvs-examine) (const cvs-status) (const cvs-quickdir))) + +;;;###autoload +(defcustom cvs-dired-use-hook '(4) + "Whether or not opening a CVS directory should run PCL-CVS. +A value of nil means never do it. +ALWAYS means to always do it unless a prefix argument is given to the + command that prompted the opening of the directory. +Anything else means to do it only if the prefix arg is equal to this value." + :group 'pcl-cvs + :type '(choice (const :tag "Never" nil) + (const :tag "Always" always) + (const :tag "Prefix" (4)))) + +;;;###autoload +(progn (defun cvs-dired-noselect (dir) + "Run `cvs-examine' if DIR is a CVS administrative directory. +The exact behavior is determined also by `cvs-dired-use-hook'." + (when (stringp dir) + (setq dir (directory-file-name dir)) + (when (and (string= "CVS" (file-name-nondirectory dir)) + (file-readable-p (expand-file-name "Entries" dir)) + cvs-dired-use-hook + (if (eq cvs-dired-use-hook 'always) + (not current-prefix-arg) + (equal current-prefix-arg cvs-dired-use-hook))) + (save-excursion + (funcall cvs-dired-action (file-name-directory dir) t t)))))) + +;; +;; hook into VC +;; + +(add-hook 'vc-post-command-functions 'cvs-vc-command-advice) + +(defun cvs-vc-command-advice (command files flags) + (when (and (equal command "cvs") + (progn + (while (and (stringp (car flags)) + (string-match "\\`-" (car flags))) + (pop flags)) + ;; don't parse output we don't understand. + (member (car flags) cvs-parse-known-commands)) + ;; Don't parse "update -p" output. + (not (and (member (car flags) '("update" "checkout")) + (let ((found-p nil)) + (dolist (flag flags found-p) + (if (equal flag "-p") (setq found-p t))))))) + (save-current-buffer + (let ((buffer (current-buffer)) + (dir default-directory) + (cvs-from-vc t)) + (dolist (cvs-buf (buffer-list)) + (set-buffer cvs-buf) + ;; look for a corresponding pcl-cvs buffer + (when (and (eq major-mode 'cvs-mode) + (cvs-string-prefix-p default-directory dir)) + (let ((subdir (substring dir (length default-directory)))) + (set-buffer buffer) + (set (make-local-variable 'cvs-buffer) cvs-buf) + ;; `cvs -q add file' produces no useful output :-( + (when (and (equal (car flags) "add") + (goto-char (point-min)) + (looking-at ".*to add this file permanently\n\\'")) + (dolist (file (if (listp files) files (list files))) + (insert "cvs add: scheduling file `" + (file-name-nondirectory file) + "' for addition\n"))) + ;; VC never (?) does `cvs -n update' so dcd=nil + ;; should probably always be the right choice. + (cvs-parse-process nil subdir)))))))) + +;; +;; Hook into write-buffer +;; + +(defun cvs-mark-buffer-changed () + (let* ((file (expand-file-name buffer-file-name)) + (version (and (fboundp 'vc-backend) + (eq (vc-backend file) 'CVS) + (vc-working-revision file)))) + (when version + (save-excursion + (dolist (cvs-buf (buffer-list)) + (set-buffer cvs-buf) + ;; look for a corresponding pcl-cvs buffer + (when (and (eq major-mode 'cvs-mode) + (cvs-string-prefix-p default-directory file)) + (let* ((file (substring file (length default-directory))) + (fi (cvs-create-fileinfo + (if (string= "0" version) + 'ADDED 'MODIFIED) + (or (file-name-directory file) "") + (file-name-nondirectory file) + "cvs-mark-buffer-changed"))) + (cvs-addto-collection cvs-cookies fi)))))))) + +(add-hook 'after-save-hook 'cvs-mark-buffer-changed) + + +(provide 'pcvs) + +;; arch-tag: 8e3a7494-0453-4389-9ab3-a557ce9fab61 +;;; pcvs.el ends here diff -r 681cd08dc0f7 -r e93288477c43 lisp/vc/smerge-mode.el --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/lisp/vc/smerge-mode.el Sun Jun 13 22:57:55 2010 +0000 @@ -0,0 +1,1231 @@ +;;; smerge-mode.el --- Minor mode to resolve diff3 conflicts + +;; Copyright (C) 1999, 2000, 2001, 2002, 2003, 2004, 2005, 2006, +;; 2007, 2008, 2009, 2010 Free Software Foundation, Inc. + +;; Author: Stefan Monnier +;; Keywords: vc, tools, revision control, merge, diff3, cvs, conflict + +;; 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 3 of the License, 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. If not, see . + +;;; Commentary: + +;; Provides a lightweight alternative to emerge/ediff. +;; To use it, simply add to your .emacs the following lines: +;; +;; (autoload 'smerge-mode "smerge-mode" nil t) +;; +;; you can even have it turned on automatically with the following +;; piece of code in your .emacs: +;; +;; (defun sm-try-smerge () +;; (save-excursion +;; (goto-char (point-min)) +;; (when (re-search-forward "^<<<<<<< " nil t) +;; (smerge-mode 1)))) +;; (add-hook 'find-file-hook 'sm-try-smerge t) + +;;; Todo: + +;; - if requested, ask the user whether he wants to call ediff right away + +;;; Code: + +(eval-when-compile (require 'cl)) +(require 'diff-mode) ;For diff-auto-refine-mode. + + +;;; The real definition comes later. +(defvar smerge-mode) + +(defgroup smerge () + "Minor mode to highlight and resolve diff3 conflicts." + :group 'tools + :prefix "smerge-") + +(defcustom smerge-diff-buffer-name "*vc-diff*" + "Buffer name to use for displaying diffs." + :group 'smerge + :type '(choice + (const "*vc-diff*") + (const "*cvs-diff*") + (const "*smerge-diff*") + string)) + +(defcustom smerge-diff-switches + (append '("-d" "-b") + (if (listp diff-switches) diff-switches (list diff-switches))) + "A list of strings specifying switches to be passed to diff. +Used in `smerge-diff-base-mine' and related functions." + :group 'smerge + :type '(repeat string)) + +(defcustom smerge-auto-leave t + "Non-nil means to leave `smerge-mode' when the last conflict is resolved." + :group 'smerge + :type 'boolean) + +(defface smerge-mine + '((((min-colors 88) (background light)) + (:foreground "blue1")) + (((background light)) + (:foreground "blue")) + (((min-colors 88) (background dark)) + (:foreground "cyan1")) + (((background dark)) + (:foreground "cyan"))) + "Face for your code." + :group 'smerge) +(define-obsolete-face-alias 'smerge-mine-face 'smerge-mine "22.1") +(defvar smerge-mine-face 'smerge-mine) + +(defface smerge-other + '((((background light)) + (:foreground "darkgreen")) + (((background dark)) + (:foreground "lightgreen"))) + "Face for the other code." + :group 'smerge) +(define-obsolete-face-alias 'smerge-other-face 'smerge-other "22.1") +(defvar smerge-other-face 'smerge-other) + +(defface smerge-base + '((((min-colors 88) (background light)) + (:foreground "red1")) + (((background light)) + (:foreground "red")) + (((background dark)) + (:foreground "orange"))) + "Face for the base code." + :group 'smerge) +(define-obsolete-face-alias 'smerge-base-face 'smerge-base "22.1") +(defvar smerge-base-face 'smerge-base) + +(defface smerge-markers + '((((background light)) + (:background "grey85")) + (((background dark)) + (:background "grey30"))) + "Face for the conflict markers." + :group 'smerge) +(define-obsolete-face-alias 'smerge-markers-face 'smerge-markers "22.1") +(defvar smerge-markers-face 'smerge-markers) + +(defface smerge-refined-change + '((t :background "yellow")) + "Face used for char-based changes shown by `smerge-refine'." + :group 'smerge) + +(easy-mmode-defmap smerge-basic-map + `(("n" . smerge-next) + ("p" . smerge-prev) + ("r" . smerge-resolve) + ("a" . smerge-keep-all) + ("b" . smerge-keep-base) + ("o" . smerge-keep-other) + ("m" . smerge-keep-mine) + ("E" . smerge-ediff) + ("C" . smerge-combine-with-next) + ("R" . smerge-refine) + ("\C-m" . smerge-keep-current) + ("=" . ,(make-sparse-keymap "Diff")) + ("=<" "base-mine" . smerge-diff-base-mine) + ("=>" "base-other" . smerge-diff-base-other) + ("==" "mine-other" . smerge-diff-mine-other)) + "The base keymap for `smerge-mode'.") + +(defcustom smerge-command-prefix "\C-c^" + "Prefix for `smerge-mode' commands." + :group 'smerge + :type '(choice (const :tag "ESC" "\e") + (const :tag "C-c ^" "\C-c^" ) + (const :tag "none" "") + string)) + +(easy-mmode-defmap smerge-mode-map + `((,smerge-command-prefix . ,smerge-basic-map)) + "Keymap for `smerge-mode'.") + +(defvar smerge-check-cache nil) +(make-variable-buffer-local 'smerge-check-cache) +(defun smerge-check (n) + (condition-case nil + (let ((state (cons (point) (buffer-modified-tick)))) + (unless (equal (cdr smerge-check-cache) state) + (smerge-match-conflict) + (setq smerge-check-cache (cons (match-data) state))) + (nth (* 2 n) (car smerge-check-cache))) + (error nil))) + +(easy-menu-define smerge-mode-menu smerge-mode-map + "Menu for `smerge-mode'." + '("SMerge" + ["Next" smerge-next :help "Go to next conflict"] + ["Previous" smerge-prev :help "Go to previous conflict"] + "--" + ["Keep All" smerge-keep-all :help "Keep all three versions" + :active (smerge-check 1)] + ["Keep Current" smerge-keep-current :help "Use current (at point) version" + :active (and (smerge-check 1) (> (smerge-get-current) 0))] + "--" + ["Revert to Base" smerge-keep-base :help "Revert to base version" + :active (smerge-check 2)] + ["Keep Other" smerge-keep-other :help "Keep `other' version" + :active (smerge-check 3)] + ["Keep Yours" smerge-keep-mine :help "Keep your version" + :active (smerge-check 1)] + "--" + ["Diff Base/Mine" smerge-diff-base-mine + :help "Diff `base' and `mine' for current conflict" + :active (smerge-check 2)] + ["Diff Base/Other" smerge-diff-base-other + :help "Diff `base' and `other' for current conflict" + :active (smerge-check 2)] + ["Diff Mine/Other" smerge-diff-mine-other + :help "Diff `mine' and `other' for current conflict" + :active (smerge-check 1)] + "--" + ["Invoke Ediff" smerge-ediff + :help "Use Ediff to resolve the conflicts" + :active (smerge-check 1)] + ["Auto Resolve" smerge-resolve + :help "Try auto-resolution heuristics" + :active (smerge-check 1)] + ["Combine" smerge-combine-with-next + :help "Combine current conflict with next" + :active (smerge-check 1)] + )) + +(easy-menu-define smerge-context-menu nil + "Context menu for mine area in `smerge-mode'." + '(nil + ["Keep Current" smerge-keep-current :help "Use current (at point) version"] + ["Kill Current" smerge-kill-current :help "Remove current (at point) version"] + ["Keep All" smerge-keep-all :help "Keep all three versions"] + "---" + ["More..." (popup-menu smerge-mode-menu) :help "Show full SMerge mode menu"] + )) + +(defconst smerge-font-lock-keywords + '((smerge-find-conflict + (1 smerge-mine-face prepend t) + (2 smerge-base-face prepend t) + (3 smerge-other-face prepend t) + ;; FIXME: `keep' doesn't work right with syntactic fontification. + (0 smerge-markers-face keep) + (4 nil t t) + (5 nil t t))) + "Font lock patterns for `smerge-mode'.") + +(defconst smerge-begin-re "^<<<<<<< \\(.*\\)\n") +(defconst smerge-end-re "^>>>>>>> .*\n") +(defconst smerge-base-re "^||||||| .*\n") +(defconst smerge-other-re "^=======\n") + +(defvar smerge-conflict-style nil + "Keep track of which style of conflict is in use. +Can be nil if the style is undecided, or else: +- `diff3-E' +- `diff3-A'") + +;; Compiler pacifiers +(defvar font-lock-mode) +(defvar font-lock-keywords) + +;;;; +;;;; Actual code +;;;; + +;; Define smerge-next and smerge-prev +(easy-mmode-define-navigation smerge smerge-begin-re "conflict" nil nil + (if diff-auto-refine-mode + (condition-case nil (smerge-refine) (error nil)))) + +(defconst smerge-match-names ["conflict" "mine" "base" "other"]) + +(defun smerge-ensure-match (n) + (unless (match-end n) + (error "No `%s'" (aref smerge-match-names n)))) + +(defun smerge-auto-leave () + (when (and smerge-auto-leave + (save-excursion (goto-char (point-min)) + (not (re-search-forward smerge-begin-re nil t)))) + (when (and (listp buffer-undo-list) smerge-mode) + (push (list 'apply 'smerge-mode 1) buffer-undo-list)) + (smerge-mode -1))) + + +(defun smerge-keep-all () + "Concatenate all versions." + (interactive) + (smerge-match-conflict) + (let ((mb2 (or (match-beginning 2) (point-max))) + (me2 (or (match-end 2) (point-min)))) + (delete-region (match-end 3) (match-end 0)) + (delete-region (max me2 (match-end 1)) (match-beginning 3)) + (if (and (match-end 2) (/= (match-end 1) (match-end 3))) + (delete-region (match-end 1) (match-beginning 2))) + (delete-region (match-beginning 0) (min (match-beginning 1) mb2)) + (smerge-auto-leave))) + +(defun smerge-keep-n (n) + (smerge-remove-props (match-beginning 0) (match-end 0)) + ;; We used to use replace-match, but that did not preserve markers so well. + (delete-region (match-end n) (match-end 0)) + (delete-region (match-beginning 0) (match-beginning n))) + +(defun smerge-combine-with-next () + "Combine the current conflict with the next one." + ;; `smerge-auto-combine' relies on the finish position (at the beginning + ;; of the closing marker). + (interactive) + (smerge-match-conflict) + (let ((ends nil)) + (dolist (i '(3 2 1 0)) + (push (if (match-end i) (copy-marker (match-end i) t)) ends)) + (setq ends (apply 'vector ends)) + (goto-char (aref ends 0)) + (if (not (re-search-forward smerge-begin-re nil t)) + (error "No next conflict") + (smerge-match-conflict) + (let ((match-data (mapcar (lambda (m) (if m (copy-marker m))) + (match-data)))) + ;; First copy the in-between text in each alternative. + (dolist (i '(1 2 3)) + (when (aref ends i) + (goto-char (aref ends i)) + (insert-buffer-substring (current-buffer) + (aref ends 0) (car match-data)))) + (delete-region (aref ends 0) (car match-data)) + ;; Then move the second conflict's alternatives into the first. + (dolist (i '(1 2 3)) + (set-match-data match-data) + (when (and (aref ends i) (match-end i)) + (goto-char (aref ends i)) + (insert-buffer-substring (current-buffer) + (match-beginning i) (match-end i)))) + (delete-region (car match-data) (cadr match-data)) + ;; Free the markers. + (dolist (m match-data) (if m (move-marker m nil))) + (mapc (lambda (m) (if m (move-marker m nil))) ends))))) + +(defvar smerge-auto-combine-max-separation 2 + "Max number of lines between conflicts that should be combined.") + +(defun smerge-auto-combine () + "Automatically combine conflicts that are near each other." + (interactive) + (save-excursion + (goto-char (point-min)) + (while (smerge-find-conflict) + ;; 2 is 1 (default) + 1 (the begin markers). + (while (save-excursion + (smerge-find-conflict + (line-beginning-position + (+ 2 smerge-auto-combine-max-separation)))) + (forward-line -1) ;Go back inside the conflict. + (smerge-combine-with-next) + (forward-line 1) ;Move past the end of the conflict. + )))) + +(defvar smerge-resolve-function + (lambda () (error "Don't know how to resolve")) + "Mode-specific merge function. +The function is called with zero or one argument (non-nil if the resolution +function should only apply safe heuristics) and with the match data set +according to `smerge-match-conflict'.") +(add-to-list 'debug-ignored-errors "Don't know how to resolve") + +(defvar smerge-text-properties + `(help-echo "merge conflict: mouse-3 shows a menu" + ;; mouse-face highlight + keymap (keymap (down-mouse-3 . smerge-popup-context-menu)))) + +(defun smerge-remove-props (beg end) + (remove-overlays beg end 'smerge 'refine) + (remove-overlays beg end 'smerge 'conflict) + ;; Now that we use overlays rather than text-properties, this function + ;; does not cause refontification any more. It can be seen very clearly + ;; in buffers where jit-lock-contextually is not t, in which case deleting + ;; the "<<<<<<< foobar" leading line leaves the rest of the conflict + ;; highlighted as if it were still a valid conflict. Note that in many + ;; important cases (such as the previous example) we're actually called + ;; during font-locking so inhibit-modification-hooks is non-nil, so we + ;; can't just modify the buffer and expect font-lock to be triggered as in: + ;; (put-text-property beg end 'smerge-force-highlighting nil) + (with-silent-modifications + (remove-text-properties beg end '(fontified nil)))) + +(defun smerge-popup-context-menu (event) + "Pop up the Smerge mode context menu under mouse." + (interactive "e") + (if (and smerge-mode + (save-excursion (posn-set-point (event-end event)) (smerge-check 1))) + (progn + (posn-set-point (event-end event)) + (smerge-match-conflict) + (let ((i (smerge-get-current)) + o) + (if (<= i 0) + ;; Out of range + (popup-menu smerge-mode-menu) + ;; Install overlay. + (setq o (make-overlay (match-beginning i) (match-end i))) + (unwind-protect + (progn + (overlay-put o 'face 'highlight) + (sit-for 0) ;Display the new highlighting. + (popup-menu smerge-context-menu)) + ;; Delete overlay. + (delete-overlay o))))) + ;; There's no conflict at point, the text-props are just obsolete. + (save-excursion + (let ((beg (re-search-backward smerge-end-re nil t)) + (end (re-search-forward smerge-begin-re nil t))) + (smerge-remove-props (or beg (point-min)) (or end (point-max))) + (push event unread-command-events))))) + +(defun smerge-apply-resolution-patch (buf m0b m0e m3b m3e &optional m2b) + "Replace the conflict with a bunch of subconflicts. +BUF contains a plain diff between match-1 and match-3." + (let ((line 1) + (textbuf (current-buffer)) + (name1 (progn (goto-char m0b) + (buffer-substring (+ (point) 8) (line-end-position)))) + (name2 (when m2b (goto-char m2b) (forward-line -1) + (buffer-substring (+ (point) 8) (line-end-position)))) + (name3 (progn (goto-char m0e) (forward-line -1) + (buffer-substring (+ (point) 8) (line-end-position))))) + (smerge-remove-props m0b m0e) + (delete-region m3e m0e) + (delete-region m0b m3b) + (setq m3b m0b) + (setq m3e (- m3e (- m3b m0b))) + (goto-char m3b) + (with-current-buffer buf + (goto-char (point-min)) + (while (not (eobp)) + (if (not (looking-at "\\([0-9]+\\)\\(?:,\\([0-9]+\\)\\)?\\([acd]\\)\\([0-9]+\\)\\(?:,\\([0-9]+\\)\\)?$")) + (error "Unexpected patch hunk header: %s" + (buffer-substring (point) (line-end-position))) + (let* ((op (char-after (match-beginning 3))) + (startline (+ (string-to-number (match-string 1)) + ;; No clue why this is the way it is, but line + ;; numbers seem to be off-by-one for `a' ops. + (if (eq op ?a) 1 0))) + (endline (if (eq op ?a) startline + (1+ (if (match-end 2) + (string-to-number (match-string 2)) + startline)))) + (lines (- endline startline)) + (otherlines (cond + ((eq op ?d) nil) + ((null (match-end 5)) 1) + (t (- (string-to-number (match-string 5)) + (string-to-number (match-string 4)) -1)))) + othertext) + (forward-line 1) ;Skip header. + (forward-line lines) ;Skip deleted text. + (if (eq op ?c) (forward-line 1)) ;Skip separator. + (setq othertext + (if (null otherlines) "" + (let ((pos (point))) + (dotimes (i otherlines) (delete-char 2) (forward-line 1)) + (buffer-substring pos (point))))) + (with-current-buffer textbuf + (forward-line (- startline line)) + (insert "<<<<<<< " name1 "\n" othertext + (if name2 (concat "||||||| " name2 "\n") "") + "=======\n") + (forward-line lines) + (insert ">>>>>>> " name3 "\n") + (setq line endline)))))))) + +(defun smerge-resolve (&optional safe) + "Resolve the conflict at point intelligently. +This relies on mode-specific knowledge and thus only works in some +major modes. Uses `smerge-resolve-function' to do the actual work." + (interactive) + (smerge-match-conflict) + (smerge-remove-props (match-beginning 0) (match-end 0)) + (let ((md (match-data)) + (m0b (match-beginning 0)) + (m1b (match-beginning 1)) + (m2b (match-beginning 2)) + (m3b (match-beginning 3)) + (m0e (match-end 0)) + (m1e (match-end 1)) + (m2e (match-end 2)) + (m3e (match-end 3)) + (buf (generate-new-buffer " *smerge*")) + m b o) + (unwind-protect + (progn + (cond + ;; Trivial diff3 -A non-conflicts. + ((and (eq (match-end 1) (match-end 3)) + (eq (match-beginning 1) (match-beginning 3))) + (smerge-keep-n 3)) + ;; Mode-specific conflict resolution. + ((condition-case nil + (atomic-change-group + (if safe + (funcall smerge-resolve-function safe) + (funcall smerge-resolve-function)) + t) + (error nil)) + ;; Nothing to do: the resolution function has done it already. + nil) + ;; Non-conflict. + ((and (eq m1e m3e) (eq m1b m3b)) + (set-match-data md) (smerge-keep-n 3)) + ;; Refine a 2-way conflict using "diff -b". + ;; In case of a 3-way conflict with an empty base + ;; (i.e. 2 conflicting additions), we do the same, presuming + ;; that the 2 additions should be somehow merged rather + ;; than concatenated. + ((let ((lines (count-lines m3b m3e))) + (setq m (make-temp-file "smm")) + (write-region m1b m1e m nil 'silent) + (setq o (make-temp-file "smo")) + (write-region m3b m3e o nil 'silent) + (not (or (eq m1b m1e) (eq m3b m3e) + (and (not (zerop (call-process diff-command + nil buf nil "-b" o m))) + ;; TODO: We don't know how to do the refinement + ;; if there's a non-empty ancestor and m1 and m3 + ;; aren't just plain equal. + m2b (not (eq m2b m2e))) + (with-current-buffer buf + (goto-char (point-min)) + ;; Make sure there's some refinement. + (looking-at + (concat "1," (number-to-string lines) "c")))))) + (smerge-apply-resolution-patch buf m0b m0e m3b m3e m2b)) + ;; "Mere whitespace changes" conflicts. + ((when m2e + (setq b (make-temp-file "smb")) + (write-region m2b m2e b nil 'silent) + (with-current-buffer buf (erase-buffer)) + ;; Only minor whitespace changes made locally. + ;; BEWARE: pass "-c" 'cause the output is reused in the next test. + (zerop (call-process diff-command nil buf nil "-bc" b m))) + (set-match-data md) + (smerge-keep-n 3)) + ;; Try "diff -b BASE MINE | patch OTHER". + ((when (and (not safe) m2e b + ;; If the BASE is empty, this would just concatenate + ;; the two, which is rarely right. + (not (eq m2b m2e))) + ;; BEWARE: we're using here the patch of the previous test. + (with-current-buffer buf + (zerop (call-process-region + (point-min) (point-max) "patch" t nil nil + "-r" "/dev/null" "--no-backup-if-mismatch" + "-fl" o)))) + (save-restriction + (narrow-to-region m0b m0e) + (smerge-remove-props m0b m0e) + (insert-file-contents o nil nil nil t))) + ;; Try "diff -b BASE OTHER | patch MINE". + ((when (and (not safe) m2e b + ;; If the BASE is empty, this would just concatenate + ;; the two, which is rarely right. + (not (eq m2b m2e))) + (write-region m3b m3e o nil 'silent) + (call-process diff-command nil buf nil "-bc" b o) + (with-current-buffer buf + (zerop (call-process-region + (point-min) (point-max) "patch" t nil nil + "-r" "/dev/null" "--no-backup-if-mismatch" + "-fl" m)))) + (save-restriction + (narrow-to-region m0b m0e) + (smerge-remove-props m0b m0e) + (insert-file-contents m nil nil nil t))) + (t + (error "Don't know how to resolve")))) + (if (buffer-name buf) (kill-buffer buf)) + (if m (delete-file m)) + (if b (delete-file b)) + (if o (delete-file o)))) + (smerge-auto-leave)) + +(defun smerge-resolve-all () + "Perform automatic resolution on all conflicts." + (interactive) + (save-excursion + (goto-char (point-min)) + (while (re-search-forward smerge-begin-re nil t) + (condition-case nil + (progn + (smerge-match-conflict) + (smerge-resolve 'safe)) + (error nil))))) + +(defun smerge-batch-resolve () + ;; command-line-args-left is what is left of the command line. + (if (not noninteractive) + (error "`smerge-batch-resolve' is to be used only with -batch")) + (while command-line-args-left + (let ((file (pop command-line-args-left))) + (if (string-match "\\.rej\\'" file) + ;; .rej files should never contain diff3 markers, on the other hand, + ;; in Arch, .rej files are sometimes used to indicate that the + ;; main file has diff3 markers. So you can pass **/*.rej and + ;; it will DTRT. + (setq file (substring file 0 (match-beginning 0)))) + (message "Resolving conflicts in %s..." file) + (when (file-readable-p file) + (with-current-buffer (find-file-noselect file) + (smerge-resolve-all) + (save-buffer) + (kill-buffer (current-buffer))))))) + +(defun smerge-keep-base () + "Revert to the base version." + (interactive) + (smerge-match-conflict) + (smerge-ensure-match 2) + (smerge-keep-n 2) + (smerge-auto-leave)) + +(defun smerge-keep-other () + "Use \"other\" version." + (interactive) + (smerge-match-conflict) + ;;(smerge-ensure-match 3) + (smerge-keep-n 3) + (smerge-auto-leave)) + +(defun smerge-keep-mine () + "Keep your version." + (interactive) + (smerge-match-conflict) + ;;(smerge-ensure-match 1) + (smerge-keep-n 1) + (smerge-auto-leave)) + +(defun smerge-get-current () + (let ((i 3)) + (while (or (not (match-end i)) + (< (point) (match-beginning i)) + (>= (point) (match-end i))) + (decf i)) + i)) + +(defun smerge-keep-current () + "Use the current (under the cursor) version." + (interactive) + (smerge-match-conflict) + (let ((i (smerge-get-current))) + (if (<= i 0) (error "Not inside a version") + (smerge-keep-n i) + (smerge-auto-leave)))) + +(defun smerge-kill-current () + "Remove the current (under the cursor) version." + (interactive) + (smerge-match-conflict) + (let ((i (smerge-get-current))) + (if (<= i 0) (error "Not inside a version") + (let ((left nil)) + (dolist (n '(3 2 1)) + (if (and (match-end n) (/= (match-end n) (match-end i))) + (push n left))) + (if (and (cdr left) + (/= (match-end (car left)) (match-end (cadr left)))) + (ding) ;We don't know how to do that. + (smerge-keep-n (car left)) + (smerge-auto-leave)))))) + +(defun smerge-diff-base-mine () + "Diff 'base' and 'mine' version in current conflict region." + (interactive) + (smerge-diff 2 1)) + +(defun smerge-diff-base-other () + "Diff 'base' and 'other' version in current conflict region." + (interactive) + (smerge-diff 2 3)) + +(defun smerge-diff-mine-other () + "Diff 'mine' and 'other' version in current conflict region." + (interactive) + (smerge-diff 1 3)) + +(defun smerge-match-conflict () + "Get info about the conflict. Puts the info in the `match-data'. +The submatches contain: + 0: the whole conflict. + 1: your code. + 2: the base code. + 3: other code. +An error is raised if not inside a conflict." + (save-excursion + (condition-case nil + (let* ((orig-point (point)) + + (_ (forward-line 1)) + (_ (re-search-backward smerge-begin-re)) + + (start (match-beginning 0)) + (mine-start (match-end 0)) + (filename (or (match-string 1) "")) + + (_ (re-search-forward smerge-end-re)) + (_ (assert (< orig-point (match-end 0)))) + + (other-end (match-beginning 0)) + (end (match-end 0)) + + (_ (re-search-backward smerge-other-re start)) + + (mine-end (match-beginning 0)) + (other-start (match-end 0)) + + base-start base-end) + + ;; handle the various conflict styles + (cond + ((save-excursion + (goto-char mine-start) + (re-search-forward smerge-begin-re end t)) + ;; There's a nested conflict and we're after the beginning + ;; of the outer one but before the beginning of the inner one. + ;; Of course, maybe this is not a nested conflict but in that + ;; case it can only be something nastier that we don't know how + ;; to handle, so may as well arbitrarily decide to treat it as + ;; a nested conflict. --Stef + (error "There is a nested conflict")) + + ((re-search-backward smerge-base-re start t) + ;; a 3-parts conflict + (set (make-local-variable 'smerge-conflict-style) 'diff3-A) + (setq base-end mine-end) + (setq mine-end (match-beginning 0)) + (setq base-start (match-end 0))) + + ((string= filename (file-name-nondirectory + (or buffer-file-name ""))) + ;; a 2-parts conflict + (set (make-local-variable 'smerge-conflict-style) 'diff3-E)) + + ((and (not base-start) + (or (eq smerge-conflict-style 'diff3-A) + (equal filename "ANCESTOR") + (string-match "\\`[.0-9]+\\'" filename))) + ;; a same-diff conflict + (setq base-start mine-start) + (setq base-end mine-end) + (setq mine-start other-start) + (setq mine-end other-end))) + + (store-match-data (list start end + mine-start mine-end + base-start base-end + other-start other-end + (when base-start (1- base-start)) base-start + (1- other-start) other-start)) + t) + (search-failed (error "Point not in conflict region"))))) + +(add-to-list 'debug-ignored-errors "Point not in conflict region") + +(defun smerge-conflict-overlay (pos) + "Return the conflict overlay at POS if any." + (let ((ols (overlays-at pos)) + conflict) + (dolist (ol ols) + (if (and (eq (overlay-get ol 'smerge) 'conflict) + (> (overlay-end ol) pos)) + (setq conflict ol))) + conflict)) + +(defun smerge-find-conflict (&optional limit) + "Find and match a conflict region. Intended as a font-lock MATCHER. +The submatches are the same as in `smerge-match-conflict'. +Returns non-nil if a match is found between point and LIMIT. +Point is moved to the end of the conflict." + (let ((found nil) + (pos (point)) + conflict) + ;; First check to see if point is already inside a conflict, using + ;; the conflict overlays. + (while (and (not found) (setq conflict (smerge-conflict-overlay pos))) + ;; Check the overlay's validity and kill it if it's out of date. + (condition-case nil + (progn + (goto-char (overlay-start conflict)) + (smerge-match-conflict) + (goto-char (match-end 0)) + (if (<= (point) pos) + (error "Matching backward!") + (setq found t))) + (error (smerge-remove-props + (overlay-start conflict) (overlay-end conflict)) + (goto-char pos)))) + ;; If we're not already inside a conflict, look for the next conflict + ;; and add/update its overlay. + (while (and (not found) (re-search-forward smerge-begin-re limit t)) + (condition-case nil + (progn + (smerge-match-conflict) + (goto-char (match-end 0)) + (let ((conflict (smerge-conflict-overlay (1- (point))))) + (if conflict + ;; Update its location, just in case it got messed up. + (move-overlay conflict (match-beginning 0) (match-end 0)) + (setq conflict (make-overlay (match-beginning 0) (match-end 0) + nil 'front-advance nil)) + (overlay-put conflict 'evaporate t) + (overlay-put conflict 'smerge 'conflict) + (let ((props smerge-text-properties)) + (while props + (overlay-put conflict (pop props) (pop props)))))) + (setq found t)) + (error nil))) + found)) + +;;; Refined change highlighting + +(defvar smerge-refine-forward-function 'smerge-refine-forward + "Function used to determine an \"atomic\" element. +You can set it to `forward-char' to get char-level granularity. +Its behavior has mainly two restrictions: +- if this function encounters a newline, it's important that it stops right + after the newline. + This only matters if `smerge-refine-ignore-whitespace' is nil. +- it needs to be unaffected by changes performed by the `preproc' argument + to `smerge-refine-subst'. + This only matters if `smerge-refine-weight-hack' is nil.") + +(defvar smerge-refine-ignore-whitespace t + "If non-nil, indicate that `smerge-refine' should try to ignore change in whitespace.") + +(defvar smerge-refine-weight-hack t + "If non-nil, pass to diff as many lines as there are chars in the region. +I.e. each atomic element (e.g. word) will be copied as many times (on different +lines) as it has chars. This has two advantages: +- if `diff' tries to minimize the number *lines* (rather than chars) + added/removed, this adjust the weights so that adding/removing long + symbols is considered correspondingly more costly. +- `smerge-refine-forward-function' only needs to be called when chopping up + the regions, and `forward-char' can be used afterwards. +It has the following disadvantages: +- cannot use `diff -w' because the weighting causes added spaces in a line + to be represented as added copies of some line, so `diff -w' can't do the + right thing any more. +- may in degenerate cases take a 1KB input region and turn it into a 1MB + file to pass to diff.") + +(defun smerge-refine-forward (n) + (let ((case-fold-search nil) + (re "[[:upper:]]?[[:lower:]]+\\|[[:upper:]]+\\|[[:digit:]]+\\|.\\|\n")) + (when (and smerge-refine-ignore-whitespace + ;; smerge-refine-weight-hack causes additional spaces to + ;; appear as additional lines as well, so even if diff ignore + ;; whitespace changes, it'll report added/removed lines :-( + (not smerge-refine-weight-hack)) + (setq re (concat "[ \t]*\\(?:" re "\\)"))) + (dotimes (i n) + (unless (looking-at re) (error "Smerge refine internal error")) + (goto-char (match-end 0))))) + +(defun smerge-refine-chopup-region (beg end file &optional preproc) + "Chopup the region into small elements, one per line. +Save the result into FILE. +If non-nil, PREPROC is called with no argument in a buffer that contains +a copy of the text, just before chopping it up. It can be used to replace +chars to try and eliminate some spurious differences." + ;; We used to chop up char-by-char rather than word-by-word like ediff + ;; does. It had the benefit of simplicity and very fine results, but it + ;; often suffered from problem that diff would find correlations where + ;; there aren't any, so the resulting "change" didn't make much sense. + ;; You can still get this behavior by setting + ;; `smerge-refine-forward-function' to `forward-char'. + (let ((buf (current-buffer))) + (with-temp-buffer + (insert-buffer-substring buf beg end) + (when preproc (goto-char (point-min)) (funcall preproc)) + (when smerge-refine-ignore-whitespace + ;; It doesn't make much of a difference for diff-fine-highlight + ;; because we still have the _/+//! prefix anyway. Can still be + ;; useful in other circumstances. + (subst-char-in-region (point-min) (point-max) ?\n ?\s)) + (goto-char (point-min)) + (while (not (eobp)) + (funcall smerge-refine-forward-function 1) + (let ((s (if (prog2 (forward-char -1) (bolp) (forward-char 1)) + nil + (buffer-substring (line-beginning-position) (point))))) + ;; We add \n after each char except after \n, so we get + ;; one line per text char, where each line contains + ;; just one char, except for \n chars which are + ;; represented by the empty line. + (unless (eq (char-before) ?\n) (insert ?\n)) + ;; HACK ALERT!! + (if smerge-refine-weight-hack + (dotimes (i (1- (length s))) (insert s "\n"))))) + (unless (bolp) (error "Smerge refine internal error")) + (let ((coding-system-for-write 'emacs-mule)) + (write-region (point-min) (point-max) file nil 'nomessage))))) + +(defun smerge-refine-highlight-change (buf beg match-num1 match-num2 props) + (with-current-buffer buf + (goto-char beg) + (let* ((startline (- (string-to-number match-num1) 1)) + (beg (progn (funcall (if smerge-refine-weight-hack + 'forward-char + smerge-refine-forward-function) + startline) + (point))) + (end (progn (funcall (if smerge-refine-weight-hack + 'forward-char + smerge-refine-forward-function) + (if match-num2 + (- (string-to-number match-num2) + startline) + 1)) + (point)))) + (when smerge-refine-ignore-whitespace + (skip-chars-backward " \t\n" beg) (setq end (point)) + (goto-char beg) + (skip-chars-forward " \t\n" end) (setq beg (point))) + (when (> end beg) + (let ((ol (make-overlay + beg end nil + ;; Make them tend to shrink rather than spread when editing. + 'front-advance nil))) + (overlay-put ol 'evaporate t) + (dolist (x props) (overlay-put ol (car x) (cdr x))) + ol))))) + +(defun smerge-refine-subst (beg1 end1 beg2 end2 props &optional preproc) + "Show fine differences in the two regions BEG1..END1 and BEG2..END2. +PROPS is an alist of properties to put (via overlays) on the changes. +If non-nil, PREPROC is called with no argument in a buffer that contains +a copy of a region, just before preparing it to for `diff'. It can be +used to replace chars to try and eliminate some spurious differences." + (let* ((buf (current-buffer)) + (pos (point)) + (file1 (make-temp-file "diff1")) + (file2 (make-temp-file "diff2"))) + ;; Chop up regions into smaller elements and save into files. + (smerge-refine-chopup-region beg1 end1 file1 preproc) + (smerge-refine-chopup-region beg2 end2 file2 preproc) + + ;; Call diff on those files. + (unwind-protect + (with-temp-buffer + (let ((coding-system-for-read 'emacs-mule)) + (call-process diff-command nil t nil + (if (and smerge-refine-ignore-whitespace + (not smerge-refine-weight-hack)) + ;; Pass -a so diff treats it as a text file even + ;; if it contains \0 and such. + ;; Pass -d so as to get the smallest change, but + ;; also and more importantly because otherwise it + ;; may happen that diff doesn't behave like + ;; smerge-refine-weight-hack expects it to. + ;; See http://thread.gmane.org/gmane.emacs.devel/82685. + "-awd" "-ad") + file1 file2)) + ;; Process diff's output. + (goto-char (point-min)) + (let ((last1 nil) + (last2 nil)) + (while (not (eobp)) + (if (not (looking-at "\\([0-9]+\\)\\(?:,\\([0-9]+\\)\\)?\\([acd]\\)\\([0-9]+\\)\\(?:,\\([0-9]+\\)\\)?$")) + (error "Unexpected patch hunk header: %s" + (buffer-substring (point) (line-end-position)))) + (let ((op (char-after (match-beginning 3))) + (m1 (match-string 1)) + (m2 (match-string 2)) + (m4 (match-string 4)) + (m5 (match-string 5))) + (when (memq op '(?d ?c)) + (setq last1 + (smerge-refine-highlight-change buf beg1 m1 m2 props))) + (when (memq op '(?a ?c)) + (setq last2 + (smerge-refine-highlight-change buf beg2 m4 m5 props)))) + (forward-line 1) ;Skip hunk header. + (and (re-search-forward "^[0-9]" nil 'move) ;Skip hunk body. + (goto-char (match-beginning 0)))) + ;; (assert (or (null last1) (< (overlay-start last1) end1))) + ;; (assert (or (null last2) (< (overlay-start last2) end2))) + (if smerge-refine-weight-hack + (progn + ;; (assert (or (null last1) (<= (overlay-end last1) end1))) + ;; (assert (or (null last2) (<= (overlay-end last2) end2))) + ) + ;; smerge-refine-forward-function when calling in chopup may + ;; have stopped because it bumped into EOB whereas in + ;; smerge-refine-weight-hack it may go a bit further. + (if (and last1 (> (overlay-end last1) end1)) + (move-overlay last1 (overlay-start last1) end1)) + (if (and last2 (> (overlay-end last2) end2)) + (move-overlay last2 (overlay-start last2) end2)) + ))) + (goto-char pos) + (delete-file file1) + (delete-file file2)))) + +(defun smerge-refine (&optional part) + "Highlight the words of the conflict that are different. +For 3-way conflicts, highlights only two of the three parts. +A numeric argument PART can be used to specify which two parts; +repeating the command will highlight other two parts." + (interactive + (if (integerp current-prefix-arg) (list current-prefix-arg) + (smerge-match-conflict) + (let* ((prop (get-text-property (match-beginning 0) 'smerge-refine-part)) + (part (if (and (consp prop) + (eq (buffer-chars-modified-tick) (car prop))) + (cdr prop)))) + ;; If already highlighted, cycle. + (list (if (integerp part) (1+ (mod part 3))))))) + + (if (and (integerp part) (or (< part 1) (> part 3))) + (error "No conflict part nb %s" part)) + (smerge-match-conflict) + (remove-overlays (match-beginning 0) (match-end 0) 'smerge 'refine) + ;; Ignore `part' if not applicable, and default it if not provided. + (setq part (cond ((null (match-end 2)) 2) + ((eq (match-end 1) (match-end 3)) 1) + ((integerp part) part) + (t 2))) + (let ((n1 (if (eq part 1) 2 1)) + (n2 (if (eq part 3) 2 3))) + (smerge-ensure-match n1) + (smerge-ensure-match n2) + (with-silent-modifications + (put-text-property (match-beginning 0) (1+ (match-beginning 0)) + 'smerge-refine-part + (cons (buffer-chars-modified-tick) part))) + (smerge-refine-subst (match-beginning n1) (match-end n1) + (match-beginning n2) (match-end n2) + '((smerge . refine) + (face . smerge-refined-change))))) + +(defun smerge-diff (n1 n2) + (smerge-match-conflict) + (smerge-ensure-match n1) + (smerge-ensure-match n2) + (let ((name1 (aref smerge-match-names n1)) + (name2 (aref smerge-match-names n2)) + ;; Read them before the match-data gets clobbered. + (beg1 (match-beginning n1)) + (end1 (match-end n1)) + (beg2 (match-beginning n2)) + (end2 (match-end n2)) + (file1 (make-temp-file "smerge1")) + (file2 (make-temp-file "smerge2")) + (dir default-directory) + (file (if buffer-file-name (file-relative-name buffer-file-name))) + ;; We would want to use `emacs-mule-unix' for read&write, but we + ;; bump into problems with the coding-system used by diff to write + ;; the file names and the time stamps in the header. + ;; `buffer-file-coding-system' is not always correct either, but if + ;; the OS/user uses only one coding-system, then it works. + (coding-system-for-read buffer-file-coding-system)) + (write-region beg1 end1 file1 nil 'nomessage) + (write-region beg2 end2 file2 nil 'nomessage) + (unwind-protect + (with-current-buffer (get-buffer-create smerge-diff-buffer-name) + (setq default-directory dir) + (let ((inhibit-read-only t)) + (erase-buffer) + (let ((status + (apply 'call-process diff-command nil t nil + (append smerge-diff-switches + (list "-L" (concat name1 "/" file) + "-L" (concat name2 "/" file) + file1 file2))))) + (if (eq status 0) (insert "No differences found.\n")))) + (goto-char (point-min)) + (diff-mode) + (display-buffer (current-buffer) t)) + (delete-file file1) + (delete-file file2)))) + +;; compiler pacifiers +(defvar smerge-ediff-windows) +(defvar smerge-ediff-buf) +(defvar ediff-buffer-A) +(defvar ediff-buffer-B) +(defvar ediff-buffer-C) +(defvar ediff-ancestor-buffer) +(defvar ediff-quit-hook) +(declare-function ediff-cleanup-mess "ediff-util" nil) + +;;;###autoload +(defun smerge-ediff (&optional name-mine name-other name-base) + "Invoke ediff to resolve the conflicts. +NAME-MINE, NAME-OTHER, and NAME-BASE, if non-nil, are used for the +buffer names." + (interactive) + (let* ((buf (current-buffer)) + (mode major-mode) + ;;(ediff-default-variant 'default-B) + (config (current-window-configuration)) + (filename (file-name-nondirectory buffer-file-name)) + (mine (generate-new-buffer + (or name-mine (concat "*" filename " MINE*")))) + (other (generate-new-buffer + (or name-other (concat "*" filename " OTHER*")))) + base) + (with-current-buffer mine + (buffer-disable-undo) + (insert-buffer-substring buf) + (goto-char (point-min)) + (while (smerge-find-conflict) + (when (match-beginning 2) (setq base t)) + (smerge-keep-n 1)) + (buffer-enable-undo) + (set-buffer-modified-p nil) + (funcall mode)) + + (with-current-buffer other + (buffer-disable-undo) + (insert-buffer-substring buf) + (goto-char (point-min)) + (while (smerge-find-conflict) + (smerge-keep-n 3)) + (buffer-enable-undo) + (set-buffer-modified-p nil) + (funcall mode)) + + (when base + (setq base (generate-new-buffer + (or name-base (concat "*" filename " BASE*")))) + (with-current-buffer base + (buffer-disable-undo) + (insert-buffer-substring buf) + (goto-char (point-min)) + (while (smerge-find-conflict) + (if (match-end 2) + (smerge-keep-n 2) + (delete-region (match-beginning 0) (match-end 0)))) + (buffer-enable-undo) + (set-buffer-modified-p nil) + (funcall mode))) + + ;; the rest of the code is inspired from vc.el + ;; Fire up ediff. + (set-buffer + (if base + (ediff-merge-buffers-with-ancestor mine other base) + ;; nil 'ediff-merge-revisions-with-ancestor buffer-file-name) + (ediff-merge-buffers mine other))) + ;; nil 'ediff-merge-revisions buffer-file-name))) + + ;; Ediff is now set up, and we are in the control buffer. + ;; Do a few further adjustments and take precautions for exit. + (set (make-local-variable 'smerge-ediff-windows) config) + (set (make-local-variable 'smerge-ediff-buf) buf) + (set (make-local-variable 'ediff-quit-hook) + (lambda () + (let ((buffer-A ediff-buffer-A) + (buffer-B ediff-buffer-B) + (buffer-C ediff-buffer-C) + (buffer-Ancestor ediff-ancestor-buffer) + (buf smerge-ediff-buf) + (windows smerge-ediff-windows)) + (ediff-cleanup-mess) + (with-current-buffer buf + (erase-buffer) + (insert-buffer-substring buffer-C) + (kill-buffer buffer-A) + (kill-buffer buffer-B) + (kill-buffer buffer-C) + (when (bufferp buffer-Ancestor) (kill-buffer buffer-Ancestor)) + (set-window-configuration windows) + (message "Conflict resolution finished; you may save the buffer"))))) + (message "Please resolve conflicts now; exit ediff when done"))) + +(defun smerge-makeup-conflict (pt1 pt2 pt3 &optional pt4) + "Insert diff3 markers to make a new conflict. +Uses point and mark for two of the relevant positions and previous marks +for the other ones. +By default, makes up a 2-way conflict, +with a \\[universal-argument] prefix, makes up a 3-way conflict." + (interactive + (list (point) + (mark) + (progn (pop-mark) (mark)) + (when current-prefix-arg (pop-mark) (mark)))) + ;; Start from the end so as to avoid problems with pos-changes. + (destructuring-bind (pt1 pt2 pt3 &optional pt4) + (sort (list* pt1 pt2 pt3 (if pt4 (list pt4))) '>=) + (goto-char pt1) (beginning-of-line) + (insert ">>>>>>> OTHER\n") + (goto-char pt2) (beginning-of-line) + (insert "=======\n") + (goto-char pt3) (beginning-of-line) + (when pt4 + (insert "||||||| BASE\n") + (goto-char pt4) (beginning-of-line)) + (insert "<<<<<<< MINE\n")) + (if smerge-mode nil (smerge-mode 1)) + (smerge-refine)) + + +(defconst smerge-parsep-re + (concat smerge-begin-re "\\|" smerge-end-re "\\|" + smerge-base-re "\\|" smerge-other-re "\\|")) + +;;;###autoload +(define-minor-mode smerge-mode + "Minor mode to simplify editing output from the diff3 program. +\\{smerge-mode-map}" + :group 'smerge :lighter " SMerge" + (when (and (boundp 'font-lock-mode) font-lock-mode) + (save-excursion + (if smerge-mode + (font-lock-add-keywords nil smerge-font-lock-keywords 'append) + (font-lock-remove-keywords nil smerge-font-lock-keywords)) + (goto-char (point-min)) + (while (smerge-find-conflict) + (save-excursion + (font-lock-fontify-region (match-beginning 0) (match-end 0) nil))))) + (if (string-match (regexp-quote smerge-parsep-re) paragraph-separate) + (unless smerge-mode + (set (make-local-variable 'paragraph-separate) + (replace-match "" t t paragraph-separate))) + (when smerge-mode + (set (make-local-variable 'paragraph-separate) + (concat smerge-parsep-re paragraph-separate)))) + (unless smerge-mode + (smerge-remove-props (point-min) (point-max)))) + +;;;###autoload +(defun smerge-start-session () + "Turn on `smerge-mode' and move point to first conflict marker. +If no conflict maker is found, turn off `smerge-mode'." + (interactive) + (smerge-mode 1) + (condition-case nil + (unless (looking-at smerge-begin-re) + (smerge-next)) + (error (smerge-auto-leave)))) + +(provide 'smerge-mode) + +;; arch-tag: 605c8d1e-e43d-4943-a6f3-1bcc4333e690 +;;; smerge-mode.el ends here diff -r 681cd08dc0f7 -r e93288477c43 lisp/vc/vc-annotate.el --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/lisp/vc/vc-annotate.el Sun Jun 13 22:57:55 2010 +0000 @@ -0,0 +1,676 @@ +;;; vc-annotate.el --- VC Annotate Support + +;; Copyright (C) 1997, 1998, 2000, 2001, 2002, 2003, 2004, 2005, 2006, +;; 2007, 2008, 2009, 2010 Free Software Foundation, Inc. + +;; Author: Martin Lorentzson +;; Maintainer: FSF +;; Keywords: vc tools + +;; 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 3 of the License, 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. If not, see . + +;;; Commentary: +;; + +(require 'vc-hooks) +(require 'vc) + +;;; Code: +(eval-when-compile + (require 'cl)) + +(defcustom vc-annotate-display-mode 'fullscale + "Which mode to color the output of \\[vc-annotate] with by default." + :type '(choice (const :tag "By Color Map Range" nil) + (const :tag "Scale to Oldest" scale) + (const :tag "Scale Oldest->Newest" fullscale) + (number :tag "Specify Fractional Number of Days" + :value "20.5")) + :group 'vc) + +(defcustom vc-annotate-color-map + (if (and (tty-display-color-p) (<= (display-color-cells) 8)) + ;; A custom sorted TTY colormap + (let* ((colors + (sort + (delq nil + (mapcar (lambda (x) + (if (not (or + (string-equal (car x) "white") + (string-equal (car x) "black") )) + (car x))) + (tty-color-alist))) + (lambda (a b) + (cond + ((or (string-equal a "red") (string-equal b "blue")) t) + ((or (string-equal b "red") (string-equal a "blue")) nil) + ((string-equal a "yellow") t) + ((string-equal b "yellow") nil) + ((string-equal a "cyan") t) + ((string-equal b "cyan") nil) + ((string-equal a "green") t) + ((string-equal b "green") nil) + ((string-equal a "magenta") t) + ((string-equal b "magenta") nil) + (t (string< a b)))))) + (date 20.) + (delta (/ (- 360. date) (1- (length colors))))) + (mapcar (lambda (x) + (prog1 + (cons date x) + (setq date (+ date delta)))) colors)) + ;; Normal colormap: hue stepped from 0-240deg, value=1., saturation=0.75 + '(( 20. . "#FF3F3F") + ( 40. . "#FF6C3F") + ( 60. . "#FF993F") + ( 80. . "#FFC63F") + (100. . "#FFF33F") + (120. . "#DDFF3F") + (140. . "#B0FF3F") + (160. . "#83FF3F") + (180. . "#56FF3F") + (200. . "#3FFF56") + (220. . "#3FFF83") + (240. . "#3FFFB0") + (260. . "#3FFFDD") + (280. . "#3FF3FF") + (300. . "#3FC6FF") + (320. . "#3F99FF") + (340. . "#3F6CFF") + (360. . "#3F3FFF"))) + "Association list of age versus color, for \\[vc-annotate]. +Ages are given in units of fractional days. Default is eighteen +steps using a twenty day increment, from red to blue. For TTY +displays with 8 or fewer colors, the default is red to blue with +all other colors between (excluding black and white)." + :type 'alist + :group 'vc) + +(defcustom vc-annotate-very-old-color "#3F3FFF" + "Color for lines older than the current color range in \\[vc-annotate]." + :type 'string + :group 'vc) + +(defcustom vc-annotate-background "black" + "Background color for \\[vc-annotate]. +Default color is used if nil." + :type '(choice (const :tag "Default background" nil) (color)) + :group 'vc) + +(defcustom vc-annotate-menu-elements '(2 0.5 0.1 0.01) + "Menu elements for the mode-specific menu of VC-Annotate mode. +List of factors, used to expand/compress the time scale. See `vc-annotate'." + :type '(repeat number) + :group 'vc) + +(defvar vc-annotate-mode-map + (let ((m (make-sparse-keymap))) + (define-key m "a" 'vc-annotate-revision-previous-to-line) + (define-key m "d" 'vc-annotate-show-diff-revision-at-line) + (define-key m "D" 'vc-annotate-show-changeset-diff-revision-at-line) + (define-key m "f" 'vc-annotate-find-revision-at-line) + (define-key m "j" 'vc-annotate-revision-at-line) + (define-key m "l" 'vc-annotate-show-log-revision-at-line) + (define-key m "n" 'vc-annotate-next-revision) + (define-key m "p" 'vc-annotate-prev-revision) + (define-key m "w" 'vc-annotate-working-revision) + (define-key m "v" 'vc-annotate-toggle-annotation-visibility) + m) + "Local keymap used for VC-Annotate mode.") + +;;; Annotate functionality + +;; Declare globally instead of additional parameter to +;; temp-buffer-show-function (not possible to pass more than one +;; parameter). The use of annotate-ratio is deprecated in favor of +;; annotate-mode, which replaces it with the more sensible "span-to +;; days", along with autoscaling support. +(defvar vc-annotate-ratio nil "Global variable.") + +;; internal buffer-local variables +(defvar vc-annotate-backend nil) +(defvar vc-annotate-parent-file nil) +(defvar vc-annotate-parent-rev nil) +(defvar vc-annotate-parent-display-mode nil) + +(defconst vc-annotate-font-lock-keywords + ;; The fontification is done by vc-annotate-lines instead of font-lock. + '((vc-annotate-lines))) + +(define-derived-mode vc-annotate-mode special-mode "Annotate" + "Major mode for output buffers of the `vc-annotate' command. + +You can use the mode-specific menu to alter the time-span of the used +colors. See variable `vc-annotate-menu-elements' for customizing the +menu items." + ;; Frob buffer-invisibility-spec so that if it is originally a naked t, + ;; it will become a list, to avoid initial annotations being invisible. + (add-to-invisibility-spec 'foo) + (remove-from-invisibility-spec 'foo) + (set (make-local-variable 'truncate-lines) t) + (set (make-local-variable 'font-lock-defaults) + '(vc-annotate-font-lock-keywords t)) + (hack-dir-local-variables-non-file-buffer)) + +(defun vc-annotate-toggle-annotation-visibility () + "Toggle whether or not the annotation is visible." + (interactive) + (funcall (if (memq 'vc-annotate-annotation buffer-invisibility-spec) + 'remove-from-invisibility-spec + 'add-to-invisibility-spec) + 'vc-annotate-annotation) + (force-window-update (current-buffer))) + +(defun vc-annotate-display-default (ratio) + "Display the output of \\[vc-annotate] using the default color range. +The color range is given by `vc-annotate-color-map', scaled by RATIO. +The current time is used as the offset." + (interactive (progn (kill-local-variable 'vc-annotate-color-map) '(1.0))) + (message "Redisplaying annotation...") + (vc-annotate-display ratio) + (message "Redisplaying annotation...done")) + +(defun vc-annotate-oldest-in-map (color-map) + "Return the oldest time in the COLOR-MAP." + ;; Since entries should be sorted, we can just use the last one. + (caar (last color-map))) + +(defun vc-annotate-get-time-set-line-props () + (let ((bol (point)) + (date (vc-call-backend vc-annotate-backend 'annotate-time)) + (inhibit-read-only t)) + (assert (>= (point) bol)) + (put-text-property bol (point) 'invisible 'vc-annotate-annotation) + date)) + +(defun vc-annotate-display-autoscale (&optional full) + "Highlight the output of \\[vc-annotate] using an autoscaled color map. +Autoscaling means that the map is scaled from the current time to the +oldest annotation in the buffer, or, with prefix argument FULL, to +cover the range from the oldest annotation to the newest." + (interactive "P") + (let ((newest 0.0) + (oldest 999999.) ;Any CVS users at the founding of Rome? + (current (vc-annotate-convert-time (current-time))) + date) + (message "Redisplaying annotation...") + ;; Run through this file and find the oldest and newest dates annotated. + (save-excursion + (goto-char (point-min)) + (while (not (eobp)) + (when (setq date (vc-annotate-get-time-set-line-props)) + (when (> date newest) + (setq newest date)) + (when (< date oldest) + (setq oldest date))) + (forward-line 1))) + (vc-annotate-display + (/ (- (if full newest current) oldest) + (vc-annotate-oldest-in-map vc-annotate-color-map)) + (if full newest)) + (message "Redisplaying annotation...done \(%s\)" + (if full + (format "Spanned from %.1f to %.1f days old" + (- current oldest) + (- current newest)) + (format "Spanned to %.1f days old" (- current oldest)))))) + +;; Menu -- Using easymenu.el +(easy-menu-define vc-annotate-mode-menu vc-annotate-mode-map + "VC Annotate Display Menu" + `("VC-Annotate" + ["By Color Map Range" (unless (null vc-annotate-display-mode) + (setq vc-annotate-display-mode nil) + (vc-annotate-display-select)) + :style toggle :selected (null vc-annotate-display-mode)] + ,@(let ((oldest-in-map (vc-annotate-oldest-in-map vc-annotate-color-map))) + (mapcar (lambda (element) + (let ((days (* element oldest-in-map))) + `[,(format "Span %.1f days" days) + (vc-annotate-display-select nil ,days) + :style toggle :selected + (eql vc-annotate-display-mode ,days) ])) + vc-annotate-menu-elements)) + ["Span ..." + (vc-annotate-display-select + nil (float (string-to-number (read-string "Span how many days? "))))] + "--" + ["Span to Oldest" + (unless (eq vc-annotate-display-mode 'scale) + (vc-annotate-display-select nil 'scale)) + :help + "Use an autoscaled color map from the oldest annotation to the current time" + :style toggle :selected + (eq vc-annotate-display-mode 'scale)] + ["Span Oldest->Newest" + (unless (eq vc-annotate-display-mode 'fullscale) + (vc-annotate-display-select nil 'fullscale)) + :help + "Use an autoscaled color map from the oldest to the newest annotation" + :style toggle :selected + (eq vc-annotate-display-mode 'fullscale)] + "--" + ["Toggle annotation visibility" vc-annotate-toggle-annotation-visibility + :help + "Toggle whether the annotation is visible or not"] + ["Annotate previous revision" vc-annotate-prev-revision + :help "Visit the annotation of the revision previous to this one"] + ["Annotate next revision" vc-annotate-next-revision + :help "Visit the annotation of the revision after this one"] + ["Annotate revision at line" vc-annotate-revision-at-line + :help + "Visit the annotation of the revision identified in the current line"] + ["Annotate revision previous to line" vc-annotate-revision-previous-to-line + :help "Visit the annotation of the revision before the revision at line"] + ["Annotate latest revision" vc-annotate-working-revision + :help "Visit the annotation of the working revision of this file"] + "--" + ["Show log of revision at line" vc-annotate-show-log-revision-at-line + :help "Visit the log of the revision at line"] + ["Show diff of revision at line" vc-annotate-show-diff-revision-at-line + :help "Visit the diff of the revision at line from its previous revision"] + ["Show changeset diff of revision at line" + vc-annotate-show-changeset-diff-revision-at-line + :enable + (eq 'repository (vc-call-backend ,vc-annotate-backend 'revision-granularity)) + :help "Visit the diff of the revision at line from its previous revision"] + ["Visit revision at line" vc-annotate-find-revision-at-line + :help "Visit the revision identified in the current line"])) + +(defun vc-annotate-display-select (&optional buffer mode) + "Highlight the output of \\[vc-annotate]. +By default, the current buffer is highlighted, unless overridden by +BUFFER. `vc-annotate-display-mode' specifies the highlighting mode to +use; you may override this using the second optional arg MODE." + (interactive) + (when mode (setq vc-annotate-display-mode mode)) + (pop-to-buffer (or buffer (current-buffer))) + (cond ((null vc-annotate-display-mode) + ;; The ratio is global, thus relative to the global color-map. + (kill-local-variable 'vc-annotate-color-map) + (vc-annotate-display-default (or vc-annotate-ratio 1.0))) + ;; One of the auto-scaling modes + ((eq vc-annotate-display-mode 'scale) + (vc-exec-after `(vc-annotate-display-autoscale))) + ((eq vc-annotate-display-mode 'fullscale) + (vc-exec-after `(vc-annotate-display-autoscale t))) + ((numberp vc-annotate-display-mode) ; A fixed number of days lookback + (vc-annotate-display-default + (/ vc-annotate-display-mode + (vc-annotate-oldest-in-map vc-annotate-color-map)))) + (t (error "No such display mode: %s" + vc-annotate-display-mode)))) + +;;;###autoload +(defun vc-annotate (file rev &optional display-mode buf move-point-to) + "Display the edit history of the current file using colors. + +This command creates a buffer that shows, for each line of the current +file, when it was last edited and by whom. Additionally, colors are +used to show the age of each line--blue means oldest, red means +youngest, and intermediate colors indicate intermediate ages. By +default, the time scale stretches back one year into the past; +everything that is older than that is shown in blue. + +With a prefix argument, this command asks two questions in the +minibuffer. First, you may enter a revision number; then the buffer +displays and annotates that revision instead of the working revision +\(type RET in the minibuffer to leave that default unchanged). Then, +you are prompted for the time span in days which the color range +should cover. For example, a time span of 20 days means that changes +over the past 20 days are shown in red to blue, according to their +age, and everything that is older than that is shown in blue. + +If MOVE-POINT-TO is given, move the point to that line. + +Customization variables: + +`vc-annotate-menu-elements' customizes the menu elements of the +mode-specific menu. `vc-annotate-color-map' and +`vc-annotate-very-old-color' define the mapping of time to colors. +`vc-annotate-background' specifies the background color." + (interactive + (save-current-buffer + (vc-ensure-vc-buffer) + (list buffer-file-name + (let ((def (vc-working-revision buffer-file-name))) + (if (null current-prefix-arg) def + (read-string + (format "Annotate from revision (default %s): " def) + nil nil def))) + (if (null current-prefix-arg) + vc-annotate-display-mode + (float (string-to-number + (read-string "Annotate span days (default 20): " + nil nil "20"))))))) + (vc-ensure-vc-buffer) + (setq vc-annotate-display-mode display-mode) ;Not sure why. --Stef + (let* ((temp-buffer-name (format "*Annotate %s (rev %s)*" (buffer-name) rev)) + (temp-buffer-show-function 'vc-annotate-display-select) + ;; If BUF is specified, we presume the caller maintains current line, + ;; so we don't need to do it here. This implementation may give + ;; strange results occasionally in the case of REV != WORKFILE-REV. + (current-line (or move-point-to (unless buf + (save-restriction + (widen) + (line-number-at-pos)))))) + (message "Annotating...") + ;; If BUF is specified it tells in which buffer we should put the + ;; annotations. This is used when switching annotations to another + ;; revision, so we should update the buffer's name. + (when buf (with-current-buffer buf + (rename-buffer temp-buffer-name t) + ;; In case it had to be uniquified. + (setq temp-buffer-name (buffer-name)))) + (with-output-to-temp-buffer temp-buffer-name + (let ((backend (vc-backend file)) + (coding-system-for-read buffer-file-coding-system)) + (vc-call-backend backend 'annotate-command file + (get-buffer temp-buffer-name) rev) + ;; we must setup the mode first, and then set our local + ;; variables before the show-function is called at the exit of + ;; with-output-to-temp-buffer + (with-current-buffer temp-buffer-name + (unless (equal major-mode 'vc-annotate-mode) + (vc-annotate-mode)) + (set (make-local-variable 'vc-annotate-backend) backend) + (set (make-local-variable 'vc-annotate-parent-file) file) + (set (make-local-variable 'vc-annotate-parent-rev) rev) + (set (make-local-variable 'vc-annotate-parent-display-mode) + display-mode)))) + + (with-current-buffer temp-buffer-name + (vc-exec-after + `(progn + ;; Ideally, we'd rather not move point if the user has already + ;; moved it elsewhere, but really point here is not the position + ;; of the user's cursor :-( + (when ,current-line ;(and (bobp)) + (goto-line ,current-line) + (setq vc-sentinel-movepoint (point))) + (unless (active-minibuffer-window) + (message "Annotating... done"))))))) + +(defun vc-annotate-prev-revision (prefix) + "Visit the annotation of the revision previous to this one. + +With a numeric prefix argument, annotate the revision that many +revisions previous." + (interactive "p") + (vc-annotate-warp-revision (- 0 prefix))) + +(defun vc-annotate-next-revision (prefix) + "Visit the annotation of the revision after this one. + +With a numeric prefix argument, annotate the revision that many +revisions after." + (interactive "p") + (vc-annotate-warp-revision prefix)) + +(defun vc-annotate-working-revision () + "Visit the annotation of the working revision of this file." + (interactive) + (if (not (equal major-mode 'vc-annotate-mode)) + (message "Cannot be invoked outside of a vc annotate buffer") + (let ((warp-rev (vc-working-revision vc-annotate-parent-file))) + (if (equal warp-rev vc-annotate-parent-rev) + (message "Already at revision %s" warp-rev) + (vc-annotate-warp-revision warp-rev))))) + +(defun vc-annotate-extract-revision-at-line () + "Extract the revision number of the current line. +Return a cons (REV . FILENAME)." + ;; This function must be invoked from a buffer in vc-annotate-mode + (let ((rev (vc-call-backend vc-annotate-backend + 'annotate-extract-revision-at-line))) + (if (or (null rev) (consp rev)) + rev + (cons rev vc-annotate-parent-file)))) + +(defun vc-annotate-revision-at-line () + "Visit the annotation of the revision identified in the current line." + (interactive) + (if (not (equal major-mode 'vc-annotate-mode)) + (message "Cannot be invoked outside of a vc annotate buffer") + (let ((rev-at-line (vc-annotate-extract-revision-at-line))) + (if (not rev-at-line) + (message "Cannot extract revision number from the current line") + (if (and (equal (car rev-at-line) vc-annotate-parent-rev) + (string= (cdr rev-at-line) vc-annotate-parent-file)) + (message "Already at revision %s" rev-at-line) + (vc-annotate-warp-revision (car rev-at-line) (cdr rev-at-line))))))) + +(defun vc-annotate-find-revision-at-line () + "Visit the revision identified in the current line." + (interactive) + (if (not (equal major-mode 'vc-annotate-mode)) + (message "Cannot be invoked outside of a vc annotate buffer") + (let ((rev-at-line (vc-annotate-extract-revision-at-line))) + (if (not rev-at-line) + (message "Cannot extract revision number from the current line") + (switch-to-buffer-other-window + (vc-find-revision (cdr rev-at-line) (car rev-at-line))))))) + +(defun vc-annotate-revision-previous-to-line () + "Visit the annotation of the revision before the revision at line." + (interactive) + (if (not (equal major-mode 'vc-annotate-mode)) + (message "Cannot be invoked outside of a vc annotate buffer") + (let* ((rev-at-line (vc-annotate-extract-revision-at-line)) + (prev-rev nil) + (rev (car rev-at-line)) + (fname (cdr rev-at-line))) + (if (not rev-at-line) + (message "Cannot extract revision number from the current line") + (setq prev-rev + (vc-call-backend vc-annotate-backend 'previous-revision + fname rev)) + (vc-annotate-warp-revision prev-rev fname))))) + +(defvar log-view-vc-backend) +(defvar log-view-vc-fileset) + +(defun vc-annotate-show-log-revision-at-line () + "Visit the log of the revision at line. +If the VC backend supports it, only show the log entry for the revision. +If a *vc-change-log* buffer exists and already shows a log for +the file in question, search for the log entry required and move point ." + (interactive) + (if (not (equal major-mode 'vc-annotate-mode)) + (message "Cannot be invoked outside of a vc annotate buffer") + (let ((rev-at-line (vc-annotate-extract-revision-at-line))) + (if (not rev-at-line) + (message "Cannot extract revision number from the current line") + (let ((backend vc-annotate-backend) + (log-buf (get-buffer "*vc-change-log*")) + pos) + (if (and + log-buf + ;; Look for a log buffer that already displays the correct file. + (with-current-buffer log-buf + (and (eq backend log-view-vc-backend) + (null (cdr log-view-vc-fileset)) + (string= (car log-view-vc-fileset) (cdr rev-at-line)) + ;; Check if the entry we require can be found. + (vc-call-backend + backend 'show-log-entry (car rev-at-line)) + (setq pos (point))))) + (progn + (pop-to-buffer log-buf) + (goto-char pos)) + ;; Ask the backend to display a single log entry. + (vc-print-log-internal + vc-annotate-backend (list (cdr rev-at-line)) + (car rev-at-line) t 1))))))) + +(defun vc-annotate-show-diff-revision-at-line-internal (filediff) + (if (not (equal major-mode 'vc-annotate-mode)) + (message "Cannot be invoked outside of a vc annotate buffer") + (let* ((rev-at-line (vc-annotate-extract-revision-at-line)) + (prev-rev nil) + (rev (car rev-at-line)) + (fname (cdr rev-at-line))) + (if (not rev-at-line) + (message "Cannot extract revision number from the current line") + (setq prev-rev + (vc-call-backend vc-annotate-backend 'previous-revision + fname rev)) + (if (not prev-rev) + (message "Cannot diff from any revision prior to %s" rev) + (save-window-excursion + (vc-diff-internal + nil + ;; The value passed here should follow what + ;; `vc-deduce-fileset' returns. + (list vc-annotate-backend + (if filediff + (list fname) + nil)) + prev-rev rev)) + (switch-to-buffer "*vc-diff*")))))) + +(defun vc-annotate-show-diff-revision-at-line () + "Visit the diff of the revision at line from its previous revision." + (interactive) + (vc-annotate-show-diff-revision-at-line-internal t)) + +(defun vc-annotate-show-changeset-diff-revision-at-line () + "Visit the diff of the revision at line from its previous revision for all files in the changeset." + (interactive) + (when (eq 'file (vc-call-backend vc-annotate-backend 'revision-granularity)) + (error "The %s backend does not support changeset diffs" vc-annotate-backend)) + (vc-annotate-show-diff-revision-at-line-internal nil)) + +(defun vc-annotate-warp-revision (revspec &optional file) + "Annotate the revision described by REVSPEC. + +If REVSPEC is a positive integer, warp that many revisions forward, +if possible, otherwise echo a warning message. If REVSPEC is a +negative integer, warp that many revisions backward, if possible, +otherwise echo a warning message. If REVSPEC is a string, then it +describes a revision number, so warp to that revision." + (if (not (equal major-mode 'vc-annotate-mode)) + (message "Cannot be invoked outside of a vc annotate buffer") + (let* ((buf (current-buffer)) + (oldline (line-number-at-pos)) + (revspeccopy revspec) + (newrev nil)) + (cond + ((and (integerp revspec) (> revspec 0)) + (setq newrev vc-annotate-parent-rev) + (while (and (> revspec 0) newrev) + (setq newrev (vc-call-backend vc-annotate-backend 'next-revision + (or file vc-annotate-parent-file) newrev)) + (setq revspec (1- revspec))) + (unless newrev + (message "Cannot increment %d revisions from revision %s" + revspeccopy vc-annotate-parent-rev))) + ((and (integerp revspec) (< revspec 0)) + (setq newrev vc-annotate-parent-rev) + (while (and (< revspec 0) newrev) + (setq newrev (vc-call-backend vc-annotate-backend 'previous-revision + (or file vc-annotate-parent-file) newrev)) + (setq revspec (1+ revspec))) + (unless newrev + (message "Cannot decrement %d revisions from revision %s" + (- 0 revspeccopy) vc-annotate-parent-rev))) + ((stringp revspec) (setq newrev revspec)) + (t (error "Invalid argument to vc-annotate-warp-revision"))) + (when newrev + (vc-annotate (or file vc-annotate-parent-file) newrev + vc-annotate-parent-display-mode + buf + ;; Pass the current line so that vc-annotate will + ;; place the point in the line. + (min oldline (progn (goto-char (point-max)) + (forward-line -1) + (line-number-at-pos)))))))) + +(defun vc-annotate-compcar (threshold a-list) + "Test successive cons cells of A-LIST against THRESHOLD. +Return the first cons cell with a car that is not less than THRESHOLD, +nil if no such cell exists." + (let ((i 1) + (tmp-cons (car a-list))) + (while (and tmp-cons (< (car tmp-cons) threshold)) + (setq tmp-cons (car (nthcdr i a-list))) + (setq i (+ i 1))) + tmp-cons)) ; Return the appropriate value + +(defun vc-annotate-convert-time (time) + "Convert a time value to a floating-point number of days. +The argument TIME is a list as returned by `current-time' or +`encode-time', only the first two elements of that list are considered." + (/ (+ (* (float (car time)) (lsh 1 16)) (cadr time)) 24 3600)) + +(defun vc-annotate-difference (&optional offset) + "Return the time span in days to the next annotation. +This calls the backend function annotate-time, and returns the +difference in days between the time returned and the current time, +or OFFSET if present." + (let ((next-time (vc-annotate-get-time-set-line-props))) + (when next-time + (- (or offset + (vc-call-backend vc-annotate-backend 'annotate-current-time)) + next-time)))) + +(defun vc-default-annotate-current-time (backend) + "Return the current time, encoded as fractional days." + (vc-annotate-convert-time (current-time))) + +(defvar vc-annotate-offset nil) + +(defun vc-annotate-display (ratio &optional offset) + "Highlight `vc-annotate' output in the current buffer. +RATIO is the expansion that should be applied to `vc-annotate-color-map'. +The annotations are relative to the current time, unless overridden by OFFSET." + (when (/= ratio 1.0) + (set (make-local-variable 'vc-annotate-color-map) + (mapcar (lambda (elem) (cons (* (car elem) ratio) (cdr elem))) + vc-annotate-color-map))) + (set (make-local-variable 'vc-annotate-offset) offset) + (font-lock-mode 1)) + +(defun vc-annotate-lines (limit) + (while (< (point) limit) + (let ((difference (vc-annotate-difference vc-annotate-offset)) + (start (point)) + (end (progn (forward-line 1) (point)))) + (when difference + (let* ((color (or (vc-annotate-compcar difference vc-annotate-color-map) + (cons nil vc-annotate-very-old-color))) + ;; substring from index 1 to remove any leading `#' in the name + (face-name (concat "vc-annotate-face-" + (if (string-equal + (substring (cdr color) 0 1) "#") + (substring (cdr color) 1) + (cdr color)))) + ;; Make the face if not done. + (face (or (intern-soft face-name) + (let ((tmp-face (make-face (intern face-name)))) + (set-face-foreground tmp-face (cdr color)) + (when vc-annotate-background + (set-face-background tmp-face + vc-annotate-background)) + tmp-face)))) ; Return the face + (put-text-property start end 'face face))))) + ;; Pretend to font-lock there were no matches. + nil) + +(provide 'vc-annotate) + +;; arch-tag: c3454a89-80e5-4ffd-8993-671b59612898 +;;; vc-annotate.el ends here diff -r 681cd08dc0f7 -r e93288477c43 lisp/vc/vc-arch.el --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/lisp/vc/vc-arch.el Sun Jun 13 22:57:55 2010 +0000 @@ -0,0 +1,641 @@ +;;; vc-arch.el --- VC backend for the Arch version-control system + +;; Copyright (C) 2004, 2005, 2006, 2007, 2008, 2009, 2010 +;; Free Software Foundation, Inc. + +;; Author: FSF (see vc.el for full credits) +;; Maintainer: Stefan Monnier + +;; 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 3 of the License, 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. If not, see . + +;;; Commentary: + +;; The home page of the Arch version control system is at +;; +;; http://www.gnuarch.org/ +;; +;; This is derived from vc-mcvs.el as follows: +;; - cp vc-mcvs.el vc-arch.el and then M-% mcvs RET arch RET +;; +;; Then of course started the hacking. +;; +;; What has been partly tested: +;; - Open a file. +;; - C-x v = without any prefix arg. +;; - C-x v v to commit a change to a single file. + +;; Bugs: + +;; - *VC-log*'s initial content lacks the `Summary:' lines. +;; - All files under the tree are considered as "under Arch's control" +;; without regards to =tagging-method and such. +;; - Files are always considered as `edited'. +;; - C-x v l does not work. +;; - C-x v i does not work. +;; - C-x v ~ does not work. +;; - C-x v u does not work. +;; - C-x v s does not work. +;; - C-x v r does not work. +;; - VC directory listings do not work. +;; - And more... + +;;; Code: + +(eval-when-compile (require 'vc) (require 'cl)) + +;;; Properties of the backend + +(defun vc-arch-revision-granularity () 'repository) +(defun vc-arch-checkout-model (files) 'implicit) + +;;; +;;; Customization options +;;; + +;; It seems Arch diff does not accept many options, so this is not +;; very useful. It exists mainly so that the VC backends are all +;; consistent with regards to their treatment of diff switches. +(defcustom vc-arch-diff-switches t + "String or list of strings specifying switches for Arch diff under VC. +If nil, use the value of `vc-diff-switches'. If t, use no switches." + :type '(choice (const :tag "Unspecified" nil) + (const :tag "None" t) + (string :tag "Argument String") + (repeat :tag "Argument List" :value ("") string)) + :version "23.1" + :group 'vc) + +(define-obsolete-variable-alias 'vc-arch-command 'vc-arch-program "23.1") + +(defcustom vc-arch-program + (let ((candidates '("tla" "baz"))) + (while (and candidates (not (executable-find (car candidates)))) + (setq candidates (cdr candidates))) + (or (car candidates) "tla")) + "Name of the Arch executable." + :type 'string + :group 'vc) + +;; Clear up the cache to force vc-call to check again and discover +;; new functions when we reload this file. +(put 'Arch 'vc-functions nil) + +;;;###autoload (defun vc-arch-registered (file) +;;;###autoload (if (vc-find-root file "{arch}/=tagging-method") +;;;###autoload (progn +;;;###autoload (load "vc-arch") +;;;###autoload (vc-arch-registered file)))) + +(defun vc-arch-add-tagline () + "Add an `arch-tag' to the end of the current file." + (interactive) + (comment-normalize-vars) + (goto-char (point-max)) + (forward-comment -1) + (skip-chars-forward " \t\n") + (cond + ((not (bolp)) (insert "\n\n")) + ((not (eq ?\n (char-before (1- (point))))) (insert "\n"))) + (let ((beg (point)) + (idfile (and buffer-file-name + (expand-file-name + (concat ".arch-ids/" + (file-name-nondirectory buffer-file-name) + ".id") + (file-name-directory buffer-file-name))))) + (insert "arch-tag: ") + (if (and idfile (file-exists-p idfile)) + ;; If the file is unreadable, we do want to get an error here. + (progn + (insert-file-contents idfile) + (forward-line 1) + (delete-file idfile)) + (condition-case nil + (call-process "uuidgen" nil t) + (file-error (insert (format "%s <%s> %s" + (current-time-string) + user-mail-address + (+ (nth 2 (current-time)) + (buffer-size))))))) + (comment-region beg (point)))) + +(defconst vc-arch-tagline-re "^\\W*arch-tag:[ \t]*\\(.*[^ \t\n]\\)") + +(defmacro vc-with-current-file-buffer (file &rest body) + (declare (indent 2) (debug t)) + `(let ((-kill-buf- nil) + (-file- ,file)) + (with-current-buffer (or (find-buffer-visiting -file-) + (setq -kill-buf- (generate-new-buffer " temp"))) + ;; Avoid find-file-literally since it can do many undesirable extra + ;; things (among which, call us back into an infinite loop). + (if -kill-buf- (insert-file-contents -file-)) + (unwind-protect + (progn ,@body) + (if (buffer-live-p -kill-buf-) (kill-buffer -kill-buf-)))))) + +(defun vc-arch-file-source-p (file) + "Can return nil, `maybe' or a non-nil value. +Only the value `maybe' can be trusted :-(." + ;; FIXME: Check the tag and name of parent dirs. + (unless (string-match "\\`[,+]" (file-name-nondirectory file)) + (or (string-match "\\`{arch}/" + (file-relative-name file (vc-arch-root file))) + (file-exists-p + ;; Check the presence of an ID file. + (expand-file-name + (concat ".arch-ids/" (file-name-nondirectory file) ".id") + (file-name-directory file))) + ;; Check the presence of a tagline. + (vc-with-current-file-buffer file + (save-excursion + (goto-char (point-max)) + (or (re-search-backward vc-arch-tagline-re (- (point) 1000) t) + (progn + (goto-char (point-min)) + (re-search-forward vc-arch-tagline-re (+ (point) 1000) t))))) + ;; FIXME: check =tagging-method to see whether untagged files might + ;; be source or not. + (with-current-buffer + (find-file-noselect (expand-file-name "{arch}/=tagging-method" + (vc-arch-root file))) + (let ((untagged-source t)) ;Default is `names'. + (save-excursion + (goto-char (point-min)) + (if (re-search-forward "^[ \t]*\\(\\(tagline\\|implicit\\|names\\)\\|explicit\\)" nil t) + (setq untagged-source (match-end 2))) + (if (re-search-forward "^[ \t]*untagged-source[ \t]+\\(\\(source\\)\\|precious\\|backup\\|junk\\|unrecognized\\)" nil t) + (setq untagged-source (match-end 2)))) + (if untagged-source 'maybe)))))) + +(defun vc-arch-file-id (file) + ;; Don't include the kind of ID this is because it seems to be too messy. + (let ((idfile (expand-file-name + (concat ".arch-ids/" (file-name-nondirectory file) ".id") + (file-name-directory file)))) + (if (file-exists-p idfile) + (with-temp-buffer + (insert-file-contents idfile) + (looking-at ".*[^ \n\t]") + (match-string 0)) + (with-current-buffer (find-file-noselect file) + (save-excursion + (goto-char (point-max)) + (if (or (re-search-backward vc-arch-tagline-re (- (point) 1000) t) + (progn + (goto-char (point-min)) + (re-search-forward vc-arch-tagline-re (+ (point) 1000) t))) + (match-string 1) + (concat "./" (file-relative-name file (vc-arch-root file))))))))) + +(defun vc-arch-tagging-method (file) + (with-current-buffer + (find-file-noselect + (expand-file-name "{arch}/=tagging-method" (vc-arch-root file))) + (save-excursion + (goto-char (point-min)) + (if (re-search-forward + "^[ \t]*\\(tagline\\|implicit\\|names\\|explicit\\)" nil t) + (intern (match-string 1)) + 'names)))) + +(defun vc-arch-root (file) + "Return the root directory of an Arch project, if any." + (or (vc-file-getprop file 'arch-root) + ;; Check the =tagging-method, in case someone naively manually + ;; creates a {arch} directory somewhere. + (let ((root (vc-find-root file "{arch}/=tagging-method"))) + (when root + (vc-file-setprop + file 'arch-root root))))) + +(defun vc-arch-register (files &optional rev comment) + (if rev (error "Explicit initial revision not supported for Arch")) + (dolist (file files) + (let ((tagmet (vc-arch-tagging-method file))) + (if (and (memq tagmet '(tagline implicit)) comment-start) + (with-current-buffer (find-file-noselect file) + (if (buffer-modified-p) + (error "Save %s first" (buffer-name))) + (vc-arch-add-tagline) + (save-buffer))))) + (vc-arch-command nil 0 files "add")) + +(defun vc-arch-registered (file) + ;; Don't seriously check whether it's source or not. Checking would + ;; require running TLA, so it's better to not do it, so it also works if + ;; TLA is not installed. + (and (vc-arch-root file) + (vc-arch-file-source-p file))) + +(defun vc-arch-default-version (file) + (or (vc-file-getprop (vc-arch-root file) 'arch-default-version) + (let* ((root (vc-arch-root file)) + (f (expand-file-name "{arch}/++default-version" root))) + (if (file-readable-p f) + (vc-file-setprop + root 'arch-default-version + (with-temp-buffer + (insert-file-contents f) + ;; Strip the terminating newline. + (buffer-substring (point-min) (1- (point-max))))))))) + +(defun vc-arch-workfile-unchanged-p (file) + "Stub: arch workfiles are always considered to be in a changed state," + nil) + +(defun vc-arch-state (file) + ;; There's no checkout operation and merging is not done from VC + ;; so the only operation that's state dependent that VC supports is commit + ;; which is only activated if the file is `edited'. + (let* ((root (vc-arch-root file)) + (ver (vc-arch-default-version file)) + (pat (concat "\\`" (subst-char-in-string ?/ ?% ver))) + (dir (expand-file-name ",,inode-sigs/" + (expand-file-name "{arch}" root))) + (sigfile nil)) + (dolist (f (if (file-directory-p dir) (directory-files dir t pat))) + (if (or (not sigfile) (file-newer-than-file-p f sigfile)) + (setq sigfile f))) + (if (not sigfile) + 'edited ;We know nothing. + (let ((id (vc-arch-file-id file))) + (setq id (replace-regexp-in-string "[ \t]" "_" id)) + (with-current-buffer (find-file-noselect sigfile) + (goto-char (point-min)) + (while (and (search-forward id nil 'move) + (save-excursion + (goto-char (- (match-beginning 0) 2)) + ;; For `names', the lines start with `?./foo/bar'. + ;; For others there's 2 chars before the ./foo/bar. + (or (not (or (bolp) (looking-at "\n?"))) + ;; Ignore E_ entries used for foo.id files. + (looking-at "E_"))))) + (if (eobp) + ;; ID not found. + (if (equal (file-name-nondirectory sigfile) + (subst-char-in-string + ?/ ?% (vc-arch-working-revision file))) + 'added + ;; Might be `added' or `up-to-date' as well. + ;; FIXME: Check in the patch logs to find out. + 'edited) + ;; Found the ID, let's check the inode. + (if (not (re-search-forward + "\t.*mtime=\\([0-9]+\\):size=\\([0-9]+\\)" + (line-end-position) t)) + ;; Buh? Unexpected format. + 'edited + (let ((ats (file-attributes file))) + (if (and (eq (nth 7 ats) (string-to-number (match-string 2))) + (equal (format-time-string "%s" (nth 5 ats)) + (match-string 1))) + 'up-to-date + 'edited))))))))) + +(defun vc-arch-dir-status (dir callback) + "Run 'tla inventory' for DIR and pass results to CALLBACK. +CALLBACK expects (ENTRIES &optional MORE-TO-COME); see +`vc-dir-refresh'." + (let ((default-directory dir)) + (vc-arch-command t 'async nil "changes")) + ;; The updating could be done asynchronously. + (vc-exec-after + `(vc-arch-after-dir-status ',callback))) + +(defun vc-arch-after-dir-status (callback) + (let* ((state-map '(("M " . edited) + ("Mb" . edited) ;binary + ("D " . removed) + ("D/" . removed) ;directory + ("A " . added) + ("A/" . added) ;directory + ("=>" . renamed) + ("/>" . renamed) ;directory + ("lf" . symlink-to-file) + ("fl" . file-to-symlink) + ("--" . permissions-changed) + ("-/" . permissions-changed) ;directory + )) + (state-map-regexp (regexp-opt (mapcar 'car state-map) t)) + (entry-regexp (concat "^" state-map-regexp " \\(.*\\)$")) + result) + (goto-char (point-min)) + ;;(message "Got %s" (buffer-string)) + (while (re-search-forward entry-regexp nil t) + (let* ((state-string (match-string 1)) + (state (cdr (assoc state-string state-map))) + (filename (match-string 2))) + (push (list filename state) result))) + + (funcall callback result nil))) + +(defun vc-arch-working-revision (file) + (let* ((root (expand-file-name "{arch}" (vc-arch-root file))) + (defbranch (vc-arch-default-version file))) + (when (and defbranch (string-match "\\`\\(.+@[^/\n]+\\)/\\(\\(\\(.*?\\)\\(?:--.*\\)?\\)--.*\\)\\'" defbranch)) + (let* ((archive (match-string 1 defbranch)) + (category (match-string 4 defbranch)) + (branch (match-string 3 defbranch)) + (version (match-string 2 defbranch)) + (sealed nil) (rev-nb 0) + (rev nil) + logdir tmp) + (setq logdir (expand-file-name category root)) + (setq logdir (expand-file-name branch logdir)) + (setq logdir (expand-file-name version logdir)) + (setq logdir (expand-file-name archive logdir)) + (setq logdir (expand-file-name "patch-log" logdir)) + (dolist (file (if (file-directory-p logdir) (directory-files logdir))) + ;; Revision names go: base-0, patch-N, version-0, versionfix-M. + (when (and (eq (aref file 0) ?v) (not sealed)) + (setq sealed t rev-nb 0)) + (if (and (string-match "-\\([0-9]+\\)\\'" file) + (setq tmp (string-to-number (match-string 1 file))) + (or (not sealed) (eq (aref file 0) ?v)) + (>= tmp rev-nb)) + (setq rev-nb tmp rev file))) + ;; Use "none-000" if the tree hasn't yet been committed on the + ;; default branch. We'll then get "Arch:000[branch]" on the mode-line. + (concat defbranch "--" (or rev "none-000")))))) + + +(defcustom vc-arch-mode-line-rewrite + '(("\\`.*--\\(.*--.*\\)--\\(v?\\).*-\\([0-9]+\\)\\'" . "\\2\\3[\\1]")) + "Rewrite rules to shorten Arch's revision names on the mode-line." + :type '(repeat (cons regexp string)) + :group 'vc) + +(defun vc-arch-mode-line-string (file) + "Return string for placement in modeline by `vc-mode-line' for FILE." + (let ((rev (vc-working-revision file))) + (dolist (rule vc-arch-mode-line-rewrite) + (if (string-match (car rule) rev) + (setq rev (replace-match (cdr rule) t nil rev)))) + (format "Arch%c%s" + (case (vc-state file) + ((up-to-date needs-update) ?-) + (added ?@) + (t ?:)) + rev))) + +(defun vc-arch-diff3-rej-p (rej) + (let ((attrs (file-attributes rej))) + (and attrs (< (nth 7 attrs) 60) + (with-temp-buffer + (insert-file-contents rej) + (goto-char (point-min)) + (looking-at "Conflicts occured, diff3 conflict markers left in file\\."))))) + +(defun vc-arch-delete-rej-if-obsolete () + "For use in `after-save-hook'." + (save-excursion + (let ((rej (concat buffer-file-name ".rej"))) + (when (and buffer-file-name (vc-arch-diff3-rej-p rej)) + (unless (re-search-forward "^<<<<<<< " nil t) + ;; The .rej file is obsolete. + (condition-case nil (delete-file rej) (error nil)) + ;; Remove the hook so that it is not called multiple times. + (remove-hook 'after-save-hook 'vc-arch-delete-rej-if-obsolete t)))))) + +(defun vc-arch-find-file-hook () + (let ((rej (concat buffer-file-name ".rej"))) + (when (and buffer-file-name (file-exists-p rej)) + (if (vc-arch-diff3-rej-p rej) + (save-excursion + (goto-char (point-min)) + (if (not (re-search-forward "^<<<<<<< " nil t)) + ;; The .rej file is obsolete. + (condition-case nil (delete-file rej) (error nil)) + (smerge-mode 1) + (add-hook 'after-save-hook + 'vc-arch-delete-rej-if-obsolete nil t) + (message "There are unresolved conflicts in this file"))) + (message "There are unresolved conflicts in %s" + (file-name-nondirectory rej)))))) + +(defun vc-arch-checkin (files rev comment &optional extra-args-ignored) + (if rev (error "Committing to a specific revision is unsupported")) + ;; FIXME: This implementation probably only works for singleton filesets + (let ((summary (file-relative-name (car files) (vc-arch-root (car files))))) + ;; Extract a summary from the comment. + (when (or (string-match "\\`Summary:[ \t]*\\(.*[^ \t\n]\\)\\([ \t]*\n\\)*" comment) + (string-match "\\`[ \t]*\\(.*[^ \t\n]\\)[ \t]*\\(\n?\\'\\|\n\\([ \t]*\n\\)+\\)" comment)) + (setq summary (match-string 1 comment)) + (setq comment (substring comment (match-end 0)))) + (vc-arch-command nil 0 files "commit" "-s" summary "-L" comment "--" + (vc-switches 'Arch 'checkin)))) + +(defun vc-arch-diff (files &optional oldvers newvers buffer) + "Get a difference report using Arch between two versions of FILES." + ;; FIXME: This implementation only works for singleton filesets. To make + ;; it work for more cases, we have to either call `file-diffs' manually on + ;; each and every `file' in the fileset, or use `changes --diffs' (and + ;; variants) and maybe filter the output with `filterdiff' to only include + ;; the files in which we're interested. + (let ((file (car files))) + (if (and newvers + (vc-up-to-date-p file) + (equal newvers (vc-working-revision file))) + ;; Newvers is the base revision and the current file is unchanged, + ;; so we can diff with the current file. + (setq newvers nil)) + (if newvers + (error "Diffing specific revisions not implemented") + (let* (process-file-side-effects + (async (not vc-disable-async-diff)) + ;; Run the command from the root dir. + (default-directory (vc-arch-root file)) + (status + (vc-arch-command + (or buffer "*vc-diff*") + (if async 'async 1) + nil "file-diffs" + (vc-switches 'Arch 'diff) + (file-relative-name file) + (if (equal oldvers (vc-working-revision file)) + nil + oldvers)))) + (if async 1 status))))) ; async diff, pessimistic assumption. + +(defun vc-arch-delete-file (file) + (vc-arch-command nil 0 file "rm")) + +(defun vc-arch-rename-file (old new) + (vc-arch-command nil 0 new "mv" (file-relative-name old))) + +(defalias 'vc-arch-responsible-p 'vc-arch-root) + +(defun vc-arch-command (buffer okstatus file &rest flags) + "A wrapper around `vc-do-command' for use in vc-arch.el." + (apply 'vc-do-command (or buffer "*vc*") okstatus vc-arch-program file flags)) + +(defun vc-arch-init-revision () nil) + +;;; Completion of versions and revisions. + +(defun vc-arch--version-completion-table (root string) + (delq nil + (mapcar + (lambda (d) + (when (string-match "/\\([^/]+\\)/\\([^/]+\\)\\'" d) + (concat (match-string 2 d) "/" (match-string 1 d)))) + (let ((default-directory root)) + (file-expand-wildcards + (concat "*/*/" + (if (string-match "/" string) + (concat (substring string (match-end 0)) + "*/" (substring string 0 (match-beginning 0))) + (concat "*/" string)) + "*")))))) + +(defun vc-arch-revision-completion-table (files) + (lexical-let ((files files)) + (lambda (string pred action) + ;; FIXME: complete revision patches as well. + (let* ((root (expand-file-name "{arch}" (vc-arch-root (car files)))) + (table (vc-arch--version-completion-table root string))) + (complete-with-action action table string pred))))) + +;;; Trimming revision libraries. + +;; This code is not directly related to VC and there are many variants of +;; this functionality available as scripts, but I like this version better, +;; so maybe others will like it too. + +(defun vc-arch-trim-find-least-useful-rev (revs) + (let* ((first (pop revs)) + (second (pop revs)) + (third (pop revs)) + ;; We try to give more importance to recent revisions. The idea is + ;; that it's OK if checking out a revision 1000-patch-old is ten + ;; times slower than checking out a revision 100-patch-old. But at + ;; the same time a 2-patch-old rev isn't really ten times more + ;; important than a 20-patch-old, so we use an arbitrary constant + ;; "100" to reduce this effect for recent revisions. Making this + ;; constant a float has the side effect of causing the subsequent + ;; computations to be done as floats as well. + (max (+ 100.0 (car (or (car (last revs)) third)))) + (cost (lambda () (/ (- (car third) (car first)) (- max (car second))))) + (minrev second) + (mincost (funcall cost))) + (while revs + (setq first second) + (setq second third) + (setq third (pop revs)) + (when (< (funcall cost) mincost) + (setq minrev second) + (setq mincost (funcall cost)))) + minrev)) + +(defun vc-arch-trim-make-sentinel (revs) + (if (null revs) (lambda (proc msg) (message "VC-Arch trimming ... done")) + (lexical-let ((revs revs)) + (lambda (proc msg) + (message "VC-Arch trimming %s..." (file-name-nondirectory (car revs))) + (rename-file (car revs) (concat (car revs) "*rm*")) + (setq proc (start-process "vc-arch-trim" nil + "rm" "-rf" (concat (car revs) "*rm*"))) + (set-process-sentinel proc (vc-arch-trim-make-sentinel (cdr revs))))))) + +(defun vc-arch-trim-one-revlib (dir) + "Delete half of the revisions in the revision library." + (interactive "Ddirectory: ") + (let ((garbage (directory-files dir 'full "\\`,," 'nosort))) + (when garbage + (funcall (vc-arch-trim-make-sentinel garbage) nil nil))) + (let ((revs + (sort (delq nil + (mapcar + (lambda (f) + (when (string-match "-\\([0-9]+\\)\\'" f) + (cons (string-to-number (match-string 1 f)) f))) + (directory-files dir nil nil 'nosort))) + 'car-less-than-car)) + (subdirs nil)) + (when (cddr revs) + (dotimes (i (/ (length revs) 2)) + (let ((minrev (vc-arch-trim-find-least-useful-rev revs))) + (setq revs (delq minrev revs)) + (push minrev subdirs))) + (funcall (vc-arch-trim-make-sentinel + (mapcar (lambda (x) (expand-file-name (cdr x) dir)) subdirs)) + nil nil)))) + +(defun vc-arch-trim-revlib () + "Delete half of the revisions in the revision library." + (interactive) + (let ((rl-dir (with-output-to-string + (call-process vc-arch-program nil standard-output nil + "my-revision-library")))) + (while (string-match "\\(.*\\)\n" rl-dir) + (let ((dir (match-string 1 rl-dir))) + (setq rl-dir + (if (and (file-directory-p dir) (file-writable-p dir)) + dir + (substring rl-dir (match-end 0)))))) + (unless (file-writable-p rl-dir) + (error "No writable revlib directory found")) + (message "Revlib at %s" rl-dir) + (let* ((archives (directory-files rl-dir 'full "[^.]\\|...")) + (categories + (apply 'append + (mapcar (lambda (dir) + (when (file-directory-p dir) + (directory-files dir 'full "[^.]\\|..."))) + archives))) + (branches + (apply 'append + (mapcar (lambda (dir) + (when (file-directory-p dir) + (directory-files dir 'full "[^.]\\|..."))) + categories))) + (versions + (apply 'append + (mapcar (lambda (dir) + (when (file-directory-p dir) + (directory-files dir 'full "--.*--"))) + branches)))) + (mapc 'vc-arch-trim-one-revlib versions)) + )) + +(defvar vc-arch-extra-menu-map + (let ((map (make-sparse-keymap))) + (define-key map [add-tagline] + '(menu-item "Add tagline" vc-arch-add-tagline)) + map)) + +(defun vc-arch-extra-menu () vc-arch-extra-menu-map) + + +;;; Less obvious implementations. + +(defun vc-arch-find-revision (file rev buffer) + (let ((out (make-temp-file "vc-out"))) + (unwind-protect + (progn + (with-temp-buffer + (vc-arch-command (current-buffer) 1 nil "file-diffs" file rev) + (call-process-region (point-min) (point-max) + "patch" nil nil nil "-R" "-o" out file)) + (with-current-buffer buffer + (insert-file-contents out))) + (delete-file out)))) + +(provide 'vc-arch) + +;; arch-tag: a35c7c1c-5237-429d-88ef-3d718fd2e704 +;;; vc-arch.el ends here diff -r 681cd08dc0f7 -r e93288477c43 lisp/vc/vc-bzr.el --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/lisp/vc/vc-bzr.el Sun Jun 13 22:57:55 2010 +0000 @@ -0,0 +1,1057 @@ +;;; vc-bzr.el --- VC backend for the bzr revision control system + +;; Copyright (C) 2006, 2007, 2008, 2009, 2010 Free Software Foundation, Inc. + +;; Author: Dave Love +;; Riccardo Murri +;; Keywords: vc tools +;; Created: Sept 2006 +;; Version: 2008-01-04 (Bzr revno 25) +;; URL: http://launchpad.net/vc-bzr + +;; 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 3 of the License, 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. If not, see . + +;;; Commentary: + +;; See concerning bzr. See +;; for alternate development +;; branches of `vc-bzr'. + +;; Load this library to register bzr support in VC. + +;; Known bugs +;; ========== + +;; When editing a symlink and *both* the symlink and its target +;; are bzr-versioned, `vc-bzr` presently runs `bzr status` on the +;; symlink, thereby not detecting whether the actual contents +;; (that is, the target contents) are changed. +;; See https://bugs.launchpad.net/vc-bzr/+bug/116607 + +;; For an up-to-date list of bugs, please see: +;; https://bugs.launchpad.net/vc-bzr/+bugs + +;;; Properties of the backend + +(defun vc-bzr-revision-granularity () 'repository) +(defun vc-bzr-checkout-model (files) 'implicit) + +;;; Code: + +(eval-when-compile + (require 'cl) + (require 'vc) ;; for vc-exec-after + (require 'vc-dir)) + +;; Clear up the cache to force vc-call to check again and discover +;; new functions when we reload this file. +(put 'Bzr 'vc-functions nil) + +(defgroup vc-bzr nil + "VC bzr backend." + :version "22.2" + :group 'vc) + +(defcustom vc-bzr-program "bzr" + "Name of the bzr command (excluding any arguments)." + :group 'vc-bzr + :type 'string) + +(defcustom vc-bzr-diff-switches nil + "String or list of strings specifying switches for bzr diff under VC. +If nil, use the value of `vc-diff-switches'. If t, use no switches." + :type '(choice (const :tag "Unspecified" nil) + (const :tag "None" t) + (string :tag "Argument String") + (repeat :tag "Argument List" :value ("") string)) + :group 'vc-bzr) + +(defcustom vc-bzr-log-switches nil + "String or list of strings specifying switches for bzr log under VC." + :type '(choice (const :tag "None" nil) + (string :tag "Argument String") + (repeat :tag "Argument List" :value ("") string)) + :group 'vc-bzr) + +;; since v0.9, bzr supports removing the progress indicators +;; by setting environment variable BZR_PROGRESS_BAR to "none". +(defun vc-bzr-command (bzr-command buffer okstatus file-or-list &rest args) + "Wrapper round `vc-do-command' using `vc-bzr-program' as COMMAND. +Invoke the bzr command adding `BZR_PROGRESS_BAR=none' and +`LC_MESSAGES=C' to the environment." + (let ((process-environment + (list* "BZR_PROGRESS_BAR=none" ; Suppress progress output (bzr >=0.9) + "LC_MESSAGES=C" ; Force English output + process-environment))) + (apply 'vc-do-command (or buffer "*vc*") okstatus vc-bzr-program + file-or-list bzr-command args))) + + +;;;###autoload +(defconst vc-bzr-admin-dirname ".bzr" + "Name of the directory containing Bzr repository status files.") +;;;###autoload +(defconst vc-bzr-admin-checkout-format-file + (concat vc-bzr-admin-dirname "/checkout/format")) +(defconst vc-bzr-admin-dirstate + (concat vc-bzr-admin-dirname "/checkout/dirstate")) +(defconst vc-bzr-admin-branch-format-file + (concat vc-bzr-admin-dirname "/branch/format")) +(defconst vc-bzr-admin-revhistory + (concat vc-bzr-admin-dirname "/branch/revision-history")) +(defconst vc-bzr-admin-lastrev + (concat vc-bzr-admin-dirname "/branch/last-revision")) + +;;;###autoload (defun vc-bzr-registered (file) +;;;###autoload (if (vc-find-root file vc-bzr-admin-checkout-format-file) +;;;###autoload (progn +;;;###autoload (load "vc-bzr") +;;;###autoload (vc-bzr-registered file)))) + +(defun vc-bzr-root (file) + "Return the root directory of the bzr repository containing FILE." + ;; Cache technique copied from vc-arch.el. + (or (vc-file-getprop file 'bzr-root) + (let ((root (vc-find-root file vc-bzr-admin-checkout-format-file))) + (when root (vc-file-setprop file 'bzr-root root))))) + +(require 'sha1) ;For sha1-program + +(defun vc-bzr-sha1 (file) + (with-temp-buffer + (set-buffer-multibyte nil) + (let ((prog sha1-program) + (args nil) + process-file-side-effects) + (when (consp prog) + (setq args (cdr prog)) + (setq prog (car prog))) + (apply 'process-file prog (file-relative-name file) t nil args) + (buffer-substring (point-min) (+ (point-min) 40))))) + +(defun vc-bzr-state-heuristic (file) + "Like `vc-bzr-state' but hopefully without running Bzr." + ;; `bzr status' was excrutiatingly slow with large histories and + ;; pending merges, so try to avoid using it until they fix their + ;; performance problems. + ;; This function tries first to parse Bzr internal file + ;; `checkout/dirstate', but it may fail if Bzr internal file format + ;; has changed. As a safeguard, the `checkout/dirstate' file is + ;; only parsed if it contains the string `#bazaar dirstate flat + ;; format 3' in the first line. + ;; If the `checkout/dirstate' file cannot be parsed, fall back to + ;; running `vc-bzr-state'." + (lexical-let ((root (vc-bzr-root file))) + (when root ; Short cut. + ;; This looks at internal files. May break if they change + ;; their format. + (lexical-let ((dirstate (expand-file-name vc-bzr-admin-dirstate root))) + (condition-case nil + (with-temp-buffer + (insert-file-contents dirstate) + (goto-char (point-min)) + (if (not (looking-at "#bazaar dirstate flat format 3")) + (vc-bzr-state file) ; Some other unknown format? + (let* ((relfile (file-relative-name file root)) + (reldir (file-name-directory relfile))) + (if (re-search-forward + (concat "^\0" + (if reldir (regexp-quote + (directory-file-name reldir))) + "\0" + (regexp-quote (file-name-nondirectory relfile)) + "\0" + "[^\0]*\0" ;id? + "\\([^\0]*\\)\0" ;"a/f/d", a=removed? + "\\([^\0]*\\)\0" ;sha1 (empty if conflicted)? + "\\([^\0]*\\)\0" ;size?p + "[^\0]*\0" ;"y/n", executable? + "[^\0]*\0" ;? + "\\([^\0]*\\)\0" ;"a/f/d" a=added? + "\\([^\0]*\\)\0" ;sha1 again? + "\\([^\0]*\\)\0" ;size again? + "[^\0]*\0" ;"y/n", executable again? + "[^\0]*\0" ;last revid? + ;; There are more fields when merges are pending. + ) + nil t) + ;; Apparently the second sha1 is the one we want: when + ;; there's a conflict, the first sha1 is absent (and the + ;; first size seems to correspond to the file with + ;; conflict markers). + (cond + ((eq (char-after (match-beginning 1)) ?a) 'removed) + ((eq (char-after (match-beginning 4)) ?a) 'added) + ((or (and (eq (string-to-number (match-string 3)) + (nth 7 (file-attributes file))) + (equal (match-string 5) + (vc-bzr-sha1 file))) + (and + ;; It looks like for lightweight + ;; checkouts \2 is empty and we need to + ;; look for size in \6. + (eq (match-beginning 2) (match-end 2)) + (eq (string-to-number (match-string 6)) + (nth 7 (file-attributes file))) + (equal (match-string 5) + (vc-bzr-sha1 file)))) + 'up-to-date) + (t 'edited)) + 'unregistered)))) + ;; Either the dirstate file can't be read, or the sha1 + ;; executable is missing, or ... + ;; In either case, recent versions of Bzr aren't that slow + ;; any more. + (error (vc-bzr-state file))))))) + + +(defun vc-bzr-registered (file) + "Return non-nil if FILE is registered with bzr." + (let ((state (vc-bzr-state-heuristic file))) + (not (memq state '(nil unregistered ignored))))) + +(defconst vc-bzr-state-words + "added\\|ignored\\|kind changed\\|modified\\|removed\\|renamed\\|unknown" + "Regexp matching file status words as reported in `bzr' output.") + +(defun vc-bzr-file-name-relative (filename) + "Return file name FILENAME stripped of the initial Bzr repository path." + (lexical-let* + ((filename* (expand-file-name filename)) + (rootdir (vc-bzr-root filename*))) + (when rootdir + (file-relative-name filename* rootdir)))) + +(defun vc-bzr-status (file) + "Return FILE status according to Bzr. +Return value is a cons (STATUS . WARNING), where WARNING is a +string or nil, and STATUS is one of the symbols: `added', +`ignored', `kindchanged', `modified', `removed', `renamed', `unknown', +which directly correspond to `bzr status' output, or 'unchanged +for files whose copy in the working tree is identical to the one +in the branch repository, or nil for files that are not +registered with Bzr. + +If any error occurred in running `bzr status', then return nil." + (with-temp-buffer + (let ((ret (condition-case nil + (vc-bzr-command "status" t 0 file) + (file-error nil))) ; vc-bzr-program not found. + (status 'unchanged)) + ;; the only secure status indication in `bzr status' output + ;; is a couple of lines following the pattern:: + ;; | : + ;; | + ;; if the file is up-to-date, we get no status report from `bzr', + ;; so if the regexp search for the above pattern fails, we consider + ;; the file to be up-to-date. + (goto-char (point-min)) + (when (re-search-forward + ;; bzr prints paths relative to the repository root. + (concat "^\\(" vc-bzr-state-words "\\):[ \t\n]+" + (regexp-quote (vc-bzr-file-name-relative file)) + ;; Bzr appends a '/' to directory names and + ;; '*' to executable files + (if (file-directory-p file) "/?" "\\*?") + "[ \t\n]*$") + nil t) + (lexical-let ((statusword (match-string 1))) + ;; Erase the status text that matched. + (delete-region (match-beginning 0) (match-end 0)) + (setq status + (intern (replace-regexp-in-string " " "" statusword))))) + (when status + (goto-char (point-min)) + (skip-chars-forward " \n\t") ;Throw away spaces. + (cons status + ;; "bzr" will output warnings and informational messages to + ;; stderr; due to Emacs' `vc-do-command' (and, it seems, + ;; `start-process' itself) limitations, we cannot catch stderr + ;; and stdout into different buffers. So, if there's anything + ;; left in the buffer after removing the above status + ;; keywords, let us just presume that any other message from + ;; "bzr" is a user warning, and display it. + (unless (eobp) (buffer-substring (point) (point-max)))))))) + +(defun vc-bzr-state (file) + (lexical-let ((result (vc-bzr-status file))) + (when (consp result) + (when (cdr result) + (message "Warnings in `bzr' output: %s" (cdr result))) + (cdr (assq (car result) + '((added . added) + (kindchanged . edited) + (renamed . edited) + (modified . edited) + (removed . removed) + (ignored . ignored) + (unknown . unregistered) + (unchanged . up-to-date))))))) + +(defun vc-bzr-resolve-when-done () + "Call \"bzr resolve\" if the conflict markers have been removed." + (save-excursion + (goto-char (point-min)) + (unless (re-search-forward "^<<<<<<< " nil t) + (vc-bzr-command "resolve" nil 0 buffer-file-name) + ;; Remove the hook so that it is not called multiple times. + (remove-hook 'after-save-hook 'vc-bzr-resolve-when-done t)))) + +(defun vc-bzr-find-file-hook () + (when (and buffer-file-name + ;; FIXME: We should check that "bzr status" says "conflict". + (file-exists-p (concat buffer-file-name ".BASE")) + (file-exists-p (concat buffer-file-name ".OTHER")) + (file-exists-p (concat buffer-file-name ".THIS")) + ;; If "bzr status" says there's a conflict but there are no + ;; conflict markers, it's not clear what we should do. + (save-excursion + (goto-char (point-min)) + (re-search-forward "^<<<<<<< " nil t))) + ;; TODO: the merge algorithm used in `bzr merge' is nicely configurable, + ;; but the one in `bzr pull' isn't, so it would be good to provide an + ;; elisp function to remerge from the .BASE/OTHER/THIS files. + (smerge-start-session) + (add-hook 'after-save-hook 'vc-bzr-resolve-when-done nil t) + (message "There are unresolved conflicts in this file"))) + +(defun vc-bzr-workfile-unchanged-p (file) + (eq 'unchanged (car (vc-bzr-status file)))) + +(defun vc-bzr-working-revision (file) + ;; Together with the code in vc-state-heuristic, this makes it possible + ;; to get the initial VC state of a Bzr file even if Bzr is not installed. + (lexical-let* + ((rootdir (vc-bzr-root file)) + (branch-format-file (expand-file-name vc-bzr-admin-branch-format-file + rootdir)) + (revhistory-file (expand-file-name vc-bzr-admin-revhistory rootdir)) + (lastrev-file (expand-file-name vc-bzr-admin-lastrev rootdir))) + ;; This looks at internal files to avoid forking a bzr process. + ;; May break if they change their format. + (if (and (file-exists-p branch-format-file) + ;; For lightweight checkouts (obtained with bzr checkout --lightweight) + ;; the branch-format-file does not contain the revision + ;; information, we need to look up the branch-format-file + ;; in the place where the lightweight checkout comes + ;; from. We only do that if it's a local file. + (let ((location-fname (expand-file-name + (concat vc-bzr-admin-dirname + "/branch/location") rootdir))) + ;; The existence of this file is how we distinguish + ;; lightweight checkouts. + (if (file-exists-p location-fname) + (with-temp-buffer + (insert-file-contents location-fname) + ;; If the lightweight checkout points to a + ;; location in the local file system, then we can + ;; look there for the version information. + (when (re-search-forward "file://\\(.+\\)" nil t) + (let ((l-c-parent-dir (match-string 1))) + (when (and (memq system-type '(ms-dos windows-nt)) + (string-match-p "^/[[:alpha:]]:" l-c-parent-dir)) + ;;; The non-Windows code takes a shortcut by using the host/path + ;;; separator slash as the start of the absolute path. That + ;;; does not work on Windows, so we must remove it (bug#5345) + (setq l-c-parent-dir (substring l-c-parent-dir 1))) + (setq branch-format-file + (expand-file-name vc-bzr-admin-branch-format-file + l-c-parent-dir)) + (setq lastrev-file + (expand-file-name vc-bzr-admin-lastrev l-c-parent-dir)) + ;; FIXME: maybe it's overkill to check if both these files exist. + (and (file-exists-p branch-format-file) + (file-exists-p lastrev-file))))) + t))) + (with-temp-buffer + (insert-file-contents branch-format-file) + (goto-char (point-min)) + (cond + ((or + (looking-at "Bazaar-NG branch, format 0.0.4") + (looking-at "Bazaar-NG branch format 5")) + ;; count lines in .bzr/branch/revision-history + (insert-file-contents revhistory-file) + (number-to-string (count-lines (line-end-position) (point-max)))) + ((or + (looking-at "Bazaar Branch Format 6 (bzr 0.15)") + (looking-at "Bazaar Branch Format 7 (needs bzr 1.6)")) + ;; revno is the first number in .bzr/branch/last-revision + (insert-file-contents lastrev-file) + (when (re-search-forward "[0-9]+" nil t) + (buffer-substring (match-beginning 0) (match-end 0)))))) + ;; fallback to calling "bzr revno" + (lexical-let* + ((result (vc-bzr-command-discarding-stderr + vc-bzr-program "revno" (file-relative-name file))) + (exitcode (car result)) + (output (cdr result))) + (cond + ((eq exitcode 0) (substring output 0 -1)) + (t nil)))))) + +(defun vc-bzr-create-repo () + "Create a new Bzr repository." + (vc-bzr-command "init" nil 0 nil)) + +(defun vc-bzr-init-revision (&optional file) + "Always return nil, as Bzr cannot register explicit versions." + nil) + +(defun vc-bzr-previous-revision (file rev) + (if (string-match "\\`[0-9]+\\'" rev) + (number-to-string (1- (string-to-number rev))) + (concat "before:" rev))) + +(defun vc-bzr-next-revision (file rev) + (if (string-match "\\`[0-9]+\\'" rev) + (number-to-string (1+ (string-to-number rev))) + (error "Don't know how to compute the next revision of %s" rev))) + +(defun vc-bzr-register (files &optional rev comment) + "Register FILE under bzr. +Signal an error unless REV is nil. +COMMENT is ignored." + (if rev (error "Can't register explicit revision with bzr")) + (vc-bzr-command "add" nil 0 files)) + +;; Could run `bzr status' in the directory and see if it succeeds, but +;; that's relatively expensive. +(defalias 'vc-bzr-responsible-p 'vc-bzr-root + "Return non-nil if FILE is (potentially) controlled by bzr. +The criterion is that there is a `.bzr' directory in the same +or a superior directory.") + +(defun vc-bzr-could-register (file) + "Return non-nil if FILE could be registered under bzr." + (and (vc-bzr-responsible-p file) ; shortcut + (condition-case () + (with-temp-buffer + (vc-bzr-command "add" t 0 file "--dry-run") + ;; The command succeeds with no output if file is + ;; registered (in bzr 0.8). + (goto-char (point-min)) + (looking-at "added ")) + (error)))) + +(defun vc-bzr-unregister (file) + "Unregister FILE from bzr." + (vc-bzr-command "remove" nil 0 file "--keep")) + +(declare-function log-edit-extract-headers "log-edit" (headers string)) + +(defun vc-bzr-checkin (files rev comment) + "Check FILE in to bzr with log message COMMENT. +REV non-nil gets an error." + (if rev (error "Can't check in a specific revision with bzr")) + (apply 'vc-bzr-command "commit" nil 0 + files (cons "-m" (log-edit-extract-headers '(("Author" . "--author") + ("Date" . "--commit-time") + ("Fixes" . "--fixes")) + comment)))) + +(defun vc-bzr-find-revision (file rev buffer) + "Fetch revision REV of file FILE and put it into BUFFER." + (with-current-buffer buffer + (if (and rev (stringp rev) (not (string= rev ""))) + (vc-bzr-command "cat" t 0 file "-r" rev) + (vc-bzr-command "cat" t 0 file)))) + +(defun vc-bzr-checkout (file &optional editable rev) + (if rev (error "Operation not supported") + ;; Else, there's nothing to do. + nil)) + +(defun vc-bzr-revert (file &optional contents-done) + (unless contents-done + (with-temp-buffer (vc-bzr-command "revert" t 0 file)))) + +(defvar log-view-message-re) +(defvar log-view-file-re) +(defvar log-view-font-lock-keywords) +(defvar log-view-current-tag-function) +(defvar log-view-per-file-logs) + +(define-derived-mode vc-bzr-log-view-mode log-view-mode "Bzr-Log-View" + (remove-hook 'log-view-mode-hook 'vc-bzr-log-view-mode) ;Deactivate the hack. + (require 'add-log) + (set (make-local-variable 'log-view-per-file-logs) nil) + (set (make-local-variable 'log-view-file-re) "\\`a\\`") + (set (make-local-variable 'log-view-message-re) + (if (eq vc-log-view-type 'short) + "^ *\\([0-9.]+\\): \\(.*?\\)[ \t]+\\([0-9]\\{4\\}-[0-9]\\{2\\}-[0-9]\\{2\\}\\)\\( \\[merge\\]\\)?" + "^ *\\(?:revno: \\([0-9.]+\\)\\|merged: .+\\)")) + (set (make-local-variable 'log-view-font-lock-keywords) + ;; log-view-font-lock-keywords is careful to use the buffer-local + ;; value of log-view-message-re only since Emacs-23. + (if (eq vc-log-view-type 'short) + (append `((,log-view-message-re + (1 'log-view-message-face) + (2 'change-log-name) + (3 'change-log-date) + (4 'change-log-list nil lax)))) + (append `((,log-view-message-re . 'log-view-message-face)) + ;; log-view-font-lock-keywords + '(("^ *\\(?:committer\\|author\\): \ +\\([^<(]+?\\)[ ]*[(<]\\([[:alnum:]_.+-]+@[[:alnum:]_.-]+\\)[>)]" + (1 'change-log-name) + (2 'change-log-email)) + ("^ *timestamp: \\(.*\\)" (1 'change-log-date-face))))))) + +(defun vc-bzr-print-log (files buffer &optional shortlog start-revision limit) + "Get bzr change log for FILES into specified BUFFER." + ;; `vc-do-command' creates the buffer, but we need it before running + ;; the command. + (vc-setup-buffer buffer) + ;; If the buffer exists from a previous invocation it might be + ;; read-only. + ;; FIXME: `vc-bzr-command' runs `bzr log' with `LC_MESSAGES=C', so + ;; the log display may not what the user wants - but I see no other + ;; way of getting the above regexps working. + (with-current-buffer buffer + (apply 'vc-bzr-command "log" buffer 'async files + (append + (when shortlog '("--line")) + (when start-revision (list (format "-r..%s" start-revision))) + (when limit (list "-l" (format "%s" limit))) + (if (stringp vc-bzr-log-switches) + (list vc-bzr-log-switches) + vc-bzr-log-switches))))) + +(defun vc-bzr-log-incoming (buffer remote-location) + (apply 'vc-bzr-command "missing" buffer 'async nil + (list "--theirs-only" (unless (string= remote-location "") remote-location)))) + +(defun vc-bzr-log-outgoing (buffer remote-location) + (apply 'vc-bzr-command "missing" buffer 'async nil + (list "--mine-only" (unless (string= remote-location "") remote-location)))) + +(defun vc-bzr-show-log-entry (revision) + "Find entry for patch name REVISION in bzr change log buffer." + (goto-char (point-min)) + (when revision + (let (case-fold-search + found) + (if (re-search-forward + ;; "revno:" can appear either at the beginning of a line, + ;; or indented. + (concat "^[ ]*-+\n[ ]*revno: " + ;; The revision can contain ".", quote it so that it + ;; does not interfere with regexp matching. + (regexp-quote revision) "$") nil t) + (progn + (beginning-of-line 0) + (setq found t)) + (goto-char (point-min))) + found))) + +(defun vc-bzr-diff (files &optional rev1 rev2 buffer) + "VC bzr backend for diff." + ;; `bzr diff' exits with code 1 if diff is non-empty. + (apply #'vc-bzr-command "diff" (or buffer "*vc-diff*") + (if vc-disable-async-diff 1 'async) files + "--diff-options" (mapconcat 'identity + (vc-switches 'bzr 'diff) + " ") + ;; This `when' is just an optimization because bzr-1.2 is *much* + ;; faster when the revision argument is not given. + (when (or rev1 rev2) + (list "-r" (format "%s..%s" + (or rev1 "revno:-1") + (or rev2 "")))))) + + +;; FIXME: vc-{next,previous}-revision need fixing in vc.el to deal with +;; straight integer revisions. + +(defun vc-bzr-delete-file (file) + "Delete FILE and delete it in the bzr repository." + (condition-case () + (delete-file file) + (file-error nil)) + (vc-bzr-command "remove" nil 0 file)) + +(defun vc-bzr-rename-file (old new) + "Rename file from OLD to NEW using `bzr mv'." + (vc-bzr-command "mv" nil 0 new old)) + +(defvar vc-bzr-annotation-table nil + "Internal use.") +(make-variable-buffer-local 'vc-bzr-annotation-table) + +(defun vc-bzr-annotate-command (file buffer &optional revision) + "Prepare BUFFER for `vc-annotate' on FILE. +Each line is tagged with the revision number, which has a `help-echo' +property containing author and date information." + (apply #'vc-bzr-command "annotate" buffer 'async file "--long" "--all" + (if revision (list "-r" revision))) + (lexical-let ((table (make-hash-table :test 'equal))) + (set-process-filter + (get-buffer-process buffer) + (lambda (proc string) + (when (process-buffer proc) + (with-current-buffer (process-buffer proc) + (setq string (concat (process-get proc :vc-left-over) string)) + (while (string-match "^\\( *[0-9.]+ *\\) \\([^\n ]+\\) +\\([0-9]\\{8\\}\\)\\( |.*\n\\)" string) + (let* ((rev (match-string 1 string)) + (author (match-string 2 string)) + (date (match-string 3 string)) + (key (substring string (match-beginning 0) + (match-beginning 4))) + (line (match-string 4 string)) + (tag (gethash key table)) + (inhibit-read-only t)) + (setq string (substring string (match-end 0))) + (unless tag + (setq tag + (propertize + (format "%s %-7.7s" rev author) + 'help-echo (format "Revision: %d, author: %s, date: %s" + (string-to-number rev) + author date) + 'mouse-face 'highlight)) + (puthash key tag table)) + (goto-char (process-mark proc)) + (insert tag line) + (move-marker (process-mark proc) (point)))) + (process-put proc :vc-left-over string))))))) + +(declare-function vc-annotate-convert-time "vc-annotate" (time)) + +(defun vc-bzr-annotate-time () + (when (re-search-forward "^ *[0-9.]+ +[^\n ]* +|" nil t) + (let ((prop (get-text-property (line-beginning-position) 'help-echo))) + (string-match "[0-9]+\\'" prop) + (let ((str (match-string-no-properties 0 prop))) + (vc-annotate-convert-time + (encode-time 0 0 0 + (string-to-number (substring str 6 8)) + (string-to-number (substring str 4 6)) + (string-to-number (substring str 0 4)))))))) + +(defun vc-bzr-annotate-extract-revision-at-line () + "Return revision for current line of annoation buffer, or nil. +Return nil if current line isn't annotated." + (save-excursion + (beginning-of-line) + (if (looking-at "^ *\\([0-9.]+\\) +[^\n ]* +|") + (match-string-no-properties 1)))) + +(defun vc-bzr-command-discarding-stderr (command &rest args) + "Execute shell command COMMAND (with ARGS); return its output and exitcode. +Return value is a cons (EXITCODE . OUTPUT), where EXITCODE is +the (numerical) exit code of the process, and OUTPUT is a string +containing whatever the process sent to its standard output +stream. Standard error output is discarded." + (with-temp-buffer + (cons + (apply #'process-file command nil (list (current-buffer) nil) nil args) + (buffer-substring (point-min) (point-max))))) + +(defstruct (vc-bzr-extra-fileinfo + (:copier nil) + (:constructor vc-bzr-create-extra-fileinfo (extra-name)) + (:conc-name vc-bzr-extra-fileinfo->)) + extra-name) ;; original name for rename targets, new name for + +(defun vc-bzr-dir-printer (info) + "Pretty-printer for the vc-dir-fileinfo structure." + (let ((extra (vc-dir-fileinfo->extra info))) + (vc-default-dir-printer 'Bzr info) + (when extra + (insert (propertize + (format " (renamed from %s)" + (vc-bzr-extra-fileinfo->extra-name extra)) + 'face 'font-lock-comment-face))))) + +;; FIXME: this needs testing, it's probably incomplete. +(defun vc-bzr-after-dir-status (update-function relative-dir) + (let ((status-str nil) + (translation '(("+N " . added) + ("-D " . removed) + (" M " . edited) ;; file text modified + (" *" . edited) ;; execute bit changed + (" M*" . edited) ;; text modified + execute bit changed + ;; FIXME: what about ignored files? + (" D " . missing) + ;; For conflicts, should we list the .THIS/.BASE/.OTHER? + ("C " . conflict) + ("? " . unregistered) + ;; No such state, but we need to distinguish this case. + ("R " . renamed) + ("RM " . renamed) + ;; For a non existent file FOO, the output is: + ;; bzr: ERROR: Path(s) do not exist: FOO + ("bzr" . not-found) + ;; If the tree is not up to date, bzr will print this warning: + ;; working tree is out of date, run 'bzr update' + ;; ignore it. + ;; FIXME: maybe this warning can be put in the vc-dir header... + ("wor" . not-found) + ;; Ignore "P " and "P." for pending patches. + ("P " . not-found) + ("P. " . not-found) + )) + (translated nil) + (result nil)) + (goto-char (point-min)) + (while (not (eobp)) + (setq status-str + (buffer-substring-no-properties (point) (+ (point) 3))) + (setq translated (cdr (assoc status-str translation))) + (cond + ((eq translated 'conflict) + ;; For conflicts the file appears twice in the listing: once + ;; with the M flag and once with the C flag, so take care + ;; not to add it twice to `result'. Ugly. + (let* ((file + (buffer-substring-no-properties + ;;For files with conflicts the format is: + ;;C Text conflict in FILENAME + ;; Bah. + (+ (point) 21) (line-end-position))) + (entry (assoc file result))) + (when entry + (setf (nth 1 entry) 'conflict)))) + ((eq translated 'renamed) + (re-search-forward "R[ M] \\(.*\\) => \\(.*\\)$" (line-end-position) t) + (let ((new-name (file-relative-name (match-string 2) relative-dir)) + (old-name (file-relative-name (match-string 1) relative-dir))) + (push (list new-name 'edited + (vc-bzr-create-extra-fileinfo old-name)) result))) + ;; do nothing for non existent files + ((eq translated 'not-found)) + (t + (push (list (file-relative-name + (buffer-substring-no-properties + (+ (point) 4) + (line-end-position)) relative-dir) + translated) result))) + (forward-line)) + (funcall update-function result))) + +(defun vc-bzr-dir-status (dir update-function) + "Return a list of conses (file . state) for DIR." + (vc-bzr-command "status" (current-buffer) 'async dir "-v" "-S") + (vc-exec-after + `(vc-bzr-after-dir-status (quote ,update-function) + ;; "bzr status" results are relative to + ;; the bzr root directory, NOT to the + ;; directory "bzr status" was invoked in. + ;; Ugh. + ;; We pass the relative directory here so + ;; that `vc-bzr-after-dir-status' can + ;; frob the results accordingly. + (file-relative-name ,dir (vc-bzr-root ,dir))))) + +(defun vc-bzr-dir-status-files (dir files default-state update-function) + "Return a list of conses (file . state) for DIR." + (apply 'vc-bzr-command "status" (current-buffer) 'async dir "-v" "-S" files) + (vc-exec-after + `(vc-bzr-after-dir-status (quote ,update-function) + (file-relative-name ,dir (vc-bzr-root ,dir))))) + +(defvar vc-bzr-shelve-map + (let ((map (make-sparse-keymap))) + ;; Turn off vc-dir marking + (define-key map [mouse-2] 'ignore) + + (define-key map [down-mouse-3] 'vc-bzr-shelve-menu) + (define-key map "\C-k" 'vc-bzr-shelve-delete-at-point) + (define-key map "=" 'vc-bzr-shelve-show-at-point) + (define-key map "\C-m" 'vc-bzr-shelve-show-at-point) + (define-key map "A" 'vc-bzr-shelve-apply-and-keep-at-point) + (define-key map "P" 'vc-bzr-shelve-apply-at-point) + (define-key map "S" 'vc-bzr-shelve-snapshot) + map)) + +(defvar vc-bzr-shelve-menu-map + (let ((map (make-sparse-keymap "Bzr Shelve"))) + (define-key map [de] + '(menu-item "Delete shelf" vc-bzr-shelve-delete-at-point + :help "Delete the current shelf")) + (define-key map [ap] + '(menu-item "Apply and keep shelf" vc-bzr-shelve-apply-and-keep-at-point + :help "Apply the current shelf and keep it")) + (define-key map [po] + '(menu-item "Apply and remove shelf (pop)" vc-bzr-shelve-apply-at-point + :help "Apply the current shelf and remove it")) + (define-key map [sh] + '(menu-item "Show shelve" vc-bzr-shelve-show-at-point + :help "Show the contents of the current shelve")) + map)) + +(defvar vc-bzr-extra-menu-map + (let ((map (make-sparse-keymap))) + (define-key map [bzr-sn] + '(menu-item "Shelve a snapshot" vc-bzr-shelve-snapshot + :help "Shelve the current state of the tree and keep the current state")) + (define-key map [bzr-sh] + '(menu-item "Shelve..." vc-bzr-shelve + :help "Shelve changes")) + map)) + +(defun vc-bzr-extra-menu () vc-bzr-extra-menu-map) + +(defun vc-bzr-extra-status-menu () vc-bzr-extra-menu-map) + +(defun vc-bzr-dir-extra-headers (dir) + (let* + ((str (with-temp-buffer + (vc-bzr-command "info" t 0 dir) + (buffer-string))) + (shelve (vc-bzr-shelve-list)) + (shelve-help-echo "Use M-x vc-bzr-shelve to create shelves") + (root-dir (vc-bzr-root dir)) + (pending-merge + ;; FIXME: looking for .bzr/checkout/merge-hashes is not a + ;; reliable method to detect pending merges, disable this + ;; until a proper solution is implemented. + (and nil + (file-exists-p + (expand-file-name ".bzr/checkout/merge-hashes" root-dir)))) + (pending-merge-help-echo + (format "A merge has been performed.\nA commit from the top-level directory (%s)\nis required before being able to check in anything else" root-dir)) + (light-checkout + (when (string-match ".+light checkout root: \\(.+\\)$" str) + (match-string 1 str))) + (light-checkout-branch + (when light-checkout + (when (string-match ".+checkout of branch: \\(.+\\)$" str) + (match-string 1 str))))) + (concat + (propertize "Parent branch : " 'face 'font-lock-type-face) + (propertize + (if (string-match "parent branch: \\(.+\\)$" str) + (match-string 1 str) + "None") + 'face 'font-lock-variable-name-face) + "\n" + (when light-checkout + (concat + (propertize "Light checkout root: " 'face 'font-lock-type-face) + (propertize light-checkout 'face 'font-lock-variable-name-face) + "\n")) + (when light-checkout-branch + (concat + (propertize "Checkout of branch : " 'face 'font-lock-type-face) + (propertize light-checkout-branch 'face 'font-lock-variable-name-face) + "\n")) + (when pending-merge + (concat + (propertize "Warning : " 'face 'font-lock-warning-face + 'help-echo pending-merge-help-echo) + (propertize "Pending merges, commit recommended before any other action" + 'help-echo pending-merge-help-echo + 'face 'font-lock-warning-face) + "\n")) + (if shelve + (concat + (propertize "Shelves :\n" 'face 'font-lock-type-face + 'help-echo shelve-help-echo) + (mapconcat + (lambda (x) + (propertize x + 'face 'font-lock-variable-name-face + 'mouse-face 'highlight + 'help-echo "mouse-3: Show shelve menu\nA: Apply and keep shelf\nP: Apply and remove shelf (pop)\nS: Snapshot to a shelf\nC-k: Delete shelf" + 'keymap vc-bzr-shelve-map)) + shelve "\n")) + (concat + (propertize "Shelves : " 'face 'font-lock-type-face + 'help-echo shelve-help-echo) + (propertize "No shelved changes" + 'help-echo shelve-help-echo + 'face 'font-lock-variable-name-face)))))) + +(defun vc-bzr-shelve (name) + "Create a shelve." + (interactive "sShelf name: ") + (let ((root (vc-bzr-root default-directory))) + (when root + (vc-bzr-command "shelve" nil 0 nil "--all" "-m" name) + (vc-resynch-buffer root t t)))) + +(defun vc-bzr-shelve-show (name) + "Show the contents of shelve NAME." + (interactive "sShelve name: ") + (vc-setup-buffer "*vc-diff*") + ;; FIXME: how can you show the contents of a shelf? + (vc-bzr-command "unshelve" "*vc-diff*" 'async nil "--preview" name) + (set-buffer "*vc-diff*") + (diff-mode) + (setq buffer-read-only t) + (pop-to-buffer (current-buffer))) + +(defun vc-bzr-shelve-apply (name) + "Apply shelve NAME and remove it afterwards." + (interactive "sApply (and remove) shelf: ") + (vc-bzr-command "unshelve" nil 0 nil "--apply" name) + (vc-resynch-buffer (vc-bzr-root default-directory) t t)) + +(defun vc-bzr-shelve-apply-and-keep (name) + "Apply shelve NAME and keep it afterwards." + (interactive "sApply (and keep) shelf: ") + (vc-bzr-command "unshelve" nil 0 nil "--apply" "--keep" name) + (vc-resynch-buffer (vc-bzr-root default-directory) t t)) + +(defun vc-bzr-shelve-snapshot () + "Create a stash with the current tree state." + (interactive) + (vc-bzr-command "shelve" nil 0 nil "--all" "-m" + (let ((ct (current-time))) + (concat + (format-time-string "Snapshot on %Y-%m-%d" ct) + (format-time-string " at %H:%M" ct)))) + (vc-bzr-command "unshelve" nil 0 nil "--apply" "--keep") + (vc-resynch-buffer (vc-bzr-root default-directory) t t)) + +(defun vc-bzr-shelve-list () + (with-temp-buffer + (vc-bzr-command "shelve" (current-buffer) 1 nil "--list" "-q") + (delete + "" + (split-string + (buffer-substring (point-min) (point-max)) + "\n")))) + +(defun vc-bzr-shelve-get-at-point (point) + (save-excursion + (goto-char point) + (beginning-of-line) + (if (looking-at "^ +\\([0-9]+\\):") + (match-string 1) + (error "Cannot find shelf at point")))) + +(defun vc-bzr-shelve-delete-at-point () + (interactive) + (let ((shelve (vc-bzr-shelve-get-at-point (point)))) + (when (y-or-n-p (format "Remove shelf %s ?" shelve)) + (vc-bzr-command "unshelve" nil 0 nil "--delete-only" shelve) + (vc-dir-refresh)))) + +(defun vc-bzr-shelve-show-at-point () + (interactive) + (vc-bzr-shelve-show (vc-bzr-shelve-get-at-point (point)))) + +(defun vc-bzr-shelve-apply-at-point () + (interactive) + (vc-bzr-shelve-apply (vc-bzr-shelve-get-at-point (point)))) + +(defun vc-bzr-shelve-apply-and-keep-at-point () + (interactive) + (vc-bzr-shelve-apply-and-keep (vc-bzr-shelve-get-at-point (point)))) + +(defun vc-bzr-shelve-menu (e) + (interactive "e") + (vc-dir-at-event e (popup-menu vc-bzr-shelve-menu-map e))) + +(defun vc-bzr-revision-table (files) + (let ((vc-bzr-revisions '()) + (default-directory (file-name-directory (car files)))) + (with-temp-buffer + (vc-bzr-command "log" t 0 files "--line") + (let ((start (point-min)) + (loglines (buffer-substring-no-properties (point-min) (point-max)))) + (while (string-match "^\\([0-9]+\\):" loglines) + (push (match-string 1 loglines) vc-bzr-revisions) + (setq start (+ start (match-end 0))) + (setq loglines (buffer-substring-no-properties start (point-max)))))) + vc-bzr-revisions)) + +(defun vc-bzr-conflicted-files (dir) + (let ((default-directory (vc-bzr-root dir)) + (files ())) + (with-temp-buffer + (vc-bzr-command "status" t 0 default-directory) + (goto-char (point-min)) + (when (re-search-forward "^conflicts:\n" nil t) + (while (looking-at " \\(?:Text conflict in \\(.*\\)\\|.*\\)\n") + (if (match-end 1) + (push (expand-file-name (match-string 1)) files)) + (goto-char (match-end 0))))) + files)) + +;;; Revision completion + +(eval-and-compile + (defconst vc-bzr-revision-keywords + '("revno" "revid" "last" "before" + "tag" "date" "ancestor" "branch" "submit"))) + +(defun vc-bzr-revision-completion-table (files) + (lexical-let ((files files)) + ;; What about using `files'?!? --Stef + (lambda (string pred action) + (cond + ((string-match "\\`\\(ancestor\\|branch\\|\\(revno:\\)?[-0-9]+:\\):" + string) + (completion-table-with-context (substring string 0 (match-end 0)) + (apply-partially + 'completion-table-with-predicate + 'completion-file-name-table + 'file-directory-p t) + (substring string (match-end 0)) + pred + action)) + ((string-match "\\`\\(before\\):" string) + (completion-table-with-context (substring string 0 (match-end 0)) + (vc-bzr-revision-completion-table files) + (substring string (match-end 0)) + pred + action)) + ((string-match "\\`\\(tag\\):" string) + (let ((prefix (substring string 0 (match-end 0))) + (tag (substring string (match-end 0))) + (table nil) + process-file-side-effects) + (with-temp-buffer + ;; "bzr-1.2 tags" is much faster with --show-ids. + (process-file vc-bzr-program nil '(t) nil "tags" "--show-ids") + ;; The output is ambiguous, unless we assume that revids do not + ;; contain spaces. + (goto-char (point-min)) + (while (re-search-forward "^\\(.*[^ \n]\\) +[^ \n]*$" nil t) + (push (match-string-no-properties 1) table))) + (completion-table-with-context prefix table tag pred action))) + + ((string-match "\\`\\([a-z]+\\):" string) + ;; no actual completion for the remaining keywords. + (completion-table-with-context (substring string 0 (match-end 0)) + (if (member (match-string 1 string) + vc-bzr-revision-keywords) + ;; If it's a valid keyword, + ;; use a non-empty table to + ;; indicate it. + '("") nil) + (substring string (match-end 0)) + pred + action)) + (t + ;; Could use completion-table-with-terminator, except that it + ;; currently doesn't work right w.r.t pcm and doesn't give + ;; the *Completions* output we want. + (complete-with-action action (eval-when-compile + (mapcar (lambda (s) (concat s ":")) + vc-bzr-revision-keywords)) + string pred)))))) + +(eval-after-load "vc" + '(add-to-list 'vc-directory-exclusion-list vc-bzr-admin-dirname t)) + +(provide 'vc-bzr) +;; arch-tag: 8101bad8-4e92-4e7d-85ae-d8e08b4e7c06 +;;; vc-bzr.el ends here diff -r 681cd08dc0f7 -r e93288477c43 lisp/vc/vc-cvs.el --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/lisp/vc/vc-cvs.el Sun Jun 13 22:57:55 2010 +0000 @@ -0,0 +1,1213 @@ +;;; vc-cvs.el --- non-resident support for CVS version-control + +;; Copyright (C) 1995, 1998, 1999, 2000, 2001, 2002, 2003, 2004, 2005, +;; 2006, 2007, 2008, 2009, 2010 Free Software Foundation, Inc. + +;; Author: FSF (see vc.el for full credits) +;; Maintainer: Andre Spiegel + +;; 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 3 of the License, 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. If not, see . + +;;; Commentary: + +;;; Code: + +(eval-when-compile (require 'cl) (require 'vc)) + +;; Clear up the cache to force vc-call to check again and discover +;; new functions when we reload this file. +(put 'CVS 'vc-functions nil) + +;;; Properties of the backend. + +(defun vc-cvs-revision-granularity () 'file) + +(defun vc-cvs-checkout-model (files) + "CVS-specific version of `vc-checkout-model'." + (if (getenv "CVSREAD") + 'announce + (let* ((file (if (consp files) (car files) files)) + (attrib (file-attributes file))) + (or (vc-file-getprop file 'vc-checkout-model) + (vc-file-setprop + file 'vc-checkout-model + (if (and attrib ;; don't check further if FILE doesn't exist + ;; If the file is not writable (despite CVSREAD being + ;; undefined), this is probably because the file is being + ;; "watched" by other developers. + ;; (If vc-mistrust-permissions was t, we actually shouldn't + ;; trust this, but there is no other way to learn this from + ;; CVS at the moment (version 1.9).) + (string-match "r-..-..-." (nth 8 attrib))) + 'announce + 'implicit)))))) + +;;; +;;; Customization options +;;; + +(defcustom vc-cvs-global-switches nil + "Global switches to pass to any CVS command." + :type '(choice (const :tag "None" nil) + (string :tag "Argument String") + (repeat :tag "Argument List" + :value ("") + string)) + :version "22.1" + :group 'vc) + +(defcustom vc-cvs-register-switches nil + "Switches for registering a file into CVS. +A string or list of strings passed to the checkin program by +\\[vc-register]. If nil, use the value of `vc-register-switches'. +If t, use no switches." + :type '(choice (const :tag "Unspecified" nil) + (const :tag "None" t) + (string :tag "Argument String") + (repeat :tag "Argument List" :value ("") string)) + :version "21.1" + :group 'vc) + +(defcustom vc-cvs-diff-switches nil + "String or list of strings specifying switches for CVS diff under VC. +If nil, use the value of `vc-diff-switches'. If t, use no switches." + :type '(choice (const :tag "Unspecified" nil) + (const :tag "None" t) + (string :tag "Argument String") + (repeat :tag "Argument List" :value ("") string)) + :version "21.1" + :group 'vc) + +(defcustom vc-cvs-header (or (cdr (assoc 'CVS vc-header-alist)) '("\$Id\$")) + "Header keywords to be inserted by `vc-insert-headers'." + :version "21.1" + :type '(repeat string) + :group 'vc) + +(defcustom vc-cvs-use-edit t + "Non-nil means to use `cvs edit' to \"check out\" a file. +This is only meaningful if you don't use the implicit checkout model +\(i.e. if you have $CVSREAD set)." + :type 'boolean + :version "21.1" + :group 'vc) + +(defcustom vc-cvs-stay-local 'only-file + "Non-nil means use local operations when possible for remote repositories. +This avoids slow queries over the network and instead uses heuristics +and past information to determine the current status of a file. + +If value is the symbol `only-file' `vc-dir' will connect to the +server, but heuristics will be used to determine the status for +all other VC operations. + +The value can also be a regular expression or list of regular +expressions to match against the host name of a repository; then VC +only stays local for hosts that match it. Alternatively, the value +can be a list of regular expressions where the first element is the +symbol `except'; then VC always stays local except for hosts matched +by these regular expressions." + :type '(choice (const :tag "Always stay local" t) + (const :tag "Only for file operations" only-file) + (const :tag "Don't stay local" nil) + (list :format "\nExamine hostname and %v" + :tag "Examine hostname ..." + (set :format "%v" :inline t + (const :format "%t" :tag "don't" except)) + (regexp :format " stay local,\n%t: %v" + :tag "if it matches") + (repeat :format "%v%i\n" :inline t (regexp :tag "or")))) + :version "23.1" + :group 'vc) + +(defcustom vc-cvs-sticky-date-format-string "%c" + "Format string for mode-line display of sticky date. +Format is according to `format-time-string'. Only used if +`vc-cvs-sticky-tag-display' is t." + :type '(string) + :version "22.1" + :group 'vc) + +(defcustom vc-cvs-sticky-tag-display t + "Specify the mode-line display of sticky tags. +Value t means default display, nil means no display at all. If the +value is a function or macro, it is called with the sticky tag and +its' type as parameters, in that order. TYPE can have three different +values: `symbolic-name' (TAG is a string), `revision-number' (TAG is a +string) and `date' (TAG is a date as returned by `encode-time'). The +return value of the function or macro will be displayed as a string. + +Here's an example that will display the formatted date for sticky +dates and the word \"Sticky\" for sticky tag names and revisions. + + (lambda (tag type) + (cond ((eq type 'date) (format-time-string + vc-cvs-sticky-date-format-string tag)) + ((eq type 'revision-number) \"Sticky\") + ((eq type 'symbolic-name) \"Sticky\"))) + +Here's an example that will abbreviate to the first character only, +any text before the first occurrence of `-' for sticky symbolic tags. +If the sticky tag is a revision number, the word \"Sticky\" is +displayed. Date and time is displayed for sticky dates. + + (lambda (tag type) + (cond ((eq type 'date) (format-time-string \"%Y%m%d %H:%M\" tag)) + ((eq type 'revision-number) \"Sticky\") + ((eq type 'symbolic-name) + (condition-case nil + (progn + (string-match \"\\\\([^-]*\\\\)\\\\(.*\\\\)\" tag) + (concat (substring (match-string 1 tag) 0 1) \":\" + (substring (match-string 2 tag) 1 nil))) + (error tag))))) ; Fall-back to given tag name. + +See also variable `vc-cvs-sticky-date-format-string'." + :type '(choice boolean function) + :version "22.1" + :group 'vc) + +;;; +;;; Internal variables +;;; + + +;;; +;;; State-querying functions +;;; + +;;;###autoload (defun vc-cvs-registered (f) +;;;###autoload (when (file-readable-p (expand-file-name +;;;###autoload "CVS/Entries" (file-name-directory f))) +;;;###autoload (load "vc-cvs") +;;;###autoload (vc-cvs-registered f))) + +(defun vc-cvs-registered (file) + "Check if FILE is CVS registered." + (let ((dirname (or (file-name-directory file) "")) + (basename (file-name-nondirectory file)) + ;; make sure that the file name is searched case-sensitively + (case-fold-search nil)) + (if (file-readable-p (expand-file-name "CVS/Entries" dirname)) + (or (string= basename "") + (with-temp-buffer + (vc-cvs-get-entries dirname) + (goto-char (point-min)) + (cond ((re-search-forward + (concat "^/" (regexp-quote basename) "/[^/]") nil t) + (beginning-of-line) + (vc-cvs-parse-entry file) + t) + (t nil)))) + nil))) + +(defun vc-cvs-state (file) + "CVS-specific version of `vc-state'." + (if (vc-stay-local-p file 'CVS) + (let ((state (vc-file-getprop file 'vc-state))) + ;; If we should stay local, use the heuristic but only if + ;; we don't have a more precise state already available. + (if (memq state '(up-to-date edited nil)) + (vc-cvs-state-heuristic file) + state)) + (with-temp-buffer + (cd (file-name-directory file)) + (let (process-file-side-effects) + (vc-cvs-command t 0 file "status")) + (vc-cvs-parse-status t)))) + +(defun vc-cvs-state-heuristic (file) + "CVS-specific state heuristic." + ;; If the file has not changed since checkout, consider it `up-to-date'. + ;; Otherwise consider it `edited'. + (let ((checkout-time (vc-file-getprop file 'vc-checkout-time)) + (lastmod (nth 5 (file-attributes file)))) + (cond + ((equal checkout-time lastmod) 'up-to-date) + ((string= (vc-working-revision file) "0") 'added) + ((null checkout-time) 'unregistered) + (t 'edited)))) + +(defun vc-cvs-working-revision (file) + "CVS-specific version of `vc-working-revision'." + ;; There is no need to consult RCS headers under CVS, because we + ;; get the workfile version for free when we recognize that a file + ;; is registered in CVS. + (vc-cvs-registered file) + (vc-file-getprop file 'vc-working-revision)) + +(defun vc-cvs-mode-line-string (file) + "Return string for placement into the modeline for FILE. +Compared to the default implementation, this function does two things: +Handle the special case of a CVS file that is added but not yet +committed and support display of sticky tags." + (let* ((sticky-tag (vc-file-getprop file 'vc-cvs-sticky-tag)) + help-echo + (string + (let ((def-ml (vc-default-mode-line-string 'CVS file))) + (setq help-echo + (get-text-property 0 'help-echo def-ml)) + def-ml))) + (propertize + (if (zerop (length sticky-tag)) + string + (setq help-echo (format "%s on the '%s' branch" + help-echo sticky-tag)) + (concat string "[" sticky-tag "]")) + 'help-echo help-echo))) + + +;;; +;;; State-changing functions +;;; + +(defun vc-cvs-register (files &optional rev comment) + "Register FILES into the CVS version-control system. +COMMENT can be used to provide an initial description of FILES. +Passes either `vc-cvs-register-switches' or `vc-register-switches' +to the CVS command." + ;; Register the directories if needed. + (let (dirs) + (dolist (file files) + (and (not (vc-cvs-responsible-p file)) + (vc-cvs-could-register file) + (push (directory-file-name (file-name-directory file)) dirs))) + (if dirs (vc-cvs-register dirs))) + (apply 'vc-cvs-command nil 0 files + "add" + (and comment (string-match "[^\t\n ]" comment) + (concat "-m" comment)) + (vc-switches 'CVS 'register))) + +(defun vc-cvs-responsible-p (file) + "Return non-nil if CVS thinks it is responsible for FILE." + (file-directory-p (expand-file-name "CVS" + (if (file-directory-p file) + file + (file-name-directory file))))) + +(defun vc-cvs-could-register (file) + "Return non-nil if FILE could be registered in CVS. +This is only possible if CVS is managing FILE's directory or one of +its parents." + (let ((dir file)) + (while (and (stringp dir) + (not (equal dir (setq dir (file-name-directory dir)))) + dir) + (setq dir (if (file-exists-p + (expand-file-name "CVS/Entries" dir)) + t + (directory-file-name dir)))) + (eq dir t))) + +(defun vc-cvs-checkin (files rev comment &optional extra-args-ignored) + "CVS-specific version of `vc-backend-checkin'." + (unless (or (not rev) (vc-cvs-valid-revision-number-p rev)) + (if (not (vc-cvs-valid-symbolic-tag-name-p rev)) + (error "%s is not a valid symbolic tag name" rev) + ;; If the input revison is a valid symbolic tag name, we create it + ;; as a branch, commit and switch to it. + (apply 'vc-cvs-command nil 0 files "tag" "-b" (list rev)) + (apply 'vc-cvs-command nil 0 files "update" "-r" (list rev)) + (mapc (lambda (file) (vc-file-setprop file 'vc-cvs-sticky-tag rev)) + files))) + (let ((status (apply 'vc-cvs-command nil 1 files + "ci" (if rev (concat "-r" rev)) + (concat "-m" comment) + (vc-switches 'CVS 'checkin)))) + (set-buffer "*vc*") + (goto-char (point-min)) + (when (not (zerop status)) + ;; Check checkin problem. + (cond + ((re-search-forward "Up-to-date check failed" nil t) + (mapc (lambda (file) (vc-file-setprop file 'vc-state 'needs-merge)) + files) + (error "%s" (substitute-command-keys + (concat "Up-to-date check failed: " + "type \\[vc-next-action] to merge in changes")))) + (t + (pop-to-buffer (current-buffer)) + (goto-char (point-min)) + (shrink-window-if-larger-than-buffer) + (error "Check-in failed")))) + ;; Single-file commit? Then update the revision by parsing the buffer. + ;; Otherwise we can't necessarily tell what goes with what; clear + ;; its properties so they have to be refetched. + (if (= (length files) 1) + (vc-file-setprop + (car files) 'vc-working-revision + (vc-parse-buffer "^\\(new\\|initial\\) revision: \\([0-9.]+\\)" 2)) + (mapc 'vc-file-clearprops files)) + ;; Anyway, forget the checkout model of the file, because we might have + ;; guessed wrong when we found the file. After commit, we can + ;; tell it from the permissions of the file (see + ;; vc-cvs-checkout-model). + (mapc (lambda (file) (vc-file-setprop file 'vc-checkout-model nil)) + files) + + ;; if this was an explicit check-in (does not include creation of + ;; a branch), remove the sticky tag. + (if (and rev (not (vc-cvs-valid-symbolic-tag-name-p rev))) + (vc-cvs-command nil 0 files "update" "-A")))) + +(defun vc-cvs-find-revision (file rev buffer) + (apply 'vc-cvs-command + buffer 0 file + "-Q" ; suppress diagnostic output + "update" + (and rev (not (string= rev "")) + (concat "-r" rev)) + "-p" + (vc-switches 'CVS 'checkout))) + +(defun vc-cvs-checkout (file &optional editable rev) + "Checkout a revision of FILE into the working area. +EDITABLE non-nil means that the file should be writable. +REV is the revision to check out." + (message "Checking out %s..." file) + ;; Change buffers to get local value of vc-checkout-switches. + (with-current-buffer (or (get-file-buffer file) (current-buffer)) + (if (and (file-exists-p file) (not rev)) + ;; If no revision was specified, just make the file writable + ;; if necessary (using `cvs-edit' if requested). + (and editable (not (eq (vc-cvs-checkout-model (list file)) 'implicit)) + (if vc-cvs-use-edit + (vc-cvs-command nil 0 file "edit") + (set-file-modes file (logior (file-modes file) 128)) + (if (equal file buffer-file-name) (toggle-read-only -1)))) + ;; Check out a particular revision (or recreate the file). + (vc-file-setprop file 'vc-working-revision nil) + (apply 'vc-cvs-command nil 0 file + (and editable "-w") + "update" + (when rev + (unless (eq rev t) + ;; default for verbose checkout: clear the + ;; sticky tag so that the actual update will + ;; get the head of the trunk + (if (string= rev "") + "-A" + (concat "-r" rev)))) + (vc-switches 'CVS 'checkout))) + (vc-mode-line file 'CVS)) + (message "Checking out %s...done" file)) + +(defun vc-cvs-delete-file (file) + (vc-cvs-command nil 0 file "remove" "-f")) + +(defun vc-cvs-revert (file &optional contents-done) + "Revert FILE to the working revision on which it was based." + (vc-default-revert 'CVS file contents-done) + (unless (eq (vc-cvs-checkout-model (list file)) 'implicit) + (if vc-cvs-use-edit + (vc-cvs-command nil 0 file "unedit") + ;; Make the file read-only by switching off all w-bits + (set-file-modes file (logand (file-modes file) 3950))))) + +(defun vc-cvs-merge (file first-revision &optional second-revision) + "Merge changes into current working copy of FILE. +The changes are between FIRST-REVISION and SECOND-REVISION." + (vc-cvs-command nil 0 file + "update" "-kk" + (concat "-j" first-revision) + (concat "-j" second-revision)) + (vc-file-setprop file 'vc-state 'edited) + (with-current-buffer (get-buffer "*vc*") + (goto-char (point-min)) + (if (re-search-forward "conflicts during merge" nil t) + (progn + (vc-file-setprop file 'vc-state 'conflict) + ;; signal error + 1) + (vc-file-setprop file 'vc-state 'edited) + ;; signal success + 0))) + +(defun vc-cvs-merge-news (file) + "Merge in any new changes made to FILE." + (message "Merging changes into %s..." file) + ;; (vc-file-setprop file 'vc-working-revision nil) + (vc-file-setprop file 'vc-checkout-time 0) + (vc-cvs-command nil nil file "update") + ;; Analyze the merge result reported by CVS, and set + ;; file properties accordingly. + (with-current-buffer (get-buffer "*vc*") + (goto-char (point-min)) + ;; get new working revision + (if (re-search-forward + "^Merging differences between [0-9.]* and \\([0-9.]*\\) into" nil t) + (vc-file-setprop file 'vc-working-revision (match-string 1)) + (vc-file-setprop file 'vc-working-revision nil)) + ;; get file status + (prog1 + (if (eq (buffer-size) 0) + 0 ;; there were no news; indicate success + (if (re-search-forward + (concat "^\\([CMUP] \\)?" + (regexp-quote + (substring file (1+ (length (expand-file-name + "." default-directory))))) + "\\( already contains the differences between \\)?") + nil t) + (cond + ;; Merge successful, we are in sync with repository now + ((or (match-string 2) + (string= (match-string 1) "U ") + (string= (match-string 1) "P ")) + (vc-file-setprop file 'vc-state 'up-to-date) + (vc-file-setprop file 'vc-checkout-time + (nth 5 (file-attributes file))) + 0);; indicate success to the caller + ;; Merge successful, but our own changes are still in the file + ((string= (match-string 1) "M ") + (vc-file-setprop file 'vc-state 'edited) + 0);; indicate success to the caller + ;; Conflicts detected! + (t + (vc-file-setprop file 'vc-state 'conflict) + 1);; signal the error to the caller + ) + (pop-to-buffer "*vc*") + (error "Couldn't analyze cvs update result"))) + (message "Merging changes into %s...done" file)))) + +(defun vc-cvs-modify-change-comment (files rev comment) + "Modify the change comments for FILES on a specified REV. +Will fail unless you have administrative privileges on the repo." + (vc-cvs-command nil 0 files "admin" (concat "-m" rev ":" comment))) + +;;; +;;; History functions +;;; + +(declare-function vc-rcs-print-log-cleanup "vc-rcs" ()) + +(defun vc-cvs-print-log (files buffer &optional shortlog start-revision-ignored limit) + "Get change logs associated with FILES." + (require 'vc-rcs) + ;; It's just the catenation of the individual logs. + (vc-cvs-command + buffer + (if (vc-stay-local-p files 'CVS) 'async 0) + files "log") + (with-current-buffer buffer + (vc-exec-after (vc-rcs-print-log-cleanup))) + (when limit 'limit-unsupported)) + +(defun vc-cvs-comment-history (file) + "Get comment history of a file." + (vc-call-backend 'RCS 'comment-history file)) + +(defun vc-cvs-diff (files &optional oldvers newvers buffer) + "Get a difference report using CVS between two revisions of FILE." + (let* (process-file-side-effects + (async (and (not vc-disable-async-diff) + (vc-stay-local-p files 'CVS))) + (invoke-cvs-diff-list nil) + status) + ;; Look through the file list and see if any files have backups + ;; that can be used to do a plain "diff" instead of "cvs diff". + (dolist (file files) + (let ((ov oldvers) + (nv newvers)) + (when (or (not ov) (string-equal ov "")) + (setq ov (vc-working-revision file))) + (when (string-equal nv "") + (setq nv nil)) + (let ((file-oldvers (vc-version-backup-file file ov)) + (file-newvers (if (not nv) + file + (vc-version-backup-file file nv))) + (coding-system-for-read (vc-coding-system-for-diff file))) + (if (and file-oldvers file-newvers) + (progn + ;; This used to append diff-switches and vc-diff-switches, + ;; which was consistent with the vc-diff-switches doc at that + ;; time, but not with the actual behavior of any other VC diff. + (apply 'vc-do-command (or buffer "*vc-diff*") 1 "diff" nil + ;; Not a CVS diff, does not use vc-cvs-diff-switches. + (append (vc-switches nil 'diff) + (list (file-relative-name file-oldvers) + (file-relative-name file-newvers)))) + (setq status 0)) + (push file invoke-cvs-diff-list))))) + (when invoke-cvs-diff-list + (setq status (apply 'vc-cvs-command (or buffer "*vc-diff*") + (if async 'async 1) + invoke-cvs-diff-list "diff" + (and oldvers (concat "-r" oldvers)) + (and newvers (concat "-r" newvers)) + (vc-switches 'CVS 'diff)))) + (if async 1 status))) ; async diff, pessimistic assumption + +(defconst vc-cvs-annotate-first-line-re "^[0-9]") + +(defun vc-cvs-annotate-process-filter (process string) + (setq string (concat (process-get process 'output) string)) + (if (not (string-match vc-cvs-annotate-first-line-re string)) + ;; Still waiting for the first real line. + (process-put process 'output string) + (let ((vc-filter (process-get process 'vc-filter))) + (set-process-filter process vc-filter) + (funcall vc-filter process (substring string (match-beginning 0)))))) + +(defun vc-cvs-annotate-command (file buffer &optional revision) + "Execute \"cvs annotate\" on FILE, inserting the contents in BUFFER. +Optional arg REVISION is a revision to annotate from." + (vc-cvs-command buffer + (if (vc-stay-local-p file 'CVS) + 'async 0) + file "annotate" + (if revision (concat "-r" revision))) + ;; Strip the leading few lines. + (let ((proc (get-buffer-process buffer))) + (if proc + ;; If running asynchronously, use a process filter. + (progn + (process-put proc 'vc-filter (process-filter proc)) + (set-process-filter proc 'vc-cvs-annotate-process-filter)) + (with-current-buffer buffer + (goto-char (point-min)) + (re-search-forward vc-cvs-annotate-first-line-re) + (delete-region (point-min) (1- (point))))))) + +(declare-function vc-annotate-convert-time "vc-annotate" (time)) + +(defun vc-cvs-annotate-current-time () + "Return the current time, based at midnight of the current day, and +encoded as fractional days." + (vc-annotate-convert-time + (apply 'encode-time 0 0 0 (nthcdr 3 (decode-time (current-time)))))) + +(defun vc-cvs-annotate-time () + "Return the time of the next annotation (as fraction of days) +systime, or nil if there is none." + (let* ((bol (point)) + (cache (get-text-property bol 'vc-cvs-annotate-time)) + (inhibit-read-only t) + (inhibit-modification-hooks t)) + (cond + (cache) + ((looking-at + "^\\S-+\\s-+\\S-+\\s-+\\([0-9]+\\)-\\(\\sw+\\)-\\([0-9]+\\)): ") + (let ((day (string-to-number (match-string 1))) + (month (cdr (assq (intern (match-string 2)) + '((Jan . 1) (Feb . 2) (Mar . 3) + (Apr . 4) (May . 5) (Jun . 6) + (Jul . 7) (Aug . 8) (Sep . 9) + (Oct . 10) (Nov . 11) (Dec . 12))))) + (year (let ((tmp (string-to-number (match-string 3)))) + ;; Years 0..68 are 2000..2068. + ;; Years 69..99 are 1969..1999. + (+ (cond ((> 69 tmp) 2000) + ((> 100 tmp) 1900) + (t 0)) + tmp)))) + (put-text-property + bol (1+ bol) 'vc-cvs-annotate-time + (setq cache (cons + ;; Position at end makes for nicer overlay result. + ;; Don't put actual buffer pos here, but only relative + ;; distance, so we don't ever move backward in the + ;; goto-char below, even if the text is moved. + (- (match-end 0) (match-beginning 0)) + (vc-annotate-convert-time + (encode-time 0 0 0 day month year)))))))) + (when cache + (goto-char (+ bol (car cache))) ; Fontify from here to eol. + (cdr cache)))) ; days (float) + +(defun vc-cvs-annotate-extract-revision-at-line () + (save-excursion + (beginning-of-line) + (if (re-search-forward "^\\([0-9]+\\.[0-9]+\\(\\.[0-9]+\\)*\\) +(" + (line-end-position) t) + (match-string-no-properties 1) + nil))) + +(defun vc-cvs-previous-revision (file rev) + (vc-call-backend 'RCS 'previous-revision file rev)) + +(defun vc-cvs-next-revision (file rev) + (vc-call-backend 'RCS 'next-revision file rev)) + +;; FIXME: This should probably be replaced by code using cvs2cl. +(defun vc-cvs-update-changelog (files) + (vc-call-backend 'RCS 'update-changelog files)) + +;;; +;;; Tag system +;;; + +(defun vc-cvs-create-tag (dir name branchp) + "Assign to DIR's current revision a given NAME. +If BRANCHP is non-nil, the name is created as a branch (and the current +workspace is immediately moved to that new branch)." + (vc-cvs-command nil 0 dir "tag" "-c" (if branchp "-b") name) + (when branchp (vc-cvs-command nil 0 dir "update" "-r" name))) + +(defun vc-cvs-retrieve-tag (dir name update) + "Retrieve a tag at and below DIR. +NAME is the name of the tag; if it is empty, do a `cvs update'. +If UPDATE is non-nil, then update (resynch) any affected buffers." + (with-current-buffer (get-buffer-create "*vc*") + (let ((default-directory dir) + (sticky-tag)) + (erase-buffer) + (if (or (not name) (string= name "")) + (vc-cvs-command t 0 nil "update") + (vc-cvs-command t 0 nil "update" "-r" name) + (setq sticky-tag name)) + (when update + (goto-char (point-min)) + (while (not (eobp)) + (if (looking-at "\\([CMUP]\\) \\(.*\\)") + (let* ((file (expand-file-name (match-string 2) dir)) + (state (match-string 1)) + (buffer (find-buffer-visiting file))) + (when buffer + (cond + ((or (string= state "U") + (string= state "P")) + (vc-file-setprop file 'vc-state 'up-to-date) + (vc-file-setprop file 'vc-working-revision nil) + (vc-file-setprop file 'vc-checkout-time + (nth 5 (file-attributes file)))) + ((or (string= state "M") + (string= state "C")) + (vc-file-setprop file 'vc-state 'edited) + (vc-file-setprop file 'vc-working-revision nil) + (vc-file-setprop file 'vc-checkout-time 0))) + (vc-file-setprop file 'vc-cvs-sticky-tag sticky-tag) + (vc-resynch-buffer file t t)))) + (forward-line 1)))))) + + +;;; +;;; Miscellaneous +;;; + +(defun vc-cvs-make-version-backups-p (file) + "Return non-nil if version backups should be made for FILE." + (vc-stay-local-p file 'CVS)) + +(defun vc-cvs-check-headers () + "Check if the current file has any headers in it." + (save-excursion + (goto-char (point-min)) + (re-search-forward "\\$[A-Za-z\300-\326\330-\366\370-\377]+\ +\\(: [\t -#%-\176\240-\377]*\\)?\\$" nil t))) + + +;;; +;;; Internal functions +;;; + +(defun vc-cvs-command (buffer okstatus files &rest flags) + "A wrapper around `vc-do-command' for use in vc-cvs.el. +The difference to vc-do-command is that this function always invokes `cvs', +and that it passes `vc-cvs-global-switches' to it before FLAGS." + (apply 'vc-do-command (or buffer "*vc*") okstatus "cvs" files + (if (stringp vc-cvs-global-switches) + (cons vc-cvs-global-switches flags) + (append vc-cvs-global-switches + flags)))) + +(defun vc-cvs-stay-local-p (file) ;Back-compatibility. + (vc-stay-local-p file 'CVS)) + +(defun vc-cvs-repository-hostname (dirname) + "Hostname of the CVS server associated to workarea DIRNAME." + (let ((rootname (expand-file-name "CVS/Root" dirname))) + (when (file-readable-p rootname) + (with-temp-buffer + (let ((coding-system-for-read + (or file-name-coding-system + default-file-name-coding-system))) + (vc-insert-file rootname)) + (goto-char (point-min)) + (nth 2 (vc-cvs-parse-root + (buffer-substring (point) + (line-end-position)))))))) + +(defun vc-cvs-parse-uhp (path) + "parse user@host/path into (user@host /path)" + (if (string-match "\\([^/]+\\)\\(/.*\\)" path) + (list (match-string 1 path) (match-string 2 path)) + (list nil path))) + +(defun vc-cvs-parse-root (root) + "Split CVS ROOT specification string into a list of fields. +A CVS root specification of the form + [:METHOD:][[USER@]HOSTNAME]:?/path/to/repository +is converted to a normalized record with the following structure: + \(METHOD USER HOSTNAME CVS-ROOT). +The default METHOD for a CVS root of the form + /path/to/repository +is `local'. +The default METHOD for a CVS root of the form + [USER@]HOSTNAME:/path/to/repository +is `ext'. +For an empty string, nil is returned (invalid CVS root)." + ;; Split CVS root into colon separated fields (0-4). + ;; The `x:' makes sure, that leading colons are not lost; + ;; `HOST:/PATH' is then different from `:METHOD:/PATH'. + (let* ((root-list (cdr (split-string (concat "x:" root) ":"))) + (len (length root-list)) + ;; All syntactic varieties will get a proper METHOD. + (root-list + (cond + ((= len 0) + ;; Invalid CVS root + nil) + ((= len 1) + (let ((uhp (vc-cvs-parse-uhp (car root-list)))) + (cons (if (car uhp) "ext" "local") uhp))) + ((= len 2) + ;; [USER@]HOST:PATH => method `ext' + (and (not (equal (car root-list) "")) + (cons "ext" root-list))) + ((= len 3) + ;; :METHOD:PATH or :METHOD:USER@HOSTNAME/PATH + (cons (cadr root-list) + (vc-cvs-parse-uhp (caddr root-list)))) + (t + ;; :METHOD:[USER@]HOST:PATH + (cdr root-list))))) + (if root-list + (let ((method (car root-list)) + (uhost (or (cadr root-list) "")) + (root (nth 2 root-list)) + user host) + ;; Split USER@HOST + (if (string-match "\\(.*\\)@\\(.*\\)" uhost) + (setq user (match-string 1 uhost) + host (match-string 2 uhost)) + (setq host uhost)) + ;; Remove empty HOST + (and (equal host "") + (setq host)) + ;; Fix windows style CVS root `:local:C:\\project\\cvs\\some\\dir' + (and host + (equal method "local") + (setq root (concat host ":" root) host)) + ;; Normalize CVS root record + (list method user host root))))) + +;; XXX: This does not work correctly for subdirectories. "cvs status" +;; information is context sensitive, it contains lines like: +;; cvs status: Examining DIRNAME +;; and the file entries after that don't show the full path. +;; Because of this VC directory listings only show changed files +;; at the top level for CVS. +(defun vc-cvs-parse-status (&optional full) + "Parse output of \"cvs status\" command in the current buffer. +Set file properties accordingly. Unless FULL is t, parse only +essential information. Note that this can never set the 'ignored +state." + (let (file status missing) + (goto-char (point-min)) + (while (looking-at "? \\(.*\\)") + (setq file (expand-file-name (match-string 1))) + (vc-file-setprop file 'vc-state 'unregistered) + (forward-line 1)) + (when (re-search-forward "^File: " nil t) + (when (setq missing (looking-at "no file ")) + (goto-char (match-end 0))) + (cond + ((re-search-forward "\\=\\([^ \t]+\\)" nil t) + (setq file (expand-file-name (match-string 1))) + (setq status(if (re-search-forward "\\=[ \t]+Status: \\(.*\\)" nil t) + (match-string 1) "Unknown")) + (when (and full + (re-search-forward + "\\(RCS Version\\|RCS Revision\\|Repository revision\\):\ +\[\t ]+\\([0-9.]+\\)" + nil t)) + (vc-file-setprop file 'vc-latest-revision (match-string 2))) + (vc-file-setprop + file 'vc-state + (cond + ((string-match "Up-to-date" status) + (vc-file-setprop file 'vc-checkout-time + (nth 5 (file-attributes file))) + 'up-to-date) + ((string-match "Locally Modified" status) 'edited) + ((string-match "Needs Merge" status) 'needs-merge) + ((string-match "Needs \\(Checkout\\|Patch\\)" status) + (if missing 'missing 'needs-update)) + ((string-match "Locally Added" status) 'added) + ((string-match "Locally Removed" status) 'removed) + ((string-match "File had conflicts " status) 'conflict) + ((string-match "Unknown" status) 'unregistered) + (t 'edited)))))))) + +(defun vc-cvs-after-dir-status (update-function) + ;; Heavily inspired by vc-cvs-parse-status. AKA a quick hack. + ;; This needs a lot of testing. + (let ((status nil) + (status-str nil) + (file nil) + (result nil) + (missing nil) + (ignore-next nil) + (subdir default-directory)) + (goto-char (point-min)) + (while + ;; Look for either a file entry, an unregistered file, or a + ;; directory change. + (re-search-forward + "\\(^=+\n\\([^=c?\n].*\n\\|\n\\)+\\)\\|\\(\\(^?? .*\n\\)+\\)\\|\\(^cvs status: \\(Examining\\|nothing\\) .*\n\\)" + nil t) + ;; FIXME: get rid of narrowing here. + (narrow-to-region (match-beginning 0) (match-end 0)) + (goto-char (point-min)) + ;; The subdir + (when (looking-at "cvs status: Examining \\(.+\\)") + (setq subdir (expand-file-name (match-string 1)))) + ;; Unregistered files + (while (looking-at "? \\(.*\\)") + (setq file (file-relative-name + (expand-file-name (match-string 1) subdir))) + (push (list file 'unregistered) result) + (forward-line 1)) + (when (looking-at "cvs status: nothing known about") + ;; We asked about a non existent file. The output looks like this: + + ;; cvs status: nothing known about `lisp/v.diff' + ;; =================================================================== + ;; File: no file v.diff Status: Unknown + ;; + ;; Working revision: No entry for v.diff + ;; Repository revision: No revision control file + ;; + + ;; Due to narrowing in this iteration we only see the "cvs + ;; status:" line, so just set a flag so that we can ignore the + ;; file in the next iteration. + (setq ignore-next t)) + ;; A file entry. + (when (re-search-forward "^File: \\(no file \\)?\\(.*[^ \t]\\)[ \t]+Status: \\(.*\\)" nil t) + (setq missing (match-string 1)) + (setq file (file-relative-name + (expand-file-name (match-string 2) subdir))) + (setq status-str (match-string 3)) + (setq status + (cond + ((string-match "Up-to-date" status-str) 'up-to-date) + ((string-match "Locally Modified" status-str) 'edited) + ((string-match "Needs Merge" status-str) 'needs-merge) + ((string-match "Needs \\(Checkout\\|Patch\\)" status-str) + (if missing 'missing 'needs-update)) + ((string-match "Locally Added" status-str) 'added) + ((string-match "Locally Removed" status-str) 'removed) + ((string-match "File had conflicts " status-str) 'conflict) + ((string-match "Unknown" status-str) 'unregistered) + (t 'edited))) + (if ignore-next + (setq ignore-next nil) + (unless (eq status 'up-to-date) + (push (list file status) result)))) + (goto-char (point-max)) + (widen)) + (funcall update-function result)) + ;; Alternative implementation: use the "update" command instead of + ;; the "status" command. + ;; (let ((result nil) + ;; (translation '((?? . unregistered) + ;; (?A . added) + ;; (?C . conflict) + ;; (?M . edited) + ;; (?P . needs-merge) + ;; (?R . removed) + ;; (?U . needs-update)))) + ;; (goto-char (point-min)) + ;; (while (not (eobp)) + ;; (if (looking-at "^[ACMPRU?] \\(.*\\)$") + ;; (push (list (match-string 1) + ;; (cdr (assoc (char-after) translation))) + ;; result) + ;; (cond + ;; ((looking-at "cvs update: warning: \\(.*\\) was lost") + ;; ;; Format is: + ;; ;; cvs update: warning: FILENAME was lost + ;; ;; U FILENAME + ;; (push (list (match-string 1) 'missing) result) + ;; ;; Skip the "U" line + ;; (forward-line 1)) + ;; ((looking-at "cvs update: New directory `\\(.*\\)' -- ignored") + ;; (push (list (match-string 1) 'unregistered) result)))) + ;; (forward-line 1)) + ;; (funcall update-function result))) + ) + +;; Based on vc-cvs-dir-state-heuristic from Emacs 22. +;; FIXME does not mention unregistered files. +(defun vc-cvs-dir-status-heuristic (dir update-function &optional basedir) + "Find the CVS state of all files in DIR, using only local information." + (let (file basename status result dirlist) + (with-temp-buffer + (vc-cvs-get-entries dir) + (goto-char (point-min)) + (while (not (eobp)) + (if (looking-at "D/\\([^/]*\\)////") + (push (expand-file-name (match-string 1) dir) dirlist) + ;; CVS-removed files are not taken under VC control. + (when (looking-at "/\\([^/]*\\)/[^/-]") + (setq basename (match-string 1) + file (expand-file-name basename dir) + status (or (vc-file-getprop file 'vc-state) + (vc-cvs-parse-entry file t))) + (unless (eq status 'up-to-date) + (push (list (if basedir + (file-relative-name file basedir) + basename) + status) result)))) + (forward-line 1))) + (dolist (subdir dirlist) + (setq result (append result + (vc-cvs-dir-status-heuristic subdir nil + (or basedir dir))))) + (if basedir result + (funcall update-function result)))) + +(defun vc-cvs-dir-status (dir update-function) + "Create a list of conses (file . state) for DIR." + ;; FIXME check all files in DIR instead? + (let ((local (vc-stay-local-p dir 'CVS))) + (if (and local (not (eq local 'only-file))) + (vc-cvs-dir-status-heuristic dir update-function) + (vc-cvs-command (current-buffer) 'async dir "-f" "status") + ;; Alternative implementation: use the "update" command instead of + ;; the "status" command. + ;; (vc-cvs-command (current-buffer) 'async + ;; (file-relative-name dir) + ;; "-f" "-n" "update" "-d" "-P") + (vc-exec-after + `(vc-cvs-after-dir-status (quote ,update-function)))))) + +(defun vc-cvs-dir-status-files (dir files default-state update-function) + "Create a list of conses (file . state) for DIR." + (apply 'vc-cvs-command (current-buffer) 'async dir "-f" "status" files) + (vc-exec-after + `(vc-cvs-after-dir-status (quote ,update-function)))) + +(defun vc-cvs-file-to-string (file) + "Read the content of FILE and return it as a string." + (condition-case nil + (with-temp-buffer + (insert-file-contents file) + (goto-char (point-min)) + (buffer-substring (point) (point-max))) + (file-error nil))) + +(defun vc-cvs-dir-extra-headers (dir) + "Extract and represent per-directory properties of a CVS working copy." + (let ((repo + (condition-case nil + (with-temp-buffer + (insert-file-contents "CVS/Root") + (goto-char (point-min)) + (and (looking-at ":ext:") (delete-char 5)) + (concat (buffer-substring (point) (1- (point-max))) "\n")) + (file-error nil))) + (module + (condition-case nil + (with-temp-buffer + (insert-file-contents "CVS/Repository") + (goto-char (point-min)) + (skip-chars-forward "^\n") + (concat (buffer-substring (point-min) (point)) "\n")) + (file-error nil)))) + (concat + (cond (repo + (concat (propertize "Repository : " 'face 'font-lock-type-face) + (propertize repo 'face 'font-lock-variable-name-face))) + (t "")) + (cond (module + (concat (propertize "Module : " 'face 'font-lock-type-face) + (propertize module 'face 'font-lock-variable-name-face))) + (t "")) + (if (file-readable-p "CVS/Tag") + (let ((tag (vc-cvs-file-to-string "CVS/Tag"))) + (cond + ((string-match "\\`T" tag) + (concat (propertize "Tag : " 'face 'font-lock-type-face) + (propertize (substring tag 1) + 'face 'font-lock-variable-name-face))) + ((string-match "\\`D" tag) + (concat (propertize "Date : " 'face 'font-lock-type-face) + (propertize (substring tag 1) + 'face 'font-lock-variable-name-face))) + (t "")))) + + ;; In CVS, branch is a per-file property, not a per-directory property. + ;; We can't really do this here without making dangerous assumptions. + ;;(propertize "Branch: " 'face 'font-lock-type-face) + ;;(propertize "ADD CODE TO PRINT THE BRANCH NAME\n" + ;; 'face 'font-lock-warning-face) + ))) + +(defun vc-cvs-get-entries (dir) + "Insert the CVS/Entries file from below DIR into the current buffer. +This function ensures that the correct coding system is used for that, +which may not be the one that is used for the files' contents. +CVS/Entries should only be accessed through this function." + (let ((coding-system-for-read (or file-name-coding-system + default-file-name-coding-system))) + (vc-insert-file (expand-file-name "CVS/Entries" dir)))) + +(defun vc-cvs-valid-symbolic-tag-name-p (tag) + "Return non-nil if TAG is a valid symbolic tag name." + ;; According to the CVS manual, a valid symbolic tag must start with + ;; an uppercase or lowercase letter and can contain uppercase and + ;; lowercase letters, digits, `-', and `_'. + (and (string-match "^[a-zA-Z]" tag) + (not (string-match "[^a-z0-9A-Z-_]" tag)))) + +(defun vc-cvs-valid-revision-number-p (tag) + "Return non-nil if TAG is a valid revision number." + (and (string-match "^[0-9]" tag) + (not (string-match "[^0-9.]" tag)))) + +(defun vc-cvs-parse-sticky-tag (match-type match-tag) + "Parse and return the sticky tag as a string. +`match-data' is protected." + (let ((data (match-data)) + (tag) + (type (cond ((string= match-type "D") 'date) + ((string= match-type "T") + (if (vc-cvs-valid-symbolic-tag-name-p match-tag) + 'symbolic-name + 'revision-number)) + (t nil)))) + (unwind-protect + (progn + (cond + ;; Sticky Date tag. Convert to a proper date value (`encode-time') + ((eq type 'date) + (string-match + "\\([0-9]+\\)\\.\\([0-9]+\\)\\.\\([0-9]+\\)\\.\\([0-9]+\\)\\.\\([0-9]+\\)\\.\\([0-9]+\\)" + match-tag) + (let* ((year-tmp (string-to-number (match-string 1 match-tag))) + (month (string-to-number (match-string 2 match-tag))) + (day (string-to-number (match-string 3 match-tag))) + (hour (string-to-number (match-string 4 match-tag))) + (min (string-to-number (match-string 5 match-tag))) + (sec (string-to-number (match-string 6 match-tag))) + ;; Years 0..68 are 2000..2068. + ;; Years 69..99 are 1969..1999. + (year (+ (cond ((> 69 year-tmp) 2000) + ((> 100 year-tmp) 1900) + (t 0)) + year-tmp))) + (setq tag (encode-time sec min hour day month year)))) + ;; Sticky Tag name or revision number + ((eq type 'symbolic-name) (setq tag match-tag)) + ((eq type 'revision-number) (setq tag match-tag)) + ;; Default is no sticky tag at all + (t nil)) + (cond ((eq vc-cvs-sticky-tag-display nil) nil) + ((eq vc-cvs-sticky-tag-display t) + (cond ((eq type 'date) (format-time-string + vc-cvs-sticky-date-format-string + tag)) + ((eq type 'symbolic-name) tag) + ((eq type 'revision-number) tag) + (t nil))) + ((functionp vc-cvs-sticky-tag-display) + (funcall vc-cvs-sticky-tag-display tag type)) + (t nil))) + + (set-match-data data)))) + +(defun vc-cvs-parse-entry (file &optional set-state) + "Parse a line from CVS/Entries. +Compare modification time to that of the FILE, set file properties +accordingly. However, `vc-state' is set only if optional arg SET-STATE +is non-nil." + (cond + ;; entry for a "locally added" file (not yet committed) + ((looking-at "/[^/]+/0/") + (vc-file-setprop file 'vc-checkout-time 0) + (vc-file-setprop file 'vc-working-revision "0") + (if set-state (vc-file-setprop file 'vc-state 'added))) + ;; normal entry + ((looking-at + (concat "/[^/]+" + ;; revision + "/\\([^/]*\\)" + ;; timestamp and optional conflict field + "/\\([^/]*\\)/" + ;; options + "\\([^/]*\\)/" + ;; sticky tag + "\\(.\\|\\)" ;Sticky tag type (date or tag name, could be empty) + "\\(.*\\)")) ;Sticky tag + (vc-file-setprop file 'vc-working-revision (match-string 1)) + (vc-file-setprop file 'vc-cvs-sticky-tag + (vc-cvs-parse-sticky-tag (match-string 4) + (match-string 5))) + ;; Compare checkout time and modification time. + ;; This is intentionally different from the algorithm that CVS uses + ;; (which is based on textual comparison), because there can be problems + ;; generating a time string that looks exactly like the one from CVS. + (let* ((time (match-string 2)) + (mtime (nth 5 (file-attributes file))) + (parsed-time (progn (require 'parse-time) + (parse-time-string (concat time " +0000"))))) + (cond ((and (not (string-match "\\+" time)) + (car parsed-time) + (equal mtime (apply 'encode-time parsed-time))) + (vc-file-setprop file 'vc-checkout-time mtime) + (if set-state (vc-file-setprop file 'vc-state 'up-to-date))) + (t + (vc-file-setprop file 'vc-checkout-time 0) + (if set-state (vc-file-setprop file 'vc-state 'edited)))))))) + +;; Completion of revision names. +;; Just so I don't feel like I'm duplicating code from pcl-cvs, I'll use +;; `cvs log' so I can list all the revision numbers rather than only +;; tag names. + +(defun vc-cvs-revision-table (file) + (let (process-file-side-effects + (default-directory (file-name-directory file)) + (res nil)) + (with-temp-buffer + (vc-cvs-command t nil file "log") + (goto-char (point-min)) + (when (re-search-forward "^symbolic names:\n" nil t) + (while (looking-at "^ \\(.*\\): \\(.*\\)") + (push (cons (match-string 1) (match-string 2)) res) + (forward-line 1))) + (while (re-search-forward "^revision \\([0-9.]+\\)" nil t) + (push (match-string 1) res)) + res))) + +(defun vc-cvs-revision-completion-table (files) + (lexical-let ((files files) + table) + (setq table (lazy-completion-table + table (lambda () (vc-cvs-revision-table (car files))))) + table)) + + +(provide 'vc-cvs) + +;; arch-tag: 60e1402a-aa53-4607-927a-cf74f144b432 +;;; vc-cvs.el ends here diff -r 681cd08dc0f7 -r e93288477c43 lisp/vc/vc-dav.el --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/lisp/vc/vc-dav.el Sun Jun 13 22:57:55 2010 +0000 @@ -0,0 +1,189 @@ +;;; vc-dav.el --- vc.el support for WebDAV + +;; Copyright (C) 2001, 2004, 2005, 2006, 2007, 2008, 2009, 2010 Free Software Foundation, Inc. + +;; Author: Bill Perry +;; Maintainer: Bill Perry +;; Keywords: url, vc + +;; 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 3 of the License, 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. If not, see . + + +;;; Commentary: + +;;; Todo: +;; +;; - Some methods need to be updated to match the current vc.el. +;; - rename "version" -> "revision" +;; - some methods need to take a fileset as a parameter instead of a +;; single file. + +;;; Code: + +(require 'url) +(require 'url-dav) + +;;; Required functions for a vc backend +(defun vc-dav-registered (url) + "Return t if URL is registered with a DAV aware server." + (url-dav-vc-registered url)) + +(defun vc-dav-state (url) + "Return the current version control state of URL. +For a list of possible values, see `vc-state'." + ;; Things we can support for WebDAV + ;; + ;; up-to-date - use lockdiscovery + ;; edited - check for an active lock by us + ;; USER - use lockdiscovery + owner + ;; + ;; These don't make sense for WebDAV + ;; needs-patch + ;; needs-merge + ;; unlocked-changes + (let ((locks (url-dav-active-locks url))) + (cond + ((null locks) 'up-to-date) + ((assoc url locks) + ;; SOMEBODY has a lock... let's find out who. + (setq locks (cdr (assoc url locks))) + (if (rassoc url-dav-lock-identifier locks) + ;; _WE_ have a lock + 'edited + (cdr (car locks))))))) + +(defun vc-dav-checkout-model (url) + "Indicate whether URL needs to be \"checked out\" before it can be edited. +See `vc-checkout-model' for a list of possible values." + ;; The only thing we can support with webdav is 'locking + 'locking) + +;; This should figure out the version # of the file somehow. What is +;; the most appropriate property in WebDAV to look at for this? +(defun vc-dav-workfile-version (url) + "Return the current workfile version of URL." + "Unknown") + +(defun vc-dav-register (url &optional rev comment) + "Register URL in the DAV backend." + ;; Do we need to do anything here? FIXME? + ) + +(defun vc-dav-checkin (url rev comment) + "Commit changes in URL to WebDAV. +If REV is non-nil, that should become the new revision number. +COMMENT is used as a check-in comment." + ;; This should PUT the resource and release any locks that we hold. + ) + +(defun vc-dav-checkout (url &optional editable rev destfile) + "Check out revision REV of URL into the working area. + +If EDITABLE is non-nil URL should be writable by the user and if +locking is used for URL, a lock should also be set. + +If REV is non-nil, that is the revision to check out. If REV is the +empty string, that means to check ou tht ehead of the trunk. + +If optional arg DESTFILE is given, it is an alternate filename to +write the contents to. +" + ;; This should LOCK the resource. + ) + +(defun vc-dav-revert (url &optional contents-done) + "Revert URL back to the current workfile version. + +If optional arg CONTENTS-DONE is non-nil, then the contents of FILE +have already been reverted from a version backup, and this function +only needs to update the status of URL within the backend. +" + ;; Should do a GET if !contents_done + ;; Should UNLOCK the file. + ) + +(defun vc-dav-print-log (url) + "Insert the revision log of URL into the *vc* buffer." + ) + +(defun vc-dav-diff (url &optional rev1 rev2) + "Insert the diff for URL into the *vc-diff* buffer. +If REV1 and REV2 are non-nil report differences from REV1 to REV2. +If REV1 is nil, use the current workfile version as the older version. +If REV2 is nil, use the current workfile contents as the nwer version. + +It should return a status of either 0 (no differences found), or +1 (either non-empty diff or the diff is run asynchronously). +" + ;; We should do this asynchronously... + ;; How would we do it at all, that is the question! + ) + + + +;;; Optional functions +;; Should be faster than vc-dav-state - but how? +(defun vc-dav-state-heuristic (url) + "Estimate the version control state of URL at visiting time." + (vc-dav-state url)) + +;; This should use url-dav-get-properties with a depth of `1' to get +;; all the properties. +(defun vc-dav-dir-state (url) + "find the version control state of all files in DIR in a fast way." + ) + +(defun vc-dav-workfile-unchanged-p (url) + "Return non-nil if URL is unchanged from its current workfile version." + ;; Probably impossible with webdav + ) + +(defun vc-dav-responsible-p (url) + "Return non-nil if DAV considers itself `responsible' for URL." + ;; Check for DAV support on the web server. + t) + +(defun vc-dav-could-register (url) + "Return non-nil if URL could be registered under this backend." + ;; Check for DAV support on the web server. + t) + +;;; Unimplemented functions +;; +;; vc-dav-latest-on-branch-p(URL) +;; Return non-nil if the current workfile version of FILE is the +;; latest on its branch. There are no branches in webdav yet. +;; +;; vc-dav-mode-line-string(url) +;; Return a dav-specific mode line string for URL. Are there any +;; specific states that we want exposed? +;; +;; vc-dav-dired-state-info(url) +;; Translate the `vc-state' property of URL into a string that can +;; be used in a vc-dired buffer. Are there any extra states that +;; we want exposed? +;; +;; vc-dav-receive-file(url rev) +;; Let this backend `receive' a file that is already registered +;; under another backend. The default just calls `register', which +;; should be sufficient for WebDAV. +;; +;; vc-dav-unregister(url) +;; Unregister URL. Not possible with WebDAV, other than by +;; deleting the resource. + +(provide 'vc-dav) + +;; arch-tag: 0a0fb9fe-8190-4c0a-a179-5c291d3a467e +;;; vc-dav.el ends here diff -r 681cd08dc0f7 -r e93288477c43 lisp/vc/vc-dir.el --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/lisp/vc/vc-dir.el Sun Jun 13 22:57:55 2010 +0000 @@ -0,0 +1,1256 @@ +;;; vc-dir.el --- Directory status display under VC + +;; Copyright (C) 2007, 2008, 2009, 2010 +;; Free Software Foundation, Inc. + +;; Author: Dan Nicolaescu +;; Keywords: vc tools + +;; 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 3 of the License, 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. If not, see . + +;;; Credits: + +;; The original VC directory status implementation was based on dired. +;; This implementation was inspired by PCL-CVS. +;; Many people contributed comments, ideas and code to this +;; implementation. These include: +;; +;; Alexandre Julliard +;; Stefan Monnier +;; Tom Tromey + +;;; Commentary: +;; + +;;; Todo: see vc.el. + +(require 'vc-hooks) +(require 'vc) +(require 'tool-bar) +(require 'ewoc) + +;;; Code: +(eval-when-compile + (require 'cl)) + +(defcustom vc-dir-mode-hook nil + "Normal hook run by `vc-dir-mode'. +See `run-hooks'." + :type 'hook + :group 'vc) + +;; Used to store information for the files displayed in the directory buffer. +;; Each item displayed corresponds to one of these defstructs. +(defstruct (vc-dir-fileinfo + (:copier nil) + (:type list) ;So we can use `member' on lists of FIs. + (:constructor + ;; We could define it as an alias for `list'. + vc-dir-create-fileinfo (name state &optional extra marked directory)) + (:conc-name vc-dir-fileinfo->)) + name ;Keep it as first, for `member'. + state + ;; For storing backend specific information. + extra + marked + ;; To keep track of not updated files during a global refresh + needs-update + ;; To distinguish files and directories. + directory) + +(defvar vc-ewoc nil) + +(defvar vc-dir-process-buffer nil + "The buffer used for the asynchronous call that computes status.") + +(defvar vc-dir-backend nil + "The backend used by the current *vc-dir* buffer.") + +(defun vc-dir-move-to-goal-column () + ;; Used to keep the cursor on the file name column. + (beginning-of-line) + (unless (eolp) + ;; Must be in sync with vc-default-dir-printer. + (forward-char 25))) + +(defun vc-dir-prepare-status-buffer (bname dir backend &optional create-new) + "Find a buffer named BNAME showing DIR, or create a new one." + (setq dir (file-name-as-directory (expand-file-name dir))) + (let* ;; Look for another buffer name BNAME visiting the same directory. + ((buf (save-excursion + (unless create-new + (dolist (buffer vc-dir-buffers) + (when (buffer-live-p buffer) + (set-buffer buffer) + (when (and (derived-mode-p 'vc-dir-mode) + (eq vc-dir-backend backend) + (string= default-directory dir)) + (return buffer)))))))) + (or buf + ;; Create a new buffer named BNAME. + ;; We pass a filename to create-file-buffer because it is what + ;; the function expects, and also what uniquify needs (if active) + (with-current-buffer (create-file-buffer (expand-file-name bname dir)) + (cd dir) + (vc-setup-buffer (current-buffer)) + ;; Reset the vc-parent-buffer-name so that it does not appear + ;; in the mode-line. + (setq vc-parent-buffer-name nil) + (current-buffer))))) + +(defvar vc-dir-menu-map + (let ((map (make-sparse-keymap "VC-dir"))) + (define-key map [quit] + '(menu-item "Quit" quit-window + :help "Quit")) + (define-key map [kill] + '(menu-item "Kill Update Command" vc-dir-kill-dir-status-process + :enable (vc-dir-busy) + :help "Kill the command that updates the directory buffer")) + (define-key map [refresh] + '(menu-item "Refresh" revert-buffer + :enable (not (vc-dir-busy)) + :help "Refresh the contents of the directory buffer")) + (define-key map [remup] + '(menu-item "Hide up-to-date" vc-dir-hide-up-to-date + :help "Hide up-to-date items from display")) + ;; Movement. + (define-key map [sepmv] '("--")) + (define-key map [next-line] + '(menu-item "Next line" vc-dir-next-line + :help "Go to the next line" :keys "n")) + (define-key map [previous-line] + '(menu-item "Previous line" vc-dir-previous-line + :help "Go to the previous line")) + ;; Marking. + (define-key map [sepmrk] '("--")) + (define-key map [unmark-all] + '(menu-item "Unmark All" vc-dir-unmark-all-files + :help "Unmark all files that are in the same state as the current file\ +\nWith prefix argument unmark all files")) + (define-key map [unmark-previous] + '(menu-item "Unmark previous " vc-dir-unmark-file-up + :help "Move to the previous line and unmark the file")) + + (define-key map [mark-all] + '(menu-item "Mark All" vc-dir-mark-all-files + :help "Mark all files that are in the same state as the current file\ +\nWith prefix argument mark all files")) + (define-key map [unmark] + '(menu-item "Unmark" vc-dir-unmark + :help "Unmark the current file or all files in the region")) + + (define-key map [mark] + '(menu-item "Mark" vc-dir-mark + :help "Mark the current file or all files in the region")) + + (define-key map [sepopn] '("--")) + (define-key map [qr] + '(menu-item "Query Replace in Files..." vc-dir-query-replace-regexp + :help "Replace a string in the marked files")) + (define-key map [se] + '(menu-item "Search Files..." vc-dir-search + :help "Search a regexp in the marked files")) + (define-key map [ires] + '(menu-item "Isearch Regexp Files..." vc-dir-isearch-regexp + :help "Incremental search a regexp in the marked files")) + (define-key map [ise] + '(menu-item "Isearch Files..." vc-dir-isearch + :help "Incremental search a string in the marked files")) + (define-key map [open-other] + '(menu-item "Open in other window" vc-dir-find-file-other-window + :help "Find the file on the current line, in another window")) + (define-key map [open] + '(menu-item "Open file" vc-dir-find-file + :help "Find the file on the current line")) + (define-key map [sepvcdet] '("--")) + ;; FIXME: This needs a key binding. And maybe a better name + ;; ("Insert" like PCL-CVS uses does not sound that great either)... + (define-key map [ins] + '(menu-item "Show File" vc-dir-show-fileentry + :help "Show a file in the VC status listing even though it might be up to date")) + (define-key map [annotate] + '(menu-item "Annotate" vc-annotate + :help "Display the edit history of the current file using colors")) + (define-key map [diff] + '(menu-item "Compare with Base Version" vc-diff + :help "Compare file set with the base version")) + (define-key map [logo] + '(menu-item "Show Outgoing Log" vc-log-outgoing + :help "Show a log of changes that will be sent with a push operation")) + (define-key map [logi] + '(menu-item "Show Incoming Log" vc-log-incoming + :help "Show a log of changes that will be received with a pull operation")) + (define-key map [log] + '(menu-item "Show history" vc-print-log + :help "List the change log of the current file set in a window")) + (define-key map [rlog] + '(menu-item "Show Top of the Tree History " vc-print-root-log + :help "List the change log for the current tree in a window")) + ;; VC commands. + (define-key map [sepvccmd] '("--")) + (define-key map [update] + '(menu-item "Update to latest version" vc-update + :help "Update the current fileset's files to their tip revisions")) + (define-key map [revert] + '(menu-item "Revert to base version" vc-revert + :help "Revert working copies of the selected fileset to their repository contents.")) + (define-key map [next-action] + ;; FIXME: This really really really needs a better name! + ;; And a key binding too. + '(menu-item "Check In/Out" vc-next-action + :help "Do the next logical version control operation on the current fileset")) + (define-key map [register] + '(menu-item "Register" vc-register + :help "Register file set into the version control system")) + map) + "Menu for VC dir.") + +;; VC backends can use this to add mode-specific menu items to +;; vc-dir-menu-map. +(defun vc-dir-menu-map-filter (orig-binding) + (when (and (symbolp orig-binding) (fboundp orig-binding)) + (setq orig-binding (indirect-function orig-binding))) + (let ((ext-binding + (when (derived-mode-p 'vc-dir-mode) + (vc-call-backend vc-dir-backend 'extra-status-menu)))) + (if (null ext-binding) + orig-binding + (append orig-binding + '("----") + ext-binding)))) + +(defvar vc-dir-mode-map + (let ((map (make-sparse-keymap))) + ;; VC commands + (define-key map "v" 'vc-next-action) ;; C-x v v + (define-key map "=" 'vc-diff) ;; C-x v = + (define-key map "i" 'vc-register) ;; C-x v i + (define-key map "+" 'vc-update) ;; C-x v + + (define-key map "l" 'vc-print-log) ;; C-x v l + ;; More confusing than helpful, probably + ;;(define-key map "R" 'vc-revert) ;; u is taken by vc-dir-unmark. + ;;(define-key map "A" 'vc-annotate) ;; g is taken by revert-buffer + ;; bound by `special-mode'. + ;; Marking. + (define-key map "m" 'vc-dir-mark) + (define-key map "M" 'vc-dir-mark-all-files) + (define-key map "u" 'vc-dir-unmark) + (define-key map "U" 'vc-dir-unmark-all-files) + (define-key map "\C-?" 'vc-dir-unmark-file-up) + (define-key map "\M-\C-?" 'vc-dir-unmark-all-files) + ;; Movement. + (define-key map "n" 'vc-dir-next-line) + (define-key map " " 'vc-dir-next-line) + (define-key map "\t" 'vc-dir-next-directory) + (define-key map "p" 'vc-dir-previous-line) + (define-key map [backtab] 'vc-dir-previous-directory) + ;;; Rebind paragraph-movement commands. + (define-key map "\M-}" 'vc-dir-next-directory) + (define-key map "\M-{" 'vc-dir-previous-directory) + (define-key map [C-down] 'vc-dir-next-directory) + (define-key map [C-up] 'vc-dir-previous-directory) + ;; The remainder. + (define-key map "f" 'vc-dir-find-file) + (define-key map "\C-m" 'vc-dir-find-file) + (define-key map "o" 'vc-dir-find-file-other-window) + (define-key map "\C-c\C-c" 'vc-dir-kill-dir-status-process) + (define-key map [down-mouse-3] 'vc-dir-menu) + (define-key map [mouse-2] 'vc-dir-toggle-mark) + (define-key map [follow-link] 'mouse-face) + (define-key map "x" 'vc-dir-hide-up-to-date) + (define-key map [?\C-k] 'vc-dir-kill-line) + (define-key map "S" 'vc-dir-search) ;; FIXME: Maybe use A like dired? + (define-key map "Q" 'vc-dir-query-replace-regexp) + (define-key map (kbd "M-s a C-s") 'vc-dir-isearch) + (define-key map (kbd "M-s a M-C-s") 'vc-dir-isearch-regexp) + + ;; Hook up the menu. + (define-key map [menu-bar vc-dir-mode] + `(menu-item + ;; VC backends can use this to add mode-specific menu items to + ;; vc-dir-menu-map. + "VC-dir" ,vc-dir-menu-map :filter vc-dir-menu-map-filter)) + map) + "Keymap for directory buffer.") + +(defmacro vc-dir-at-event (event &rest body) + "Evaluate BODY with point located at event-start of EVENT. +If BODY uses EVENT, it should be a variable, + otherwise it will be evaluated twice." + (let ((posn (make-symbol "vc-dir-at-event-posn"))) + `(save-excursion + (unless (equal ,event '(tool-bar)) + (let ((,posn (event-start ,event))) + (set-buffer (window-buffer (posn-window ,posn))) + (goto-char (posn-point ,posn)))) + ,@body))) + +(defun vc-dir-menu (e) + "Popup the VC dir menu." + (interactive "e") + (vc-dir-at-event e (popup-menu vc-dir-menu-map e))) + +(defvar vc-dir-tool-bar-map + (let ((map (make-sparse-keymap))) + (tool-bar-local-item-from-menu 'vc-dir-find-file "open" + map vc-dir-mode-map) + (tool-bar-local-item "bookmark_add" + 'vc-dir-toggle-mark 'vc-dir-toggle-mark map + :help "Toggle mark on current item" + :label "Toggle Mark") + (tool-bar-local-item-from-menu 'vc-dir-previous-line "left-arrow" + map vc-dir-mode-map + :rtl "right-arrow") + (tool-bar-local-item-from-menu 'vc-dir-next-line "right-arrow" + map vc-dir-mode-map + :rtl "left-arrow") + (tool-bar-local-item-from-menu 'vc-print-log "info" + map vc-dir-mode-map) + (tool-bar-local-item-from-menu 'revert-buffer "refresh" + map vc-dir-mode-map) + (tool-bar-local-item-from-menu 'nonincremental-search-forward + "search" map nil + :label "Search") + (tool-bar-local-item-from-menu 'vc-dir-query-replace-regexp + "search-replace" map vc-dir-mode-map + :label "Replace") + (tool-bar-local-item-from-menu 'vc-dir-kill-dir-status-process "cancel" + map vc-dir-mode-map + :label "Cancel") + (tool-bar-local-item-from-menu 'quit-window "exit" + map vc-dir-mode-map) + map)) + +(defun vc-dir-node-directory (node) + ;; Compute the directory for NODE. + ;; If it's a directory node, get it from the node. + (let ((data (ewoc-data node))) + (or (vc-dir-fileinfo->directory data) + ;; Otherwise compute it from the file name. + (file-name-directory + (directory-file-name + (expand-file-name + (vc-dir-fileinfo->name data))))))) + +(defun vc-dir-update (entries buffer &optional noinsert) + "Update BUFFER's ewoc from the list of ENTRIES. +If NOINSERT, ignore elements on ENTRIES which are not in the ewoc." + ;; Add ENTRIES to the vc-dir buffer BUFFER. + (with-current-buffer buffer + ;; Insert the entries sorted by name into the ewoc. + ;; We assume the ewoc is sorted too, which should be the + ;; case if we always add entries with vc-dir-update. + (setq entries + ;; Sort: first files and then subdirectories. + ;; XXX: this is VERY inefficient, it computes the directory + ;; names too many times + (sort entries + (lambda (entry1 entry2) + (let ((dir1 (file-name-directory + (directory-file-name (expand-file-name (car entry1))))) + (dir2 (file-name-directory + (directory-file-name (expand-file-name (car entry2)))))) + (cond + ((string< dir1 dir2) t) + ((not (string= dir1 dir2)) nil) + ((string< (car entry1) (car entry2)))))))) + ;; Insert directory entries in the right places. + (let ((entry (car entries)) + (node (ewoc-nth vc-ewoc 0)) + (to-remove nil) + (dotname (file-relative-name default-directory))) + ;; Insert . if it is not present. + (unless node + (ewoc-enter-last + vc-ewoc (vc-dir-create-fileinfo + dotname nil nil nil default-directory)) + (setq node (ewoc-nth vc-ewoc 0))) + + (while (and entry node) + (let* ((entryfile (car entry)) + (entrydir (file-name-directory (directory-file-name + (expand-file-name entryfile)))) + (nodedir (vc-dir-node-directory node))) + (cond + ;; First try to find the directory. + ((string-lessp nodedir entrydir) + (setq node (ewoc-next vc-ewoc node))) + ((string-equal nodedir entrydir) + ;; Found the directory, find the place for the file name. + (let ((nodefile (vc-dir-fileinfo->name (ewoc-data node)))) + (cond + ((string= nodefile dotname) + (setq node (ewoc-next vc-ewoc node))) + ((string-lessp nodefile entryfile) + (setq node (ewoc-next vc-ewoc node))) + ((string-equal nodefile entryfile) + (if (nth 1 entry) + (progn + (setf (vc-dir-fileinfo->state (ewoc-data node)) (nth 1 entry)) + (setf (vc-dir-fileinfo->extra (ewoc-data node)) (nth 2 entry)) + (setf (vc-dir-fileinfo->needs-update (ewoc-data node)) nil) + (ewoc-invalidate vc-ewoc node)) + ;; If the state is nil, the file does not exist + ;; anymore, so remember the entry so we can remove + ;; it after we are done inserting all ENTRIES. + (push node to-remove)) + (setq entries (cdr entries)) + (setq entry (car entries)) + (setq node (ewoc-next vc-ewoc node))) + (t + (ewoc-enter-before vc-ewoc node + (apply 'vc-dir-create-fileinfo entry)) + (setq entries (cdr entries)) + (setq entry (car entries)))))) + (t + ;; We might need to insert a directory node if the + ;; previous node was in a different directory. + (let* ((rd (file-relative-name entrydir)) + (prev-node (ewoc-prev vc-ewoc node)) + (prev-dir (vc-dir-node-directory prev-node))) + (unless (string-equal entrydir prev-dir) + (ewoc-enter-before + vc-ewoc node (vc-dir-create-fileinfo rd nil nil nil entrydir)))) + ;; Now insert the node itself. + (ewoc-enter-before vc-ewoc node + (apply 'vc-dir-create-fileinfo entry)) + (setq entries (cdr entries) entry (car entries)))))) + ;; We're past the last node, all remaining entries go to the end. + (unless (or node noinsert) + (let ((lastdir (vc-dir-node-directory (ewoc-nth vc-ewoc -1)))) + (dolist (entry entries) + (let ((entrydir (file-name-directory + (directory-file-name (expand-file-name (car entry)))))) + ;; Insert a directory node if needed. + (unless (string-equal lastdir entrydir) + (setq lastdir entrydir) + (let ((rd (file-relative-name entrydir))) + (ewoc-enter-last + vc-ewoc (vc-dir-create-fileinfo rd nil nil nil entrydir)))) + ;; Now insert the node itself. + (ewoc-enter-last vc-ewoc + (apply 'vc-dir-create-fileinfo entry)))))) + (when to-remove + (let ((inhibit-read-only t)) + (apply 'ewoc-delete vc-ewoc (nreverse to-remove))))))) + +(defun vc-dir-busy () + (and (buffer-live-p vc-dir-process-buffer) + (get-buffer-process vc-dir-process-buffer))) + +(defun vc-dir-kill-dir-status-process () + "Kill the temporary buffer and associated process." + (interactive) + (when (buffer-live-p vc-dir-process-buffer) + (let ((proc (get-buffer-process vc-dir-process-buffer))) + (when proc (delete-process proc)) + (setq vc-dir-process-buffer nil) + (setq mode-line-process nil)))) + +(defun vc-dir-kill-query () + ;; Make sure that when the status buffer is killed the update + ;; process running in background is also killed. + (if (vc-dir-busy) + (when (y-or-n-p "Status update process running, really kill status buffer? ") + (vc-dir-kill-dir-status-process) + t) + t)) + +(defun vc-dir-next-line (arg) + "Go to the next line. +If a prefix argument is given, move by that many lines." + (interactive "p") + (with-no-warnings + (ewoc-goto-next vc-ewoc arg) + (vc-dir-move-to-goal-column))) + +(defun vc-dir-previous-line (arg) + "Go to the previous line. +If a prefix argument is given, move by that many lines." + (interactive "p") + (ewoc-goto-prev vc-ewoc arg) + (vc-dir-move-to-goal-column)) + +(defun vc-dir-next-directory () + "Go to the next directory." + (interactive) + (let ((orig (point))) + (if + (catch 'foundit + (while t + (let* ((next (ewoc-next vc-ewoc (ewoc-locate vc-ewoc)))) + (cond ((not next) + (throw 'foundit t)) + (t + (progn + (ewoc-goto-node vc-ewoc next) + (vc-dir-move-to-goal-column) + (if (vc-dir-fileinfo->directory (ewoc-data next)) + (throw 'foundit nil)))))))) + (goto-char orig)))) + +(defun vc-dir-previous-directory () + "Go to the previous directory." + (interactive) + (let ((orig (point))) + (if + (catch 'foundit + (while t + (let* ((prev (ewoc-prev vc-ewoc (ewoc-locate vc-ewoc)))) + (cond ((not prev) + (throw 'foundit t)) + (t + (progn + (ewoc-goto-node vc-ewoc prev) + (vc-dir-move-to-goal-column) + (if (vc-dir-fileinfo->directory (ewoc-data prev)) + (throw 'foundit nil)))))))) + (goto-char orig)))) + +(defun vc-dir-mark-unmark (mark-unmark-function) + (if (use-region-p) + (let ((firstl (line-number-at-pos (region-beginning))) + (lastl (line-number-at-pos (region-end)))) + (save-excursion + (goto-char (region-beginning)) + (while (<= (line-number-at-pos) lastl) + (funcall mark-unmark-function)))) + (funcall mark-unmark-function))) + +(defun vc-dir-parent-marked-p (arg) + ;; Return nil if none of the parent directories of arg is marked. + (let* ((argdir (vc-dir-node-directory arg)) + (arglen (length argdir)) + (crt arg) + data dir) + ;; Go through the predecessors, checking if any directory that is + ;; a parent is marked. + (while (setq crt (ewoc-prev vc-ewoc crt)) + (setq data (ewoc-data crt)) + (setq dir (vc-dir-node-directory crt)) + (when (and (vc-dir-fileinfo->directory data) + (vc-string-prefix-p dir argdir)) + (when (vc-dir-fileinfo->marked data) + (error "Cannot mark `%s', parent directory `%s' marked" + (vc-dir-fileinfo->name (ewoc-data arg)) + (vc-dir-fileinfo->name data))))) + nil)) + +(defun vc-dir-children-marked-p (arg) + ;; Return nil if none of the children of arg is marked. + (let* ((argdir-re (concat "\\`" (regexp-quote (vc-dir-node-directory arg)))) + (is-child t) + (crt arg) + data dir) + (while (and is-child (setq crt (ewoc-next vc-ewoc crt))) + (setq data (ewoc-data crt)) + (setq dir (vc-dir-node-directory crt)) + (if (string-match argdir-re dir) + (when (vc-dir-fileinfo->marked data) + (error "Cannot mark `%s', child `%s' marked" + (vc-dir-fileinfo->name (ewoc-data arg)) + (vc-dir-fileinfo->name data))) + ;; We are done, we got to an entry that is not a child of `arg'. + (setq is-child nil))) + nil)) + +(defun vc-dir-mark-file (&optional arg) + ;; Mark ARG or the current file and move to the next line. + (let* ((crt (or arg (ewoc-locate vc-ewoc))) + (file (ewoc-data crt)) + (isdir (vc-dir-fileinfo->directory file))) + (when (or (and isdir (not (vc-dir-children-marked-p crt))) + (and (not isdir) (not (vc-dir-parent-marked-p crt)))) + (setf (vc-dir-fileinfo->marked file) t) + (ewoc-invalidate vc-ewoc crt) + (unless (or arg (mouse-event-p last-command-event)) + (vc-dir-next-line 1))))) + +(defun vc-dir-mark () + "Mark the current file or all files in the region. +If the region is active, mark all the files in the region. +Otherwise mark the file on the current line and move to the next +line." + (interactive) + (vc-dir-mark-unmark 'vc-dir-mark-file)) + +(defun vc-dir-mark-all-files (arg) + "Mark all files with the same state as the current one. +With a prefix argument mark all files. +If the current entry is a directory, mark all child files. + +The commands operate on files that are on the same state. +This command is intended to make it easy to select all files that +share the same state." + (interactive "P") + (if arg + ;; Mark all files. + (progn + ;; First check that no directory is marked, we can't mark + ;; files in that case. + (ewoc-map + (lambda (filearg) + (when (and (vc-dir-fileinfo->directory filearg) + (vc-dir-fileinfo->marked filearg)) + (error "Cannot mark all files, directory `%s' marked" + (vc-dir-fileinfo->name filearg)))) + vc-ewoc) + (ewoc-map + (lambda (filearg) + (unless (vc-dir-fileinfo->marked filearg) + (setf (vc-dir-fileinfo->marked filearg) t) + t)) + vc-ewoc)) + (let ((data (ewoc-data (ewoc-locate vc-ewoc)))) + (if (vc-dir-fileinfo->directory data) + ;; It's a directory, mark child files. + (let ((crt (ewoc-locate vc-ewoc))) + (unless (vc-dir-children-marked-p crt) + (while (setq crt (ewoc-next vc-ewoc crt)) + (let ((crt-data (ewoc-data crt))) + (unless (vc-dir-fileinfo->directory crt-data) + (setf (vc-dir-fileinfo->marked crt-data) t) + (ewoc-invalidate vc-ewoc crt)))))) + ;; It's a file + (let ((state (vc-dir-fileinfo->state data)) + (crt (ewoc-nth vc-ewoc 0))) + (while crt + (let ((crt-data (ewoc-data crt))) + (when (and (not (vc-dir-fileinfo->marked crt-data)) + (eq (vc-dir-fileinfo->state crt-data) state) + (not (vc-dir-fileinfo->directory crt-data))) + (vc-dir-mark-file crt))) + (setq crt (ewoc-next vc-ewoc crt)))))))) + +(defun vc-dir-unmark-file () + ;; Unmark the current file and move to the next line. + (let* ((crt (ewoc-locate vc-ewoc)) + (file (ewoc-data crt))) + (setf (vc-dir-fileinfo->marked file) nil) + (ewoc-invalidate vc-ewoc crt) + (unless (mouse-event-p last-command-event) + (vc-dir-next-line 1)))) + +(defun vc-dir-unmark () + "Unmark the current file or all files in the region. +If the region is active, unmark all the files in the region. +Otherwise mark the file on the current line and move to the next +line." + (interactive) + (vc-dir-mark-unmark 'vc-dir-unmark-file)) + +(defun vc-dir-unmark-file-up () + "Move to the previous line and unmark the file." + (interactive) + ;; If we're on the first line, we won't move up, but we will still + ;; remove the mark. This seems a bit odd but it is what buffer-menu + ;; does. + (let* ((prev (ewoc-goto-prev vc-ewoc 1)) + (file (ewoc-data prev))) + (setf (vc-dir-fileinfo->marked file) nil) + (ewoc-invalidate vc-ewoc prev) + (vc-dir-move-to-goal-column))) + +(defun vc-dir-unmark-all-files (arg) + "Unmark all files with the same state as the current one. +With a prefix argument unmark all files. +If the current entry is a directory, unmark all the child files. + +The commands operate on files that are on the same state. +This command is intended to make it easy to deselect all files +that share the same state." + (interactive "P") + (if arg + (ewoc-map + (lambda (filearg) + (when (vc-dir-fileinfo->marked filearg) + (setf (vc-dir-fileinfo->marked filearg) nil) + t)) + vc-ewoc) + (let* ((crt (ewoc-locate vc-ewoc)) + (data (ewoc-data crt))) + (if (vc-dir-fileinfo->directory data) + ;; It's a directory, unmark child files. + (while (setq crt (ewoc-next vc-ewoc crt)) + (let ((crt-data (ewoc-data crt))) + (unless (vc-dir-fileinfo->directory crt-data) + (setf (vc-dir-fileinfo->marked crt-data) nil) + (ewoc-invalidate vc-ewoc crt)))) + ;; It's a file + (let ((crt-state (vc-dir-fileinfo->state (ewoc-data crt)))) + (ewoc-map + (lambda (filearg) + (when (and (vc-dir-fileinfo->marked filearg) + (eq (vc-dir-fileinfo->state filearg) crt-state)) + (setf (vc-dir-fileinfo->marked filearg) nil) + t)) + vc-ewoc)))))) + +(defun vc-dir-toggle-mark-file () + (let* ((crt (ewoc-locate vc-ewoc)) + (file (ewoc-data crt))) + (if (vc-dir-fileinfo->marked file) + (vc-dir-unmark-file) + (vc-dir-mark-file)))) + +(defun vc-dir-toggle-mark (e) + (interactive "e") + (vc-dir-at-event e (vc-dir-mark-unmark 'vc-dir-toggle-mark-file))) + +(defun vc-dir-delete-file () + "Delete the marked files, or the current file if no marks." + (interactive) + (mapc 'vc-delete-file (or (vc-dir-marked-files) + (list (vc-dir-current-file))))) + +(defun vc-dir-find-file () + "Find the file on the current line." + (interactive) + (find-file (vc-dir-current-file))) + +(defun vc-dir-find-file-other-window (&optional event) + "Find the file on the current line, in another window." + (interactive (list last-nonmenu-event)) + (if event (posn-set-point (event-end event))) + (find-file-other-window (vc-dir-current-file))) + +(defun vc-dir-isearch () + "Search for a string through all marked buffers using Isearch." + (interactive) + (multi-isearch-files + (mapcar 'car (vc-dir-marked-only-files-and-states)))) + +(defun vc-dir-isearch-regexp () + "Search for a regexp through all marked buffers using Isearch." + (interactive) + (multi-isearch-files-regexp + (mapcar 'car (vc-dir-marked-only-files-and-states)))) + +(defun vc-dir-search (regexp) + "Search through all marked files for a match for REGEXP. +For marked directories, use the files displayed from those directories. +Stops when a match is found. +To continue searching for next match, use command \\[tags-loop-continue]." + (interactive "sSearch marked files (regexp): ") + (tags-search regexp '(mapcar 'car (vc-dir-marked-only-files-and-states)))) + +(defun vc-dir-query-replace-regexp (from to &optional delimited) + "Do `query-replace-regexp' of FROM with TO, on all marked files. +For marked directories, use the files displayed from those directories. +If a directory is marked, then use the files displayed for that directory. +Third arg DELIMITED (prefix arg) means replace only word-delimited matches. +If you exit (\\[keyboard-quit], RET or q), you can resume the query replace +with the command \\[tags-loop-continue]." + ;; FIXME: this is almost a copy of `dired-do-replace-regexp'. This + ;; should probably be made generic and used in both places instead of + ;; duplicating it here. + (interactive + (let ((common + (query-replace-read-args + "Query replace regexp in marked files" t t))) + (list (nth 0 common) (nth 1 common) (nth 2 common)))) + (dolist (file (mapcar 'car (vc-dir-marked-only-files-and-states))) + (let ((buffer (get-file-buffer file))) + (if (and buffer (with-current-buffer buffer + buffer-read-only)) + (error "File `%s' is visited read-only" file)))) + (tags-query-replace from to delimited + '(mapcar 'car (vc-dir-marked-only-files-and-states)))) + +(defun vc-dir-current-file () + (let ((node (ewoc-locate vc-ewoc))) + (unless node + (error "No file available")) + (expand-file-name (vc-dir-fileinfo->name (ewoc-data node))))) + +(defun vc-dir-marked-files () + "Return the list of marked files." + (mapcar + (lambda (elem) (expand-file-name (vc-dir-fileinfo->name elem))) + (ewoc-collect vc-ewoc 'vc-dir-fileinfo->marked))) + +(defun vc-dir-marked-only-files-and-states () + "Return the list of conses (FILE . STATE) for the marked files. +For marked directories return the corresponding conses for the +child files." + (let ((crt (ewoc-nth vc-ewoc 0)) + result) + (while crt + (let ((crt-data (ewoc-data crt))) + (if (vc-dir-fileinfo->marked crt-data) + ;; FIXME: use vc-dir-child-files-and-states here instead of duplicating it. + (if (vc-dir-fileinfo->directory crt-data) + (let* ((dir (vc-dir-fileinfo->directory crt-data)) + (dirlen (length dir)) + data) + (while + (and (setq crt (ewoc-next vc-ewoc crt)) + (vc-string-prefix-p dir + (progn + (setq data (ewoc-data crt)) + (vc-dir-node-directory crt)))) + (unless (vc-dir-fileinfo->directory data) + (push + (cons (expand-file-name (vc-dir-fileinfo->name data)) + (vc-dir-fileinfo->state data)) + result)))) + (push (cons (expand-file-name (vc-dir-fileinfo->name crt-data)) + (vc-dir-fileinfo->state crt-data)) + result) + (setq crt (ewoc-next vc-ewoc crt))) + (setq crt (ewoc-next vc-ewoc crt))))) + (nreverse result))) + +(defun vc-dir-child-files-and-states () + "Return the list of conses (FILE . STATE) for child files of the current entry if it's a directory. +If it is a file, return the corresponding cons for the file itself." + (let* ((crt (ewoc-locate vc-ewoc)) + (crt-data (ewoc-data crt)) + result) + (if (vc-dir-fileinfo->directory crt-data) + (let* ((dir (vc-dir-fileinfo->directory crt-data)) + (dirlen (length dir)) + data) + (while + (and (setq crt (ewoc-next vc-ewoc crt)) + (vc-string-prefix-p dir (progn + (setq data (ewoc-data crt)) + (vc-dir-node-directory crt)))) + (unless (vc-dir-fileinfo->directory data) + (push + (cons (expand-file-name (vc-dir-fileinfo->name data)) + (vc-dir-fileinfo->state data)) + result)))) + (push + (cons (expand-file-name (vc-dir-fileinfo->name crt-data)) + (vc-dir-fileinfo->state crt-data)) result)) + (nreverse result))) + +(defun vc-dir-recompute-file-state (fname def-dir) + (let* ((file-short (file-relative-name fname def-dir)) + (remove-me-when-CVS-works + (when (eq vc-dir-backend 'CVS) + ;; FIXME: Warning: UGLY HACK. The CVS backend caches the state + ;; info, this forces the backend to update it. + (vc-call-backend vc-dir-backend 'registered fname))) + (state (vc-call-backend vc-dir-backend 'state fname)) + (extra (vc-call-backend vc-dir-backend + 'status-fileinfo-extra fname))) + (list file-short state extra))) + +(defun vc-dir-find-child-files (dirname) + ;; Give a DIRNAME string return the list of all child files shown in + ;; the current *vc-dir* buffer. + (let ((crt (ewoc-nth vc-ewoc 0)) + children + dname) + ;; Find DIR + (while (and crt (not (vc-string-prefix-p + dirname (vc-dir-node-directory crt)))) + (setq crt (ewoc-next vc-ewoc crt))) + (while (and crt (vc-string-prefix-p + dirname + (setq dname (vc-dir-node-directory crt)))) + (let ((data (ewoc-data crt))) + (unless (vc-dir-fileinfo->directory data) + (push (expand-file-name (vc-dir-fileinfo->name data)) children))) + (setq crt (ewoc-next vc-ewoc crt))) + children)) + +(defun vc-dir-resync-directory-files (dirname) + ;; Update the entries for all the child files of DIRNAME shown in + ;; the current *vc-dir* buffer. + (let ((files (vc-dir-find-child-files dirname)) + (ddir default-directory) + fileentries) + (when files + (dolist (crt files) + (push (vc-dir-recompute-file-state crt ddir) + fileentries)) + (vc-dir-update fileentries (current-buffer))))) + +(defun vc-dir-resynch-file (&optional fname) + "Update the entries for FNAME in any directory buffers that list it." + (let ((file (or fname (expand-file-name buffer-file-name))) + (drop '())) + (save-current-buffer + ;; look for a vc-dir buffer that might show this file. + (dolist (status-buf vc-dir-buffers) + (if (not (buffer-live-p status-buf)) + (push status-buf drop) + (set-buffer status-buf) + (if (not (derived-mode-p 'vc-dir-mode)) + (push status-buf drop) + (let ((ddir default-directory)) + (when (vc-string-prefix-p ddir file) + (if (file-directory-p file) + (progn + (vc-dir-resync-directory-files file) + (ewoc-set-hf vc-ewoc + (vc-dir-headers vc-dir-backend default-directory) "")) + (let ((state (vc-dir-recompute-file-state file ddir))) + (vc-dir-update + (list state) + status-buf (eq (cadr state) 'up-to-date)))))))))) + ;; Remove out-of-date entries from vc-dir-buffers. + (dolist (b drop) (setq vc-dir-buffers (delq b vc-dir-buffers))))) + +(defvar use-vc-backend) ;; dynamically bound + +(define-derived-mode vc-dir-mode special-mode "VC dir" + "Major mode for VC directory buffers. +Marking/Unmarking key bindings and actions: +m - mark a file/directory + - if the region is active, mark all the files in region. + Restrictions: - a file cannot be marked if any parent directory is marked + - a directory cannot be marked if any child file or + directory is marked +u - unmark a file/directory + - if the region is active, unmark all the files in region. +M - if the cursor is on a file: mark all the files with the same state as + the current file + - if the cursor is on a directory: mark all child files + - with a prefix argument: mark all files +U - if the cursor is on a file: unmark all the files with the same state + as the current file + - if the cursor is on a directory: unmark all child files + - with a prefix argument: unmark all files +mouse-2 - toggles the mark state + +VC commands +VC commands in the `C-x v' prefix can be used. +VC commands act on the marked entries. If nothing is marked, VC +commands act on the current entry. + +Search & Replace +S - searches the marked files +Q - does a query replace on the marked files +M-s a C-s - does an isearch on the marked files +M-s a C-M-s - does a regexp isearch on the marked files +If nothing is marked, these commands act on the current entry. +When a directory is current or marked, the Search & Replace +commands act on the child files of that directory that are displayed in +the *vc-dir* buffer. + +\\{vc-dir-mode-map}" + (set (make-local-variable 'vc-dir-backend) use-vc-backend) + (setq buffer-read-only t) + (when (boundp 'tool-bar-map) + (set (make-local-variable 'tool-bar-map) vc-dir-tool-bar-map)) + (let ((buffer-read-only nil)) + (erase-buffer) + (set (make-local-variable 'vc-dir-process-buffer) nil) + (set (make-local-variable 'vc-ewoc) (ewoc-create #'vc-dir-printer)) + (set (make-local-variable 'revert-buffer-function) + 'vc-dir-revert-buffer-function) + (setq list-buffers-directory (expand-file-name "*vc-dir*" default-directory)) + (add-to-list 'vc-dir-buffers (current-buffer)) + ;; Make sure that if the directory buffer is killed, the update + ;; process running in the background is also killed. + (add-hook 'kill-buffer-query-functions 'vc-dir-kill-query nil t) + (hack-dir-local-variables-non-file-buffer) + (vc-dir-refresh))) + +(defun vc-dir-headers (backend dir) + "Display the headers in the *VC dir* buffer. +It calls the `dir-extra-headers' backend method to display backend +specific headers." + (concat + ;; First layout the common headers. + (propertize "VC backend : " 'face 'font-lock-type-face) + (propertize (format "%s\n" backend) 'face 'font-lock-variable-name-face) + (propertize "Working dir: " 'face 'font-lock-type-face) + (propertize (format "%s\n" (abbreviate-file-name dir)) + 'face 'font-lock-variable-name-face) + ;; Then the backend specific ones. + (vc-call-backend backend 'dir-extra-headers dir) + "\n")) + +(defun vc-dir-refresh-files (files default-state) + "Refresh some files in the *VC-dir* buffer." + (let ((def-dir default-directory) + (backend vc-dir-backend)) + (vc-set-mode-line-busy-indicator) + ;; Call the `dir-status-file' backend function. + ;; `dir-status-file' is supposed to be asynchronous. + ;; It should compute the results, and then call the function + ;; passed as an argument in order to update the vc-dir buffer + ;; with the results. + (unless (buffer-live-p vc-dir-process-buffer) + (setq vc-dir-process-buffer + (generate-new-buffer (format " *VC-%s* tmp status" backend)))) + (lexical-let ((buffer (current-buffer))) + (with-current-buffer vc-dir-process-buffer + (cd def-dir) + (erase-buffer) + (vc-call-backend + backend 'dir-status-files def-dir files default-state + (lambda (entries &optional more-to-come) + ;; ENTRIES is a list of (FILE VC_STATE EXTRA) items. + ;; If MORE-TO-COME is true, then more updates will come from + ;; the asynchronous process. + (with-current-buffer buffer + (vc-dir-update entries buffer) + (unless more-to-come + (setq mode-line-process nil) + ;; Remove the ones that haven't been updated at all. + ;; Those not-updated are those whose state is nil because the + ;; file/dir doesn't exist and isn't versioned. + (ewoc-filter vc-ewoc + (lambda (info) + ;; The state for directory entries might + ;; have been changed to 'up-to-date, + ;; reset it, othewise it will be removed when doing 'x' + ;; next time. + ;; FIXME: There should be a more elegant way to do this. + (when (and (vc-dir-fileinfo->directory info) + (eq (vc-dir-fileinfo->state info) + 'up-to-date)) + (setf (vc-dir-fileinfo->state info) nil)) + + (not (vc-dir-fileinfo->needs-update info)))))))))))) + +(defun vc-dir-revert-buffer-function (&optional ignore-auto noconfirm) + (vc-dir-refresh)) + +(defun vc-dir-refresh () + "Refresh the contents of the *VC-dir* buffer. +Throw an error if another update process is in progress." + (interactive) + (if (vc-dir-busy) + (error "Another update process is in progress, cannot run two at a time") + (let ((def-dir default-directory) + (backend vc-dir-backend)) + (vc-set-mode-line-busy-indicator) + ;; Call the `dir-status' backend function. + ;; `dir-status' is supposed to be asynchronous. + ;; It should compute the results, and then call the function + ;; passed as an argument in order to update the vc-dir buffer + ;; with the results. + + ;; Create a buffer that can be used by `dir-status' and call + ;; `dir-status' with this buffer as the current buffer. Use + ;; `vc-dir-process-buffer' to remember this buffer, so that + ;; it can be used later to kill the update process in case it + ;; takes too long. + (unless (buffer-live-p vc-dir-process-buffer) + (setq vc-dir-process-buffer + (generate-new-buffer (format " *VC-%s* tmp status" backend)))) + ;; set the needs-update flag on all non-directory entries + (ewoc-map (lambda (info) + (unless (vc-dir-fileinfo->directory info) + (setf (vc-dir-fileinfo->needs-update info) t) nil)) + vc-ewoc) + (lexical-let ((buffer (current-buffer))) + (with-current-buffer vc-dir-process-buffer + (cd def-dir) + (erase-buffer) + (vc-call-backend + backend 'dir-status def-dir + (lambda (entries &optional more-to-come) + ;; ENTRIES is a list of (FILE VC_STATE EXTRA) items. + ;; If MORE-TO-COME is true, then more updates will come from + ;; the asynchronous process. + (with-current-buffer buffer + (vc-dir-update entries buffer) + (unless more-to-come + (let ((remaining + (ewoc-collect + vc-ewoc 'vc-dir-fileinfo->needs-update))) + (if remaining + (vc-dir-refresh-files + (mapcar 'vc-dir-fileinfo->name remaining) + 'up-to-date) + (setq mode-line-process nil))))))))) + (ewoc-set-hf vc-ewoc (vc-dir-headers backend def-dir) "")))) + +(defun vc-dir-show-fileentry (file) + "Insert an entry for a specific file into the current *VC-dir* listing. +This is typically used if the file is up-to-date (or has been added +outside of VC) and one wants to do some operation on it." + (interactive "fShow file: ") + (vc-dir-update (list (list (file-relative-name file) (vc-state file))) (current-buffer))) + +(defun vc-dir-hide-up-to-date () + "Hide up-to-date items from display." + (interactive) + (let ((crt (ewoc-nth vc-ewoc -1)) + (first (ewoc-nth vc-ewoc 0))) + ;; Go over from the last item to the first and remove the + ;; up-to-date files and directories with no child files. + (while (not (eq crt first)) + (let* ((data (ewoc-data crt)) + (dir (vc-dir-fileinfo->directory data)) + (next (ewoc-next vc-ewoc crt)) + (prev (ewoc-prev vc-ewoc crt)) + ;; ewoc-delete does not work without this... + (inhibit-read-only t)) + (when (or + ;; Remove directories with no child files. + (and dir + (or + ;; Nothing follows this directory. + (not next) + ;; Next item is a directory. + (vc-dir-fileinfo->directory (ewoc-data next)))) + ;; Remove files in the up-to-date state. + (eq (vc-dir-fileinfo->state data) 'up-to-date)) + (ewoc-delete vc-ewoc crt)) + (setq crt prev))))) + +(defun vc-dir-kill-line () + "Remove the current line from display." + (interactive) + (let ((crt (ewoc-locate vc-ewoc)) + (inhibit-read-only t)) + (ewoc-delete vc-ewoc crt))) + +(defun vc-dir-printer (fileentry) + (vc-call-backend vc-dir-backend 'dir-printer fileentry)) + +(defun vc-dir-deduce-fileset (&optional state-model-only-files) + (let ((marked (vc-dir-marked-files)) + files + only-files-list + state + model) + (if marked + (progn + (setq files marked) + (when state-model-only-files + (setq only-files-list (vc-dir-marked-only-files-and-states)))) + (let ((crt (vc-dir-current-file))) + (setq files (list crt)) + (when state-model-only-files + (setq only-files-list (vc-dir-child-files-and-states))))) + + (when state-model-only-files + (setq state (cdar only-files-list)) + ;; Check that all files are in a consistent state, since we use that + ;; state to decide which operation to perform. + (dolist (crt (cdr only-files-list)) + (unless (vc-compatible-state (cdr crt) state) + (error "When applying VC operations to multiple files, the files are required\nto be in similar VC states.\n%s in state %s clashes with %s in state %s" + (car crt) (cdr crt) (caar only-files-list) state))) + (setq only-files-list (mapcar 'car only-files-list)) + (when (and state (not (eq state 'unregistered))) + (setq model (vc-checkout-model vc-dir-backend only-files-list)))) + (list vc-dir-backend files only-files-list state model))) + +;;;###autoload +(defun vc-dir (dir &optional backend) + "Show the VC status for \"interesting\" files in and below DIR. +This allows you to mark files and perform VC operations on them. +The list omits files which are up to date, with no changes in your copy +or the repository, if there is nothing in particular to say about them. + +Preparing the list of file status takes time; when the buffer +first appears, it has only the first few lines of summary information. +The file lines appear later. + +Optional second argument BACKEND specifies the VC backend to use. +Interactively, a prefix argument means to ask for the backend. + +These are the commands available for use in the file status buffer: + +\\{vc-dir-mode-map}" + + (interactive + (list + ;; When you hit C-x v d in a visited VC file, + ;; the *vc-dir* buffer visits the directory under its truename; + ;; therefore it makes sense to always do that. + ;; Otherwise if you do C-x v d -> C-x C-f -> C-c v d + ;; you may get a new *vc-dir* buffer, different from the original + (file-truename (read-file-name "VC status for directory: " + default-directory default-directory t + nil #'file-directory-p)) + (if current-prefix-arg + (intern + (completing-read + "Use VC backend: " + (mapcar (lambda (b) (list (symbol-name b))) + vc-handled-backends) + nil t nil nil))))) + (unless backend + (setq backend (vc-responsible-backend dir))) + (let (pop-up-windows) ; based on cvs-examine; bug#6204 + (pop-to-buffer (vc-dir-prepare-status-buffer "*vc-dir*" dir backend))) + (if (derived-mode-p 'vc-dir-mode) + (vc-dir-refresh) + ;; FIXME: find a better way to pass the backend to `vc-dir-mode'. + (let ((use-vc-backend backend)) + (vc-dir-mode)))) + +(defun vc-default-dir-extra-headers (backend dir) + ;; Be loud by default to remind people to add code to display + ;; backend specific headers. + ;; XXX: change this to return nil before the release. + (concat + (propertize "Extra : " 'face 'font-lock-type-face) + (propertize "Please add backend specific headers here. It's easy!" + 'face 'font-lock-warning-face))) + +(defvar vc-dir-filename-mouse-map + (let ((map (make-sparse-keymap))) + (define-key map [mouse-2] 'vc-dir-find-file-other-window) + map) + "Local keymap for visiting a file.") + +(defun vc-default-dir-printer (backend fileentry) + "Pretty print FILEENTRY." + ;; If you change the layout here, change vc-dir-move-to-goal-column. + ;; VC backends can implement backend specific versions of this + ;; function. Changes here might need to be reflected in the + ;; vc-BACKEND-dir-printer functions. + (let* ((isdir (vc-dir-fileinfo->directory fileentry)) + (state (if isdir "" (vc-dir-fileinfo->state fileentry))) + (filename (vc-dir-fileinfo->name fileentry))) + (insert + (propertize + (format "%c" (if (vc-dir-fileinfo->marked fileentry) ?* ? )) + 'face 'font-lock-type-face) + " " + (propertize + (format "%-20s" state) + 'face (cond ((eq state 'up-to-date) 'font-lock-builtin-face) + ((memq state '(missing conflict)) 'font-lock-warning-face) + (t 'font-lock-variable-name-face)) + 'mouse-face 'highlight) + " " + (propertize + (format "%s" filename) + 'face + (if isdir 'font-lock-comment-delimiter-face 'font-lock-function-name-face) + 'help-echo + (if isdir + "Directory\nVC operations can be applied to it\nmouse-3: Pop-up menu" + "File\nmouse-3: Pop-up menu") + 'mouse-face 'highlight + 'keymap vc-dir-filename-mouse-map)))) + +(defun vc-default-extra-status-menu (backend) + nil) + +(defun vc-default-status-fileinfo-extra (backend file) + "Default absence of extra information returned for a file." + nil) + +(provide 'vc-dir) + +;; arch-tag: 0274a2e3-e8e9-4b1a-a73c-e8b9129d5d15 +;;; vc-dir.el ends here diff -r 681cd08dc0f7 -r e93288477c43 lisp/vc/vc-dispatcher.el --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/lisp/vc/vc-dispatcher.el Sun Jun 13 22:57:55 2010 +0000 @@ -0,0 +1,695 @@ +;;; vc-dispatcher.el -- generic command-dispatcher facility. + +;; Copyright (C) 2008, 2009, 2010 +;; Free Software Foundation, Inc. + +;; Author: FSF (see below for full credits) +;; Maintainer: Eric S. Raymond +;; Keywords: vc tools + +;; 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 3 of the License, 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. If not, see . + +;;; Credits: + +;; Designed and implemented by Eric S. Raymond, originally as part of VC mode. +;; Stefan Monnier and Dan Nicolaescu contributed substantial work on the +;; vc-dir front end. + +;;; Commentary: + +;; Goals: +;; +;; There is a class of front-ending problems that Emacs might be used +;; to address that involves selecting sets of files, or possibly +;; directories, and passing the selection set to slave commands. The +;; prototypical example, from which this code is derived, is talking +;; to version-control systems. +;; +;; vc-dispatcher.el is written to decouple the UI issues in such front +;; ends from their application-specific logic. It also provides a +;; service layer for running the slave commands either synchronously +;; or asynchronously and managing the message/error logs from the +;; command runs. +;; +;; Similar UI problems can be expected to come up in applications +;; areas other than VCSes; IDEs and document search are two obvious ones. +;; This mode is intended to ensure that the Emacs interfaces for all such +;; beasts are consistent and carefully designed. But even if nothing +;; but VC ever uses it, getting the layer separation right will be +;; a valuable thing. + +;; Dispatcher's universe: +;; +;; The universe consists of the file tree rooted at the current +;; directory. The dispatcher's upper layer deduces some subset +;; of the file tree from the state of the currently visited buffer +;; and returns that subset, presumably to a client mode. +;; +;; The user may be looking at either of two different views; a buffer +;; visiting a file, or a directory buffer generated by vc-dispatcher. +;; +;; The lower layer of this mode runs commands in subprocesses, either +;; synchronously or asynchronously. Commands may be launched in one +;; of two ways: they may be run immediately, or the calling mode can +;; create a closure associated with a text-entry buffer, to be +;; executed when the user types C-c to ship the buffer contents. In +;; either case the command messages and error (if any) will remain +;; available in a status buffer. + +;; Special behavior of dispatcher directory buffers: +;; +;; In dispatcher directory buffers, facilities to perform basic +;; navigation and selection operations are provided by keymap and menu +;; entries that dispatcher sets up itself, so they'll be uniform +;; across all dispatcher-using client modes. Client modes are +;; expected to append to these to provide mode-specific bindings. +;; +;; The standard map associates a 'state' slot (that the client mode +;; may set) with each directory entry. The dispatcher knows nothing +;; about the semantics of individual states, but mark and unmark commands +;; treat all entries with the same state as the currently selected one as +;; a unit. + +;; The interface: +;; +;; The main interface to the lower level is vc-do-command. This launches a +;; command, synchronously or asynchronously, making the output available +;; in a command log buffer. Two other functions, (vc-start-logentry) and +;; (vc-finish-logentry), allow you to associate a command closure with an +;; annotation buffer so that when the user confirms the comment the closure +;; is run (with the comment as part of its context). +;; +;; The interface to the upper level has the two main entry points (vc-dir) +;; and (vc-dispatcher-selection-set) and a couple of convenience functions. +;; (vc-dir) sets up a dispatcher browsing buffer; (vc-dispatcher-selection-set) +;; returns a selection set of files, either the marked files in a browsing +;; buffer or the singleton set consisting of the file visited by the current +;; buffer (when that is appropriate). It also does what is needed to ensure +;; that on-disk files and the contents of their visiting Emacs buffers +;; coincide. +;; +;; When the client mode adds a local vc-mode-line-hook to a buffer, it +;; will be called with the buffer file name as argument whenever the +;; dispatcher resynchs the buffer. + +;; To do: +;; +;; - log buffers need font-locking. +;; + +;; General customization +(defcustom vc-logentry-check-hook nil + "Normal hook run by `vc-finish-logentry'. +Use this to impose your own rules on the entry in addition to any the +dispatcher client mode imposes itself." + :type 'hook + :group 'vc) + +(defcustom vc-delete-logbuf-window t + "If non-nil, delete the log buffer and window after each logical action. +If nil, bury that buffer instead. +This is most useful if you have multiple windows on a frame and would like to +preserve the setting." + :type 'boolean + :group 'vc) + +(defcustom vc-command-messages nil + "If non-nil, display run messages from back-end commands." + :type 'boolean + :group 'vc) + +(defcustom vc-suppress-confirm nil + "If non-nil, treat user as expert; suppress yes-no prompts on some things." + :type 'boolean + :group 'vc) + +;; Variables the user doesn't need to know about. + +(defvar vc-log-operation nil) +(defvar vc-log-after-operation-hook nil) +(defvar vc-log-fileset) + +;; In a log entry buffer, this is a local variable +;; that points to the buffer for which it was made +;; (either a file, or a directory buffer). +(defvar vc-parent-buffer nil) +(put 'vc-parent-buffer 'permanent-local t) +(defvar vc-parent-buffer-name nil) +(put 'vc-parent-buffer-name 'permanent-local t) + +;; Common command execution logic + +(defun vc-process-filter (p s) + "An alternative output filter for async process P. +One difference with the default filter is that this inserts S after markers. +Another is that undo information is not kept." + (let ((buffer (process-buffer p))) + (when (buffer-live-p buffer) + (with-current-buffer buffer + (save-excursion + (let ((buffer-undo-list t) + (inhibit-read-only t)) + (goto-char (process-mark p)) + (insert s) + (set-marker (process-mark p) (point)))))))) + +(defun vc-setup-buffer (buf) + "Prepare BUF for executing a slave command and make it current." + (let ((camefrom (current-buffer)) + (olddir default-directory)) + (set-buffer (get-buffer-create buf)) + (kill-all-local-variables) + (set (make-local-variable 'vc-parent-buffer) camefrom) + (set (make-local-variable 'vc-parent-buffer-name) + (concat " from " (buffer-name camefrom))) + (setq default-directory olddir) + (let ((buffer-undo-list t) + (inhibit-read-only t)) + (erase-buffer)))) + +(defvar vc-sentinel-movepoint) ;Dynamically scoped. + +(defun vc-process-sentinel (p s) + (let ((previous (process-get p 'vc-previous-sentinel)) + (buf (process-buffer p))) + ;; Impatient users sometime kill "slow" buffers; check liveness + ;; to avoid "error in process sentinel: Selecting deleted buffer". + (when (buffer-live-p buf) + (when previous (funcall previous p s)) + (with-current-buffer buf + (setq mode-line-process + (let ((status (process-status p))) + ;; Leave mode-line uncluttered, normally. + (unless (eq 'exit status) + (format " (%s)" status)))) + (let (vc-sentinel-movepoint) + ;; Normally, we want async code such as sentinels to not move point. + (save-excursion + (goto-char (process-mark p)) + (let ((cmds (process-get p 'vc-sentinel-commands))) + (process-put p 'vc-sentinel-commands nil) + (dolist (cmd cmds) + ;; Each sentinel may move point and the next one should be run + ;; at that new point. We could get the same result by having + ;; each sentinel read&set process-mark, but since `cmd' needs + ;; to work both for async and sync processes, this would be + ;; difficult to achieve. + (vc-exec-after cmd)))) + ;; But sometimes the sentinels really want to move point. + (when vc-sentinel-movepoint + (let ((win (get-buffer-window (current-buffer) 0))) + (if (not win) + (goto-char vc-sentinel-movepoint) + (with-selected-window win + (goto-char vc-sentinel-movepoint)))))))))) + +(defun vc-set-mode-line-busy-indicator () + (setq mode-line-process + (concat " " (propertize "[waiting...]" + 'face 'mode-line-emphasis + 'help-echo + "A command is in progress in this buffer")))) + +(defun vc-exec-after (code) + "Eval CODE when the current buffer's process is done. +If the current buffer has no process, just evaluate CODE. +Else, add CODE to the process' sentinel." + (let ((proc (get-buffer-process (current-buffer)))) + (cond + ;; If there's no background process, just execute the code. + ;; We used to explicitly call delete-process on exited processes, + ;; but this led to timing problems causing process output to be + ;; lost. Terminated processes get deleted automatically + ;; anyway. -- cyd + ((or (null proc) (eq (process-status proc) 'exit)) + ;; Make sure we've read the process's output before going further. + (when proc (accept-process-output proc)) + (eval code)) + ;; If a process is running, add CODE to the sentinel + ((eq (process-status proc) 'run) + (vc-set-mode-line-busy-indicator) + (let ((previous (process-sentinel proc))) + (unless (eq previous 'vc-process-sentinel) + (process-put proc 'vc-previous-sentinel previous)) + (set-process-sentinel proc 'vc-process-sentinel)) + (process-put proc 'vc-sentinel-commands + ;; We keep the code fragments in the order given + ;; so that vc-diff-finish's message shows up in + ;; the presence of non-nil vc-command-messages. + (append (process-get proc 'vc-sentinel-commands) + (list code)))) + (t (error "Unexpected process state")))) + nil) + +(defvar vc-post-command-functions nil + "Hook run at the end of `vc-do-command'. +Each function is called inside the buffer in which the command was run +and is passed 3 arguments: the COMMAND, the FILES and the FLAGS.") + +(defvar w32-quote-process-args) + +(defun vc-delistify (filelist) + "Smash a FILELIST into a file list string suitable for info messages." + ;; FIXME what about file names with spaces? + (if (not filelist) "." (mapconcat 'identity filelist " "))) + +;;;###autoload +(defun vc-do-command (buffer okstatus command file-or-list &rest flags) + "Execute a slave command, notifying user and checking for errors. +Output from COMMAND goes to BUFFER, or the current buffer if +BUFFER is t. If the destination buffer is not already current, +set it up properly and erase it. The command is considered +successful if its exit status does not exceed OKSTATUS (if +OKSTATUS is nil, that means to ignore error status, if it is +`async', that means not to wait for termination of the +subprocess; if it is t it means to ignore all execution errors). +FILE-OR-LIST is the name of a working file; it may be a list of +files or be nil (to execute commands that don't expect a file +name or set of files). If an optional list of FLAGS is present, +that is inserted into the command line before the filename. +Return the return value of the slave command in the synchronous +case, and the process object in the asynchronous case." + ;; FIXME: file-relative-name can return a bogus result because + ;; it doesn't look at the actual file-system to see if symlinks + ;; come into play. + (let* ((files + (mapcar (lambda (f) (file-relative-name (expand-file-name f))) + (if (listp file-or-list) file-or-list (list file-or-list)))) + (full-command + ;; What we're doing here is preparing a version of the command + ;; for display in a debug-progress message. If it's fewer than + ;; 20 characters display the entire command (without trailing + ;; newline). Otherwise display the first 20 followed by an ellipsis. + (concat (if (string= (substring command -1) "\n") + (substring command 0 -1) + command) + " " + (vc-delistify (mapcar (lambda (s) (if (> (length s) 20) (concat (substring s 0 2) "...") s)) flags)) + " " (vc-delistify files)))) + (save-current-buffer + (unless (or (eq buffer t) + (and (stringp buffer) + (string= (buffer-name) buffer)) + (eq buffer (current-buffer))) + (vc-setup-buffer buffer)) + ;; If there's some previous async process still running, just kill it. + (let ((oldproc (get-buffer-process (current-buffer)))) + ;; If we wanted to wait for oldproc to finish before doing + ;; something, we'd have used vc-eval-after. + ;; Use `delete-process' rather than `kill-process' because we don't + ;; want any of its output to appear from now on. + (when oldproc (delete-process oldproc))) + (let ((squeezed (remq nil flags)) + (inhibit-read-only t) + (status 0)) + (when files + (setq squeezed (nconc squeezed files))) + (let (;; Since some functions need to parse the output + ;; from external commands, set LC_MESSAGES to C. + (process-environment (cons "LC_MESSAGES=C" process-environment)) + (w32-quote-process-args t)) + (if (eq okstatus 'async) + ;; Run asynchronously. + (let ((proc + (let ((process-connection-type nil)) + (apply 'start-file-process command (current-buffer) + command squeezed)))) + (when vc-command-messages + (message "Running %s in background..." full-command)) + ;;(set-process-sentinel proc (lambda (p msg) (delete-process p))) + (set-process-filter proc 'vc-process-filter) + (setq status proc) + (when vc-command-messages + (vc-exec-after + `(message "Running %s in background... done" ',full-command)))) + ;; Run synchronously + (when vc-command-messages + (message "Running %s in foreground..." full-command)) + (let ((buffer-undo-list t)) + (setq status (apply 'process-file command nil t nil squeezed))) + (when (and (not (eq t okstatus)) + (or (not (integerp status)) + (and okstatus (< okstatus status)))) + (unless (eq ?\s (aref (buffer-name (current-buffer)) 0)) + (pop-to-buffer (current-buffer)) + (goto-char (point-min)) + (shrink-window-if-larger-than-buffer)) + (error "Running %s...FAILED (%s)" full-command + (if (integerp status) (format "status %d" status) status))) + (when vc-command-messages + (message "Running %s...OK = %d" full-command status)))) + (vc-exec-after + `(run-hook-with-args 'vc-post-command-functions + ',command ',file-or-list ',flags)) + status)))) + +;; These functions are used to ensure that the view the user sees is up to date +;; even if the dispatcher client mode has messed with file contents (as in, +;; for example, VCS keyword expansion). + +(declare-function view-mode-exit "view" (&optional return-to-alist exit-action all-win)) + +(defun vc-position-context (posn) + "Save a bit of the text around POSN in the current buffer. +Used to help us find the corresponding position again later +if markers are destroyed or corrupted." + ;; A lot of this was shamelessly lifted from Sebastian Kremer's + ;; rcs.el mode. + (list posn + (buffer-size) + (buffer-substring posn + (min (point-max) (+ posn 100))))) + +(defun vc-find-position-by-context (context) + "Return the position of CONTEXT in the current buffer. +If CONTEXT cannot be found, return nil." + (let ((context-string (nth 2 context))) + (if (equal "" context-string) + (point-max) + (save-excursion + (let ((diff (- (nth 1 context) (buffer-size)))) + (when (< diff 0) (setq diff (- diff))) + (goto-char (nth 0 context)) + (if (or (search-forward context-string nil t) + ;; Can't use search-backward since the match may continue + ;; after point. + (progn (goto-char (- (point) diff (length context-string))) + ;; goto-char doesn't signal an error at + ;; beginning of buffer like backward-char would + (search-forward context-string nil t))) + ;; to beginning of OSTRING + (- (point) (length context-string)))))))) + +(defun vc-context-matches-p (posn context) + "Return t if POSN matches CONTEXT, nil otherwise." + (let* ((context-string (nth 2 context)) + (len (length context-string)) + (end (+ posn len))) + (if (> end (1+ (buffer-size))) + nil + (string= context-string (buffer-substring posn end))))) + +(defun vc-buffer-context () + "Return a list (POINT-CONTEXT MARK-CONTEXT REPARSE). +Used by `vc-restore-buffer-context' to later restore the context." + (let ((point-context (vc-position-context (point))) + ;; Use mark-marker to avoid confusion in transient-mark-mode. + (mark-context (when (eq (marker-buffer (mark-marker)) (current-buffer)) + (vc-position-context (mark-marker)))) + ;; Make the right thing happen in transient-mark-mode. + (mark-active nil)) + (list point-context mark-context nil))) + +(defun vc-restore-buffer-context (context) + "Restore point/mark, and reparse any affected compilation buffers. +CONTEXT is that which `vc-buffer-context' returns." + (let ((point-context (nth 0 context)) + (mark-context (nth 1 context))) + ;; if necessary, restore point and mark + (if (not (vc-context-matches-p (point) point-context)) + (let ((new-point (vc-find-position-by-context point-context))) + (when new-point (goto-char new-point)))) + (and mark-active + mark-context + (not (vc-context-matches-p (mark) mark-context)) + (let ((new-mark (vc-find-position-by-context mark-context))) + (when new-mark (set-mark new-mark)))))) + +(defun vc-revert-buffer-internal (&optional arg no-confirm) + "Revert buffer, keeping point and mark where user expects them. +Try to be clever in the face of changes due to expanded version-control +key words. This is important for typeahead to work as expected. +ARG and NO-CONFIRM are passed on to `revert-buffer'." + (interactive "P") + (widen) + (let ((context (vc-buffer-context))) + ;; Use save-excursion here, because it may be able to restore point + ;; and mark properly even in cases where vc-restore-buffer-context + ;; would fail. However, save-excursion might also get it wrong -- + ;; in this case, vc-restore-buffer-context gives it a second try. + (save-excursion + ;; t means don't call normal-mode; + ;; that's to preserve various minor modes. + (revert-buffer arg no-confirm t)) + (vc-restore-buffer-context context))) + +(defvar vc-mode-line-hook nil) +(make-variable-buffer-local 'vc-mode-line-hook) +(put 'vc-mode-line-hook 'permanent-local t) + +(defun vc-resynch-window (file &optional keep noquery reset-vc-info) + "If FILE is in the current buffer, either revert or unvisit it. +The choice between revert (to see expanded keywords) and unvisit +depends on KEEP. NOQUERY if non-nil inhibits confirmation for +reverting. NOQUERY should be t *only* if it is known the only +difference between the buffer and the file is due to +modifications by the dispatcher client code, rather than user +editing!" + (and (string= buffer-file-name file) + (if keep + (when (file-exists-p file) + (when reset-vc-info + (vc-file-clearprops file)) + (vc-revert-buffer-internal t noquery) + + ;; VC operations might toggle the read-only state. In + ;; that case we need to adjust the `view-mode' status + ;; when `view-read-only' is non-nil. + (and view-read-only + (if (file-writable-p file) + (and view-mode + (let ((view-old-buffer-read-only nil)) + (view-mode-exit))) + (and (not view-mode) + (not (eq (get major-mode 'mode-class) 'special)) + (view-mode-enter)))) + + ;; FIXME: Why use a hook? Why pass it buffer-file-name? + (run-hook-with-args 'vc-mode-line-hook buffer-file-name)) + (kill-buffer (current-buffer))))) + +(declare-function vc-dir-resynch-file "vc-dir" (&optional fname)) +(declare-function vc-string-prefix-p "vc" (prefix string)) + +(defun vc-resynch-buffers-in-directory (directory &optional keep noquery reset-vc-info) + "Resync all buffers that visit files in DIRECTORY." + (dolist (buffer (buffer-list)) + (let ((fname (buffer-file-name buffer))) + (when (and fname (vc-string-prefix-p directory fname)) + (with-current-buffer buffer + (vc-resynch-buffer fname keep noquery reset-vc-info)))))) + +(defun vc-resynch-buffer (file &optional keep noquery reset-vc-info) + "If FILE is currently visited, resynch its buffer." + (if (string= buffer-file-name file) + (vc-resynch-window file keep noquery reset-vc-info) + (if (file-directory-p file) + (vc-resynch-buffers-in-directory file keep noquery reset-vc-info) + (let ((buffer (get-file-buffer file))) + (when buffer + (with-current-buffer buffer + (vc-resynch-window file keep noquery reset-vc-info)))))) + ;; Try to avoid unnecessary work, a *vc-dir* buffer is only present + ;; if this is true. + (when vc-dir-buffers + (vc-dir-resynch-file file))) + +(defun vc-buffer-sync (&optional not-urgent) + "Make sure the current buffer and its working file are in sync. +NOT-URGENT means it is ok to continue if the user says not to save." + (when (buffer-modified-p) + (if (or vc-suppress-confirm + (y-or-n-p (format "Buffer %s modified; save it? " (buffer-name)))) + (save-buffer) + (unless not-urgent + (error "Aborted"))))) + +;; Command closures + +;; Set up key bindings for use while editing log messages + +(defun vc-log-edit (fileset mode) + "Set up `log-edit' for use on FILE." + (setq default-directory + (with-current-buffer vc-parent-buffer default-directory)) + (log-edit 'vc-finish-logentry + nil + `((log-edit-listfun . (lambda () + ;; FIXME: Should expand the list + ;; for directories. + (mapcar 'file-relative-name + ',fileset))) + (log-edit-diff-function . (lambda () (vc-diff nil)))) + nil + mode) + (set (make-local-variable 'vc-log-fileset) fileset) + (set-buffer-modified-p nil) + (setq buffer-file-name nil)) + +(defun vc-start-logentry (files comment initial-contents msg logbuf mode action &optional after-hook) + "Accept a comment for an operation on FILES. +If COMMENT is nil, pop up a LOGBUF buffer, emit MSG, and set the +action on close to ACTION. If COMMENT is a string and +INITIAL-CONTENTS is non-nil, then COMMENT is used as the initial +contents of the log entry buffer. If COMMENT is a string and +INITIAL-CONTENTS is nil, do action immediately as if the user had +entered COMMENT. If COMMENT is t, also do action immediately with an +empty comment. Remember the file's buffer in `vc-parent-buffer' +\(current one if no file). Puts the log-entry buffer in major-mode +MODE, defaulting to `log-edit-mode' if MODE is nil. +AFTER-HOOK specifies the local value for `vc-log-after-operation-hook'." + (let ((parent + (if (vc-dispatcher-browsing) + ;; If we are called from a directory browser, the parent buffer is + ;; the current buffer. + (current-buffer) + (if (and files (equal (length files) 1)) + (get-file-buffer (car files)) + (current-buffer))))) + (if (and comment (not initial-contents)) + (set-buffer (get-buffer-create logbuf)) + (pop-to-buffer (get-buffer-create logbuf))) + (set (make-local-variable 'vc-parent-buffer) parent) + (set (make-local-variable 'vc-parent-buffer-name) + (concat " from " (buffer-name vc-parent-buffer))) + (vc-log-edit files mode) + (make-local-variable 'vc-log-after-operation-hook) + (when after-hook + (setq vc-log-after-operation-hook after-hook)) + (setq vc-log-operation action) + (when comment + (erase-buffer) + (when (stringp comment) (insert comment))) + (if (or (not comment) initial-contents) + (message "%s Type C-c C-c when done" msg) + (vc-finish-logentry (eq comment t))))) + +(declare-function vc-dir-move-to-goal-column "vc-dir" ()) +;; vc-finish-logentry is typically called from a log-edit buffer (see +;; vc-start-logentry). +(defun vc-finish-logentry (&optional nocomment) + "Complete the operation implied by the current log entry. +Use the contents of the current buffer as a check-in or registration +comment. If the optional arg NOCOMMENT is non-nil, then don't check +the buffer contents as a comment." + (interactive) + ;; Check and record the comment, if any. + (unless nocomment + (run-hooks 'vc-logentry-check-hook)) + ;; Sync parent buffer in case the user modified it while editing the comment. + ;; But not if it is a vc-dir buffer. + (with-current-buffer vc-parent-buffer + (or (vc-dispatcher-browsing) (vc-buffer-sync))) + (unless vc-log-operation + (error "No log operation is pending")) + + ;; save the parameters held in buffer-local variables + (let ((logbuf (current-buffer)) + (log-operation vc-log-operation) + ;; FIXME: When coming from VC-Dir, we should check that the + ;; set of selected files is still equal to vc-log-fileset, + ;; to avoid surprises. + (log-fileset vc-log-fileset) + (log-entry (buffer-string)) + (after-hook vc-log-after-operation-hook)) + (pop-to-buffer vc-parent-buffer) + ;; OK, do it to it + (save-excursion + (funcall log-operation + log-fileset + log-entry)) + ;; Remove checkin window (after the checkin so that if that fails + ;; we don't zap the log buffer and the typing therein). + ;; -- IMO this should be replaced with quit-window + (cond ((and logbuf vc-delete-logbuf-window) + (delete-windows-on logbuf (selected-frame)) + ;; Kill buffer and delete any other dedicated windows/frames. + (kill-buffer logbuf)) + (logbuf + (with-selected-window (or (get-buffer-window logbuf 0) + (selected-window)) + (with-current-buffer logbuf + (bury-buffer))))) + ;; Now make sure we see the expanded headers + (when log-fileset + (mapc + (lambda (file) (vc-resynch-buffer file vc-keep-workfiles t)) + log-fileset)) + (when (vc-dispatcher-browsing) + (vc-dir-move-to-goal-column)) + (run-hooks after-hook 'vc-finish-logentry-hook))) + +(defun vc-dispatcher-browsing () + "Are we in a directory browser buffer?" + (derived-mode-p 'vc-dir-mode)) + +;; These are unused. +;; (defun vc-dispatcher-in-fileset-p (fileset) +;; (let ((member nil)) +;; (while (and (not member) fileset) +;; (let ((elem (pop fileset))) +;; (if (if (file-directory-p elem) +;; (eq t (compare-strings buffer-file-name nil (length elem) +;; elem nil nil)) +;; (eq (current-buffer) (get-file-buffer elem))) +;; (setq member t)))) +;; member)) + +;; (defun vc-dispatcher-selection-set (&optional observer) +;; "Deduce a set of files to which to apply an operation. Return a cons +;; cell (SELECTION . FILESET), where SELECTION is what the user chose +;; and FILES is the flist with any directories replaced by the listed files +;; within them. + +;; If we're in a directory display, the fileset is the list of marked files (if +;; there is one) else the file on the current line. If not in a directory +;; display, but the current buffer visits a file, the fileset is a singleton +;; containing that file. Otherwise, throw an error." +;; (let ((selection +;; (cond +;; ;; Browsing with vc-dir +;; ((vc-dispatcher-browsing) +;; ;; If no files are marked, temporarily mark current file +;; ;; and choose on that basis (so we get subordinate files) +;; (if (not (vc-dir-marked-files)) +;; (prog2 +;; (vc-dir-mark-file) +;; (cons (vc-dir-marked-files) (vc-dir-marked-only-files)) +;; (vc-dir-unmark-all-files t)) +;; (cons (vc-dir-marked-files) (vc-dir-marked-only-files)))) +;; ;; Visiting an eligible file +;; ((buffer-file-name) +;; (cons (list buffer-file-name) (list buffer-file-name))) +;; ;; No eligible file -- if there's a parent buffer, deduce from there +;; ((and vc-parent-buffer (or (buffer-file-name vc-parent-buffer) +;; (with-current-buffer vc-parent-buffer +;; (vc-dispatcher-browsing)))) +;; (with-current-buffer vc-parent-buffer +;; (vc-dispatcher-selection-set))) +;; ;; No good set here, throw error +;; (t (error "No fileset is available here"))))) +;; ;; We assume, in order to avoid unpleasant surprises to the user, +;; ;; that a fileset is not in good shape to be handed to the user if the +;; ;; buffers visiting the fileset don't match the on-disk contents. +;; (unless observer +;; (save-some-buffers +;; nil (lambda () (vc-dispatcher-in-fileset-p (cdr selection))))) +;; selection)) + +(provide 'vc-dispatcher) + +;; arch-tag: 7d08b17f-5470-4799-914b-bfb9fcf6a246 +;;; vc-dispatcher.el ends here diff -r 681cd08dc0f7 -r e93288477c43 lisp/vc/vc-git.el --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/lisp/vc/vc-git.el Sun Jun 13 22:57:55 2010 +0000 @@ -0,0 +1,1031 @@ +;;; vc-git.el --- VC backend for the git version control system + +;; Copyright (C) 2006, 2007, 2008, 2009, 2010 Free Software Foundation, Inc. + +;; Author: Alexandre Julliard +;; Keywords: vc tools + +;; 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 3 of the License, 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. If not, see . + +;;; Commentary: + +;; This file contains a VC backend for the git version control +;; system. +;; + +;;; Installation: + +;; To install: put this file on the load-path and add Git to the list +;; of supported backends in `vc-handled-backends'; the following line, +;; placed in your ~/.emacs, will accomplish this: +;; +;; (add-to-list 'vc-handled-backends 'Git) + +;;; Todo: +;; - check if more functions could use vc-git-command instead +;; of start-process. +;; - changelog generation + +;; Implement the rest of the vc interface. See the comment at the +;; beginning of vc.el. The current status is: +;; ("??" means: "figure out what to do about it") +;; +;; FUNCTION NAME STATUS +;; BACKEND PROPERTIES +;; * revision-granularity OK +;; STATE-QUERYING FUNCTIONS +;; * registered (file) OK +;; * state (file) OK +;; - state-heuristic (file) NOT NEEDED +;; * working-revision (file) OK +;; - latest-on-branch-p (file) NOT NEEDED +;; * checkout-model (files) OK +;; - workfile-unchanged-p (file) OK +;; - mode-line-string (file) OK +;; STATE-CHANGING FUNCTIONS +;; * create-repo () OK +;; * register (files &optional rev comment) OK +;; - init-revision (file) NOT NEEDED +;; - responsible-p (file) OK +;; - could-register (file) NOT NEEDED, DEFAULT IS GOOD +;; - receive-file (file rev) NOT NEEDED +;; - unregister (file) OK +;; * checkin (files rev comment) OK +;; * find-revision (file rev buffer) OK +;; * checkout (file &optional editable rev) OK +;; * revert (file &optional contents-done) OK +;; - rollback (files) COULD BE SUPPORTED +;; - merge (file rev1 rev2) It would be possible to merge +;; changes into a single file, but +;; when committing they wouldn't +;; be identified as a merge +;; by git, so it's probably +;; not a good idea. +;; - merge-news (file) see `merge' +;; - steal-lock (file &optional revision) NOT NEEDED +;; HISTORY FUNCTIONS +;; * print-log (files buffer &optional shortlog start-revision limit) OK +;; - log-view-mode () OK +;; - show-log-entry (revision) OK +;; - comment-history (file) ?? +;; - update-changelog (files) COULD BE SUPPORTED +;; * diff (file &optional rev1 rev2 buffer) OK +;; - revision-completion-table (files) OK +;; - annotate-command (file buf &optional rev) OK +;; - annotate-time () OK +;; - annotate-current-time () NOT NEEDED +;; - annotate-extract-revision-at-line () OK +;; TAG SYSTEM +;; - create-tag (dir name branchp) OK +;; - retrieve-tag (dir name update) OK +;; MISCELLANEOUS +;; - make-version-backups-p (file) NOT NEEDED +;; - repository-hostname (dirname) NOT NEEDED +;; - previous-revision (file rev) OK +;; - next-revision (file rev) OK +;; - check-headers () COULD BE SUPPORTED +;; - clear-headers () NOT NEEDED +;; - delete-file (file) OK +;; - rename-file (old new) OK +;; - find-file-hook () NOT NEEDED + +(eval-when-compile + (require 'cl) + (require 'vc) + (require 'vc-dir) + (require 'grep)) + +(defcustom vc-git-diff-switches t + "String or list of strings specifying switches for Git diff under VC. +If nil, use the value of `vc-diff-switches'. If t, use no switches." + :type '(choice (const :tag "Unspecified" nil) + (const :tag "None" t) + (string :tag "Argument String") + (repeat :tag "Argument List" :value ("") string)) + :version "23.1" + :group 'vc) + +(defvar vc-git-commits-coding-system 'utf-8 + "Default coding system for git commits.") + +;;; BACKEND PROPERTIES + +(defun vc-git-revision-granularity () 'repository) +(defun vc-git-checkout-model (files) 'implicit) + +;;; STATE-QUERYING FUNCTIONS + +;;;###autoload (defun vc-git-registered (file) +;;;###autoload "Return non-nil if FILE is registered with git." +;;;###autoload (if (vc-find-root file ".git") ; Short cut. +;;;###autoload (progn +;;;###autoload (load "vc-git") +;;;###autoload (vc-git-registered file)))) + +(defun vc-git-registered (file) + "Check whether FILE is registered with git." + (let ((dir (vc-git-root file))) + (when dir + (with-temp-buffer + (let* (process-file-side-effects + ;; Do not use the `file-name-directory' here: git-ls-files + ;; sometimes fails to return the correct status for relative + ;; path specs. + ;; See also: http://marc.info/?l=git&m=125787684318129&w=2 + (name (file-relative-name file dir)) + (str (ignore-errors + (cd dir) + (vc-git--out-ok "ls-files" "-c" "-z" "--" name) + ;; If result is empty, use ls-tree to check for deleted + ;; file. + (when (eq (point-min) (point-max)) + (vc-git--out-ok "ls-tree" "--name-only" "-z" "HEAD" + "--" name)) + (buffer-string)))) + (and str + (> (length str) (length name)) + (string= (substring str 0 (1+ (length name))) + (concat name "\0")))))))) + +(defun vc-git--state-code (code) + "Convert from a string to a added/deleted/modified state." + (case (string-to-char code) + (?M 'edited) + (?A 'added) + (?D 'removed) + (?U 'edited) ;; FIXME + (?T 'edited))) ;; FIXME + +(defun vc-git-state (file) + "Git-specific version of `vc-state'." + ;; FIXME: This can't set 'ignored or 'conflict yet + ;; The 'ignored state could be detected with `git ls-files -i -o + ;; --exclude-standard` It also can't set 'needs-update or + ;; 'needs-merge. The rough equivalent would be that upstream branch + ;; for current branch is in fast-forward state i.e. current branch + ;; is direct ancestor of corresponding upstream branch, and the file + ;; was modified upstream. But we can't check that without a network + ;; operation. + (if (not (vc-git-registered file)) + 'unregistered + (vc-git--call nil "add" "--refresh" "--" (file-relative-name file)) + (let ((diff (vc-git--run-command-string + file "diff-index" "-z" "HEAD" "--"))) + (if (and diff (string-match ":[0-7]\\{6\\} [0-7]\\{6\\} [0-9a-f]\\{40\\} [0-9a-f]\\{40\\} \\([ADMUT]\\)\0[^\0]+\0" + diff)) + (vc-git--state-code (match-string 1 diff)) + (if (vc-git--empty-db-p) 'added 'up-to-date))))) + +(defun vc-git-working-revision (file) + "Git-specific version of `vc-working-revision'." + (let* (process-file-side-effects + (str (with-output-to-string + (with-current-buffer standard-output + (vc-git--out-ok "symbolic-ref" "HEAD"))))) + (if (string-match "^\\(refs/heads/\\)?\\(.+\\)$" str) + (match-string 2 str) + str))) + +(defun vc-git-workfile-unchanged-p (file) + (eq 'up-to-date (vc-git-state file))) + +(defun vc-git-mode-line-string (file) + "Return string for placement into the modeline for FILE." + (let* ((branch (vc-git-working-revision file)) + (def-ml (vc-default-mode-line-string 'Git file)) + (help-echo (get-text-property 0 'help-echo def-ml))) + (if (zerop (length branch)) + (propertize + (concat def-ml "!") + 'help-echo (concat help-echo "\nNo current branch (detached HEAD)")) + (propertize def-ml + 'help-echo (concat help-echo "\nCurrent branch: " branch))))) + +(defstruct (vc-git-extra-fileinfo + (:copier nil) + (:constructor vc-git-create-extra-fileinfo + (old-perm new-perm &optional rename-state orig-name)) + (:conc-name vc-git-extra-fileinfo->)) + old-perm new-perm ;; Permission flags. + rename-state ;; Rename or copy state. + orig-name) ;; Original name for renames or copies. + +(defun vc-git-escape-file-name (name) + "Escape a file name if necessary." + (if (string-match "[\n\t\"\\]" name) + (concat "\"" + (mapconcat (lambda (c) + (case c + (?\n "\\n") + (?\t "\\t") + (?\\ "\\\\") + (?\" "\\\"") + (t (char-to-string c)))) + name "") + "\"") + name)) + +(defun vc-git-file-type-as-string (old-perm new-perm) + "Return a string describing the file type based on its permissions." + (let* ((old-type (lsh (or old-perm 0) -9)) + (new-type (lsh (or new-perm 0) -9)) + (str (case new-type + (?\100 ;; File. + (case old-type + (?\100 nil) + (?\120 " (type change symlink -> file)") + (?\160 " (type change subproject -> file)"))) + (?\120 ;; Symlink. + (case old-type + (?\100 " (type change file -> symlink)") + (?\160 " (type change subproject -> symlink)") + (t " (symlink)"))) + (?\160 ;; Subproject. + (case old-type + (?\100 " (type change file -> subproject)") + (?\120 " (type change symlink -> subproject)") + (t " (subproject)"))) + (?\110 nil) ;; Directory (internal, not a real git state). + (?\000 ;; Deleted or unknown. + (case old-type + (?\120 " (symlink)") + (?\160 " (subproject)"))) + (t (format " (unknown type %o)" new-type))))) + (cond (str (propertize str 'face 'font-lock-comment-face)) + ((eq new-type ?\110) "/") + (t "")))) + +(defun vc-git-rename-as-string (state extra) + "Return a string describing the copy or rename associated with INFO, +or an empty string if none." + (let ((rename-state (when extra + (vc-git-extra-fileinfo->rename-state extra)))) + (if rename-state + (propertize + (concat " (" + (if (eq rename-state 'copy) "copied from " + (if (eq state 'added) "renamed from " + "renamed to ")) + (vc-git-escape-file-name + (vc-git-extra-fileinfo->orig-name extra)) + ")") + 'face 'font-lock-comment-face) + ""))) + +(defun vc-git-permissions-as-string (old-perm new-perm) + "Format a permission change as string." + (propertize + (if (or (not old-perm) + (not new-perm) + (eq 0 (logand ?\111 (logxor old-perm new-perm)))) + " " + (if (eq 0 (logand ?\111 old-perm)) "+x" "-x")) + 'face 'font-lock-type-face)) + +(defun vc-git-dir-printer (info) + "Pretty-printer for the vc-dir-fileinfo structure." + (let* ((isdir (vc-dir-fileinfo->directory info)) + (state (if isdir "" (vc-dir-fileinfo->state info))) + (extra (vc-dir-fileinfo->extra info)) + (old-perm (when extra (vc-git-extra-fileinfo->old-perm extra))) + (new-perm (when extra (vc-git-extra-fileinfo->new-perm extra)))) + (insert + " " + (propertize (format "%c" (if (vc-dir-fileinfo->marked info) ?* ? )) + 'face 'font-lock-type-face) + " " + (propertize + (format "%-12s" state) + 'face (cond ((eq state 'up-to-date) 'font-lock-builtin-face) + ((eq state 'missing) 'font-lock-warning-face) + (t 'font-lock-variable-name-face)) + 'mouse-face 'highlight) + " " (vc-git-permissions-as-string old-perm new-perm) + " " + (propertize (vc-git-escape-file-name (vc-dir-fileinfo->name info)) + 'face (if isdir 'font-lock-comment-delimiter-face + 'font-lock-function-name-face) + 'help-echo + (if isdir + "Directory\nVC operations can be applied to it\nmouse-3: Pop-up menu" + "File\nmouse-3: Pop-up menu") + 'keymap vc-dir-filename-mouse-map + 'mouse-face 'highlight) + (vc-git-file-type-as-string old-perm new-perm) + (vc-git-rename-as-string state extra)))) + +(defun vc-git-after-dir-status-stage (stage files update-function) + "Process sentinel for the various dir-status stages." + (let (next-stage result) + (goto-char (point-min)) + (case stage + (update-index + (setq next-stage (if (vc-git--empty-db-p) 'ls-files-added + (if files 'ls-files-up-to-date 'diff-index)))) + (ls-files-added + (setq next-stage 'ls-files-unknown) + (while (re-search-forward "\\([0-7]\\{6\\}\\) [0-9a-f]\\{40\\} 0\t\\([^\0]+\\)\0" nil t) + (let ((new-perm (string-to-number (match-string 1) 8)) + (name (match-string 2))) + (push (list name 'added (vc-git-create-extra-fileinfo 0 new-perm)) + result)))) + (ls-files-up-to-date + (setq next-stage 'diff-index) + (while (re-search-forward "\\([0-7]\\{6\\}\\) [0-9a-f]\\{40\\} 0\t\\([^\0]+\\)\0" nil t) + (let ((perm (string-to-number (match-string 1) 8)) + (name (match-string 2))) + (push (list name 'up-to-date + (vc-git-create-extra-fileinfo perm perm)) + result)))) + (ls-files-unknown + (when files (setq next-stage 'ls-files-ignored)) + (while (re-search-forward "\\([^\0]*?\\)\0" nil t 1) + (push (list (match-string 1) 'unregistered + (vc-git-create-extra-fileinfo 0 0)) + result))) + (ls-files-ignored + (while (re-search-forward "\\([^\0]*?\\)\0" nil t 1) + (push (list (match-string 1) 'ignored + (vc-git-create-extra-fileinfo 0 0)) + result))) + (diff-index + (setq next-stage 'ls-files-unknown) + (while (re-search-forward + ":\\([0-7]\\{6\\}\\) \\([0-7]\\{6\\}\\) [0-9a-f]\\{40\\} [0-9a-f]\\{40\\} \\(\\([ADMUT]\\)\0\\([^\0]+\\)\\|\\([CR]\\)[0-9]*\0\\([^\0]+\\)\0\\([^\0]+\\)\\)\0" + nil t 1) + (let ((old-perm (string-to-number (match-string 1) 8)) + (new-perm (string-to-number (match-string 2) 8)) + (state (or (match-string 4) (match-string 6))) + (name (or (match-string 5) (match-string 7))) + (new-name (match-string 8))) + (if new-name ; Copy or rename. + (if (eq ?C (string-to-char state)) + (push (list new-name 'added + (vc-git-create-extra-fileinfo old-perm new-perm + 'copy name)) + result) + (push (list name 'removed + (vc-git-create-extra-fileinfo 0 0 + 'rename new-name)) + result) + (push (list new-name 'added + (vc-git-create-extra-fileinfo old-perm new-perm + 'rename name)) + result)) + (push (list name (vc-git--state-code state) + (vc-git-create-extra-fileinfo old-perm new-perm)) + result)))))) + (when result + (setq result (nreverse result)) + (when files + (dolist (entry result) (setq files (delete (car entry) files))) + (unless files (setq next-stage nil)))) + (when (or result (not next-stage)) + (funcall update-function result next-stage)) + (when next-stage + (vc-git-dir-status-goto-stage next-stage files update-function)))) + +(defun vc-git-dir-status-goto-stage (stage files update-function) + (erase-buffer) + (case stage + (update-index + (if files + (vc-git-command (current-buffer) 'async files "add" "--refresh" "--") + (vc-git-command (current-buffer) 'async nil + "update-index" "--refresh"))) + (ls-files-added + (vc-git-command (current-buffer) 'async files + "ls-files" "-z" "-c" "-s" "--")) + (ls-files-up-to-date + (vc-git-command (current-buffer) 'async files + "ls-files" "-z" "-c" "-s" "--")) + (ls-files-unknown + (vc-git-command (current-buffer) 'async files + "ls-files" "-z" "-o" "--directory" + "--no-empty-directory" "--exclude-standard" "--")) + (ls-files-ignored + (vc-git-command (current-buffer) 'async files + "ls-files" "-z" "-o" "-i" "--directory" + "--no-empty-directory" "--exclude-standard" "--")) + ;; --relative added in Git 1.5.5. + (diff-index + (vc-git-command (current-buffer) 'async files + "diff-index" "--relative" "-z" "-M" "HEAD" "--"))) + (vc-exec-after + `(vc-git-after-dir-status-stage ',stage ',files ',update-function))) + +(defun vc-git-dir-status (dir update-function) + "Return a list of (FILE STATE EXTRA) entries for DIR." + ;; Further things that would have to be fixed later: + ;; - how to handle unregistered directories + ;; - how to support vc-dir on a subdir of the project tree + (vc-git-dir-status-goto-stage 'update-index nil update-function)) + +(defun vc-git-dir-status-files (dir files default-state update-function) + "Return a list of (FILE STATE EXTRA) entries for FILES in DIR." + (vc-git-dir-status-goto-stage 'update-index files update-function)) + +(defvar vc-git-stash-map + (let ((map (make-sparse-keymap))) + ;; Turn off vc-dir marking + (define-key map [mouse-2] 'ignore) + + (define-key map [down-mouse-3] 'vc-git-stash-menu) + (define-key map "\C-k" 'vc-git-stash-delete-at-point) + (define-key map "=" 'vc-git-stash-show-at-point) + (define-key map "\C-m" 'vc-git-stash-show-at-point) + (define-key map "A" 'vc-git-stash-apply-at-point) + (define-key map "P" 'vc-git-stash-pop-at-point) + (define-key map "S" 'vc-git-stash-snapshot) + map)) + +(defvar vc-git-stash-menu-map + (let ((map (make-sparse-keymap "Git Stash"))) + (define-key map [de] + '(menu-item "Delete stash" vc-git-stash-delete-at-point + :help "Delete the current stash")) + (define-key map [ap] + '(menu-item "Apply stash" vc-git-stash-apply-at-point + :help "Apply the current stash and keep it in the stash list")) + (define-key map [po] + '(menu-item "Apply and remove stash (pop)" vc-git-stash-pop-at-point + :help "Apply the current stash and remove it")) + (define-key map [sh] + '(menu-item "Show stash" vc-git-stash-show-at-point + :help "Show the contents of the current stash")) + map)) + +(defun vc-git-dir-extra-headers (dir) + (let ((str (with-output-to-string + (with-current-buffer standard-output + (vc-git--out-ok "symbolic-ref" "HEAD")))) + (stash (vc-git-stash-list)) + (stash-help-echo "Use M-x vc-git-stash to create stashes.") + branch remote remote-url) + (if (string-match "^\\(refs/heads/\\)?\\(.+\\)$" str) + (progn + (setq branch (match-string 2 str)) + (setq remote + (with-output-to-string + (with-current-buffer standard-output + (vc-git--out-ok "config" + (concat "branch." branch ".remote"))))) + (when (string-match "\\([^\n]+\\)" remote) + (setq remote (match-string 1 remote))) + (when remote + (setq remote-url + (with-output-to-string + (with-current-buffer standard-output + (vc-git--out-ok "config" + (concat "remote." remote ".url")))))) + (when (string-match "\\([^\n]+\\)" remote-url) + (setq remote-url (match-string 1 remote-url)))) + (setq branch "not (detached HEAD)")) + ;; FIXME: maybe use a different face when nothing is stashed. + (concat + (propertize "Branch : " 'face 'font-lock-type-face) + (propertize branch + 'face 'font-lock-variable-name-face) + (when remote + (concat + "\n" + (propertize "Remote : " 'face 'font-lock-type-face) + (propertize remote-url + 'face 'font-lock-variable-name-face))) + "\n" + (if stash + (concat + (propertize "Stash :\n" 'face 'font-lock-type-face + 'help-echo stash-help-echo) + (mapconcat + (lambda (x) + (propertize x + 'face 'font-lock-variable-name-face + 'mouse-face 'highlight + 'help-echo "mouse-3: Show stash menu\nRET: Show stash\nA: Apply stash\nP: Apply and remove stash (pop)\nC-k: Delete stash" + 'keymap vc-git-stash-map)) + stash "\n")) + (concat + (propertize "Stash : " 'face 'font-lock-type-face + 'help-echo stash-help-echo) + (propertize "Nothing stashed" + 'help-echo stash-help-echo + 'face 'font-lock-variable-name-face)))))) + +;;; STATE-CHANGING FUNCTIONS + +(defun vc-git-create-repo () + "Create a new Git repository." + (vc-git-command nil 0 nil "init")) + +(defun vc-git-register (files &optional rev comment) + "Register FILES into the git version-control system." + (let (flist dlist) + (dolist (crt files) + (if (file-directory-p crt) + (push crt dlist) + (push crt flist))) + (when flist + (vc-git-command nil 0 flist "update-index" "--add" "--")) + (when dlist + (vc-git-command nil 0 dlist "add")))) + +(defalias 'vc-git-responsible-p 'vc-git-root) + +(defun vc-git-unregister (file) + (vc-git-command nil 0 file "rm" "-f" "--cached" "--")) + +(declare-function log-edit-extract-headers "log-edit" (headers string)) + +(defun vc-git-checkin (files rev comment) + (let ((coding-system-for-write vc-git-commits-coding-system)) + (apply 'vc-git-command nil 0 files + (nconc (list "commit" "-m") + (log-edit-extract-headers '(("Author" . "--author") + ("Date" . "--date")) + comment) + (list "--only" "--"))))) + +(defun vc-git-find-revision (file rev buffer) + (let* (process-file-side-effects + (coding-system-for-read 'binary) + (coding-system-for-write 'binary) + (fullname (substring + (vc-git--run-command-string + file "ls-files" "-z" "--full-name" "--") + 0 -1))) + (vc-git-command + buffer 0 + (concat (if rev rev "HEAD") ":" fullname) "cat-file" "blob"))) + +(defun vc-git-checkout (file &optional editable rev) + (vc-git-command nil 0 file "checkout" (or rev "HEAD"))) + +(defun vc-git-revert (file &optional contents-done) + "Revert FILE to the version stored in the git repository." + (if contents-done + (vc-git-command nil 0 file "update-index" "--") + (vc-git-command nil 0 file "reset" "-q" "--") + (vc-git-command nil nil file "checkout" "-q" "--"))) + +;;; HISTORY FUNCTIONS + +(defun vc-git-print-log (files buffer &optional shortlog start-revision limit) + "Get change log associated with FILES. +Note that using SHORTLOG requires at least Git version 1.5.6, +for the --graph option." + (let ((coding-system-for-read vc-git-commits-coding-system)) + ;; `vc-do-command' creates the buffer, but we need it before running + ;; the command. + (vc-setup-buffer buffer) + ;; If the buffer exists from a previous invocation it might be + ;; read-only. + (let ((inhibit-read-only t)) + (with-current-buffer + buffer + (apply 'vc-git-command buffer + 'async files + (append + '("log" "--no-color") + (when shortlog + '("--graph" "--decorate" "--date=short" + "--pretty=tformat:%d%h %ad %s" "--abbrev-commit")) + (when limit (list "-n" (format "%s" limit))) + (when start-revision (list start-revision)) + '("--"))))))) + +(defun vc-git-log-outgoing (buffer remote-location) + (interactive) + (vc-git-command + buffer 0 nil + "log" + "--no-color" "--graph" "--decorate" "--date=short" + "--pretty=tformat:%d%h %ad %s" "--abbrev-commit" + (concat (if (string= remote-location "") + "@{upstream}" + remote-location) + "..HEAD"))) + +(defun vc-git-log-incoming (buffer remote-location) + (interactive) + (vc-git-command nil 0 nil "fetch") + (vc-git-command + buffer 0 nil + "log" + "--no-color" "--graph" "--decorate" "--date=short" + "--pretty=tformat:%d%h %ad %s" "--abbrev-commit" + (concat "HEAD.." (if (string= remote-location "") + "@{upstream}" + remote-location)))) + +(defvar log-view-message-re) +(defvar log-view-file-re) +(defvar log-view-font-lock-keywords) +(defvar log-view-per-file-logs) + +(define-derived-mode vc-git-log-view-mode log-view-mode "Git-Log-View" + (require 'add-log) ;; We need the faces add-log. + ;; Don't have file markers, so use impossible regexp. + (set (make-local-variable 'log-view-file-re) "\\`a\\`") + (set (make-local-variable 'log-view-per-file-logs) nil) + (set (make-local-variable 'log-view-message-re) + (if (not (eq vc-log-view-type 'long)) + "^\\(?:[*/\\| ]+ \\)?\\(?: ([^)]+)\\)?\\([0-9a-z]+\\) \\([-a-z0-9]+\\) \\(.*\\)" + "^commit *\\([0-9a-z]+\\)")) + (set (make-local-variable 'log-view-font-lock-keywords) + (if (not (eq vc-log-view-type 'long)) + '( + ;; Same as log-view-message-re, except that we don't + ;; want the shy group for the tag name. + ("^\\(?:[*/\\| ]+ \\)?\\( ([^)]+)\\)?\\([0-9a-z]+\\) \\([-a-z0-9]+\\) \\(.*\\)" + (1 'highlight nil lax) + (2 'change-log-acknowledgement) + (3 'change-log-date))) + (append + `((,log-view-message-re (1 'change-log-acknowledgement))) + ;; Handle the case: + ;; user: foo@bar + '(("^Author:[ \t]+\\([A-Za-z0-9_.+-]+@[A-Za-z0-9_.-]+\\)" + (1 'change-log-email)) + ;; Handle the case: + ;; user: FirstName LastName + ("^Author:[ \t]+\\([^<(]+?\\)[ \t]*[(<]\\([A-Za-z0-9_.+-]+@[A-Za-z0-9_.-]+\\)[>)]" + (1 'change-log-name) + (2 'change-log-email)) + ("^ +\\(?:\\(?:[Aa]cked\\|[Ss]igned-[Oo]ff\\)-[Bb]y:\\)[ \t]+\\([A-Za-z0-9_.+-]+@[A-Za-z0-9_.-]+\\)" + (1 'change-log-name)) + ("^ +\\(?:\\(?:[Aa]cked\\|[Ss]igned-[Oo]ff\\)-[Bb]y:\\)[ \t]+\\([^<(]+?\\)[ \t]*[(<]\\([A-Za-z0-9_.+-]+@[A-Za-z0-9_.-]+\\)[>)]" + (1 'change-log-name) + (2 'change-log-email)) + ("^Merge: \\([0-9a-z]+\\) \\([0-9a-z]+\\)" + (1 'change-log-acknowledgement) + (2 'change-log-acknowledgement)) + ("^Date: \\(.+\\)" (1 'change-log-date)) + ("^summary:[ \t]+\\(.+\\)" (1 'log-view-message))))))) + + +(defun vc-git-show-log-entry (revision) + "Move to the log entry for REVISION. +REVISION may have the form BRANCH, BRANCH~N, +or BRANCH^ (where \"^\" can be repeated)." + (goto-char (point-min)) + (prog1 + (when revision + (search-forward + (format "\ncommit %s" revision) nil t + (cond ((string-match "~\\([0-9]\\)\\'" revision) + (1+ (string-to-number (match-string 1 revision)))) + ((string-match "\\^+\\'" revision) + (1+ (length (match-string 0 revision)))) + (t nil)))) + (beginning-of-line))) + +(defun vc-git-diff (files &optional rev1 rev2 buffer) + "Get a difference report using Git between two revisions of FILES." + (let (process-file-side-effects) + (apply #'vc-git-command (or buffer "*vc-diff*") 1 files + (if (and rev1 rev2) "diff-tree" "diff-index") + "--exit-code" + (append (vc-switches 'git 'diff) + (list "-p" (or rev1 "HEAD") rev2 "--"))))) + +(defun vc-git-revision-table (files) + ;; What about `files'?!? --Stef + (let (process-file-side-effects + (table (list "HEAD"))) + (with-temp-buffer + (vc-git-command t nil nil "for-each-ref" "--format=%(refname)") + (goto-char (point-min)) + (while (re-search-forward "^refs/\\(heads\\|tags\\|remotes\\)/\\(.*\\)$" + nil t) + (push (match-string 2) table))) + table)) + +(defun vc-git-revision-completion-table (files) + (lexical-let ((files files) + table) + (setq table (lazy-completion-table + table (lambda () (vc-git-revision-table files)))) + table)) + +(defun vc-git-annotate-command (file buf &optional rev) + (let ((name (file-relative-name file))) + (vc-git-command buf 'async name "blame" "--date=iso" "-C" "-C" rev))) + +(declare-function vc-annotate-convert-time "vc-annotate" (time)) + +(defun vc-git-annotate-time () + (and (re-search-forward "[0-9a-f]+[^()]+(.* \\([0-9]+\\)-\\([0-9]+\\)-\\([0-9]+\\) \\([0-9]+\\):\\([0-9]+\\):\\([0-9]+\\) \\([-+0-9]+\\) +[0-9]+) " nil t) + (vc-annotate-convert-time + (apply #'encode-time (mapcar (lambda (match) + (string-to-number (match-string match))) + '(6 5 4 3 2 1 7)))))) + +(defun vc-git-annotate-extract-revision-at-line () + (save-excursion + (move-beginning-of-line 1) + (when (looking-at "\\([0-9a-f^][0-9a-f]+\\) \\(\\([^(]+\\) \\)?") + (let ((revision (match-string-no-properties 1))) + (if (match-beginning 2) + (cons revision (expand-file-name (match-string-no-properties 3) + (vc-git-root default-directory))) + revision))))) + +;;; TAG SYSTEM + +(defun vc-git-create-tag (dir name branchp) + (let ((default-directory dir)) + (and (vc-git-command nil 0 nil "update-index" "--refresh") + (if branchp + (vc-git-command nil 0 nil "checkout" "-b" name) + (vc-git-command nil 0 nil "tag" name))))) + +(defun vc-git-retrieve-tag (dir name update) + (let ((default-directory dir)) + (vc-git-command nil 0 nil "checkout" name) + ;; FIXME: update buffers if `update' is true + )) + + +;;; MISCELLANEOUS + +(defun vc-git-previous-revision (file rev) + "Git-specific version of `vc-previous-revision'." + (if file + (let* ((default-directory (file-name-directory (expand-file-name file))) + (file (file-name-nondirectory file)) + (prev-rev (with-temp-buffer + (and + (vc-git--out-ok "rev-list" "-2" rev "--" file) + (goto-char (point-max)) + (bolp) + (zerop (forward-line -1)) + (not (bobp)) + (buffer-substring-no-properties + (point) + (1- (point-max))))))) + (or (vc-git-symbolic-commit prev-rev) prev-rev)) + (with-temp-buffer + (and + (vc-git--out-ok "rev-parse" (concat rev "^")) + (buffer-substring-no-properties (point-min) (+ (point-min) 40)))))) + +(defun vc-git-next-revision (file rev) + "Git-specific version of `vc-next-revision'." + (let* ((default-directory (file-name-directory + (expand-file-name file))) + (file (file-name-nondirectory file)) + (current-rev + (with-temp-buffer + (and + (vc-git--out-ok "rev-list" "-1" rev "--" file) + (goto-char (point-max)) + (bolp) + (zerop (forward-line -1)) + (bobp) + (buffer-substring-no-properties + (point) + (1- (point-max)))))) + (next-rev + (and current-rev + (with-temp-buffer + (and + (vc-git--out-ok "rev-list" "HEAD" "--" file) + (goto-char (point-min)) + (search-forward current-rev nil t) + (zerop (forward-line -1)) + (buffer-substring-no-properties + (point) + (progn (forward-line 1) (1- (point))))))))) + (or (vc-git-symbolic-commit next-rev) next-rev))) + +(defun vc-git-delete-file (file) + (vc-git-command nil 0 file "rm" "-f" "--")) + +(defun vc-git-rename-file (old new) + (vc-git-command nil 0 (list old new) "mv" "-f" "--")) + +(defvar vc-git-extra-menu-map + (let ((map (make-sparse-keymap))) + (define-key map [git-grep] + '(menu-item "Git grep..." vc-git-grep + :help "Run the `git grep' command")) + (define-key map [git-sn] + '(menu-item "Stash a snapshot" vc-git-stash-snapshot + :help "Stash the current state of the tree and keep the current state")) + (define-key map [git-st] + '(menu-item "Create Stash..." vc-git-stash + :help "Stash away changes")) + (define-key map [git-ss] + '(menu-item "Show Stash..." vc-git-stash-show + :help "Show stash contents")) + map)) + +(defun vc-git-extra-menu () vc-git-extra-menu-map) + +(defun vc-git-extra-status-menu () vc-git-extra-menu-map) + +(defun vc-git-root (file) + (vc-find-root file ".git")) + +;; Derived from `lgrep'. +(defun vc-git-grep (regexp &optional files dir) + "Run git grep, searching for REGEXP in FILES in directory DIR. +The search is limited to file names matching shell pattern FILES. +FILES may use abbreviations defined in `grep-files-aliases', e.g. +entering `ch' is equivalent to `*.[ch]'. + +With \\[universal-argument] prefix, you can edit the constructed shell command line +before it is executed. +With two \\[universal-argument] prefixes, directly edit and run `grep-command'. + +Collect output in a buffer. While git grep runs asynchronously, you +can use \\[next-error] (M-x next-error), or \\\\[compile-goto-error] \ +in the grep output buffer, +to go to the lines where grep found matches. + +This command shares argument histories with \\[rgrep] and \\[grep]." + (interactive + (progn + (grep-compute-defaults) + (cond + ((equal current-prefix-arg '(16)) + (list (read-from-minibuffer "Run: " "git grep" + nil nil 'grep-history) + nil)) + (t (let* ((regexp (grep-read-regexp)) + (files (grep-read-files regexp)) + (dir (read-directory-name "In directory: " + nil default-directory t))) + (list regexp files dir)))))) + (require 'grep) + (when (and (stringp regexp) (> (length regexp) 0)) + (let ((command regexp)) + (if (null files) + (if (string= command "git grep") + (setq command nil)) + (setq dir (file-name-as-directory (expand-file-name dir))) + (setq command + (grep-expand-template "git grep -n -e -- " regexp files)) + (when command + (if (equal current-prefix-arg '(4)) + (setq command + (read-from-minibuffer "Confirm: " + command nil nil 'grep-history)) + (add-to-history 'grep-history command)))) + (when command + (let ((default-directory dir) + (compilation-environment '("PAGER="))) + ;; Setting process-setup-function makes exit-message-function work + ;; even when async processes aren't supported. + (compilation-start command 'grep-mode)) + (if (eq next-error-last-buffer (current-buffer)) + (setq default-directory dir)))))) + +(defun vc-git-stash (name) + "Create a stash." + (interactive "sStash name: ") + (let ((root (vc-git-root default-directory))) + (when root + (vc-git--call nil "stash" "save" name) + (vc-resynch-buffer root t t)))) + +(defun vc-git-stash-show (name) + "Show the contents of stash NAME." + (interactive "sStash name: ") + (vc-setup-buffer "*vc-git-stash*") + (vc-git-command "*vc-git-stash*" 'async nil "stash" "show" "-p" name) + (set-buffer "*vc-git-stash*") + (diff-mode) + (setq buffer-read-only t) + (pop-to-buffer (current-buffer))) + +(defun vc-git-stash-apply (name) + "Apply stash NAME." + (interactive "sApply stash: ") + (vc-git-command "*vc-git-stash*" 0 nil "stash" "apply" "-q" name) + (vc-resynch-buffer (vc-git-root default-directory) t t)) + +(defun vc-git-stash-pop (name) + "Pop stash NAME." + (interactive "sPop stash: ") + (vc-git-command "*vc-git-stash*" 0 nil "stash" "pop" "-q" name) + (vc-resynch-buffer (vc-git-root default-directory) t t)) + +(defun vc-git-stash-snapshot () + "Create a stash with the current tree state." + (interactive) + (vc-git--call nil "stash" "save" + (let ((ct (current-time))) + (concat + (format-time-string "Snapshot on %Y-%m-%d" ct) + (format-time-string " at %H:%M" ct)))) + (vc-git-command "*vc-git-stash*" 0 nil "stash" "apply" "-q" "stash@{0}") + (vc-resynch-buffer (vc-git-root default-directory) t t)) + +(defun vc-git-stash-list () + (delete + "" + (split-string + (replace-regexp-in-string + "^stash@" " " (vc-git--run-command-string nil "stash" "list")) + "\n"))) + +(defun vc-git-stash-get-at-point (point) + (save-excursion + (goto-char point) + (beginning-of-line) + (if (looking-at "^ +\\({[0-9]+}\\):") + (match-string 1) + (error "Cannot find stash at point")))) + +(defun vc-git-stash-delete-at-point () + (interactive) + (let ((stash (vc-git-stash-get-at-point (point)))) + (when (y-or-n-p (format "Remove stash %s ? " stash)) + (vc-git--run-command-string nil "stash" "drop" (format "stash@%s" stash)) + (vc-dir-refresh)))) + +(defun vc-git-stash-show-at-point () + (interactive) + (vc-git-stash-show (format "stash@%s" (vc-git-stash-get-at-point (point))))) + +(defun vc-git-stash-apply-at-point () + (interactive) + (vc-git-stash-apply (format "stash@%s" (vc-git-stash-get-at-point (point))))) + +(defun vc-git-stash-pop-at-point () + (interactive) + (vc-git-stash-pop (format "stash@%s" (vc-git-stash-get-at-point (point))))) + +(defun vc-git-stash-menu (e) + (interactive "e") + (vc-dir-at-event e (popup-menu vc-git-stash-menu-map e))) + + +;;; Internal commands + +(defun vc-git-command (buffer okstatus file-or-list &rest flags) + "A wrapper around `vc-do-command' for use in vc-git.el. +The difference to vc-do-command is that this function always invokes `git'." + (apply 'vc-do-command (or buffer "*vc*") okstatus "git" file-or-list flags)) + +(defun vc-git--empty-db-p () + "Check if the git db is empty (no commit done yet)." + (let (process-file-side-effects) + (not (eq 0 (vc-git--call nil "rev-parse" "--verify" "HEAD"))))) + +(defun vc-git--call (buffer command &rest args) + ;; We don't need to care the arguments. If there is a file name, it + ;; is always a relative one. This works also for remote + ;; directories. + (apply 'process-file "git" nil buffer nil command args)) + +(defun vc-git--out-ok (command &rest args) + (zerop (apply 'vc-git--call '(t nil) command args))) + +(defun vc-git--run-command-string (file &rest args) + "Run a git command on FILE and return its output as string. +FILE can be nil." + (let* ((ok t) + (str (with-output-to-string + (with-current-buffer standard-output + (unless (apply 'vc-git--out-ok + (if file + (append args (list (file-relative-name + file))) + args)) + (setq ok nil)))))) + (and ok str))) + +(defun vc-git-symbolic-commit (commit) + "Translate COMMIT string into symbolic form. +Returns nil if not possible." + (and commit + (let ((name (with-temp-buffer + (and + (vc-git--out-ok "name-rev" "--name-only" commit) + (goto-char (point-min)) + (= (forward-line 2) 1) + (bolp) + (buffer-substring-no-properties (point-min) + (1- (point-max))))))) + (and name (not (string= name "undefined")) name)))) + +(provide 'vc-git) + +;; arch-tag: bd10664a-0e5b-48f5-a877-6c17b135be12 +;;; vc-git.el ends here diff -r 681cd08dc0f7 -r e93288477c43 lisp/vc/vc-hg.el --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/lisp/vc/vc-hg.el Sun Jun 13 22:57:55 2010 +0000 @@ -0,0 +1,630 @@ +;;; vc-hg.el --- VC backend for the mercurial version control system + +;; Copyright (C) 2006, 2007, 2008, 2009, 2010 Free Software Foundation, Inc. + +;; Author: Ivan Kanis +;; Keywords: vc tools + +;; 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 3 of the License, 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. If not, see . + +;;; Commentary: + +;; This is a mercurial version control backend + +;;; Thanks: + +;;; Bugs: + +;;; Installation: + +;;; Todo: + +;; 1) Implement the rest of the vc interface. See the comment at the +;; beginning of vc.el. The current status is: + +;; FUNCTION NAME STATUS +;; BACKEND PROPERTIES +;; * revision-granularity OK +;; STATE-QUERYING FUNCTIONS +;; * registered (file) OK +;; * state (file) OK +;; - state-heuristic (file) NOT NEEDED +;; - dir-status (dir update-function) OK +;; - dir-status-files (dir files ds uf) OK +;; - dir-extra-headers (dir) OK +;; - dir-printer (fileinfo) OK +;; * working-revision (file) OK +;; - latest-on-branch-p (file) ?? +;; * checkout-model (files) OK +;; - workfile-unchanged-p (file) OK +;; - mode-line-string (file) NOT NEEDED +;; STATE-CHANGING FUNCTIONS +;; * register (files &optional rev comment) OK +;; * create-repo () OK +;; - init-revision () NOT NEEDED +;; - responsible-p (file) OK +;; - could-register (file) OK +;; - receive-file (file rev) ?? PROBABLY NOT NEEDED +;; - unregister (file) COMMENTED OUT, MAY BE INCORRECT +;; * checkin (files rev comment) OK +;; * find-revision (file rev buffer) OK +;; * checkout (file &optional editable rev) OK +;; * revert (file &optional contents-done) OK +;; - rollback (files) ?? PROBABLY NOT NEEDED +;; - merge (file rev1 rev2) NEEDED +;; - merge-news (file) NEEDED +;; - steal-lock (file &optional revision) NOT NEEDED +;; HISTORY FUNCTIONS +;; * print-log (files buffer &optional shortlog start-revision limit) OK +;; - log-view-mode () OK +;; - show-log-entry (revision) NOT NEEDED, DEFAULT IS GOOD +;; - comment-history (file) NOT NEEDED +;; - update-changelog (files) NOT NEEDED +;; * diff (files &optional rev1 rev2 buffer) OK +;; - revision-completion-table (files) OK? +;; - annotate-command (file buf &optional rev) OK +;; - annotate-time () OK +;; - annotate-current-time () NOT NEEDED +;; - annotate-extract-revision-at-line () OK +;; TAG SYSTEM +;; - create-tag (dir name branchp) NEEDED +;; - retrieve-tag (dir name update) NEEDED +;; MISCELLANEOUS +;; - make-version-backups-p (file) ?? +;; - repository-hostname (dirname) ?? +;; - previous-revision (file rev) OK +;; - next-revision (file rev) OK +;; - check-headers () ?? +;; - clear-headers () ?? +;; - delete-file (file) TEST IT +;; - rename-file (old new) OK +;; - find-file-hook () PROBABLY NOT NEEDED + +;; 2) Implement Stefan Monnier's advice: +;; vc-hg-registered and vc-hg-state +;; Both of those functions should be super extra careful to fail gracefully in +;; unexpected circumstances. The reason this is important is that any error +;; there will prevent the user from even looking at the file :-( +;; Ideally, just like in vc-arch and vc-cvs, checking that the file is under +;; mercurial's control and extracting the current revision should be done +;; without even using `hg' (this way even if you don't have `hg' installed, +;; Emacs is able to tell you this file is under mercurial's control). + +;;; History: +;; + +;;; Code: + +(eval-when-compile + (require 'cl) + (require 'vc) + (require 'vc-dir)) + +;;; Customization options + +(defcustom vc-hg-global-switches nil + "Global switches to pass to any Hg command." + :type '(choice (const :tag "None" nil) + (string :tag "Argument String") + (repeat :tag "Argument List" :value ("") string)) + :version "22.2" + :group 'vc) + +(defcustom vc-hg-diff-switches t ; Hg doesn't support common args like -u + "String or list of strings specifying switches for Hg diff under VC. +If nil, use the value of `vc-diff-switches'. If t, use no switches." + :type '(choice (const :tag "Unspecified" nil) + (const :tag "None" t) + (string :tag "Argument String") + (repeat :tag "Argument List" :value ("") string)) + :version "23.1" + :group 'vc) + + +;;; Properties of the backend + +(defun vc-hg-revision-granularity () 'repository) +(defun vc-hg-checkout-model (files) 'implicit) + +;;; State querying functions + +;;;###autoload (defun vc-hg-registered (file) +;;;###autoload "Return non-nil if FILE is registered with hg." +;;;###autoload (if (vc-find-root file ".hg") ; short cut +;;;###autoload (progn +;;;###autoload (load "vc-hg") +;;;###autoload (vc-hg-registered file)))) + +;; Modeled after the similar function in vc-bzr.el +(defun vc-hg-registered (file) + "Return non-nil if FILE is registered with hg." + (when (vc-hg-root file) ; short cut + (let ((state (vc-hg-state file))) ; expensive + (and state (not (memq state '(ignored unregistered))))))) + +(defun vc-hg-state (file) + "Hg-specific version of `vc-state'." + (let* + ((status nil) + (default-directory (file-name-directory file)) + (out + (with-output-to-string + (with-current-buffer + standard-output + (setq status + (condition-case nil + ;; Ignore all errors. + (let ((process-environment + ;; Avoid localization of messages so we + ;; can parse the output. + (append (list "TERM=dumb" "LANGUAGE=C" "HGRCPATH=") + process-environment))) + (process-file + "hg" nil t nil + "status" "-A" (file-relative-name file))) + ;; Some problem happened. E.g. We can't find an `hg' + ;; executable. + (error nil))))))) + (when (eq 0 status) + (when (null (string-match ".*: No such file or directory$" out)) + (let ((state (aref out 0))) + (cond + ((eq state ?=) 'up-to-date) + ((eq state ?A) 'added) + ((eq state ?M) 'edited) + ((eq state ?I) 'ignored) + ((eq state ?R) 'removed) + ((eq state ?!) 'missing) + ((eq state ??) 'unregistered) + ((eq state ?C) 'up-to-date) ;; Older mercurials use this + (t 'up-to-date))))))) + +(defun vc-hg-working-revision (file) + "Hg-specific version of `vc-working-revision'." + (let* + ((status nil) + (default-directory (file-name-directory file)) + ;; Avoid localization of messages so we can parse the output. + (avoid-local-env (append (list "TERM=dumb" "LANGUAGE=C" "HGRCPATH=") + process-environment)) + (out + (with-output-to-string + (with-current-buffer + standard-output + (setq status + (condition-case nil + (let ((process-environment avoid-local-env)) + ;; Ignore all errors. + (process-file + "hg" nil t nil + "parents" "--template" "{rev}" (file-relative-name file))) + ;; Some problem happened. E.g. We can't find an `hg' + ;; executable. + (error nil))))))) + (if (eq 0 status) + out + ;; Check if the file is in the 'added state, the above hg + ;; command does not distinguish between 'added and 'unregistered. + (setq status + (condition-case nil + (let ((process-environment avoid-local-env)) + (process-file + "hg" nil nil nil + ;; We use "log" here, if there's a faster command + ;; that returns true for an 'added file and false + ;; for an 'unregistered one, we could use that. + "log" "-l1" (file-relative-name file))) + ;; Some problem happened. E.g. We can't find an `hg' + ;; executable. + (error nil))) + (when (eq 0 status) "0")))) + +;;; History functions + +(defcustom vc-hg-log-switches nil + "String or list of strings specifying switches for hg log under VC." + :type '(choice (const :tag "None" nil) + (string :tag "Argument String") + (repeat :tag "Argument List" :value ("") string)) + :group 'vc-hg) + +(defun vc-hg-print-log (files buffer &optional shortlog start-revision limit) + "Get change log associated with FILES." + ;; `vc-do-command' creates the buffer, but we need it before running + ;; the command. + (vc-setup-buffer buffer) + ;; If the buffer exists from a previous invocation it might be + ;; read-only. + (let ((inhibit-read-only t)) + (with-current-buffer + buffer + (apply 'vc-hg-command buffer 0 files "log" + (nconc + (when start-revision (list (format "-r%s:" start-revision))) + (when limit (list "-l" (format "%s" limit))) + (when shortlog (list "--style" "compact")) + vc-hg-log-switches))))) + +(defvar log-view-message-re) +(defvar log-view-file-re) +(defvar log-view-font-lock-keywords) +(defvar log-view-per-file-logs) + +(define-derived-mode vc-hg-log-view-mode log-view-mode "Hg-Log-View" + (require 'add-log) ;; we need the add-log faces + (set (make-local-variable 'log-view-file-re) "\\`a\\`") + (set (make-local-variable 'log-view-per-file-logs) nil) + (set (make-local-variable 'log-view-message-re) + (if (eq vc-log-view-type 'short) + "^\\([0-9]+\\)\\(\\[.*\\]\\)? +\\([0-9a-z]\\{12\\}\\) +\\(\\(?:[0-9]+\\)-\\(?:[0-9]+\\)-\\(?:[0-9]+\\) \\(?:[0-9]+\\):\\(?:[0-9]+\\) \\(?:[-+0-9]+\\)\\) +\\(.*\\)$" + "^changeset:[ \t]*\\([0-9]+\\):\\(.+\\)")) + (set (make-local-variable 'log-view-font-lock-keywords) + (if (eq vc-log-view-type 'short) + (append `((,log-view-message-re + (1 'log-view-message-face) + (2 'highlight nil lax) + (3 'log-view-message-face) + (4 'change-log-date) + (5 'change-log-name)))) + (append + log-view-font-lock-keywords + '( + ;; Handle the case: + ;; user: FirstName LastName + ("^user:[ \t]+\\([^<(]+?\\)[ \t]*[(<]\\([A-Za-z0-9_.+-]+@[A-Za-z0-9_.-]+\\)[>)]" + (1 'change-log-name) + (2 'change-log-email)) + ;; Handle the cases: + ;; user: foo@bar + ;; and + ;; user: foo + ("^user:[ \t]+\\([A-Za-z0-9_.+-]+\\(?:@[A-Za-z0-9_.-]+\\)?\\)" + (1 'change-log-email)) + ("^date: \\(.+\\)" (1 'change-log-date)) + ("^tag: +\\([^ ]+\\)$" (1 'highlight)) + ("^summary:[ \t]+\\(.+\\)" (1 'log-view-message))))))) + +(defun vc-hg-diff (files &optional oldvers newvers buffer) + "Get a difference report using hg between two revisions of FILES." + (let* ((firstfile (car files)) + (working (and firstfile (vc-working-revision firstfile)))) + (when (and (equal oldvers working) (not newvers)) + (setq oldvers nil)) + (when (and (not oldvers) newvers) + (setq oldvers working)) + (apply #'vc-hg-command (or buffer "*vc-diff*") nil files "diff" + (append + (vc-switches 'hg 'diff) + (when oldvers + (if newvers + (list "-r" oldvers "-r" newvers) + (list "-r" oldvers))))))) + +(defun vc-hg-revision-table (files) + (let ((default-directory (file-name-directory (car files)))) + (with-temp-buffer + (vc-hg-command t nil files "log" "--template" "{rev} ") + (split-string + (buffer-substring-no-properties (point-min) (point-max)))))) + +;; Modeled after the similar function in vc-cvs.el +(defun vc-hg-revision-completion-table (files) + (lexical-let ((files files) + table) + (setq table (lazy-completion-table + table (lambda () (vc-hg-revision-table files)))) + table)) + +(defun vc-hg-annotate-command (file buffer &optional revision) + "Execute \"hg annotate\" on FILE, inserting the contents in BUFFER. +Optional arg REVISION is a revision to annotate from." + (vc-hg-command buffer 0 file "annotate" "-d" "-n" "--follow" + (when revision (concat "-r" revision)))) + +(declare-function vc-annotate-convert-time "vc-annotate" (time)) + +;; The format for one line output by "hg annotate -d -n" looks like this: +;;215 Wed Jun 20 21:22:58 2007 -0700: CONTENTS +;; i.e: VERSION_NUMBER DATE: CONTENTS +;; If the user has set the "--follow" option, the output looks like: +;;215 Wed Jun 20 21:22:58 2007 -0700 foo.c: CONTENTS +;; i.e. VERSION_NUMBER DATE FILENAME: CONTENTS +(defconst vc-hg-annotate-re + "^[ \t]*\\([0-9]+\\) \\(.\\{30\\}\\)\\(?:\\(: \\)\\|\\(?: +\\(.+\\): \\)\\)") + +(defun vc-hg-annotate-time () + (when (looking-at vc-hg-annotate-re) + (goto-char (match-end 0)) + (vc-annotate-convert-time + (date-to-time (match-string-no-properties 2))))) + +(defun vc-hg-annotate-extract-revision-at-line () + (save-excursion + (beginning-of-line) + (when (looking-at vc-hg-annotate-re) + (if (match-beginning 3) + (match-string-no-properties 1) + (cons (match-string-no-properties 1) + (expand-file-name (match-string-no-properties 4) + (vc-hg-root default-directory))))))) + +(defun vc-hg-previous-revision (file rev) + (let ((newrev (1- (string-to-number rev)))) + (when (>= newrev 0) + (number-to-string newrev)))) + +(defun vc-hg-next-revision (file rev) + (let ((newrev (1+ (string-to-number rev))) + (tip-revision + (with-temp-buffer + (vc-hg-command t 0 nil "tip") + (goto-char (point-min)) + (re-search-forward "^changeset:[ \t]*\\([0-9]+\\):") + (string-to-number (match-string-no-properties 1))))) + ;; We don't want to exceed the maximum possible revision number, ie + ;; the tip revision. + (when (<= newrev tip-revision) + (number-to-string newrev)))) + +;; Modeled after the similar function in vc-bzr.el +(defun vc-hg-delete-file (file) + "Delete FILE and delete it in the hg repository." + (condition-case () + (delete-file file) + (file-error nil)) + (vc-hg-command nil 0 file "remove" "--after" "--force")) + +;; Modeled after the similar function in vc-bzr.el +(defun vc-hg-rename-file (old new) + "Rename file from OLD to NEW using `hg mv'." + (vc-hg-command nil 0 new "mv" old)) + +(defun vc-hg-register (files &optional rev comment) + "Register FILES under hg. +REV is ignored. +COMMENT is ignored." + (vc-hg-command nil 0 files "add")) + +(defun vc-hg-create-repo () + "Create a new Mercurial repository." + (vc-hg-command nil 0 nil "init")) + +(defalias 'vc-hg-responsible-p 'vc-hg-root) + +;; Modeled after the similar function in vc-bzr.el +(defun vc-hg-could-register (file) + "Return non-nil if FILE could be registered under hg." + (and (vc-hg-responsible-p file) ; shortcut + (condition-case () + (with-temp-buffer + (vc-hg-command t nil file "add" "--dry-run")) + ;; The command succeeds with no output if file is + ;; registered. + (error)))) + +;; FIXME: This would remove the file. Is that correct? +;; (defun vc-hg-unregister (file) +;; "Unregister FILE from hg." +;; (vc-hg-command nil nil file "remove")) + +(declare-function log-edit-extract-headers "log-edit" (headers string)) + +(defun vc-hg-checkin (files rev comment) + "Hg-specific version of `vc-backend-checkin'. +REV is ignored." + (apply 'vc-hg-command nil 0 files + (nconc (list "commit" "-m") + (log-edit-extract-headers '(("Author" . "--user") + ("Date" . "--date")) + comment)))) + +(defun vc-hg-find-revision (file rev buffer) + (let ((coding-system-for-read 'binary) + (coding-system-for-write 'binary)) + (if rev + (vc-hg-command buffer 0 file "cat" "-r" rev) + (vc-hg-command buffer 0 file "cat")))) + +;; Modeled after the similar function in vc-bzr.el +(defun vc-hg-checkout (file &optional editable rev) + "Retrieve a revision of FILE. +EDITABLE is ignored. +REV is the revision to check out into WORKFILE." + (let ((coding-system-for-read 'binary) + (coding-system-for-write 'binary)) + (with-current-buffer (or (get-file-buffer file) (current-buffer)) + (if rev + (vc-hg-command t 0 file "cat" "-r" rev) + (vc-hg-command t 0 file "cat"))))) + +;; Modeled after the similar function in vc-bzr.el +(defun vc-hg-workfile-unchanged-p (file) + (eq 'up-to-date (vc-hg-state file))) + +;; Modeled after the similar function in vc-bzr.el +(defun vc-hg-revert (file &optional contents-done) + (unless contents-done + (with-temp-buffer (vc-hg-command t 0 file "revert")))) + +;;; Hg specific functionality. + +(defvar vc-hg-extra-menu-map + (let ((map (make-sparse-keymap))) + map)) + +(defun vc-hg-extra-menu () vc-hg-extra-menu-map) + +(defun vc-hg-extra-status-menu () vc-hg-extra-menu-map) + +(defvar log-view-vc-backend) + +(defstruct (vc-hg-extra-fileinfo + (:copier nil) + (:constructor vc-hg-create-extra-fileinfo (rename-state extra-name)) + (:conc-name vc-hg-extra-fileinfo->)) + rename-state ;; rename or copy state + extra-name) ;; original name for copies and rename targets, new name for + +(declare-function vc-default-dir-printer "vc-dir" (backend fileentry)) + +(defun vc-hg-dir-printer (info) + "Pretty-printer for the vc-dir-fileinfo structure." + (let ((extra (vc-dir-fileinfo->extra info))) + (vc-default-dir-printer 'Hg info) + (when extra + (insert (propertize + (format " (%s %s)" + (case (vc-hg-extra-fileinfo->rename-state extra) + ('copied "copied from") + ('renamed-from "renamed from") + ('renamed-to "renamed to")) + (vc-hg-extra-fileinfo->extra-name extra)) + 'face 'font-lock-comment-face))))) + +(defun vc-hg-after-dir-status (update-function) + (let ((status-char nil) + (file nil) + (translation '((?= . up-to-date) + (?C . up-to-date) + (?A . added) + (?R . removed) + (?M . edited) + (?I . ignored) + (?! . missing) + (? . copy-rename-line) + (?? . unregistered))) + (translated nil) + (result nil) + (last-added nil) + (last-line-copy nil)) + (goto-char (point-min)) + (while (not (eobp)) + (setq translated (cdr (assoc (char-after) translation))) + (setq file + (buffer-substring-no-properties (+ (point) 2) + (line-end-position))) + (cond ((not translated) + (setq last-line-copy nil)) + ((eq translated 'up-to-date) + (setq last-line-copy nil)) + ((eq translated 'copy-rename-line) + ;; For copied files the output looks like this: + ;; A COPIED_FILE_NAME + ;; ORIGINAL_FILE_NAME + (setf (nth 2 last-added) + (vc-hg-create-extra-fileinfo 'copied file)) + (setq last-line-copy t)) + ((and last-line-copy (eq translated 'removed)) + ;; For renamed files the output looks like this: + ;; A NEW_FILE_NAME + ;; ORIGINAL_FILE_NAME + ;; R ORIGINAL_FILE_NAME + ;; We need to adjust the previous entry to not think it is a copy. + (setf (vc-hg-extra-fileinfo->rename-state (nth 2 last-added)) + 'renamed-from) + (push (list file translated + (vc-hg-create-extra-fileinfo + 'renamed-to (nth 0 last-added))) result) + (setq last-line-copy nil)) + (t + (setq last-added (list file translated nil)) + (push last-added result) + (setq last-line-copy nil))) + (forward-line)) + (funcall update-function result))) + +(defun vc-hg-dir-status (dir update-function) + (vc-hg-command (current-buffer) 'async dir "status" "-C") + (vc-exec-after + `(vc-hg-after-dir-status (quote ,update-function)))) + +(defun vc-hg-dir-status-files (dir files default-state update-function) + (apply 'vc-hg-command (current-buffer) 'async dir "status" "-C" files) + (vc-exec-after + `(vc-hg-after-dir-status (quote ,update-function)))) + +(defun vc-hg-dir-extra-header (name &rest commands) + (concat (propertize name 'face 'font-lock-type-face) + (propertize + (with-temp-buffer + (apply 'vc-hg-command (current-buffer) 0 nil commands) + (buffer-substring-no-properties (point-min) (1- (point-max)))) + 'face 'font-lock-variable-name-face))) + +(defun vc-hg-dir-extra-headers (dir) + "Generate extra status headers for a Mercurial tree." + (let ((default-directory dir)) + (concat + (vc-hg-dir-extra-header "Root : " "root") "\n" + (vc-hg-dir-extra-header "Branch : " "id" "-b") "\n" + (vc-hg-dir-extra-header "Tags : " "id" "-t") ; "\n" + ;; these change after each commit + ;; (vc-hg-dir-extra-header "Local num : " "id" "-n") "\n" + ;; (vc-hg-dir-extra-header "Global id : " "id" "-i") + ))) + +(defun vc-hg-log-incoming (buffer remote-location) + (vc-hg-command buffer 1 nil "incoming" "-n" (unless (string= remote-location "") + remote-location))) + +(defun vc-hg-log-outgoing (buffer remote-location) + (vc-hg-command buffer 1 nil "outgoing" "-n" (unless (string= remote-location "") + remote-location))) + +(declare-function log-view-get-marked "log-view" ()) + +;; XXX maybe also add key bindings for these functions. +(defun vc-hg-push () + (interactive) + (let ((marked-list (log-view-get-marked))) + (if marked-list + (apply #'vc-hg-command + nil 0 nil + "push" + (apply 'nconc + (mapcar (lambda (arg) (list "-r" arg)) marked-list))) + (error "No log entries selected for push")))) + +(defun vc-hg-pull () + (interactive) + (let ((marked-list (log-view-get-marked))) + (if marked-list + (apply #'vc-hg-command + nil 0 nil + "pull" + (apply 'nconc + (mapcar (lambda (arg) (list "-r" arg)) marked-list))) + (error "No log entries selected for pull")))) + +;;; Internal functions + +(defun vc-hg-command (buffer okstatus file-or-list &rest flags) + "A wrapper around `vc-do-command' for use in vc-hg.el. +The difference to vc-do-command is that this function always invokes `hg', +and that it passes `vc-hg-global-switches' to it before FLAGS." + (apply 'vc-do-command (or buffer "*vc*") okstatus "hg" file-or-list + (if (stringp vc-hg-global-switches) + (cons vc-hg-global-switches flags) + (append vc-hg-global-switches + flags)))) + +(defun vc-hg-root (file) + (vc-find-root file ".hg")) + +(provide 'vc-hg) + +;; arch-tag: bd094dc5-715a-434f-a331-37b9fb7cd954 +;;; vc-hg.el ends here diff -r 681cd08dc0f7 -r e93288477c43 lisp/vc/vc-hooks.el --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/lisp/vc/vc-hooks.el Sun Jun 13 22:57:55 2010 +0000 @@ -0,0 +1,1055 @@ +;;; vc-hooks.el --- resident support for version-control + +;; Copyright (C) 1992, 1993, 1994, 1995, 1996, 1998, 1999, 2000, 2001, +;; 2002, 2003, 2004, 2005, 2006, 2007, 2008, 2009, 2010 +;; Free Software Foundation, Inc. + +;; Author: FSF (see vc.el for full credits) +;; Maintainer: Andre Spiegel + +;; 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 3 of the License, 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. If not, see . + +;;; Commentary: + +;; This is the always-loaded portion of VC. It takes care of +;; VC-related activities that are done when you visit a file, so that +;; vc.el itself is loaded only when you use a VC command. See the +;; commentary of vc.el. + +;;; Code: + +(eval-when-compile + (require 'cl)) + +;; Customization Variables (the rest is in vc.el) + +(defvar vc-ignore-vc-files nil) +(make-obsolete-variable 'vc-ignore-vc-files + "set `vc-handled-backends' to nil to disable VC." + "21.1") + +(defvar vc-master-templates ()) +(make-obsolete-variable 'vc-master-templates + "to define master templates for a given BACKEND, use +vc-BACKEND-master-templates. To enable or disable VC for a given +BACKEND, use `vc-handled-backends'." + "21.1") + +(defvar vc-header-alist ()) +(make-obsolete-variable 'vc-header-alist 'vc-BACKEND-header "21.1") + +(defcustom vc-ignore-dir-regexp + ;; Stop SMB, automounter, AFS, and DFS host lookups. + locate-dominating-stop-dir-regexp + "Regexp matching directory names that are not under VC's control. +The default regexp prevents fruitless and time-consuming attempts +to determine the VC status in directories in which filenames are +interpreted as hostnames." + :type 'regexp + :group 'vc) + +(defcustom vc-handled-backends '(RCS CVS SVN SCCS Bzr Git Hg Mtn Arch) + ;; RCS, CVS, SVN and SCCS come first because they are per-dir + ;; rather than per-tree. RCS comes first because of the multibackend + ;; support intended to use RCS for local commits (with a remote CVS server). + "List of version control backends for which VC will be used. +Entries in this list will be tried in order to determine whether a +file is under that sort of version control. +Removing an entry from the list prevents VC from being activated +when visiting a file managed by that backend. +An empty list disables VC altogether." + :type '(repeat symbol) + :version "23.1" + :group 'vc) + +;; Note: we don't actually have a darcs back end yet. +;; Also, Meta-CVS (corresponsding to MCVS) is unsupported. +(defcustom vc-directory-exclusion-list (purecopy '("SCCS" "RCS" "CVS" "MCVS" + ".svn" ".git" ".hg" ".bzr" + "_MTN" "_darcs" "{arch}")) + "List of directory names to be ignored when walking directory trees." + :type '(repeat string) + :group 'vc) + +(defcustom vc-make-backup-files nil + "If non-nil, backups of registered files are made as with other files. +If nil (the default), files covered by version control don't get backups." + :type 'boolean + :group 'vc + :group 'backup) + +(defcustom vc-follow-symlinks 'ask + "What to do if visiting a symbolic link to a file under version control. +Editing such a file through the link bypasses the version control system, +which is dangerous and probably not what you want. + +If this variable is t, VC follows the link and visits the real file, +telling you about it in the echo area. If it is `ask', VC asks for +confirmation whether it should follow the link. If nil, the link is +visited and a warning displayed." + :type '(choice (const :tag "Ask for confirmation" ask) + (const :tag "Visit link and warn" nil) + (const :tag "Follow link" t)) + :group 'vc) + +(defcustom vc-display-status t + "If non-nil, display revision number and lock status in modeline. +Otherwise, not displayed." + :type 'boolean + :group 'vc) + + +(defcustom vc-consult-headers t + "If non-nil, identify work files by searching for version headers." + :type 'boolean + :group 'vc) + +(defcustom vc-keep-workfiles t + "If non-nil, don't delete working files after registering changes. +If the back-end is CVS, workfiles are always kept, regardless of the +value of this flag." + :type 'boolean + :group 'vc) + +(defcustom vc-mistrust-permissions nil + "If non-nil, don't assume permissions/ownership track version-control status. +If nil, do rely on the permissions. +See also variable `vc-consult-headers'." + :type 'boolean + :group 'vc) + +(defun vc-mistrust-permissions (file) + "Internal access function to variable `vc-mistrust-permissions' for FILE." + (or (eq vc-mistrust-permissions 't) + (and vc-mistrust-permissions + (funcall vc-mistrust-permissions + (vc-backend-subdirectory-name file))))) + +(defcustom vc-stay-local 'only-file + "Non-nil means use local operations when possible for remote repositories. +This avoids slow queries over the network and instead uses heuristics +and past information to determine the current status of a file. + +If value is the symbol `only-file' `vc-dir' will connect to the +server, but heuristics will be used to determine the status for +all other VC operations. + +The value can also be a regular expression or list of regular +expressions to match against the host name of a repository; then VC +only stays local for hosts that match it. Alternatively, the value +can be a list of regular expressions where the first element is the +symbol `except'; then VC always stays local except for hosts matched +by these regular expressions." + :type '(choice + (const :tag "Always stay local" t) + (const :tag "Only for file operations" only-file) + (const :tag "Don't stay local" nil) + (list :format "\nExamine hostname and %v" :tag "Examine hostname ..." + (set :format "%v" :inline t (const :format "%t" :tag "don't" except)) + (regexp :format " stay local,\n%t: %v" :tag "if it matches") + (repeat :format "%v%i\n" :inline t (regexp :tag "or")))) + :version "23.1" + :group 'vc) + +(defun vc-stay-local-p (file &optional backend) + "Return non-nil if VC should stay local when handling FILE. +This uses the `repository-hostname' backend operation. +If FILE is a list of files, return non-nil if any of them +individually should stay local." + (if (listp file) + (delq nil (mapcar (lambda (arg) (vc-stay-local-p arg backend)) file)) + (setq backend (or backend (vc-backend file))) + (let* ((sym (vc-make-backend-sym backend 'stay-local)) + (stay-local (if (boundp sym) (symbol-value sym) vc-stay-local))) + (if (symbolp stay-local) stay-local + (let ((dirname (if (file-directory-p file) + (directory-file-name file) + (file-name-directory file)))) + (eq 'yes + (or (vc-file-getprop dirname 'vc-stay-local-p) + (vc-file-setprop + dirname 'vc-stay-local-p + (let ((hostname (vc-call-backend + backend 'repository-hostname dirname))) + (if (not hostname) + 'no + (let ((default t)) + (if (eq (car-safe stay-local) 'except) + (setq default nil stay-local (cdr stay-local))) + (when (consp stay-local) + (setq stay-local + (mapconcat 'identity stay-local "\\|"))) + (if (if (string-match stay-local hostname) + default (not default)) + 'yes 'no)))))))))))) + +;;; This is handled specially now. +;; Tell Emacs about this new kind of minor mode +;; (add-to-list 'minor-mode-alist '(vc-mode vc-mode)) + +;;;###autoload +(put 'vc-mode 'risky-local-variable t) +(make-variable-buffer-local 'vc-mode) +(put 'vc-mode 'permanent-local t) + +(defun vc-mode (&optional arg) + ;; Dummy function for C-h m + "Version Control minor mode. +This minor mode is automatically activated whenever you visit a file under +control of one of the revision control systems in `vc-handled-backends'. +VC commands are globally reachable under the prefix `\\[vc-prefix-map]': +\\{vc-prefix-map}") + +(defmacro vc-error-occurred (&rest body) + `(condition-case nil (progn ,@body nil) (error t))) + +;; We need a notion of per-file properties because the version +;; control state of a file is expensive to derive --- we compute +;; them when the file is initially found, keep them up to date +;; during any subsequent VC operations, and forget them when +;; the buffer is killed. + +(defvar vc-file-prop-obarray (make-vector 17 0) + "Obarray for per-file properties.") + +(defvar vc-touched-properties nil) + +(defun vc-file-setprop (file property value) + "Set per-file VC PROPERTY for FILE to VALUE." + (if (and vc-touched-properties + (not (memq property vc-touched-properties))) + (setq vc-touched-properties (append (list property) + vc-touched-properties))) + (put (intern file vc-file-prop-obarray) property value)) + +(defun vc-file-getprop (file property) + "Get per-file VC PROPERTY for FILE." + (get (intern file vc-file-prop-obarray) property)) + +(defun vc-file-clearprops (file) + "Clear all VC properties of FILE." + (setplist (intern file vc-file-prop-obarray) nil)) + + +;; We keep properties on each symbol naming a backend as follows: +;; * `vc-functions': an alist mapping vc-FUNCTION to vc-BACKEND-FUNCTION. + +(defun vc-make-backend-sym (backend sym) + "Return BACKEND-specific version of VC symbol SYM." + (intern (concat "vc-" (downcase (symbol-name backend)) + "-" (symbol-name sym)))) + +(defun vc-find-backend-function (backend fun) + "Return BACKEND-specific implementation of FUN. +If there is no such implementation, return the default implementation; +if that doesn't exist either, return nil." + (let ((f (vc-make-backend-sym backend fun))) + (if (fboundp f) f + ;; Load vc-BACKEND.el if needed. + (require (intern (concat "vc-" (downcase (symbol-name backend))))) + (if (fboundp f) f + (let ((def (vc-make-backend-sym 'default fun))) + (if (fboundp def) (cons def backend) nil)))))) + +(defun vc-call-backend (backend function-name &rest args) + "Call for BACKEND the implementation of FUNCTION-NAME with the given ARGS. +Calls + + (apply 'vc-BACKEND-FUN ARGS) + +if vc-BACKEND-FUN exists (after trying to find it in vc-BACKEND.el) +and else calls + + (apply 'vc-default-FUN BACKEND ARGS) + +It is usually called via the `vc-call' macro." + (let ((f (assoc function-name (get backend 'vc-functions)))) + (if f (setq f (cdr f)) + (setq f (vc-find-backend-function backend function-name)) + (push (cons function-name f) (get backend 'vc-functions))) + (cond + ((null f) + (error "Sorry, %s is not implemented for %s" function-name backend)) + ((consp f) (apply (car f) (cdr f) args)) + (t (apply f args))))) + +(defmacro vc-call (fun file &rest args) + "A convenience macro for calling VC backend functions. +Functions called by this macro must accept FILE as the first argument. +ARGS specifies any additional arguments. FUN should be unquoted. +BEWARE!! FILE is evaluated twice!!" + `(vc-call-backend (vc-backend ,file) ',fun ,file ,@args)) + +(defsubst vc-parse-buffer (pattern i) + "Find PATTERN in the current buffer and return its Ith submatch." + (goto-char (point-min)) + (if (re-search-forward pattern nil t) + (match-string i))) + +(defun vc-insert-file (file &optional limit blocksize) + "Insert the contents of FILE into the current buffer. + +Optional argument LIMIT is a regexp. If present, the file is inserted +in chunks of size BLOCKSIZE (default 8 kByte), until the first +occurrence of LIMIT is found. Anything from the start of that occurrence +to the end of the buffer is then deleted. The function returns +non-nil if FILE exists and its contents were successfully inserted." + (erase-buffer) + (when (file-exists-p file) + (if (not limit) + (insert-file-contents file) + (unless blocksize (setq blocksize 8192)) + (let ((filepos 0)) + (while + (and (< 0 (cadr (insert-file-contents + file nil filepos (incf filepos blocksize)))) + (progn (beginning-of-line) + (let ((pos (re-search-forward limit nil 'move))) + (when pos (delete-region (match-beginning 0) + (point-max))) + (not pos))))))) + (set-buffer-modified-p nil) + t)) + +(defun vc-find-root (file witness) + "Find the root of a checked out project. +The function walks up the directory tree from FILE looking for WITNESS. +If WITNESS if not found, return nil, otherwise return the root." + (let ((locate-dominating-stop-dir-regexp + (or vc-ignore-dir-regexp locate-dominating-stop-dir-regexp))) + (locate-dominating-file file witness))) + +;; Access functions to file properties +;; (Properties should be _set_ using vc-file-setprop, but +;; _retrieved_ only through these functions, which decide +;; if the property is already known or not. A property should +;; only be retrieved by vc-file-getprop if there is no +;; access function.) + +;; properties indicating the backend being used for FILE + +(defun vc-registered (file) + "Return non-nil if FILE is registered in a version control system. + +This function performs the check each time it is called. To rely +on the result of a previous call, use `vc-backend' instead. If the +file was previously registered under a certain backend, then that +backend is tried first." + (let (handler) + (cond + ((and (file-name-directory file) + (string-match vc-ignore-dir-regexp (file-name-directory file))) + nil) + ((and (boundp 'file-name-handler-alist) + (setq handler (find-file-name-handler file 'vc-registered))) + ;; handler should set vc-backend and return t if registered + (funcall handler 'vc-registered file)) + (t + ;; There is no file name handler. + ;; Try vc-BACKEND-registered for each handled BACKEND. + (catch 'found + (let ((backend (vc-file-getprop file 'vc-backend))) + (mapc + (lambda (b) + (and (vc-call-backend b 'registered file) + (vc-file-setprop file 'vc-backend b) + (throw 'found t))) + (if (or (not backend) (eq backend 'none)) + vc-handled-backends + (cons backend vc-handled-backends)))) + ;; File is not registered. + (vc-file-setprop file 'vc-backend 'none) + nil))))) + +(defun vc-backend (file-or-list) + "Return the version control type of FILE-OR-LIST, nil if it's not registered. +If the argument is a list, the files must all have the same back end." + ;; `file' can be nil in several places (typically due to the use of + ;; code like (vc-backend buffer-file-name)). + (cond ((stringp file-or-list) + (let ((property (vc-file-getprop file-or-list 'vc-backend))) + ;; Note that internally, Emacs remembers unregistered + ;; files by setting the property to `none'. + (cond ((eq property 'none) nil) + (property) + ;; vc-registered sets the vc-backend property + (t (if (vc-registered file-or-list) + (vc-file-getprop file-or-list 'vc-backend) + nil))))) + ((and file-or-list (listp file-or-list)) + (vc-backend (car file-or-list))) + (t + nil))) + + +(defun vc-backend-subdirectory-name (file) + "Return where the repository for the current directory is kept." + (symbol-name (vc-backend file))) + +(defun vc-name (file) + "Return the master name of FILE. +If the file is not registered, or the master name is not known, return nil." + ;; TODO: This should ultimately become obsolete, at least up here + ;; in vc-hooks. + (or (vc-file-getprop file 'vc-name) + ;; force computation of the property by calling + ;; vc-BACKEND-registered explicitly + (let ((backend (vc-backend file))) + (if (and backend + (vc-call-backend backend 'registered file)) + (vc-file-getprop file 'vc-name))))) + +(defun vc-checkout-model (backend files) + "Indicate how FILES are checked out. + +If FILES are not registered, this function always returns nil. +For registered files, the possible values are: + + 'implicit FILES are always writable, and checked out `implicitly' + when the user saves the first changes to the file. + + 'locking FILES are read-only if up-to-date; user must type + \\[vc-next-action] before editing. Strict locking + is assumed. + + 'announce FILES are read-only if up-to-date; user must type + \\[vc-next-action] before editing. But other users + may be editing at the same time." + (vc-call-backend backend 'checkout-model files)) + +(defun vc-user-login-name (file) + "Return the name under which the user accesses the given FILE." + (or (and (eq (string-match tramp-file-name-regexp file) 0) + ;; tramp case: execute "whoami" via tramp + (let ((default-directory (file-name-directory file)) + process-file-side-effects) + (with-temp-buffer + (if (not (zerop (process-file "whoami" nil t))) + ;; fall through if "whoami" didn't work + nil + ;; remove trailing newline + (delete-region (1- (point-max)) (point-max)) + (buffer-string))))) + ;; normal case + (user-login-name) + ;; if user-login-name is nil, return the UID as a string + (number-to-string (user-uid)))) + +(defun vc-state (file &optional backend) + "Return the version control state of FILE. + +If FILE is not registered, this function always returns nil. +For registered files, the value returned is one of: + + 'up-to-date The working file is unmodified with respect to the + latest version on the current branch, and not locked. + + 'edited The working file has been edited by the user. If + locking is used for the file, this state means that + the current version is locked by the calling user. + This status should *not* be reported for files + which have a changed mtime but the same content + as the repo copy. + + USER The current version of the working file is locked by + some other USER (a string). + + 'needs-update The file has not been edited by the user, but there is + a more recent version on the current branch stored + in the repository. + + 'needs-merge The file has been edited by the user, and there is also + a more recent version on the current branch stored in + the repository. This state can only occur if locking + is not used for the file. + + 'unlocked-changes The working version of the file is not locked, + but the working file has been changed with respect + to that version. This state can only occur for files + with locking; it represents an erroneous condition that + should be resolved by the user (vc-next-action will + prompt the user to do it). + + 'added Scheduled to go into the repository on the next commit. + Often represented by vc-working-revision = \"0\" in VCSes + with monotonic IDs like Subversion and Mercurial. + + 'removed Scheduled to be deleted from the repository on next commit. + + 'conflict The file contains conflicts as the result of a merge. + For now the conflicts are text conflicts. In the + future this might be extended to deal with metadata + conflicts too. + + 'missing The file is not present in the file system, but the VC + system still tracks it. + + 'ignored The file showed up in a dir-status listing with a flag + indicating the version-control system is ignoring it, + Note: This property is not set reliably (some VCSes + don't have useful directory-status commands) so assume + that any file with vc-state nil might be ignorable + without VC knowing it. + + 'unregistered The file is not under version control. + +A return of nil from this function means we have no information on the +status of this file." + ;; Note: in Emacs 22 and older, return of nil meant the file was + ;; unregistered. This is potentially a source of + ;; backward-compatibility bugs. + + ;; FIXME: New (sub)states needed (?): + ;; - `copied' and `moved' (might be handled by `removed' and `added') + (or (vc-file-getprop file 'vc-state) + (when (> (length file) 0) ;Why?? --Stef + (setq backend (or backend (vc-backend file))) + (when backend + (vc-state-refresh file backend))))) + +(defun vc-state-refresh (file backend) + "Quickly recompute the `state' of FILE." + (vc-file-setprop + file 'vc-state + (vc-call-backend backend 'state-heuristic file))) + +(defsubst vc-up-to-date-p (file) + "Convenience function that checks whether `vc-state' of FILE is `up-to-date'." + (eq (vc-state file) 'up-to-date)) + +(defun vc-default-state-heuristic (backend file) + "Default implementation of vc-BACKEND-state-heuristic. +It simply calls the real state computation function `vc-BACKEND-state' +and does not employ any heuristic at all." + (vc-call-backend backend 'state file)) + +(defun vc-workfile-unchanged-p (file) + "Return non-nil if FILE has not changed since the last checkout." + (let ((checkout-time (vc-file-getprop file 'vc-checkout-time)) + (lastmod (nth 5 (file-attributes file)))) + ;; This is a shortcut for determining when the workfile is + ;; unchanged. It can fail under some circumstances; see the + ;; discussion in bug#694. + (if (and checkout-time + ;; Tramp and Ange-FTP return this when they don't know the time. + (not (equal lastmod '(0 0)))) + (equal checkout-time lastmod) + (let ((unchanged (vc-call workfile-unchanged-p file))) + (vc-file-setprop file 'vc-checkout-time (if unchanged lastmod 0)) + unchanged)))) + +(defun vc-default-workfile-unchanged-p (backend file) + "Check if FILE is unchanged by diffing against the repository version. +Return non-nil if FILE is unchanged." + (zerop (condition-case err + ;; If the implementation supports it, let the output + ;; go to *vc*, not *vc-diff*, since this is an internal call. + (vc-call-backend backend 'diff (list file) nil nil "*vc*") + (wrong-number-of-arguments + ;; If this error came from the above call to vc-BACKEND-diff, + ;; try again without the optional buffer argument (for + ;; backward compatibility). Otherwise, resignal. + (if (or (not (eq (cadr err) + (indirect-function + (vc-find-backend-function backend 'diff)))) + (not (eq (caddr err) 4))) + (signal (car err) (cdr err)) + (vc-call-backend backend 'diff (list file))))))) + +(defun vc-working-revision (file &optional backend) + "Return the repository version from which FILE was checked out. +If FILE is not registered, this function always returns nil." + (or (vc-file-getprop file 'vc-working-revision) + (progn + (setq backend (or backend (vc-backend file))) + (when backend + (vc-file-setprop file 'vc-working-revision + (vc-call-backend backend 'working-revision file)))))) + +;; Backward compatibility. +(define-obsolete-function-alias + 'vc-workfile-version 'vc-working-revision "23.1") +(defun vc-default-working-revision (backend file) + (message + "`working-revision' not found: using the old `workfile-version' instead") + (vc-call-backend backend 'workfile-version file)) + +(defun vc-default-registered (backend file) + "Check if FILE is registered in BACKEND using vc-BACKEND-master-templates." + (let ((sym (vc-make-backend-sym backend 'master-templates))) + (unless (get backend 'vc-templates-grabbed) + (put backend 'vc-templates-grabbed t) + (set sym (append (delq nil + (mapcar + (lambda (template) + (and (consp template) + (eq (cdr template) backend) + (car template))) + (with-no-warnings + vc-master-templates))) + (symbol-value sym)))) + (let ((result (vc-check-master-templates file (symbol-value sym)))) + (if (stringp result) + (vc-file-setprop file 'vc-name result) + nil)))) ; Not registered + +(defun vc-possible-master (s dirname basename) + (cond + ((stringp s) (format s dirname basename)) + ((functionp s) + ;; The template is a function to invoke. If the + ;; function returns non-nil, that means it has found a + ;; master. For backward compatibility, we also handle + ;; the case that the function throws a 'found atom + ;; and a pair (cons MASTER-FILE BACKEND). + (let ((result (catch 'found (funcall s dirname basename)))) + (if (consp result) (car result) result))))) + +(defun vc-check-master-templates (file templates) + "Return non-nil if there is a master corresponding to FILE. + +TEMPLATES is a list of strings or functions. If an element is a +string, it must be a control string as required by `format', with two +string placeholders, such as \"%sRCS/%s,v\". The directory part of +FILE is substituted for the first placeholder, the basename of FILE +for the second. If a file with the resulting name exists, it is taken +as the master of FILE, and returned. + +If an element of TEMPLATES is a function, it is called with the +directory part and the basename of FILE as arguments. It should +return non-nil if it finds a master; that value is then returned by +this function." + (let ((dirname (or (file-name-directory file) "")) + (basename (file-name-nondirectory file))) + (catch 'found + (mapcar + (lambda (s) + (let ((trial (vc-possible-master s dirname basename))) + (when (and trial (file-exists-p trial) + ;; Make sure the file we found with name + ;; TRIAL is not the source file itself. + ;; That can happen with RCS-style names if + ;; the file name is truncated (e.g. to 14 + ;; chars). See if either directory or + ;; attributes differ. + (or (not (string= dirname + (file-name-directory trial))) + (not (equal (file-attributes file) + (file-attributes trial))))) + (throw 'found trial)))) + templates)))) + +(defun vc-toggle-read-only (&optional verbose) + "Change read-only status of current buffer, perhaps via version control. + +If the buffer is visiting a file registered with version control, +throw an error, because this is not a safe or really meaningful operation +on any version-control system newer than RCS. + +Otherwise, just change the read-only flag of the buffer. + +If you bind this function to \\[toggle-read-only], then Emacs +will properly intercept all attempts to toggle the read-only flag +on version-controlled buffer." + (interactive "P") + (if (vc-backend buffer-file-name) + (error "Toggling the readability of a version controlled file is likely to wreak havoc") + (toggle-read-only))) + +(defun vc-default-make-version-backups-p (backend file) + "Return non-nil if unmodified versions should be backed up locally. +The default is to switch off this feature." + nil) + +(defun vc-version-backup-file-name (file &optional rev manual regexp) + "Return a backup file name for REV or the current version of FILE. +If MANUAL is non-nil it means that a name for backups created by +the user should be returned; if REGEXP is non-nil that means to return +a regexp for matching all such backup files, regardless of the version." + (if regexp + (concat (regexp-quote (file-name-nondirectory file)) + "\\.~.+" (unless manual "\\.") "~") + (expand-file-name (concat (file-name-nondirectory file) + ".~" (subst-char-in-string + ?/ ?_ (or rev (vc-working-revision file))) + (unless manual ".") "~") + (file-name-directory file)))) + +(defun vc-delete-automatic-version-backups (file) + "Delete all existing automatic version backups for FILE." + (condition-case nil + (mapc + 'delete-file + (directory-files (or (file-name-directory file) default-directory) t + (vc-version-backup-file-name file nil nil t))) + ;; Don't fail when the directory doesn't exist. + (file-error nil))) + +(defun vc-make-version-backup (file) + "Make a backup copy of FILE, which is assumed in sync with the repository. +Before doing that, check if there are any old backups and get rid of them." + (unless (and (fboundp 'msdos-long-file-names) + (not (with-no-warnings (msdos-long-file-names)))) + (vc-delete-automatic-version-backups file) + (condition-case nil + (copy-file file (vc-version-backup-file-name file) + nil 'keep-date) + ;; It's ok if it doesn't work (e.g. directory not writable), + ;; since this is just for efficiency. + (file-error + (message + (concat "Warning: Cannot make version backup; " + "diff/revert therefore not local")))))) + +(defun vc-before-save () + "Function to be called by `basic-save-buffer' (in files.el)." + ;; If the file on disk is still in sync with the repository, + ;; and version backups should be made, copy the file to + ;; another name. This enables local diffs and local reverting. + (let ((file buffer-file-name) + backend) + (ignore-errors ;Be careful not to prevent saving the file. + (and (setq backend (vc-backend file)) + (vc-up-to-date-p file) + (eq (vc-checkout-model backend (list file)) 'implicit) + (vc-call-backend backend 'make-version-backups-p file) + (vc-make-version-backup file))))) + +(declare-function vc-dir-resynch-file "vc-dir" (&optional fname)) + +(defvar vc-dir-buffers nil "List of vc-dir buffers.") + +(defun vc-after-save () + "Function to be called by `basic-save-buffer' (in files.el)." + ;; If the file in the current buffer is under version control, + ;; up-to-date, and locking is not used for the file, set + ;; the state to 'edited and redisplay the mode line. + (let* ((file buffer-file-name) + (backend (vc-backend file))) + (and backend + (or (and (equal (vc-file-getprop file 'vc-checkout-time) + (nth 5 (file-attributes file))) + ;; File has been saved in the same second in which + ;; it was checked out. Clear the checkout-time + ;; to avoid confusion. + (vc-file-setprop file 'vc-checkout-time nil)) + t) + (eq (vc-checkout-model backend (list file)) 'implicit) + (vc-state-refresh file backend) + (vc-mode-line file backend)) + ;; Try to avoid unnecessary work, a *vc-dir* buffer is + ;; present if this is true. + (when vc-dir-buffers + (vc-dir-resynch-file file)))) + +(defvar vc-menu-entry + `(menu-item ,(purecopy "Version Control") vc-menu-map + :filter vc-menu-map-filter)) + +(when (boundp 'menu-bar-tools-menu) + ;; We do not need to worry here about the placement of this entry + ;; because menu-bar.el has already created the proper spot for us + ;; and this will simply use it. + (define-key menu-bar-tools-menu [vc] vc-menu-entry)) + +(defconst vc-mode-line-map + (let ((map (make-sparse-keymap))) + (define-key map [mode-line down-mouse-1] vc-menu-entry) + map)) + +(defun vc-mode-line (file &optional backend) + "Set `vc-mode' to display type of version control for FILE. +The value is set in the current buffer, which should be the buffer +visiting FILE. +If BACKEND is passed use it as the VC backend when computing the result." + (interactive (list buffer-file-name)) + (setq backend (or backend (vc-backend file))) + (if (not backend) + (setq vc-mode nil) + (let* ((ml-string (vc-call-backend backend 'mode-line-string file)) + (ml-echo (get-text-property 0 'help-echo ml-string))) + (setq vc-mode + (concat + " " + (if (null vc-display-status) + (symbol-name backend) + (propertize + ml-string + 'mouse-face 'mode-line-highlight + 'help-echo + (concat (or ml-echo + (format "File under the %s version control system" + backend)) + "\nmouse-1: Version Control menu") + 'local-map vc-mode-line-map))))) + ;; If the user is root, and the file is not owner-writable, + ;; then pretend that we can't write it + ;; even though we can (because root can write anything). + ;; This way, even root cannot modify a file that isn't locked. + (and (equal file buffer-file-name) + (not buffer-read-only) + (zerop (user-real-uid)) + (zerop (logand (file-modes buffer-file-name) 128)) + (setq buffer-read-only t))) + (force-mode-line-update) + backend) + +(defun vc-default-mode-line-string (backend file) + "Return string for placement in modeline by `vc-mode-line' for FILE. +Format: + + \"BACKEND-REV\" if the file is up-to-date + \"BACKEND:REV\" if the file is edited (or locked by the calling user) + \"BACKEND:LOCKER:REV\" if the file is locked by somebody else + +This function assumes that the file is registered." + (let* ((backend-name (symbol-name backend)) + (state (vc-state file backend)) + (state-echo nil) + (rev (vc-working-revision file backend))) + (propertize + (cond ((or (eq state 'up-to-date) + (eq state 'needs-update)) + (setq state-echo "Up to date file") + (concat backend-name "-" rev)) + ((stringp state) + (setq state-echo (concat "File locked by" state)) + (concat backend-name ":" state ":" rev)) + ((eq state 'added) + (setq state-echo "Locally added file") + (concat backend-name "@" rev)) + ((eq state 'conflict) + (setq state-echo "File contains conflicts after the last merge") + (concat backend-name "!" rev)) + ((eq state 'removed) + (setq state-echo "File removed from the VC system") + (concat backend-name "!" rev)) + ((eq state 'missing) + (setq state-echo "File tracked by the VC system, but missing from the file system") + (concat backend-name "?" rev)) + (t + ;; Not just for the 'edited state, but also a fallback + ;; for all other states. Think about different symbols + ;; for 'needs-update and 'needs-merge. + (setq state-echo "Locally modified file") + (concat backend-name ":" rev))) + 'help-echo (concat state-echo " under the " backend-name + " version control system")))) + +(defun vc-follow-link () + "If current buffer visits a symbolic link, visit the real file. +If the real file is already visited in another buffer, make that buffer +current, and kill the buffer that visits the link." + (let* ((true-buffer (find-buffer-visiting buffer-file-truename)) + (this-buffer (current-buffer))) + (if (eq true-buffer this-buffer) + (let ((truename buffer-file-truename)) + (kill-buffer this-buffer) + ;; In principle, we could do something like set-visited-file-name. + ;; However, it can't be exactly the same as set-visited-file-name. + ;; I'm not going to work out the details right now. -- rms. + (set-buffer (find-file-noselect truename))) + (set-buffer true-buffer) + (kill-buffer this-buffer)))) + +(defun vc-default-find-file-hook (backend) + nil) + +(defun vc-find-file-hook () + "Function for `find-file-hook' activating VC mode if appropriate." + ;; Recompute whether file is version controlled, + ;; if user has killed the buffer and revisited. + (when vc-mode + (setq vc-mode nil)) + (when buffer-file-name + (vc-file-clearprops buffer-file-name) + ;; FIXME: Why use a hook? Why pass it buffer-file-name? + (add-hook 'vc-mode-line-hook 'vc-mode-line nil t) + (let (backend) + (cond + ((setq backend (with-demoted-errors (vc-backend buffer-file-name))) + ;; Compute the state and put it in the modeline. + (vc-mode-line buffer-file-name backend) + (unless vc-make-backup-files + ;; Use this variable, not make-backup-files, + ;; because this is for things that depend on the file name. + (set (make-local-variable 'backup-inhibited) t)) + ;; Let the backend setup any buffer-local things he needs. + (vc-call-backend backend 'find-file-hook)) + ((let ((link-type (and (not (equal buffer-file-name buffer-file-truename)) + (vc-backend buffer-file-truename)))) + (cond ((not link-type) nil) ;Nothing to do. + ((eq vc-follow-symlinks nil) + (message + "Warning: symbolic link to %s-controlled source file" link-type)) + ((or (not (eq vc-follow-symlinks 'ask)) + ;; If we already visited this file by following + ;; the link, don't ask again if we try to visit + ;; it again. GUD does that, and repeated questions + ;; are painful. + (get-file-buffer + (abbreviate-file-name + (file-chase-links buffer-file-name)))) + + (vc-follow-link) + (message "Followed link to %s" buffer-file-name) + (vc-find-file-hook)) + (t + (if (yes-or-no-p (format + "Symbolic link to %s-controlled source file; follow link? " link-type)) + (progn (vc-follow-link) + (message "Followed link to %s" buffer-file-name) + (vc-find-file-hook)) + (message + "Warning: editing through the link bypasses version control") + ))))))))) + +(add-hook 'find-file-hook 'vc-find-file-hook) + +(defun vc-kill-buffer-hook () + "Discard VC info about a file when we kill its buffer." + (when buffer-file-name (vc-file-clearprops buffer-file-name))) + +(add-hook 'kill-buffer-hook 'vc-kill-buffer-hook) + +;; Now arrange for (autoloaded) bindings of the main package. +;; Bindings for this have to go in the global map, as we'll often +;; want to call them from random buffers. + +;; Autoloading works fine, but it prevents shortcuts from appearing +;; in the menu because they don't exist yet when the menu is built. +;; (autoload 'vc-prefix-map "vc" nil nil 'keymap) +(defvar vc-prefix-map + (let ((map (make-sparse-keymap))) + (define-key map "a" 'vc-update-change-log) + (define-key map "b" 'vc-switch-backend) + (define-key map "c" 'vc-rollback) + (define-key map "d" 'vc-dir) + (define-key map "g" 'vc-annotate) + (define-key map "h" 'vc-insert-headers) + (define-key map "i" 'vc-register) + (define-key map "l" 'vc-print-log) + (define-key map "L" 'vc-print-root-log) + (define-key map "I" 'vc-log-incoming) + (define-key map "O" 'vc-log-outgoing) + (define-key map "m" 'vc-merge) + (define-key map "r" 'vc-retrieve-tag) + (define-key map "s" 'vc-create-tag) + (define-key map "u" 'vc-revert) + (define-key map "v" 'vc-next-action) + (define-key map "+" 'vc-update) + (define-key map "=" 'vc-diff) + (define-key map "D" 'vc-root-diff) + (define-key map "~" 'vc-revision-other-window) + map)) +(fset 'vc-prefix-map vc-prefix-map) +(define-key global-map "\C-xv" 'vc-prefix-map) + +(defvar vc-menu-map + (let ((map (make-sparse-keymap "Version Control"))) + ;;(define-key map [show-files] + ;; '("Show Files under VC" . (vc-directory t))) + (define-key map [vc-retrieve-tag] + `(menu-item ,(purecopy "Retrieve Tag") vc-retrieve-tag + :help ,(purecopy "Retrieve tagged version or branch"))) + (define-key map [vc-create-tag] + `(menu-item ,(purecopy "Create Tag") vc-create-tag + :help ,(purecopy "Create version tag"))) + (define-key map [separator1] menu-bar-separator) + (define-key map [vc-annotate] + `(menu-item ,(purecopy "Annotate") vc-annotate + :help ,(purecopy "Display the edit history of the current file using colors"))) + (define-key map [vc-rename-file] + `(menu-item ,(purecopy "Rename File") vc-rename-file + :help ,(purecopy "Rename file"))) + (define-key map [vc-revision-other-window] + `(menu-item ,(purecopy "Show Other Version") vc-revision-other-window + :help ,(purecopy "Visit another version of the current file in another window"))) + (define-key map [vc-diff] + `(menu-item ,(purecopy "Compare with Base Version") vc-diff + :help ,(purecopy "Compare file set with the base version"))) + (define-key map [vc-root-diff] + `(menu-item ,(purecopy "Compare Tree with Base Version") vc-root-diff + :help ,(purecopy "Compare current tree with the base version"))) + (define-key map [vc-update-change-log] + `(menu-item ,(purecopy "Update ChangeLog") vc-update-change-log + :help ,(purecopy "Find change log file and add entries from recent version control logs"))) + (define-key map [vc-log-out] + `(menu-item ,(purecopy "Show Outgoing Log") vc-log-outgoing + :help ,(purecopy "Show a log of changes that will be sent with a push operation"))) + (define-key map [vc-log-in] + `(menu-item ,(purecopy "Show Incoming Log") vc-log-incoming + :help ,(purecopy "Show a log of changes that will be received with a pull operation"))) + (define-key map [vc-print-log] + `(menu-item ,(purecopy "Show History") vc-print-log + :help ,(purecopy "List the change log of the current file set in a window"))) + (define-key map [vc-print-root-log] + `(menu-item ,(purecopy "Show Top of the Tree History ") vc-print-root-log + :help ,(purecopy "List the change log for the current tree in a window"))) + (define-key map [separator2] menu-bar-separator) + (define-key map [vc-insert-header] + `(menu-item ,(purecopy "Insert Header") vc-insert-headers + :help ,(purecopy "Insert headers into a file for use with a version control system. +"))) + (define-key map [undo] + `(menu-item ,(purecopy "Undo Last Check-In") vc-rollback + :help ,(purecopy "Remove the most recent changeset committed to the repository"))) + (define-key map [vc-revert] + `(menu-item ,(purecopy "Revert to Base Version") vc-revert + :help ,(purecopy "Revert working copies of the selected file set to their repository contents"))) + (define-key map [vc-update] + `(menu-item ,(purecopy "Update to Latest Version") vc-update + :help ,(purecopy "Update the current fileset's files to their tip revisions"))) + (define-key map [vc-next-action] + `(menu-item ,(purecopy "Check In/Out") vc-next-action + :help ,(purecopy "Do the next logical version control operation on the current fileset"))) + (define-key map [vc-register] + `(menu-item ,(purecopy "Register") vc-register + :help ,(purecopy "Register file set into a version control system"))) + (define-key map [vc-dir] + `(menu-item ,(purecopy "VC Dir") vc-dir + :help ,(purecopy "Show the VC status of files in a directory"))) + map)) + +(defalias 'vc-menu-map vc-menu-map) + +(declare-function vc-responsible-backend "vc" (file)) + +(defun vc-menu-map-filter (orig-binding) + (if (and (symbolp orig-binding) (fboundp orig-binding)) + (setq orig-binding (indirect-function orig-binding))) + (let ((ext-binding + (when vc-mode + (vc-call-backend + (if buffer-file-name + (vc-backend buffer-file-name) + (vc-responsible-backend default-directory)) + 'extra-menu)))) + ;; Give the VC backend a chance to add menu entries + ;; specific for that backend. + (if (null ext-binding) + orig-binding + (append orig-binding + '((ext-menu-separator "--")) + ext-binding)))) + +(defun vc-default-extra-menu (backend) + nil) + +(provide 'vc-hooks) + +;; arch-tag: 2e5a6fa7-1d30-48e2-8bd0-e3d335f04f32 +;;; vc-hooks.el ends here diff -r 681cd08dc0f7 -r e93288477c43 lisp/vc/vc-mtn.el --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/lisp/vc/vc-mtn.el Sun Jun 13 22:57:55 2010 +0000 @@ -0,0 +1,344 @@ +;;; vc-mtn.el --- VC backend for Monotone + +;; Copyright (C) 2007, 2008, 2009, 2010 Free Software Foundation, Inc. + +;; Author: Stefan Monnier +;; Keywords: vc + +;; 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 3 of the License, 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. If not, see . + +;;; Commentary: + +;; + +;;; TODO: + +;; - The `previous-version' VC method needs to be supported, 'D' in +;; log-view-mode uses it. + +;;; Code: + +(eval-when-compile (require 'cl) (require 'vc)) + +(defcustom vc-mtn-diff-switches t + "String or list of strings specifying switches for monotone diff under VC. +If nil, use the value of `vc-diff-switches'. If t, use no switches." + :type '(choice (const :tag "Unspecified" nil) + (const :tag "None" t) + (string :tag "Argument String") + (repeat :tag "Argument List" :value ("") string)) + :version "23.1" + :group 'vc) + +(define-obsolete-variable-alias 'vc-mtn-command 'vc-mtn-program "23.1") +(defcustom vc-mtn-program "mtn" + "Name of the monotone executable." + :type 'string + :group 'vc) + +;; Clear up the cache to force vc-call to check again and discover +;; new functions when we reload this file. +(put 'Mtn 'vc-functions nil) + +(unless (executable-find vc-mtn-program) + ;; vc-mtn.el is 100% non-functional without the `mtn' executable. + (setq vc-handled-backends (delq 'Mtn vc-handled-backends))) + +;;;###autoload +(defconst vc-mtn-admin-dir "_MTN") +;;;###autoload +(defconst vc-mtn-admin-format (concat vc-mtn-admin-dir "/format")) + +;;;###autoload (defun vc-mtn-registered (file) +;;;###autoload (if (vc-find-root file vc-mtn-admin-format) +;;;###autoload (progn +;;;###autoload (load "vc-mtn") +;;;###autoload (vc-mtn-registered file)))) + +(defun vc-mtn-revision-granularity () 'repository) +(defun vc-mtn-checkout-model (files) 'implicit) + +(defun vc-mtn-root (file) + (setq file (if (file-directory-p file) + (file-name-as-directory file) + (file-name-directory file))) + (or (vc-file-getprop file 'vc-mtn-root) + (vc-file-setprop file 'vc-mtn-root + (vc-find-root file vc-mtn-admin-format)))) + + +(defun vc-mtn-registered (file) + (let ((root (vc-mtn-root file))) + (when root + (vc-mtn-state file)))) + +(defun vc-mtn-command (buffer okstatus files &rest flags) + "A wrapper around `vc-do-command' for use in vc-mtn.el." + (let ((process-environment + ;; Avoid localization of messages so we can parse the output. + (cons "LC_MESSAGES=C" process-environment))) + (apply 'vc-do-command (or buffer "*vc*") okstatus vc-mtn-program + files flags))) + +(defun vc-mtn-state (file) + ;; If `mtn' fails or returns status>0, or if the search files, just + ;; return nil. + (ignore-errors + (with-temp-buffer + (vc-mtn-command t 0 file "status") + (goto-char (point-min)) + (re-search-forward + "^ \\(?:\\(patched\\)\\|\\(added\\) \\(?:.*\\)\\)\\|no changes$") + (cond ((match-end 1) 'edited) + ((match-end 2) 'added) + (t 'up-to-date))))) + +(defun vc-mtn-after-dir-status (update-function) + (let (result) + (goto-char (point-min)) + (re-search-forward "Current branch: \\(.*\\)\nChanges against parent \\(.*\\)" nil t) + (while (re-search-forward + "^ \\(?:\\(patched \\)\\|\\(added \\)\\)\\(.*\\)$" nil t) + (cond ((match-end 1) (push (list (match-string 3) 'edited) result)) + ((match-end 2) (push (list (match-string 3) 'added) result)))) + (funcall update-function result))) + +(defun vc-mtn-dir-status (dir update-function) + (vc-mtn-command (current-buffer) 'async dir "status") + (vc-exec-after + `(vc-mtn-after-dir-status (quote ,update-function)))) + +(defun vc-mtn-working-revision (file) + ;; If `mtn' fails or returns status>0, or if the search fails, just + ;; return nil. + (ignore-errors + (with-temp-buffer + (vc-mtn-command t 0 file "status") + (goto-char (point-min)) + (re-search-forward "Current branch: \\(.*\\)\nChanges against parent \\(.*\\)") + (match-string 2)))) + +(defun vc-mtn-workfile-branch (file) + ;; If `mtn' fails or returns status>0, or if the search files, just + ;; return nil. + (ignore-errors + (with-temp-buffer + (vc-mtn-command t 0 file "status") + (goto-char (point-min)) + (re-search-forward "Current branch: \\(.*\\)\nChanges against parent \\(.*\\)") + (match-string 1)))) + +(defun vc-mtn-workfile-unchanged-p (file) + (not (eq (vc-mtn-state file) 'edited))) + +;; Mode-line rewrite code copied from vc-arch.el. + +(defcustom vc-mtn-mode-line-rewrite + '(("\\`[^:/#]*[:/#]" . "")) ;Drop the host part. + "Rewrite rules to shorten Mtn's revision names on the mode-line." + :type '(repeat (cons regexp string)) + :version "22.2" + :group 'vc) + +(defun vc-mtn-mode-line-string (file) + "Return string for placement in modeline by `vc-mode-line' for FILE." + (let ((branch (vc-mtn-workfile-branch file))) + (dolist (rule vc-mtn-mode-line-rewrite) + (if (string-match (car rule) branch) + (setq branch (replace-match (cdr rule) t nil branch)))) + (format "Mtn%c%s" + (case (vc-state file) + ((up-to-date needs-update) ?-) + (added ?@) + (t ?:)) + branch))) + +(defun vc-mtn-register (files &optional rev comment) + (vc-mtn-command nil 0 files "add")) + +(defun vc-mtn-responsible-p (file) (vc-mtn-root file)) +(defun vc-mtn-could-register (file) (vc-mtn-root file)) + +(declare-function log-edit-extract-headers "log-edit" (headers string)) + +(defun vc-mtn-checkin (files rev comment &optional extra-args-ignored) + (apply 'vc-mtn-command nil 0 files + (nconc (list "commit" "-m") + (log-edit-extract-headers '(("Author" . "--author") + ("Date" . "--date")) + comment)))) + +(defun vc-mtn-find-revision (file rev buffer) + (vc-mtn-command buffer 0 file "cat" "-r" rev)) + +;; (defun vc-mtn-checkout (file &optional editable rev) +;; ) + +(defun vc-mtn-revert (file &optional contents-done) + (unless contents-done + (vc-mtn-command nil 0 file "revert"))) + +;; (defun vc-mtn-roolback (files) +;; ) + +(defun vc-mtn-print-log (files buffer &optional shortlog start-revision limit) + (apply 'vc-mtn-command buffer 0 files "log" + (append + (when start-revision (list "--from" (format "%s" start-revision))) + (when limit (list "--last" (format "%s" limit)))))) + +(defvar log-view-message-re) +(defvar log-view-file-re) +(defvar log-view-font-lock-keywords) +(defvar log-view-per-file-logs) + +(define-derived-mode vc-mtn-log-view-mode log-view-mode "Mtn-Log-View" + ;; Don't match anything. + (set (make-local-variable 'log-view-file-re) "\\`a\\`") + (set (make-local-variable 'log-view-per-file-logs) nil) + ;; TODO: Use a more precise regexp than "[ |/]+" to avoid false positives + ;; in the ChangeLog text. + (set (make-local-variable 'log-view-message-re) + "^[ |/]+Revision: \\([0-9a-f]+\\)") + (require 'add-log) ;For change-log faces. + (set (make-local-variable 'log-view-font-lock-keywords) + (append log-view-font-lock-keywords + '(("^[ |]+Author: \\(.*\\)" (1 'change-log-email)) + ("^[ |]+Date: \\(.*\\)" (1 'change-log-date-face)))))) + +;; (defun vc-mtn-show-log-entry (revision) +;; ) + +(defun vc-mtn-diff (files &optional rev1 rev2 buffer) + "Get a difference report using monotone between two revisions of FILES." + (apply 'vc-mtn-command (or buffer "*vc-diff*") 1 files "diff" + (append + (vc-switches 'mtn 'diff) + (if rev1 (list "-r" rev1)) (if rev2 (list "-r" rev2))))) + +(defun vc-mtn-annotate-command (file buf &optional rev) + (apply 'vc-mtn-command buf 'async file "annotate" + (if rev (list "-r" rev)))) + +(declare-function vc-annotate-convert-time "vc-annotate" (time)) + +(defconst vc-mtn-annotate-full-re + "^ *\\([0-9a-f]+\\)\\.* by [^ ]+ \\([0-9]+\\)-\\([0-9]+\\)-\\([0-9]+\\): ") +(defconst vc-mtn-annotate-any-re + (concat "^\\(?: +: \\|" vc-mtn-annotate-full-re "\\)")) + +(defun vc-mtn-annotate-time () + (when (looking-at vc-mtn-annotate-any-re) + (goto-char (match-end 0)) + (let ((year (match-string 2))) + (if (not year) + ;; Look for the date on a previous line. + (save-excursion + (get-text-property (1- (previous-single-property-change + (point) 'vc-mtn-time nil (point-min))) + 'vc-mtn-time)) + (let ((time (vc-annotate-convert-time + (encode-time 0 0 0 + (string-to-number (match-string 4)) + (string-to-number (match-string 3)) + (string-to-number year) + t)))) + (let ((inhibit-read-only t) + (inhibit-modification-hooks t)) + (put-text-property (match-beginning 0) (match-end 0) + 'vc-mtn-time time)) + time))))) + +(defun vc-mtn-annotate-extract-revision-at-line () + (save-excursion + (when (or (looking-at vc-mtn-annotate-full-re) + (re-search-backward vc-mtn-annotate-full-re nil t)) + (match-string 1)))) + +;;; Revision completion. + +(defun vc-mtn-list-tags () + (with-temp-buffer + (vc-mtn-command t 0 nil "list" "tags") + (goto-char (point-min)) + (let ((tags ())) + (while (re-search-forward "^[^ ]+" nil t) + (push (match-string 0) tags)) + tags))) + +(defun vc-mtn-list-branches () + (with-temp-buffer + (vc-mtn-command t 0 nil "list" "branches") + (goto-char (point-min)) + (let ((branches ())) + (while (re-search-forward "^.+" nil t) + (push (match-string 0) branches)) + branches))) + +(defun vc-mtn-list-revision-ids (prefix) + (with-temp-buffer + (vc-mtn-command t 0 nil "complete" "revision" prefix) + (goto-char (point-min)) + (let ((ids ())) + (while (re-search-forward "^.+" nil t) + (push (match-string 0) ids)) + ids))) + +(defun vc-mtn-revision-completion-table (files) + ;; TODO: Implement completion for for selectors + ;; TODO: Implement completion for composite selectors. + (lexical-let ((files files)) + ;; What about using `files'?!? --Stef + (lambda (string pred action) + (cond + ;; "Tag" selectors. + ((string-match "\\`t:" string) + (complete-with-action action + (mapcar (lambda (tag) (concat "t:" tag)) + (vc-mtn-list-tags)) + string pred)) + ;; "Branch" selectors. + ((string-match "\\`b:" string) + (complete-with-action action + (mapcar (lambda (tag) (concat "b:" tag)) + (vc-mtn-list-branches)) + string pred)) + ;; "Head" selectors. Not sure how they differ from "branch" selectors. + ((string-match "\\`h:" string) + (complete-with-action action + (mapcar (lambda (tag) (concat "h:" tag)) + (vc-mtn-list-branches)) + string pred)) + ;; "ID" selectors. + ((string-match "\\`i:" string) + (complete-with-action action + (mapcar (lambda (tag) (concat "i:" tag)) + (vc-mtn-list-revision-ids + (substring string (match-end 0)))) + string pred)) + (t + (complete-with-action action + '("t:" "b:" "h:" "i:" + ;; Completion not implemented for these. + "a:" "c:" "d:" "e:" "l:") + string pred)))))) + + + +(provide 'vc-mtn) + +;; arch-tag: 2b89ffbc-cbb8-405a-9080-2eafd4becb70 +;;; vc-mtn.el ends here diff -r 681cd08dc0f7 -r e93288477c43 lisp/vc/vc-rcs.el --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/lisp/vc/vc-rcs.el Sun Jun 13 22:57:55 2010 +0000 @@ -0,0 +1,1470 @@ +;;; vc-rcs.el --- support for RCS version-control + +;; Copyright (C) 1992, 1993, 1994, 1995, 1996, 1997, 1998, 1999, 2000, +;; 2001, 2002, 2003, 2004, 2005, 2006, 2007, 2008, 2009, 2010 +;; Free Software Foundation, Inc. + +;; Author: FSF (see vc.el for full credits) +;; Maintainer: Andre Spiegel + +;; 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 3 of the License, 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. If not, see . + +;;; Commentary: + +;; See vc.el + +;; Some features will not work with old RCS versions. Where +;; appropriate, VC finds out which version you have, and allows or +;; disallows those features (stealing locks, for example, works only +;; from 5.6.2 onwards). +;; Even initial checkins will fail if your RCS version is so old that ci +;; doesn't understand -t-; this has been known to happen to people running +;; NExTSTEP 3.0. +;; +;; You can support the RCS -x option by customizing vc-rcs-master-templates. + +;;; Code: + +;;; +;;; Customization options +;;; + +(eval-when-compile + (require 'cl) + (require 'vc)) + +(defcustom vc-rcs-release nil + "The release number of your RCS installation, as a string. +If nil, VC itself computes this value when it is first needed." + :type '(choice (const :tag "Auto" nil) + (string :tag "Specified") + (const :tag "Unknown" unknown)) + :group 'vc) + +(defcustom vc-rcs-register-switches nil + "Switches for registering a file in RCS. +A string or list of strings passed to the checkin program by +\\[vc-register]. If nil, use the value of `vc-register-switches'. +If t, use no switches." + :type '(choice (const :tag "Unspecified" nil) + (const :tag "None" t) + (string :tag "Argument String") + (repeat :tag "Argument List" :value ("") string)) + :version "21.1" + :group 'vc) + +(defcustom vc-rcs-diff-switches nil + "String or list of strings specifying switches for RCS diff under VC. +If nil, use the value of `vc-diff-switches'. If t, use no switches." + :type '(choice (const :tag "Unspecified" nil) + (const :tag "None" t) + (string :tag "Argument String") + (repeat :tag "Argument List" :value ("") string)) + :version "21.1" + :group 'vc) + +(defcustom vc-rcs-header (or (cdr (assoc 'RCS vc-header-alist)) '("\$Id\$")) + "Header keywords to be inserted by `vc-insert-headers'." + :type '(repeat string) + :version "21.1" + :group 'vc) + +(defcustom vc-rcsdiff-knows-brief nil + "Indicates whether rcsdiff understands the --brief option. +The value is either `yes', `no', or nil. If it is nil, VC tries +to use --brief and sets this variable to remember whether it worked." + :type '(choice (const :tag "Work out" nil) (const yes) (const no)) + :group 'vc) + +;;;###autoload +(defcustom vc-rcs-master-templates + (purecopy '("%sRCS/%s,v" "%s%s,v" "%sRCS/%s")) + "Where to look for RCS master files. +For a description of possible values, see `vc-check-master-templates'." + :type '(choice (const :tag "Use standard RCS file names" + '("%sRCS/%s,v" "%s%s,v" "%sRCS/%s")) + (repeat :tag "User-specified" + (choice string + function))) + :version "21.1" + :group 'vc) + + +;;; Properties of the backend + +(defun vc-rcs-revision-granularity () 'file) + +(defun vc-rcs-checkout-model (files) + "RCS-specific version of `vc-checkout-model'." + (let ((file (if (consp files) (car files) files)) + result) + (when vc-consult-headers + (vc-file-setprop file 'vc-checkout-model nil) + (vc-rcs-consult-headers file) + (setq result (vc-file-getprop file 'vc-checkout-model))) + (or result + (progn (vc-rcs-fetch-master-state file) + (vc-file-getprop file 'vc-checkout-model))))) + +;;; +;;; State-querying functions +;;; + +;; The autoload cookie below places vc-rcs-registered directly into +;; loaddefs.el, so that vc-rcs.el does not need to be loaded for +;; every file that is visited. +;;;###autoload +(progn +(defun vc-rcs-registered (f) (vc-default-registered 'RCS f))) + +(defun vc-rcs-state (file) + "Implementation of `vc-state' for RCS." + (if (not (vc-rcs-registered file)) + 'unregistered + (or (boundp 'vc-rcs-headers-result) + (and vc-consult-headers + (vc-rcs-consult-headers file))) + (let ((state + ;; vc-working-revision might not be known; in that case the + ;; property is nil. vc-rcs-fetch-master-state knows how to + ;; handle that. + (vc-rcs-fetch-master-state file + (vc-file-getprop file + 'vc-working-revision)))) + (if (not (eq state 'up-to-date)) + state + (if (vc-workfile-unchanged-p file) + 'up-to-date + (if (eq (vc-rcs-checkout-model (list file)) 'locking) + 'unlocked-changes + 'edited)))))) + +(defun vc-rcs-state-heuristic (file) + "State heuristic for RCS." + (let (vc-rcs-headers-result) + (if (and vc-consult-headers + (setq vc-rcs-headers-result + (vc-rcs-consult-headers file)) + (eq vc-rcs-headers-result 'rev-and-lock)) + (let ((state (vc-file-getprop file 'vc-state))) + ;; If the headers say that the file is not locked, the + ;; permissions can tell us whether locking is used for + ;; the file or not. + (if (and (eq state 'up-to-date) + (not (vc-mistrust-permissions file)) + (file-exists-p file)) + (cond + ((string-match ".rw..-..-." (nth 8 (file-attributes file))) + (vc-file-setprop file 'vc-checkout-model 'implicit) + (setq state + (if (vc-rcs-workfile-is-newer file) + 'edited + 'up-to-date))) + ((string-match ".r-..-..-." (nth 8 (file-attributes file))) + (vc-file-setprop file 'vc-checkout-model 'locking)))) + state) + (if (not (vc-mistrust-permissions file)) + (let* ((attributes (file-attributes file 'string)) + (owner-name (nth 2 attributes)) + (permissions (nth 8 attributes))) + (cond ((and permissions (string-match ".r-..-..-." permissions)) + (vc-file-setprop file 'vc-checkout-model 'locking) + 'up-to-date) + ((and permissions (string-match ".rw..-..-." permissions)) + (if (eq (vc-rcs-checkout-model file) 'locking) + (if (file-ownership-preserved-p file) + 'edited + owner-name) + (if (vc-rcs-workfile-is-newer file) + 'edited + 'up-to-date))) + (t + ;; Strange permissions. Fall through to + ;; expensive state computation. + (vc-rcs-state file)))) + (vc-rcs-state file))))) + +(defun vc-rcs-dir-status (dir update-function) + ;; FIXME: this function should be rewritten or `vc-expand-dirs' + ;; should be changed to take a backend parameter. Using + ;; `vc-expand-dirs' is not TRTD because it returns files from + ;; multiple backends. It should also return 'unregistered files. + + ;; Doing individual vc-state calls is painful but there + ;; is no better way in RCS-land. + (let ((flist (vc-expand-dirs (list dir))) + (result nil)) + (dolist (file flist) + (let ((state (vc-state file)) + (frel (file-relative-name file))) + (when (and (eq (vc-backend file) 'RCS) + (not (eq state 'up-to-date))) + (push (list frel state) result)))) + (funcall update-function result))) + +(defun vc-rcs-working-revision (file) + "RCS-specific version of `vc-working-revision'." + (or (and vc-consult-headers + (vc-rcs-consult-headers file) + (vc-file-getprop file 'vc-working-revision)) + (progn + (vc-rcs-fetch-master-state file) + (vc-file-getprop file 'vc-working-revision)))) + +(defun vc-rcs-latest-on-branch-p (file &optional version) + "Return non-nil if workfile version of FILE is the latest on its branch. +When VERSION is given, perform check for that version." + (unless version (setq version (vc-working-revision file))) + (with-temp-buffer + (string= version + (if (vc-rcs-trunk-p version) + (progn + ;; Compare VERSION to the head version number. + (vc-insert-file (vc-name file) "^[0-9]") + (vc-parse-buffer "^head[ \t\n]+\\([^;]+\\);" 1)) + ;; If we are not on the trunk, we need to examine the + ;; whole current branch. + (vc-insert-file (vc-name file) "^desc") + (vc-rcs-find-most-recent-rev (vc-branch-part version)))))) + +(defun vc-rcs-workfile-unchanged-p (file) + "RCS-specific implementation of `vc-workfile-unchanged-p'." + ;; Try to use rcsdiff --brief. If rcsdiff does not understand that, + ;; do a double take and remember the fact for the future + (let* ((version (concat "-r" (vc-working-revision file))) + (status (if (eq vc-rcsdiff-knows-brief 'no) + (vc-do-command "*vc*" 1 "rcsdiff" file version) + (vc-do-command "*vc*" 2 "rcsdiff" file "--brief" version)))) + (if (eq status 2) + (if (not vc-rcsdiff-knows-brief) + (setq vc-rcsdiff-knows-brief 'no + status (vc-do-command "*vc*" 1 "rcsdiff" file version)) + (error "rcsdiff failed")) + (if (not vc-rcsdiff-knows-brief) (setq vc-rcsdiff-knows-brief 'yes))) + ;; The workfile is unchanged if rcsdiff found no differences. + (zerop status))) + + +;;; +;;; State-changing functions +;;; + +(defun vc-rcs-create-repo () + "Create a new RCS repository." + ;; RCS is totally file-oriented, so all we have to do is make the directory. + (make-directory "RCS")) + +(defun vc-rcs-register (files &optional rev comment) + "Register FILES into the RCS version-control system. +REV is the optional revision number for the files. COMMENT can be used +to provide an initial description for each FILES. +Passes either `vc-rcs-register-switches' or `vc-register-switches' +to the RCS command. + +Automatically retrieve a read-only version of the file with keywords +expanded if `vc-keep-workfiles' is non-nil, otherwise, delete the workfile." + (let (subdir name) + ;; When REV is specified, we need to force using "-t-". + (when rev (unless comment (setq comment ""))) + (dolist (file files) + (and (not (file-exists-p + (setq subdir (expand-file-name "RCS" + (file-name-directory file))))) + (not (directory-files (file-name-directory file) + nil ".*,v$" t)) + (yes-or-no-p "Create RCS subdirectory? ") + (make-directory subdir)) + (apply 'vc-do-command "*vc*" 0 "ci" file + ;; if available, use the secure registering option + (and (vc-rcs-release-p "5.6.4") "-i") + (concat (if vc-keep-workfiles "-u" "-r") rev) + (and comment (concat "-t-" comment)) + (vc-switches 'RCS 'register)) + ;; parse output to find master file name and workfile version + (with-current-buffer "*vc*" + (goto-char (point-min)) + (if (not (setq name + (if (looking-at (concat "^\\(.*\\) <-- " + (file-name-nondirectory file))) + (match-string 1)))) + ;; if we couldn't find the master name, + ;; run vc-rcs-registered to get it + ;; (will be stored into the vc-name property) + (vc-rcs-registered file) + (vc-file-setprop file 'vc-name + (if (file-name-absolute-p name) + name + (expand-file-name + name + (file-name-directory file)))))) + (vc-file-setprop file 'vc-working-revision + (if (re-search-forward + "^initial revision: \\([0-9.]+\\).*\n" + nil t) + (match-string 1)))))) + +(defun vc-rcs-responsible-p (file) + "Return non-nil if RCS thinks it would be responsible for registering FILE." + ;; TODO: check for all the patterns in vc-rcs-master-templates + (file-directory-p (expand-file-name "RCS" (file-name-directory file)))) + +(defun vc-rcs-receive-file (file rev) + "Implementation of receive-file for RCS." + (let ((checkout-model (vc-rcs-checkout-model (list file)))) + (vc-rcs-register file rev "") + (when (eq checkout-model 'implicit) + (vc-rcs-set-non-strict-locking file)) + (vc-rcs-set-default-branch file (concat rev ".1")))) + +(defun vc-rcs-unregister (file) + "Unregister FILE from RCS. +If this leaves the RCS subdirectory empty, ask the user +whether to remove it." + (let* ((master (vc-name file)) + (dir (file-name-directory master)) + (backup-info (find-backup-file-name master))) + (if (not backup-info) + (delete-file master) + (rename-file master (car backup-info) 'ok-if-already-exists) + (dolist (f (cdr backup-info)) (ignore-errors (delete-file f)))) + (and (string= (file-name-nondirectory (directory-file-name dir)) "RCS") + ;; check whether RCS dir is empty, i.e. it does not + ;; contain any files except "." and ".." + (not (directory-files dir nil + "^\\([^.]\\|\\.[^.]\\|\\.\\.[^.]\\).*")) + (yes-or-no-p (format "Directory %s is empty; remove it? " dir)) + (delete-directory dir)))) + +(defun vc-rcs-checkin (files rev comment &optional extra-args-ignored) + "RCS-specific version of `vc-backend-checkin'." + (let ((switches (vc-switches 'RCS 'checkin))) + ;; Now operate on the files + (dolist (file (vc-expand-dirs files)) + (let ((old-version (vc-working-revision file)) new-version + (default-branch (vc-file-getprop file 'vc-rcs-default-branch))) + ;; Force branch creation if an appropriate + ;; default branch has been set. + (and (not rev) + default-branch + (string-match (concat "^" (regexp-quote old-version) "\\.") + default-branch) + (setq rev default-branch) + (setq switches (cons "-f" switches))) + (if (and (not rev) old-version) + (setq rev (vc-branch-part old-version))) + (apply 'vc-do-command "*vc*" 0 "ci" (vc-name file) + ;; if available, use the secure check-in option + (and (vc-rcs-release-p "5.6.4") "-j") + (concat (if vc-keep-workfiles "-u" "-r") rev) + (concat "-m" comment) + switches) + (vc-file-setprop file 'vc-working-revision nil) + + ;; determine the new workfile version + (set-buffer "*vc*") + (goto-char (point-min)) + (when (or (re-search-forward + "new revision: \\([0-9.]+\\);" nil t) + (re-search-forward + "reverting to previous revision \\([0-9.]+\\)" nil t)) + (setq new-version (match-string 1)) + (vc-file-setprop file 'vc-working-revision new-version)) + + ;; if we got to a different branch, adjust the default + ;; branch accordingly + (cond + ((and old-version new-version + (not (string= (vc-branch-part old-version) + (vc-branch-part new-version)))) + (vc-rcs-set-default-branch file + (if (vc-rcs-trunk-p new-version) nil + (vc-branch-part new-version))) + ;; If this is an old RCS release, we might have + ;; to remove a remaining lock. + (if (not (vc-rcs-release-p "5.6.2")) + ;; exit status of 1 is also accepted. + ;; It means that the lock was removed before. + (vc-do-command "*vc*" 1 "rcs" (vc-name file) + (concat "-u" old-version))))))))) + +(defun vc-rcs-find-revision (file rev buffer) + (apply 'vc-do-command + (or buffer "*vc*") 0 "co" (vc-name file) + "-q" ;; suppress diagnostic output + (concat "-p" rev) + (vc-switches 'RCS 'checkout))) + +(defun vc-rcs-checkout (file &optional editable rev) + "Retrieve a copy of a saved version of FILE. If FILE is a directory, +attempt the checkout for all registered files beneath it." + (if (file-directory-p file) + (mapc 'vc-rcs-checkout (vc-expand-dirs (list file))) + (let ((file-buffer (get-file-buffer file)) + switches) + (message "Checking out %s..." file) + (save-excursion + ;; Change buffers to get local value of vc-checkout-switches. + (if file-buffer (set-buffer file-buffer)) + (setq switches (vc-switches 'RCS 'checkout)) + ;; Save this buffer's default-directory + ;; and use save-excursion to make sure it is restored + ;; in the same buffer it was saved in. + (let ((default-directory default-directory)) + (save-excursion + ;; Adjust the default-directory so that the check-out creates + ;; the file in the right place. + (setq default-directory (file-name-directory file)) + (let (new-version) + ;; if we should go to the head of the trunk, + ;; clear the default branch first + (and rev (string= rev "") + (vc-rcs-set-default-branch file nil)) + ;; now do the checkout + (apply 'vc-do-command + "*vc*" 0 "co" (vc-name file) + ;; If locking is not strict, force to overwrite + ;; the writable workfile. + (if (eq (vc-rcs-checkout-model (list file)) 'implicit) "-f") + (if editable "-l") + (if (stringp rev) + ;; a literal revision was specified + (concat "-r" rev) + (let ((workrev (vc-working-revision file))) + (if workrev + (concat "-r" + (if (not rev) + ;; no revision specified: + ;; use current workfile version + workrev + ;; REV is t ... + (if (not (vc-rcs-trunk-p workrev)) + ;; ... go to head of current branch + (vc-branch-part workrev) + ;; ... go to head of trunk + (vc-rcs-set-default-branch file + nil) + "")))))) + switches) + ;; determine the new workfile version + (with-current-buffer "*vc*" + (setq new-version + (vc-parse-buffer "^revision \\([0-9.]+\\).*\n" 1))) + (vc-file-setprop file 'vc-working-revision new-version) + ;; if necessary, adjust the default branch + (and rev (not (string= rev "")) + (vc-rcs-set-default-branch + file + (if (vc-rcs-latest-on-branch-p file new-version) + (if (vc-rcs-trunk-p new-version) nil + (vc-branch-part new-version)) + new-version))))) + (message "Checking out %s...done" file)))))) + +(defun vc-rcs-rollback (files) + "Roll back, undoing the most recent checkins of FILES. Directories are +expanded to all registered subfiles in them." + (if (not files) + (error "RCS backend doesn't support directory-level rollback")) + (dolist (file (vc-expand-dirs files)) + (let* ((discard (vc-working-revision file)) + (previous (if (vc-rcs-trunk-p discard) "" (vc-branch-part discard))) + (config (current-window-configuration)) + (done nil)) + (if (null (yes-or-no-p (format "Remove version %s from %s history? " + discard file))) + (error "Aborted")) + (message "Removing revision %s from %s." discard file) + (vc-do-command "*vc*" 0 "rcs" (vc-name file) (concat "-o" discard)) + ;; Check out the most recent remaining version. If it + ;; fails, because the whole branch got deleted, do a + ;; double-take and check out the version where the branch + ;; started. + (while (not done) + (condition-case err + (progn + (vc-do-command "*vc*" 0 "co" (vc-name file) "-f" + (concat "-u" previous)) + (setq done t)) + (error (set-buffer "*vc*") + (goto-char (point-min)) + (if (search-forward "no side branches present for" nil t) + (progn (setq previous (vc-branch-part previous)) + (vc-rcs-set-default-branch file previous) + ;; vc-do-command popped up a window with + ;; the error message. Get rid of it, by + ;; restoring the old window configuration. + (set-window-configuration config)) + ;; No, it was some other error: re-signal it. + (signal (car err) (cdr err))))))))) + +(defun vc-rcs-revert (file &optional contents-done) + "Revert FILE to the version it was based on. If FILE is a directory, +revert all registered files beneath it." + (if (file-directory-p file) + (mapc 'vc-rcs-revert (vc-expand-dirs (list file))) + (vc-do-command "*vc*" 0 "co" (vc-name file) "-f" + (concat (if (eq (vc-state file) 'edited) "-u" "-r") + (vc-working-revision file))))) + +(defun vc-rcs-merge (file first-version &optional second-version) + "Merge changes into current working copy of FILE. +The changes are between FIRST-VERSION and SECOND-VERSION." + (vc-do-command "*vc*" 1 "rcsmerge" (vc-name file) + "-kk" ; ignore keyword conflicts + (concat "-r" first-version) + (if second-version (concat "-r" second-version)))) + +(defun vc-rcs-steal-lock (file &optional rev) + "Steal the lock on the current workfile for FILE and revision REV. +If FILE is a directory, steal the lock on all registered files beneath it. +Needs RCS 5.6.2 or later for -M." + (if (file-directory-p file) + (mapc 'vc-rcs-steal-lock (vc-expand-dirs (list file))) + (vc-do-command "*vc*" 0 "rcs" (vc-name file) "-M" (concat "-u" rev)) + ;; Do a real checkout after stealing the lock, so that we see + ;; expanded headers. + (vc-do-command "*vc*" 0 "co" (vc-name file) "-f" (concat "-l" rev)))) + +(defun vc-rcs-modify-change-comment (files rev comment) + "Modify the change comments change on FILES on a specified REV. If FILE is a +directory the operation is applied to all registered files beneath it." + (dolist (file (vc-expand-dirs files)) + (vc-do-command "*vc*" 0 "rcs" (vc-name file) + (concat "-m" rev ":" comment)))) + + +;;; +;;; History functions +;;; + +(defun vc-rcs-print-log-cleanup () + (let ((inhibit-read-only t)) + (goto-char (point-max)) + (forward-line -1) + (while (looking-at "=*\n") + (delete-char (- (match-end 0) (match-beginning 0))) + (forward-line -1)) + (goto-char (point-min)) + (when (looking-at "[\b\t\n\v\f\r ]+") + (delete-char (- (match-end 0) (match-beginning 0)))))) + +(defun vc-rcs-print-log (files buffer &optional shortlog start-revision-ignored limit) + "Get change log associated with FILE. If FILE is a +directory the operation is applied to all registered files beneath it." + (vc-do-command (or buffer "*vc*") 0 "rlog" (mapcar 'vc-name (vc-expand-dirs files))) + (with-current-buffer (or buffer "*vc*") + (vc-rcs-print-log-cleanup)) + (when limit 'limit-unsupported)) + +(defun vc-rcs-diff (files &optional oldvers newvers buffer) + "Get a difference report using RCS between two sets of files." + (apply 'vc-do-command (or buffer "*vc-diff*") + 1 ;; Always go synchronous, the repo is local + "rcsdiff" (vc-expand-dirs files) + (append (list "-q" + (and oldvers (concat "-r" oldvers)) + (and newvers (concat "-r" newvers))) + (vc-switches 'RCS 'diff)))) + +(defun vc-rcs-comment-history (file) + "Return a string with all log entries stored in BACKEND for FILE." + (with-current-buffer "*vc*" + ;; Has to be written this way, this function is used by the CVS backend too + (vc-call-backend (vc-backend file) 'print-log (list file)) + ;; Remove cruft + (let ((separator (concat "^-+\nrevision [0-9.]+\ndate: .*\n" + "\\(branches: .*;\n\\)?" + "\\(\\*\\*\\* empty log message \\*\\*\\*\n\\)?"))) + (goto-char (point-max)) (forward-line -1) + (while (looking-at "=*\n") + (delete-char (- (match-end 0) (match-beginning 0))) + (forward-line -1)) + (goto-char (point-min)) + (if (looking-at "[\b\t\n\v\f\r ]+") + (delete-char (- (match-end 0) (match-beginning 0)))) + (goto-char (point-min)) + (re-search-forward separator nil t) + (delete-region (point-min) (point)) + (while (re-search-forward separator nil t) + (delete-region (match-beginning 0) (match-end 0)))) + ;; Return the de-crufted comment list + (buffer-string))) + +(defun vc-rcs-annotate-command (file buffer &optional revision) + "Annotate FILE, inserting the results in BUFFER. +Optional arg REVISION is a revision to annotate from." + (vc-setup-buffer buffer) + ;; Aside from the "head revision on the trunk", the instructions for + ;; each revision on the trunk are an ordered list of kill and insert + ;; commands necessary to go from the chronologically-following + ;; revision to this one. That is, associated with revision N are + ;; edits that applied to revision N+1 would result in revision N. + ;; + ;; On a branch, however, (some) things are inverted: the commands + ;; listed are those necessary to go from the chronologically-preceding + ;; revision to this one. That is, associated with revision N are + ;; edits that applied to revision N-1 would result in revision N. + ;; + ;; So, to get per-line history info, we apply reverse-chronological + ;; edits, starting with the head revision on the trunk, all the way + ;; back through the initial revision (typically "1.1" or similar), + ;; then apply forward-chronological edits -- keeping track of which + ;; revision is associated with each inserted line -- until we reach + ;; the desired revision for display (which may be either on the trunk + ;; or on a branch). + (let* ((tree (with-temp-buffer + (insert-file-contents (vc-rcs-registered file)) + (vc-rcs-parse))) + (revisions (cdr (assq 'revisions tree))) + ;; The revision N whose instructions we currently are processing. + (cur (cdr (assq 'head (cdr (assq 'headers tree))))) + ;; Alist from the parse tree for N. + (meta (cdr (assoc cur revisions))) + ;; Point and temporary string, respectively. + p s + ;; "Next-branch list". Nil means the desired revision to + ;; display lives on the trunk. Non-nil means it lives on a + ;; branch, in which case the value is a list of revision pairs + ;; (PARENT . CHILD), the first PARENT being on the trunk, that + ;; links each series of revisions in the path from the initial + ;; revision to the desired revision to display. + nbls + ;; "Path-accumulate-predicate plus revision/date/author". + ;; Until set, forward-chronological edits are not accumulated. + ;; Once set, its value (updated every revision) is used for + ;; the text property `:vc-rcs-r/d/a' for inserts during + ;; processing of forward-chronological instructions for N. + ;; See internal func `r/d/a'. + prda + ;; List of forward-chronological instructions, each of the + ;; form: (POS . ACTION), where POS is a buffer position. If + ;; ACTION is a string, it is inserted, otherwise it is taken as + ;; the number of characters to be deleted. + path + ;; N+1. When `cur' is "", this is the initial revision. + pre) + (unless revision + (setq revision cur)) + (unless (assoc revision revisions) + (error "No such revision: %s" revision)) + ;; Find which branches (if any) must be included in the edits. + (let ((par revision) + bpt kids) + (while (setq bpt (vc-branch-part par) + par (vc-branch-part bpt)) + (setq kids (cdr (assq 'branches (cdr (assoc par revisions))))) + ;; A branchpoint may have multiple children. Find the right one. + (while (not (string= bpt (vc-branch-part (car kids)))) + (setq kids (cdr kids))) + (push (cons par (car kids)) nbls))) + ;; Start with the full text. + (set-buffer buffer) + (insert (cdr (assq 'text meta))) + ;; Apply reverse-chronological edits on the trunk, computing and + ;; accumulating forward-chronological edits after some point, for + ;; later. + (flet ((r/d/a () (vector pre + (cdr (assq 'date meta)) + (cdr (assq 'author meta))))) + (while (when (setq pre cur cur (cdr (assq 'next meta))) + (not (string= "" cur))) + (setq + ;; Start accumulating the forward-chronological edits when N+1 + ;; on the trunk is either the desired revision to display, or + ;; the appropriate branchpoint for it. Do this before + ;; updating `meta' since `r/d/a' uses N+1's `meta' value. + prda (when (or prda (string= (if nbls (caar nbls) revision) pre)) + (r/d/a)) + meta (cdr (assoc cur revisions))) + ;; Edits in the parse tree specify a line number (in the buffer + ;; *BEFORE* editing occurs) to start from, but line numbers + ;; change as a result of edits. To DTRT, we apply edits in + ;; order of descending buffer position so that edits further + ;; down in the buffer occur first w/o corrupting specified + ;; buffer positions of edits occurring towards the beginning of + ;; the buffer. In this way we avoid using markers. A pleasant + ;; property of this approach is ability to push instructions + ;; onto `path' directly, w/o need to maintain rev boundaries. + (dolist (insn (cdr (assq :insn meta))) + (goto-char (point-min)) + (forward-line (1- (pop insn))) + (setq p (point)) + (case (pop insn) + (k (setq s (buffer-substring-no-properties + p (progn (forward-line (car insn)) + (point)))) + (when prda + (push `(,p . ,(propertize s :vc-rcs-r/d/a prda)) path)) + (delete-region p (point))) + (i (setq s (car insn)) + (when prda + (push `(,p . ,(length s)) path)) + (insert s))))) + ;; For the initial revision, setting `:vc-rcs-r/d/a' directly is + ;; equivalent to pushing an insert instruction (of the entire buffer + ;; contents) onto `path' then erasing the buffer, but less wasteful. + (put-text-property (point-min) (point-max) :vc-rcs-r/d/a (r/d/a)) + ;; Now apply the forward-chronological edits for the trunk. + (dolist (insn path) + (goto-char (pop insn)) + (if (stringp insn) + (insert insn) + (delete-char insn))) + ;; Now apply the forward-chronological edits (directly from the + ;; parse-tree) for the branch(es), if necessary. We re-use vars + ;; `pre' and `meta' for the sake of internal func `r/d/a'. + (while nbls + (setq pre (cdr (pop nbls))) + (while (progn + (setq meta (cdr (assoc pre revisions)) + prda nil) + (dolist (insn (cdr (assq :insn meta))) + (goto-char (point-min)) + (forward-line (1- (pop insn))) + (case (pop insn) + (k (delete-region + (point) (progn (forward-line (car insn)) + (point)))) + (i (insert (propertize + (car insn) + :vc-rcs-r/d/a + (or prda (setq prda (r/d/a)))))))) + (prog1 (not (string= (if nbls (caar nbls) revision) pre)) + (setq pre (cdr (assq 'next meta))))))))) + ;; Lastly, for each line, insert at bol nicely-formatted history info. + ;; We do two passes to collect summary information used to minimize + ;; the annotation's usage of screen real-estate: (1) Consider rendered + ;; width of revision plus author together as a unit; and (2) Omit + ;; author entirely if all authors are the same as the user. + (let ((ht (make-hash-table :test 'eq)) + (me (user-login-name)) + (maxw 0) + (all-me t) + rda w a) + (goto-char (point-max)) + (while (not (bobp)) + (forward-line -1) + (setq rda (get-text-property (point) :vc-rcs-r/d/a)) + (unless (gethash rda ht) + (setq a (aref rda 2) + all-me (and all-me (string= a me))) + (puthash rda (setq w (+ (length (aref rda 0)) + (length a))) + ht) + (setq maxw (max w maxw)))) + (let ((padding (make-string maxw 32))) + (flet ((pad (w) (substring-no-properties padding w)) + (render (rda &rest ls) + (propertize + (apply 'concat + (format-time-string "%Y-%m-%d" (aref rda 1)) + " " + (aref rda 0) + ls) + :vc-annotate-prefix t + :vc-rcs-r/d/a rda))) + (maphash + (if all-me + (lambda (rda w) + (puthash rda (render rda (pad w) ": ") ht)) + (lambda (rda w) + (puthash rda (render rda " " (pad w) " " (aref rda 2) ": ") ht))) + ht))) + (while (not (eobp)) + (insert (gethash (get-text-property (point) :vc-rcs-r/d/a) ht)) + (forward-line 1)))) + +(declare-function vc-annotate-convert-time "vc-annotate" (time)) + +(defun vc-rcs-annotate-current-time () + "Return the current time, based at midnight of the current day, and +encoded as fractional days." + (vc-annotate-convert-time + (apply 'encode-time 0 0 0 (nthcdr 3 (decode-time (current-time)))))) + +(defun vc-rcs-annotate-time () + "Return the time of the next annotation (as fraction of days) +systime, or nil if there is none. Also, reposition point." + (unless (eobp) + (prog1 (vc-annotate-convert-time + (aref (get-text-property (point) :vc-rcs-r/d/a) 1)) + (goto-char (next-single-property-change (point) :vc-annotate-prefix))))) + +(defun vc-rcs-annotate-extract-revision-at-line () + (aref (get-text-property (point) :vc-rcs-r/d/a) 0)) + + +;;; +;;; Tag system +;;; + +(defun vc-rcs-create-tag (backend dir name branchp) + (when branchp + (error "RCS backend %s does not support module branches" backend)) + (let ((result (vc-tag-precondition dir))) + (if (stringp result) + (error "File %s is not up-to-date" result) + (vc-file-tree-walk + dir + (lambda (f) + (vc-do-command "*vc*" 0 "rcs" (vc-name f) (concat "-n" name ":"))))))) + + +;;; +;;; Miscellaneous +;;; + +(defun vc-rcs-trunk-p (rev) + "Return t if REV is a revision on the trunk." + (not (eq nil (string-match "\\`[0-9]+\\.[0-9]+\\'" rev)))) + +(defun vc-rcs-minor-part (rev) + "Return the minor revision number of a revision number REV." + (string-match "[0-9]+\\'" rev) + (substring rev (match-beginning 0) (match-end 0))) + +(defun vc-rcs-previous-revision (file rev) + "Return the revision number immediately preceding REV for FILE, +or nil if there is no previous revision. This default +implementation works for MAJOR.MINOR-style revision numbers as +used by RCS and CVS." + (let ((branch (vc-branch-part rev)) + (minor-num (string-to-number (vc-rcs-minor-part rev)))) + (when branch + (if (> minor-num 1) + ;; revision does probably not start a branch or release + (concat branch "." (number-to-string (1- minor-num))) + (if (vc-rcs-trunk-p rev) + ;; we are at the beginning of the trunk -- + ;; don't know anything to return here + nil + ;; we are at the beginning of a branch -- + ;; return revision of starting point + (vc-branch-part branch)))))) + +(defun vc-rcs-next-revision (file rev) + "Return the revision number immediately following REV for FILE, +or nil if there is no next revision. This default implementation +works for MAJOR.MINOR-style revision numbers as used by RCS +and CVS." + (when (not (string= rev (vc-working-revision file))) + (let ((branch (vc-branch-part rev)) + (minor-num (string-to-number (vc-rcs-minor-part rev)))) + (concat branch "." (number-to-string (1+ minor-num)))))) + +(defun vc-rcs-update-changelog (files) + "Default implementation of update-changelog. +Uses `rcs2log' which only works for RCS and CVS." + ;; FIXME: We (c|sh)ould add support for cvs2cl + (let ((odefault default-directory) + (changelog (find-change-log)) + ;; Presumably not portable to non-Unixy systems, along with rcs2log: + (tempfile (make-temp-file + (expand-file-name "vc" + (or small-temporary-file-directory + temporary-file-directory)))) + (login-name (or user-login-name + (format "uid%d" (number-to-string (user-uid))))) + (full-name (or add-log-full-name + (user-full-name) + (user-login-name) + (format "uid%d" (number-to-string (user-uid))))) + (mailing-address (or add-log-mailing-address + user-mail-address))) + (find-file-other-window changelog) + (barf-if-buffer-read-only) + (vc-buffer-sync) + (undo-boundary) + (goto-char (point-min)) + (push-mark) + (message "Computing change log entries...") + (message "Computing change log entries... %s" + (unwind-protect + (progn + (setq default-directory odefault) + (if (eq 0 (apply 'call-process + (expand-file-name "rcs2log" + exec-directory) + nil (list t tempfile) nil + "-c" changelog + "-u" (concat login-name + "\t" full-name + "\t" mailing-address) + (mapcar + (lambda (f) + (file-relative-name + (expand-file-name f odefault))) + files))) + "done" + (pop-to-buffer (get-buffer-create "*vc*")) + (erase-buffer) + (insert-file-contents tempfile) + "failed")) + (setq default-directory (file-name-directory changelog)) + (delete-file tempfile))))) + +(defun vc-rcs-check-headers () + "Check if the current file has any headers in it." + (save-excursion + (goto-char (point-min)) + (re-search-forward "\\$[A-Za-z\300-\326\330-\366\370-\377]+\ +\\(: [\t -#%-\176\240-\377]*\\)?\\$" nil t))) + +(defun vc-rcs-clear-headers () + "Implementation of vc-clear-headers for RCS." + (let ((case-fold-search nil)) + (goto-char (point-min)) + (while (re-search-forward + (concat "\\$\\(Author\\|Date\\|Header\\|Id\\|Locker\\|Name\\|" + "RCSfile\\|Revision\\|Source\\|State\\): [^$\n]+\\$") + nil t) + (replace-match "$\\1$")))) + +(defun vc-rcs-rename-file (old new) + ;; Just move the master file (using vc-rcs-master-templates). + (vc-rename-master (vc-name old) new vc-rcs-master-templates)) + +(defun vc-rcs-find-file-hook () + ;; If the file is locked by some other user, make + ;; the buffer read-only. Like this, even root + ;; cannot modify a file that someone else has locked. + (and (stringp (vc-state buffer-file-name 'RCS)) + (setq buffer-read-only t))) + + +;;; +;;; Internal functions +;;; + +(defun vc-rcs-workfile-is-newer (file) + "Return non-nil if FILE is newer than its RCS master. +This likely means that FILE has been changed with respect +to its master version." + (let ((file-time (nth 5 (file-attributes file))) + (master-time (nth 5 (file-attributes (vc-name file))))) + (or (> (nth 0 file-time) (nth 0 master-time)) + (and (= (nth 0 file-time) (nth 0 master-time)) + (> (nth 1 file-time) (nth 1 master-time)))))) + +(defun vc-rcs-find-most-recent-rev (branch) + "Find most recent revision on BRANCH." + (goto-char (point-min)) + (let ((latest-rev -1) value) + (while (re-search-forward (concat "^\\(" (regexp-quote branch) + "\\.\\([0-9]+\\)\\)\ndate[ \t]+[0-9.]+;") + nil t) + (let ((rev (string-to-number (match-string 2)))) + (when (< latest-rev rev) + (setq latest-rev rev) + (setq value (match-string 1))))) + (or value + (vc-branch-part branch)))) + +(defun vc-rcs-fetch-master-state (file &optional working-revision) + "Compute the master file's idea of the state of FILE. +If a WORKING-REVISION is given, compute the state of that version, +otherwise determine the workfile version based on the master file. +This function sets the properties `vc-working-revision' and +`vc-checkout-model' to their correct values, based on the master +file." + (with-temp-buffer + (if (or (not (vc-insert-file (vc-name file) "^[0-9]")) + (progn (goto-char (point-min)) + (not (looking-at "^head[ \t\n]+[^;]+;$")))) + (error "File %s is not an RCS master file" (vc-name file))) + (let ((workfile-is-latest nil) + (default-branch (vc-parse-buffer "^branch[ \t\n]+\\([^;]*\\);" 1))) + (vc-file-setprop file 'vc-rcs-default-branch default-branch) + (unless working-revision + ;; Workfile version not known yet. Determine that first. It + ;; is either the head of the trunk, the head of the default + ;; branch, or the "default branch" itself, if that is a full + ;; revision number. + (cond + ;; no default branch + ((or (not default-branch) (string= "" default-branch)) + (setq working-revision + (vc-parse-buffer "^head[ \t\n]+\\([^;]+\\);" 1)) + (setq workfile-is-latest t)) + ;; default branch is actually a revision + ((string-match "^[0-9]+\\.[0-9]+\\(\\.[0-9]+\\.[0-9]+\\)*$" + default-branch) + (setq working-revision default-branch)) + ;; else, search for the head of the default branch + (t (vc-insert-file (vc-name file) "^desc") + (setq working-revision + (vc-rcs-find-most-recent-rev default-branch)) + (setq workfile-is-latest t))) + (vc-file-setprop file 'vc-working-revision working-revision)) + ;; Check strict locking + (goto-char (point-min)) + (vc-file-setprop file 'vc-checkout-model + (if (re-search-forward ";[ \t\n]*strict;" nil t) + 'locking 'implicit)) + ;; Compute state of workfile version + (goto-char (point-min)) + (let ((locking-user + (vc-parse-buffer (concat "^locks[ \t\n]+[^;]*[ \t\n]+\\([^:]+\\):" + (regexp-quote working-revision) + "[^0-9.]") + 1))) + (cond + ;; not locked + ((not locking-user) + (if (or workfile-is-latest + (vc-rcs-latest-on-branch-p file working-revision)) + ;; workfile version is latest on branch + 'up-to-date + ;; workfile version is not latest on branch + 'needs-update)) + ;; locked by the calling user + ((and (stringp locking-user) + (string= locking-user (vc-user-login-name file))) + ;; Don't call `vc-rcs-checkout-model' to avoid inf-looping. + (if (or (eq (vc-file-getprop file 'vc-checkout-model) 'locking) + workfile-is-latest + (vc-rcs-latest-on-branch-p file working-revision)) + 'edited + ;; Locking is not used for the file, but the owner does + ;; have a lock, and there is a higher version on the current + ;; branch. Not sure if this can occur, and if it is right + ;; to use `needs-merge' in this case. + 'needs-merge)) + ;; locked by somebody else + ((stringp locking-user) + locking-user) + (t + (error "Error getting state of RCS file"))))))) + +(defun vc-rcs-consult-headers (file) + "Search for RCS headers in FILE, and set properties accordingly. + +Returns: nil if no headers were found + 'rev if a workfile revision was found + 'rev-and-lock if revision and lock info was found" + (cond + ((not (get-file-buffer file)) nil) + ((let (status version locking-user) + (with-current-buffer (get-file-buffer file) + (save-excursion + (goto-char (point-min)) + (cond + ;; search for $Id or $Header + ;; ------------------------- + ;; The `\ 's below avoid an RCS 5.7 bug when checking in this file. + ((or (and (search-forward "$Id\ : " nil t) + (looking-at "[^ ]+ \\([0-9.]+\\) ")) + (and (progn (goto-char (point-min)) + (search-forward "$Header\ : " nil t)) + (looking-at "[^ ]+ \\([0-9.]+\\) "))) + (goto-char (match-end 0)) + ;; if found, store the revision number ... + (setq version (match-string-no-properties 1)) + ;; ... and check for the locking state + (cond + ((looking-at + (concat "[0-9]+[/-][01][0-9][/-][0-3][0-9] " ; date + "[0-2][0-9]:[0-5][0-9]+:[0-6][0-9]+\\([+-][0-9:]+\\)? " ; time + "[^ ]+ [^ ]+ ")) ; author & state + (goto-char (match-end 0)) ; [0-6] in regexp handles leap seconds + (cond + ;; unlocked revision + ((looking-at "\\$") + (setq locking-user 'none) + (setq status 'rev-and-lock)) + ;; revision is locked by some user + ((looking-at "\\([^ ]+\\) \\$") + (setq locking-user (match-string-no-properties 1)) + (setq status 'rev-and-lock)) + ;; everything else: false + (nil))) + ;; unexpected information in + ;; keyword string --> quit + (nil))) + ;; search for $Revision + ;; -------------------- + ((re-search-forward (concat "\\$" + "Revision: \\([0-9.]+\\) \\$") + nil t) + ;; if found, store the revision number ... + (setq version (match-string-no-properties 1)) + ;; and see if there's any lock information + (goto-char (point-min)) + (if (re-search-forward (concat "\\$" "Locker:") nil t) + (cond ((looking-at " \\([^ ]+\\) \\$") + (setq locking-user (match-string-no-properties 1)) + (setq status 'rev-and-lock)) + ((looking-at " *\\$") + (setq locking-user 'none) + (setq status 'rev-and-lock)) + (t + (setq locking-user 'none) + (setq status 'rev-and-lock))) + (setq status 'rev))) + ;; else: nothing found + ;; ------------------- + (t nil)))) + (if status (vc-file-setprop file 'vc-working-revision version)) + (and (eq status 'rev-and-lock) + (vc-file-setprop file 'vc-state + (cond + ((eq locking-user 'none) 'up-to-date) + ((string= locking-user (vc-user-login-name file)) + 'edited) + (t locking-user))) + ;; If the file has headers, we don't want to query the + ;; master file, because that would eliminate all the + ;; performance gain the headers brought us. We therefore + ;; use a heuristic now to find out whether locking is used + ;; for this file. If we trust the file permissions, and the + ;; file is not locked, then if the file is read-only we + ;; assume that locking is used for the file, otherwise + ;; locking is not used. + (not (vc-mistrust-permissions file)) + (vc-up-to-date-p file) + (if (string-match ".r-..-..-." (nth 8 (file-attributes file))) + (vc-file-setprop file 'vc-checkout-model 'locking) + (vc-file-setprop file 'vc-checkout-model 'implicit))) + status)))) + +(defun vc-release-greater-or-equal (r1 r2) + "Compare release numbers, represented as strings. +Release components are assumed cardinal numbers, not decimal fractions +\(5.10 is a higher release than 5.9\). Omitted fields are considered +lower \(5.6.7 is earlier than 5.6.7.1\). Comparison runs till the end +of the string is found, or a non-numeric component shows up \(5.6.7 is +earlier than \"5.6.7 beta\", which is probably not what you want in +some cases\). This code is suitable for existing RCS release numbers. +CVS releases are handled reasonably, too \(1.3 < 1.4* < 1.5\)." + (let (v1 v2 i1 i2) + (catch 'done + (or (and (string-match "^\\.?\\([0-9]+\\)" r1) + (setq i1 (match-end 0)) + (setq v1 (string-to-number (match-string 1 r1))) + (or (and (string-match "^\\.?\\([0-9]+\\)" r2) + (setq i2 (match-end 0)) + (setq v2 (string-to-number (match-string 1 r2))) + (if (> v1 v2) (throw 'done t) + (if (< v1 v2) (throw 'done nil) + (throw 'done + (vc-release-greater-or-equal + (substring r1 i1) + (substring r2 i2))))))) + (throw 'done t))) + (or (and (string-match "^\\.?\\([0-9]+\\)" r2) + (throw 'done nil)) + (throw 'done t))))) + +(defun vc-rcs-release-p (release) + "Return t if we have RELEASE or better." + (let ((installation (vc-rcs-system-release))) + (if (and installation + (not (eq installation 'unknown))) + (vc-release-greater-or-equal installation release)))) + +(defun vc-rcs-system-release () + "Return the RCS release installed on this system, as a string. +Return symbol `unknown' if the release cannot be deducted. The user can +override this using variable `vc-rcs-release'. + +If the user has not set variable `vc-rcs-release' and it is nil, +variable `vc-rcs-release' is set to the returned value." + (or vc-rcs-release + (setq vc-rcs-release + (or (and (zerop (vc-do-command "*vc*" nil "rcs" nil "-V")) + (with-current-buffer (get-buffer "*vc*") + (vc-parse-buffer "^RCS version \\([0-9.]+ *.*\\)" 1))) + 'unknown)))) + +(defun vc-rcs-set-non-strict-locking (file) + (vc-do-command "*vc*" 0 "rcs" file "-U") + (vc-file-setprop file 'vc-checkout-model 'implicit) + (set-file-modes file (logior (file-modes file) 128))) + +(defun vc-rcs-set-default-branch (file branch) + (vc-do-command "*vc*" 0 "rcs" (vc-name file) (concat "-b" branch)) + (vc-file-setprop file 'vc-rcs-default-branch branch)) + +(defun vc-rcs-parse (&optional buffer) + "Parse current buffer, presumed to be in RCS-style masterfile format. +Optional arg BUFFER specifies another buffer to parse. Return an alist +of two elements, w/ keys `headers' and `revisions' and values in turn +sub-alists. For `headers', the values unless otherwise specified are +strings and the keys are: + + desc -- description + head -- latest revision + branch -- the branch the \"head revision\" lies on; + absent if the head revision lies on the trunk + access -- ??? + symbols -- sub-alist of (SYMBOL . REVISION) elements + locks -- if file is checked out, something like \"ttn:1.7\" + strict -- t if \"strict locking\" is in effect, otherwise nil + comment -- may be absent; typically something like \"# \" or \"; \" + expand -- may be absent; ??? + +For `revisions', the car is REVISION (string), the cdr a sub-alist, +with string values (unless otherwise specified) and keys: + + date -- a time value (like that returned by `encode-time'); as a + special case, a year value less than 100 is augmented by 1900 + author -- username + state -- typically \"Exp\" or \"Rel\" + branches -- list of revisions that begin branches from this revision + next -- on the trunk: the chronologically-preceding revision, or \"\"; + on a branch: the chronologically-following revision, or \"\" + log -- change log entry + text -- for the head revision on the trunk, the body of the file; + other revisions have `:insn' instead + :insn -- for non-head revisions, a list of parsed instructions + in one of two forms, in both cases START meaning \"first + go to line START\": + - `(START k COUNT)' -- kill COUNT lines + - `(START i TEXT)' -- insert TEXT (a string) + The list is in descending order by START. + +The `:insn' key is a keyword to distinguish it as a vc-rcs.el extension." + (setq buffer (get-buffer (or buffer (current-buffer)))) + (set-buffer buffer) + ;; An RCS masterfile can be viewed as containing four regular (for the + ;; most part) sections: (a) the "headers", (b) the "rev headers", (c) + ;; the "description" and (d) the "rev bodies", in that order. In the + ;; returned alist (see docstring), elements from (b) and (d) are + ;; combined pairwise to form the "revisions", while those from (a) and + ;; (c) are simply combined to form the "headers". + ;; + ;; Loosely speaking, each section contains a series of alternating + ;; "tags" and "printed representations". In the (b) and (d), many + ;; such series can appear, and a revision number on a line by itself + ;; precedes the series of tags and printed representations associated + ;; with it. + ;; + ;; In (a) and (b), the printed representations (with the exception of + ;; the `comment' tag in the headers) terminate with a semicolon, which + ;; is NOT part of the "value" finally associated with the tag. All + ;; other printed representations are in "@@-format"; there is an "@", + ;; the middle part (to be translated into the value), another "@" and + ;; a newline. Each "@@" in the middle part indicates the position of + ;; a single "@" (and consequently the requirement of an additional + ;; initial step when translating to the value). + ;; + ;; Parser state includes vars that collect parts of the return value... + (let ((desc nil) (headers nil) (revs nil) + ;; ... as well as vars that support a single-pass, tag-assisted, + ;; minimal-data-copying scan. Basically -- skirting around the + ;; grouping by revision required in (b) and (d) -- we repeatedly + ;; and context-sensitively read a tag (that MUST be present), + ;; determine the bounds of the printed representation, translate + ;; it into a value, and push the tag plus value onto one of the + ;; collection vars. Finally, we return the parse tree + ;; incorporating the values of the collection vars (see "rv"). + ;; + ;; A symbol or string to keep track of context (for error messages). + context + ;; A symbol, the current tag. + tok + ;; Region (begin and end buffer positions) of the printed + ;; representation for the current tag. + b e + ;; A list of buffer positions where "@@" can be found within the + ;; printed representation region. For each location, we push two + ;; elements onto the list, 1+ and 2+ the location, respectively, + ;; with the 2+ appearing at the head. In this way, the expression + ;; `(,e ,@@-holes ,b) + ;; describes regions that can be concatenated (in reverse order) + ;; to "de-@@-format" the printed representation as the first step + ;; to translating it into some value. See internal func `gather'. + @-holes) + (flet ((sw () (skip-chars-forward " \t\n")) ; i.e., `[:space:]' + (at (tag) (save-excursion (eq tag (read buffer)))) + (to-eol () (buffer-substring-no-properties + (point) (progn (forward-line 1) + (1- (point))))) + (to-semi () (setq b (point) + e (progn (search-forward ";") + (1- (point))))) + (to-one@ () (setq @-holes nil + b (progn (search-forward "@") (point)) + e (progn (while (and (search-forward "@") + (= ?@ (char-after)) + (progn + (push (point) @-holes) + (forward-char 1) + (push (point) @-holes)))) + (1- (point))))) + (tok+val (set-b+e name &optional proc) + (unless (eq name (setq tok (read buffer))) + (error "Missing `%s' while parsing %s" name context)) + (sw) + (funcall set-b+e) + (cons tok (if proc + (funcall proc) + (buffer-substring-no-properties b e)))) + (k-semi (name &optional proc) (tok+val 'to-semi name proc)) + (gather () (let ((pairs `(,e ,@@-holes ,b)) + acc) + (while pairs + (push (buffer-substring-no-properties + (cadr pairs) (car pairs)) + acc) + (setq pairs (cddr pairs))) + (apply 'concat acc))) + (k-one@ (name &optional later) (tok+val 'to-one@ name + (if later + (lambda () t) + 'gather)))) + (save-excursion + (goto-char (point-min)) + ;; headers + (setq context 'headers) + (flet ((hpush (name &optional proc) + (push (k-semi name proc) headers))) + (hpush 'head) + (when (at 'branch) + (hpush 'branch)) + (hpush 'access) + (hpush 'symbols + (lambda () + (mapcar (lambda (together) + (let ((two (split-string together ":"))) + (setcar two (intern (car two))) + (setcdr two (cadr two)) + two)) + (split-string + (buffer-substring-no-properties b e))))) + (hpush 'locks)) + (push `(strict . ,(when (at 'strict) + (search-forward ";") + t)) + headers) + (when (at 'comment) + (push (k-one@ 'comment) headers) + (search-forward ";")) + (when (at 'expand) + (push (k-one@ 'expand) headers) + (search-forward ";")) + (setq headers (nreverse headers)) + ;; rev headers + (sw) (setq context 'rev-headers) + (while (looking-at "[0-9]") + (push `(,(to-eol) + ,(k-semi 'date + (lambda () + (let ((ls (mapcar 'string-to-number + (split-string + (buffer-substring-no-properties + b e) + "\\.")))) + ;; Hack the year -- verified to be the + ;; same algorithm used in RCS 5.7. + (when (< (car ls) 100) + (setcar ls (+ 1900 (car ls)))) + (apply 'encode-time (nreverse ls))))) + ,@(mapcar 'k-semi '(author state)) + ,(k-semi 'branches + (lambda () + (split-string + (buffer-substring-no-properties b e)))) + ,(k-semi 'next)) + revs) + (sw)) + (setq revs (nreverse revs)) + ;; desc + (sw) (setq context 'desc + desc (k-one@ 'desc)) + ;; rev bodies + (let (acc + ;; Element of `revs' that initially holds only header info. + ;; "Pairwise combination" occurs when we add body info. + rev + ;; Components of the editing commands (aside from the actual + ;; text) that comprise the `text' printed representations + ;; (not including the "head" revision). + cmd start act + ;; Ascending (reversed) `@-holes' which the internal func + ;; `incg' pops to effect incremental gathering. + asc + ;; Function to extract text (for the `a' command), either + ;; `incg' or `buffer-substring-no-properties'. (This is + ;; for speed; strictly speaking, it is sufficient to use + ;; only the former since it behaves identically to the + ;; latter in the absense of "@@".) + sub) + (flet ((incg (beg end) (let ((b beg) (e end) @-holes) + (while (and asc (< (car asc) e)) + (push (pop asc) @-holes)) + ;; Self-deprecate when work is done. + ;; Folding many dimensions into one. + ;; Thanks B.Mandelbrot, for complex sum. + ;; O beauteous math! --the Unvexed Bum + (unless asc + (setq sub 'buffer-substring-no-properties)) + (gather)))) + (while (and (sw) + (not (eobp)) + (setq context (to-eol) + rev (or (assoc context revs) + (error "Rev `%s' has body but no head" + context)))) + (push (k-one@ 'log) (cdr rev)) + ;; For rev body `text' tags, delay translation slightly... + (push (k-one@ 'text t) (cdr rev)) + ;; ... until we decide which tag and value is appropriate to + ;; collect. For the "head" revision, compute the value of the + ;; `text' printed representation by simple `gather'. For all + ;; other revisions, replace the `text' tag+value with `:insn' + ;; plus value, always scanning in-place. + (if (string= context (cdr (assq 'head headers))) + (setcdr (cadr rev) (gather)) + (if @-holes + (setq asc (nreverse @-holes) + sub 'incg) + (setq sub 'buffer-substring-no-properties)) + (goto-char b) + (setq acc nil) + (while (< (point) e) + (forward-char 1) + (setq cmd (char-before) + start (read (current-buffer)) + act (read (current-buffer))) + (forward-char 1) + (push (case cmd + (?d + ;; `d' means "delete lines". + ;; For Emacs spirit, we use `k' for "kill". + `(,start k ,act)) + (?a + ;; `a' means "append after this line" but + ;; internally we normalize it so that START + ;; specifies the actual line for insert, thus + ;; requiring less hair in the realization algs. + ;; For Emacs spirit, we use `i' for "insert". + `(,(1+ start) i + ,(funcall sub (point) (progn (forward-line act) + (point))))) + (t (error "Bad command `%c' in `text' for rev `%s'" + cmd context))) + acc)) + (goto-char (1+ e)) + (setcar (cdr rev) (cons :insn acc))))))) + ;; rv + `((headers ,desc ,@headers) + (revisions ,@revs))))) + +(provide 'vc-rcs) + +;; arch-tag: 759b4916-5b0d-431d-b647-b185b8c652cf +;;; vc-rcs.el ends here diff -r 681cd08dc0f7 -r e93288477c43 lisp/vc/vc-sccs.el --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/lisp/vc/vc-sccs.el Sun Jun 13 22:57:55 2010 +0000 @@ -0,0 +1,485 @@ +;;; vc-sccs.el --- support for SCCS version-control + +;; Copyright (C) 1992, 1993, 1994, 1995, 1996, 1997, 1998, 1999, 2000, +;; 2001, 2002, 2003, 2004, 2005, 2006, 2007, 2008, 2009, 2010 +;; Free Software Foundation, Inc. + +;; Author: FSF (see vc.el for full credits) +;; Maintainer: Andre Spiegel + +;; 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 3 of the License, 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. If not, see . + +;;; Commentary: + +;; Proper function of the SCCS diff commands requires the shellscript vcdiff +;; to be installed somewhere on Emacs's path for executables. +;; + +;;; Code: + +(eval-when-compile + (require 'vc)) + +;;; +;;; Customization options +;;; + +;; ;; Maybe a better solution is to not use "get" but "sccs get". +;; (defcustom vc-sccs-path +;; (let ((path ())) +;; (dolist (dir '("/usr/sccs" "/usr/lib/sccs" "/usr/libexec/sccs")) +;; (if (file-directory-p dir) +;; (push dir path))) +;; path) +;; "List of extra directories to search for SCCS commands." +;; :type '(repeat directory) +;; :group 'vc) + +(defcustom vc-sccs-register-switches nil + "Switches for registering a file in SCCS. +A string or list of strings passed to the checkin program by +\\[vc-register]. If nil, use the value of `vc-register-switches'. +If t, use no switches." + :type '(choice (const :tag "Unspecified" nil) + (const :tag "None" t) + (string :tag "Argument String") + (repeat :tag "Argument List" :value ("") string)) + :version "21.1" + :group 'vc) + +(defcustom vc-sccs-diff-switches nil + "String or list of strings specifying switches for SCCS diff under VC. +If nil, use the value of `vc-diff-switches'. If t, use no switches." + :type '(choice (const :tag "Unspecified" nil) + (const :tag "None" t) + (string :tag "Argument String") + (repeat :tag "Argument List" :value ("") string)) + :version "21.1" + :group 'vc) + +(defcustom vc-sccs-header (or (cdr (assoc 'SCCS vc-header-alist)) '("%W%")) + "Header keywords to be inserted by `vc-insert-headers'." + :type '(repeat string) + :group 'vc) + +;;;###autoload +(defcustom vc-sccs-master-templates + (purecopy '("%sSCCS/s.%s" "%ss.%s" vc-sccs-search-project-dir)) + "Where to look for SCCS master files. +For a description of possible values, see `vc-check-master-templates'." + :type '(choice (const :tag "Use standard SCCS file names" + ("%sSCCS/s.%s" "%ss.%s" vc-sccs-search-project-dir)) + (repeat :tag "User-specified" + (choice string + function))) + :version "21.1" + :group 'vc) + + +;;; +;;; Internal variables +;;; + +(defconst vc-sccs-name-assoc-file "VC-names") + + +;;; Properties of the backend + +(defun vc-sccs-revision-granularity () 'file) +(defun vc-sccs-checkout-model (files) 'locking) + +;;; +;;; State-querying functions +;;; + +;; The autoload cookie below places vc-sccs-registered directly into +;; loaddefs.el, so that vc-sccs.el does not need to be loaded for +;; every file that is visited. The definition is repeated below +;; so that Help and etags can find it. + +;;;###autoload (defun vc-sccs-registered(f) (vc-default-registered 'SCCS f)) +(defun vc-sccs-registered (f) (vc-default-registered 'SCCS f)) + +(defun vc-sccs-state (file) + "SCCS-specific function to compute the version control state." + (if (not (vc-sccs-registered file)) + 'unregistered + (with-temp-buffer + (if (vc-insert-file (vc-sccs-lock-file file)) + (let* ((locks (vc-sccs-parse-locks)) + (working-revision (vc-working-revision file)) + (locking-user (cdr (assoc working-revision locks)))) + (if (not locking-user) + (if (vc-workfile-unchanged-p file) + 'up-to-date + 'unlocked-changes) + (if (string= locking-user (vc-user-login-name file)) + 'edited + locking-user))) + 'up-to-date)))) + +(defun vc-sccs-state-heuristic (file) + "SCCS-specific state heuristic." + (if (not (vc-mistrust-permissions file)) + ;; This implementation assumes that any file which is under version + ;; control and has -rw-r--r-- is locked by its owner. This is true + ;; for both RCS and SCCS, which keep unlocked files at -r--r--r--. + ;; We have to be careful not to exclude files with execute bits on; + ;; scripts can be under version control too. Also, we must ignore the + ;; group-read and other-read bits, since paranoid users turn them off. + (let* ((attributes (file-attributes file 'string)) + (owner-name (nth 2 attributes)) + (permissions (nth 8 attributes))) + (if (string-match ".r-..-..-." permissions) + 'up-to-date + (if (string-match ".rw..-..-." permissions) + (if (file-ownership-preserved-p file) + 'edited + owner-name) + ;; Strange permissions. + ;; Fall through to real state computation. + (vc-sccs-state file)))) + (vc-sccs-state file))) + +(defun vc-sccs-dir-status (dir update-function) + ;; FIXME: this function should be rewritten, using `vc-expand-dirs' + ;; is not TRTD because it returns files from multiple backends. + ;; It should also return 'unregistered files. + + ;; Doing lots of individual VC-state calls is painful, but + ;; there is no better option in SCCS-land. + (let ((flist (vc-expand-dirs (list dir))) + (result nil)) + (dolist (file flist) + (let ((state (vc-state file)) + (frel (file-relative-name file))) + (when (and (eq (vc-backend file) 'SCCS) + (not (eq state 'up-to-date))) + (push (list frel state) result)))) + (funcall update-function result))) + +(defun vc-sccs-working-revision (file) + "SCCS-specific version of `vc-working-revision'." + (with-temp-buffer + ;; The working revision is always the latest revision number. + ;; To find this number, search the entire delta table, + ;; rather than just the first entry, because the + ;; first entry might be a deleted ("R") revision. + (vc-insert-file (vc-name file) "^\001e\n\001[^s]") + (vc-parse-buffer "^\001d D \\([^ ]+\\)" 1))) + +(defun vc-sccs-workfile-unchanged-p (file) + "SCCS-specific implementation of `vc-workfile-unchanged-p'." + (zerop (apply 'vc-do-command "*vc*" 1 "vcdiff" (vc-name file) + (list "--brief" "-q" + (concat "-r" (vc-working-revision file)))))) + + +;;; +;;; State-changing functions +;;; + +(defun vc-sccs-do-command (buffer okstatus command file-or-list &rest flags) + ;; (let ((load-path (append vc-sccs-path load-path))) + ;; (apply 'vc-do-command buffer okstatus command file-or-list flags)) + (apply 'vc-do-command (or buffer "*vc*") okstatus "sccs" file-or-list command flags)) + +(defun vc-sccs-create-repo () + "Create a new SCCS repository." + ;; SCCS is totally file-oriented, so all we have to do is make the directory + (make-directory "SCCS")) + +(defun vc-sccs-register (files &optional rev comment) + "Register FILES into the SCCS version-control system. +REV is the optional revision number for the file. COMMENT can be used +to provide an initial description of FILES. +Passes either `vc-sccs-register-switches' or `vc-register-switches' +to the SCCS command. + +Automatically retrieve a read-only version of the files with keywords +expanded if `vc-keep-workfiles' is non-nil, otherwise, delete the workfile." + (dolist (file files) + (let* ((dirname (or (file-name-directory file) "")) + (basename (file-name-nondirectory file)) + (project-file (vc-sccs-search-project-dir dirname basename))) + (let ((vc-name + (or project-file + (format (car vc-sccs-master-templates) dirname basename)))) + (apply 'vc-sccs-do-command nil 0 "admin" vc-name + (and rev (not (string= rev "")) (concat "-r" rev)) + "-fb" + (concat "-i" (file-relative-name file)) + (and comment (concat "-y" comment)) + (vc-switches 'SCCS 'register))) + (delete-file file) + (if vc-keep-workfiles + (vc-sccs-do-command nil 0 "get" (vc-name file)))))) + +(defun vc-sccs-responsible-p (file) + "Return non-nil if SCCS thinks it would be responsible for registering FILE." + ;; TODO: check for all the patterns in vc-sccs-master-templates + (or (file-directory-p (expand-file-name "SCCS" (file-name-directory file))) + (stringp (vc-sccs-search-project-dir (or (file-name-directory file) "") + (file-name-nondirectory file))))) + +(defun vc-sccs-checkin (files rev comment &optional extra-args-ignored) + "SCCS-specific version of `vc-backend-checkin'." + (dolist (file (vc-expand-dirs files)) + (apply 'vc-sccs-do-command nil 0 "delta" (vc-name file) + (if rev (concat "-r" rev)) + (concat "-y" comment) + (vc-switches 'SCCS 'checkin)) + (if vc-keep-workfiles + (vc-sccs-do-command nil 0 "get" (vc-name file))))) + +(defun vc-sccs-find-revision (file rev buffer) + (apply 'vc-sccs-do-command + buffer 0 "get" (vc-name file) + "-s" ;; suppress diagnostic output + "-p" + (and rev + (concat "-r" + (vc-sccs-lookup-triple file rev))) + (vc-switches 'SCCS 'checkout))) + +(defun vc-sccs-checkout (file &optional editable rev) + "Retrieve a copy of a saved revision of SCCS controlled FILE. +If FILE is a directory, all version-controlled files beneath are checked out. +EDITABLE non-nil means that the file should be writable and +locked. REV is the revision to check out." + (if (file-directory-p file) + (mapc 'vc-sccs-checkout (vc-expand-dirs (list file))) + (let ((file-buffer (get-file-buffer file)) + switches) + (message "Checking out %s..." file) + (save-excursion + ;; Change buffers to get local value of vc-checkout-switches. + (if file-buffer (set-buffer file-buffer)) + (setq switches (vc-switches 'SCCS 'checkout)) + ;; Save this buffer's default-directory + ;; and use save-excursion to make sure it is restored + ;; in the same buffer it was saved in. + (let ((default-directory default-directory)) + (save-excursion + ;; Adjust the default-directory so that the check-out creates + ;; the file in the right place. + (setq default-directory (file-name-directory file)) + + (and rev (or (string= rev "") + (not (stringp rev))) + (setq rev nil)) + (apply 'vc-sccs-do-command nil 0 "get" (vc-name file) + (if editable "-e") + (and rev (concat "-r" (vc-sccs-lookup-triple file rev))) + switches)))) + (message "Checking out %s...done" file)))) + +(defun vc-sccs-rollback (files) + "Roll back, undoing the most recent checkins of FILES. Directories +are expanded to all version-controlled subfiles." + (setq files (vc-expand-dirs files)) + (if (not files) + (error "SCCS backend doesn't support directory-level rollback")) + (dolist (file files) + (let ((discard (vc-working-revision file))) + (if (null (yes-or-no-p (format "Remove version %s from %s history? " + discard file))) + (error "Aborted")) + (message "Removing revision %s from %s..." discard file) + (vc-sccs-do-command nil 0 "rmdel" + (vc-name file) (concat "-r" discard)) + (vc-sccs-do-command nil 0 "get" (vc-name file) nil)))) + +(defun vc-sccs-revert (file &optional contents-done) + "Revert FILE to the version it was based on. If FILE is a directory, +revert all subfiles." + (if (file-directory-p file) + (mapc 'vc-sccs-revert (vc-expand-dirs (list file))) + (vc-sccs-do-command nil 0 "unget" (vc-name file)) + (vc-sccs-do-command nil 0 "get" (vc-name file)) + ;; Checking out explicit revisions is not supported under SCCS, yet. + ;; We always "revert" to the latest revision; therefore + ;; vc-working-revision is cleared here so that it gets recomputed. + (vc-file-setprop file 'vc-working-revision nil))) + +(defun vc-sccs-steal-lock (file &optional rev) + "Steal the lock on the current workfile for FILE and revision REV." + (if (file-directory-p file) + (mapc 'vc-sccs-steal-lock (vc-expand-dirs (list file))) + (vc-sccs-do-command nil 0 "unget" + (vc-name file) "-n" (if rev (concat "-r" rev))) + (vc-sccs-do-command nil 0 "get" + (vc-name file) "-g" (if rev (concat "-r" rev))))) + +(defun vc-sccs-modify-change-comment (files rev comment) + "Modify (actually, append to) the change comments for FILES on a specified REV." + (dolist (file (vc-expand-dirs files)) + (vc-sccs-do-command nil 0 "cdc" (vc-name file) + (concat "-y" comment) (concat "-r" rev)))) + + +;;; +;;; History functions +;;; + +(defun vc-sccs-print-log (files buffer &optional shortlog start-revision-ignored limit) + "Get change log associated with FILES." + (setq files (vc-expand-dirs files)) + (vc-sccs-do-command buffer 0 "prs" (mapcar 'vc-name files)) + (when limit 'limit-unsupported)) + +(defun vc-sccs-diff (files &optional oldvers newvers buffer) + "Get a difference report using SCCS between two filesets." + (setq files (vc-expand-dirs files)) + (setq oldvers (vc-sccs-lookup-triple (car files) oldvers)) + (setq newvers (vc-sccs-lookup-triple (car files) newvers)) + (apply 'vc-do-command (or buffer "*vc-diff*") + 1 "vcdiff" (mapcar 'vc-name (vc-expand-dirs files)) + (append (list "-q" + (and oldvers (concat "-r" oldvers)) + (and newvers (concat "-r" newvers))) + (vc-switches 'SCCS 'diff)))) + + +;;; +;;; Tag system. SCCS doesn't have tags, so we simulate them by maintaining +;;; our own set of name-to-revision mappings. +;;; + +(defun vc-sccs-create-tag (backend dir name branchp) + (when branchp + (error "SCCS backend %s does not support module branches" backend)) + (let ((result (vc-tag-precondition dir))) + (if (stringp result) + (error "File %s is not up-to-date" result) + (vc-file-tree-walk + dir + (lambda (f) + (vc-sccs-add-triple name f (vc-working-revision f))))))) + + +;;; +;;; Miscellaneous +;;; + +(defun vc-sccs-previous-revision (file rev) + (vc-call-backend 'RCS 'previous-revision file rev)) + +(defun vc-sccs-next-revision (file rev) + (vc-call-backend 'RCS 'next-revision file rev)) + +(defun vc-sccs-check-headers () + "Check if the current file has any headers in it." + (save-excursion + (goto-char (point-min)) + (re-search-forward "%[A-Z]%" nil t))) + +(defun vc-sccs-rename-file (old new) + ;; Move the master file (using vc-rcs-master-templates). + (vc-rename-master (vc-name old) new vc-sccs-master-templates) + ;; Update the tag file. + (with-current-buffer + (find-file-noselect + (expand-file-name vc-sccs-name-assoc-file + (file-name-directory (vc-name old)))) + (goto-char (point-min)) + ;; (replace-regexp (concat ":" (regexp-quote old) "$") (concat ":" new)) + (while (re-search-forward (concat ":" (regexp-quote old) "$") nil t) + (replace-match (concat ":" new) nil nil)) + (basic-save-buffer) + (kill-buffer (current-buffer)))) + +(defun vc-sccs-find-file-hook () + ;; If the file is locked by some other user, make + ;; the buffer read-only. Like this, even root + ;; cannot modify a file that someone else has locked. + (and (stringp (vc-state buffer-file-name 'SCCS)) + (setq buffer-read-only t))) + + +;;; +;;; Internal functions +;;; + +;; This function is wrapped with `progn' so that the autoload cookie +;; copies the whole function itself into loaddefs.el rather than just placing +;; a (autoload 'vc-sccs-search-project-dir "vc-sccs") which would not +;; help us avoid loading vc-sccs. +;;;###autoload +(progn (defun vc-sccs-search-project-dir (dirname basename) + "Return the name of a master file in the SCCS project directory. +Does not check whether the file exists but returns nil if it does not +find any project directory." + (let ((project-dir (getenv "PROJECTDIR")) dirs dir) + (when project-dir + (if (file-name-absolute-p project-dir) + (setq dirs '("SCCS" "")) + (setq dirs '("src/SCCS" "src" "source/SCCS" "source")) + (setq project-dir (expand-file-name (concat "~" project-dir)))) + (while (and (not dir) dirs) + (setq dir (expand-file-name (car dirs) project-dir)) + (unless (file-directory-p dir) + (setq dir nil) + (setq dirs (cdr dirs)))) + (and dir (expand-file-name (concat "s." basename) dir)))))) + +(defun vc-sccs-lock-file (file) + "Generate lock file name corresponding to FILE." + (let ((master (vc-name file))) + (and + master + (string-match "\\(.*/\\)\\(s\\.\\)\\(.*\\)" master) + (replace-match "p." t t master 2)))) + +(defun vc-sccs-parse-locks () + "Parse SCCS locks in current buffer. +The result is a list of the form ((REVISION . USER) (REVISION . USER) ...)." + (let (master-locks) + (goto-char (point-min)) + (while (re-search-forward "^\\([0-9.]+\\) [0-9.]+ \\([^ ]+\\) .*\n?" + nil t) + (setq master-locks + (cons (cons (match-string 1) (match-string 2)) master-locks))) + ;; FIXME: is it really necessary to reverse ? + (nreverse master-locks))) + +(defun vc-sccs-add-triple (name file rev) + (with-current-buffer + (find-file-noselect + (expand-file-name vc-sccs-name-assoc-file + (file-name-directory (vc-name file)))) + (goto-char (point-max)) + (insert name "\t:\t" file "\t" rev "\n") + (basic-save-buffer) + (kill-buffer (current-buffer)))) + +(defun vc-sccs-lookup-triple (file name) + "Return the numeric revision corresponding to a named tag of FILE. +If NAME is nil or a revision number string it's just passed through." + (if (or (null name) + (let ((firstchar (aref name 0))) + (and (>= firstchar ?0) (<= firstchar ?9)))) + name + (with-temp-buffer + (vc-insert-file + (expand-file-name vc-sccs-name-assoc-file + (file-name-directory (vc-name file)))) + (vc-parse-buffer (concat name "\t:\t" file "\t\\(.+\\)") 1)))) + +(provide 'vc-sccs) + +;; arch-tag: d751dee3-d7b3-47e1-95e3-7ae98c052041 +;;; vc-sccs.el ends here diff -r 681cd08dc0f7 -r e93288477c43 lisp/vc/vc-svn.el --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/lisp/vc/vc-svn.el Sun Jun 13 22:57:55 2010 +0000 @@ -0,0 +1,747 @@ +;;; vc-svn.el --- non-resident support for Subversion version-control + +;; Copyright (C) 2003, 2004, 2005, 2006, 2007, 2008, 2009, 2010 +;; Free Software Foundation, Inc. + +;; Author: FSF (see vc.el for full credits) +;; Maintainer: Stefan Monnier + +;; 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 3 of the License, 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. If not, see . + +;;; Commentary: + +;; Sync'd with Subversion's vc-svn.el as of revision 5801. but this version +;; has been extensively modified since to handle filesets. + +;;; Code: + +(eval-when-compile + (require 'vc)) + +;; Clear up the cache to force vc-call to check again and discover +;; new functions when we reload this file. +(put 'SVN 'vc-functions nil) + +;;; +;;; Customization options +;;; + +;; FIXME there is also svnadmin. +(defcustom vc-svn-program "svn" + "Name of the SVN executable." + :type 'string + :group 'vc) + +(defcustom vc-svn-global-switches nil + "Global switches to pass to any SVN command." + :type '(choice (const :tag "None" nil) + (string :tag "Argument String") + (repeat :tag "Argument List" + :value ("") + string)) + :version "22.1" + :group 'vc) + +(defcustom vc-svn-register-switches nil + "Switches for registering a file into SVN. +A string or list of strings passed to the checkin program by +\\[vc-register]. If nil, use the value of `vc-register-switches'. +If t, use no switches." + :type '(choice (const :tag "Unspecified" nil) + (const :tag "None" t) + (string :tag "Argument String") + (repeat :tag "Argument List" :value ("") string)) + :version "22.1" + :group 'vc) + +(defcustom vc-svn-diff-switches + t ;`svn' doesn't support common args like -c or -b. + "String or list of strings specifying extra switches for svn diff under VC. +If nil, use the value of `vc-diff-switches' (or `diff-switches'), +together with \"-x --diff-cmd=diff\" (since svn diff does not +support the default \"-c\" value of `diff-switches'). If you +want to force an empty list of arguments, use t." + :type '(choice (const :tag "Unspecified" nil) + (const :tag "None" t) + (string :tag "Argument String") + (repeat :tag "Argument List" + :value ("") + string)) + :version "22.1" + :group 'vc) + +(defcustom vc-svn-header (or (cdr (assoc 'SVN vc-header-alist)) '("\$Id\$")) + "Header keywords to be inserted by `vc-insert-headers'." + :version "22.1" + :type '(repeat string) + :group 'vc) + +;; We want to autoload it for use by the autoloaded version of +;; vc-svn-registered, but we want the value to be compiled at startup, not +;; at dump time. +;; ;;;###autoload +(defconst vc-svn-admin-directory + (cond ((and (memq system-type '(cygwin windows-nt ms-dos)) + (getenv "SVN_ASP_DOT_NET_HACK")) + "_svn") + (t ".svn")) + "The name of the \".svn\" subdirectory or its equivalent.") + +;;; Properties of the backend + +(defun vc-svn-revision-granularity () 'repository) +(defun vc-svn-checkout-model (files) 'implicit) + +;;; +;;; State-querying functions +;;; + +;;; vc-svn-admin-directory is generally not defined when the +;;; autoloaded function is called. + +;;;###autoload (defun vc-svn-registered (f) +;;;###autoload (let ((admin-dir (cond ((and (eq system-type 'windows-nt) +;;;###autoload (getenv "SVN_ASP_DOT_NET_HACK")) +;;;###autoload "_svn") +;;;###autoload (t ".svn")))) +;;;###autoload (when (file-readable-p (expand-file-name +;;;###autoload (concat admin-dir "/entries") +;;;###autoload (file-name-directory f))) +;;;###autoload (load "vc-svn") +;;;###autoload (vc-svn-registered f)))) + +(defun vc-svn-registered (file) + "Check if FILE is SVN registered." + (when (file-readable-p (expand-file-name (concat vc-svn-admin-directory + "/entries") + (file-name-directory file))) + (with-temp-buffer + (cd (file-name-directory file)) + (let* (process-file-side-effects + (status + (condition-case nil + ;; Ignore all errors. + (vc-svn-command t t file "status" "-v") + ;; Some problem happened. E.g. We can't find an `svn' + ;; executable. We used to only catch `file-error' but when + ;; the process is run on a remote host via Tramp, the error + ;; is only reported via the exit status which is turned into + ;; an `error' by vc-do-command. + (error nil)))) + (when (eq 0 status) + (let ((parsed (vc-svn-parse-status file))) + (and parsed (not (memq parsed '(ignored unregistered)))))))))) + +(defun vc-svn-state (file &optional localp) + "SVN-specific version of `vc-state'." + (let (process-file-side-effects) + (setq localp (or localp (vc-stay-local-p file 'SVN))) + (with-temp-buffer + (cd (file-name-directory file)) + (vc-svn-command t 0 file "status" (if localp "-v" "-u")) + (vc-svn-parse-status file)))) + +(defun vc-svn-state-heuristic (file) + "SVN-specific state heuristic." + (vc-svn-state file 'local)) + +;; FIXME it would be better not to have the "remote" argument, +;; but to distinguish the two output formats based on content. +(defun vc-svn-after-dir-status (callback &optional remote) + (let ((state-map '((?A . added) + (?C . conflict) + (?I . ignored) + (?M . edited) + (?D . removed) + (?R . removed) + (?? . unregistered) + ;; This is what vc-svn-parse-status does. + (?~ . edited))) + (re (if remote "^\\(.\\)...... \\([ *]\\) +\\(?:[-0-9]+\\)? \\(.*\\)$" + ;; Subexp 2 is a dummy in this case, so the numbers match. + "^\\(.\\)....\\(.\\) \\(.*\\)$")) + result) + (goto-char (point-min)) + (while (re-search-forward re nil t) + (let ((state (cdr (assq (aref (match-string 1) 0) state-map))) + (filename (match-string 3))) + (and remote (string-equal (match-string 2) "*") + ;; FIXME are there other possible combinations? + (cond ((eq state 'edited) (setq state 'needs-merge)) + ((not state) (setq state 'needs-update)))) + (when (and state (not (string= "." filename))) + (setq result (cons (list filename state) result))))) + (funcall callback result))) + +(defun vc-svn-dir-status (dir callback) + "Run 'svn status' for DIR and update BUFFER via CALLBACK. +CALLBACK is called as (CALLBACK RESULT BUFFER), where +RESULT is a list of conses (FILE . STATE) for directory DIR." + ;; FIXME should this rather be all the files in dir? + ;; FIXME: the vc-stay-local-p logic below is disabled, it ends up + ;; calling synchronously (vc-svn-registered DIR) => calling svn status -v DIR + ;; which is VERY SLOW for big trees and it makes emacs + ;; completely unresponsive during that time. + (let* ((local (and nil (vc-stay-local-p dir 'SVN))) + (remote (or t (not local) (eq local 'only-file)))) + (vc-svn-command (current-buffer) 'async nil "status" + (if remote "-u")) + (vc-exec-after + `(vc-svn-after-dir-status (quote ,callback) ,remote)))) + +(defun vc-svn-dir-status-files (dir files default-state callback) + (apply 'vc-svn-command (current-buffer) 'async nil "status" files) + (vc-exec-after + `(vc-svn-after-dir-status (quote ,callback)))) + +(defun vc-svn-dir-extra-headers (dir) + "Generate extra status headers for a Subversion working copy." + (let (process-file-side-effects) + (vc-svn-command "*vc*" 0 nil "info")) + (let ((repo + (save-excursion + (and (progn + (set-buffer "*vc*") + (goto-char (point-min)) + (re-search-forward "Repository Root: *\\(.*\\)" nil t)) + (match-string 1))))) + (concat + (cond (repo + (concat + (propertize "Repository : " 'face 'font-lock-type-face) + (propertize repo 'face 'font-lock-variable-name-face))) + (t ""))))) + +(defun vc-svn-working-revision (file) + "SVN-specific version of `vc-working-revision'." + ;; There is no need to consult RCS headers under SVN, because we + ;; get the workfile version for free when we recognize that a file + ;; is registered in SVN. + (vc-svn-registered file) + (vc-file-getprop file 'vc-working-revision)) + +;; vc-svn-mode-line-string doesn't exist because the default implementation +;; works just fine. + +(defun vc-svn-previous-revision (file rev) + (let ((newrev (1- (string-to-number rev)))) + (when (< 0 newrev) + (number-to-string newrev)))) + +(defun vc-svn-next-revision (file rev) + (let ((newrev (1+ (string-to-number rev)))) + ;; The "working revision" is an uneasy conceptual fit under Subversion; + ;; we use it as the upper bound until a better idea comes along. If the + ;; workfile version W coincides with the tree's latest revision R, then + ;; this check prevents a "no such revision: R+1" error. Otherwise, it + ;; inhibits showing of W+1 through R, which could be considered anywhere + ;; from gracious to impolite. + (unless (< (string-to-number (vc-file-getprop file 'vc-working-revision)) + newrev) + (number-to-string newrev)))) + + +;;; +;;; State-changing functions +;;; + +(defun vc-svn-create-repo () + "Create a new SVN repository." + (vc-do-command "*vc*" 0 "svnadmin" '("create" "SVN")) + (vc-do-command "*vc*" 0 vc-svn-program '(".") + "checkout" (concat "file://" default-directory "SVN"))) + +(defun vc-svn-register (files &optional rev comment) + "Register FILES into the SVN version-control system. +The COMMENT argument is ignored This does an add but not a commit. +Passes either `vc-svn-register-switches' or `vc-register-switches' +to the SVN command." + (apply 'vc-svn-command nil 0 files "add" (vc-switches 'SVN 'register))) + +(defun vc-svn-responsible-p (file) + "Return non-nil if SVN thinks it is responsible for FILE." + (file-directory-p (expand-file-name vc-svn-admin-directory + (if (file-directory-p file) + file + (file-name-directory file))))) + +(defalias 'vc-svn-could-register 'vc-svn-responsible-p + "Return non-nil if FILE could be registered in SVN. +This is only possible if SVN is responsible for FILE's directory.") + +(defun vc-svn-checkin (files rev comment &optional extra-args-ignored) + "SVN-specific version of `vc-backend-checkin'." + (if rev (error "Committing to a specific revision is unsupported in SVN")) + (let ((status (apply + 'vc-svn-command nil 1 files "ci" + (nconc (list "-m" comment) (vc-switches 'SVN 'checkin))))) + (set-buffer "*vc*") + (goto-char (point-min)) + (unless (equal status 0) + ;; Check checkin problem. + (cond + ((search-forward "Transaction is out of date" nil t) + (mapc (lambda (file) (vc-file-setprop file 'vc-state 'needs-merge)) + files) + (error (substitute-command-keys + (concat "Up-to-date check failed: " + "type \\[vc-next-action] to merge in changes")))) + (t + (pop-to-buffer (current-buffer)) + (goto-char (point-min)) + (shrink-window-if-larger-than-buffer) + (error "Check-in failed")))) + ;; Update file properties + ;; (vc-file-setprop + ;; file 'vc-working-revision + ;; (vc-parse-buffer "^\\(new\\|initial\\) revision: \\([0-9.]+\\)" 2)) + )) + +(defun vc-svn-find-revision (file rev buffer) + "SVN-specific retrieval of a specified version into a buffer." + (let (process-file-side-effects) + (apply 'vc-svn-command + buffer 0 file + "cat" + (and rev (not (string= rev "")) + (concat "-r" rev)) + (vc-switches 'SVN 'checkout)))) + +(defun vc-svn-checkout (file &optional editable rev) + (message "Checking out %s..." file) + (with-current-buffer (or (get-file-buffer file) (current-buffer)) + (vc-svn-update file editable rev (vc-switches 'SVN 'checkout))) + (vc-mode-line file 'SVN) + (message "Checking out %s...done" file)) + +(defun vc-svn-update (file editable rev switches) + (if (and (file-exists-p file) (not rev)) + ;; If no revision was specified, there's nothing to do. + nil + ;; Check out a particular version (or recreate the file). + (vc-file-setprop file 'vc-working-revision nil) + (apply 'vc-svn-command nil 0 file + "--non-interactive" ; bug#4280 + "update" + (cond + ((null rev) "-rBASE") + ((or (eq rev t) (equal rev "")) nil) + (t (concat "-r" rev))) + switches))) + +(defun vc-svn-delete-file (file) + (vc-svn-command nil 0 file "remove")) + +(defun vc-svn-rename-file (old new) + (vc-svn-command nil 0 new "move" (file-relative-name old))) + +(defun vc-svn-revert (file &optional contents-done) + "Revert FILE to the version it was based on." + (unless contents-done + (vc-svn-command nil 0 file "revert"))) + +(defun vc-svn-merge (file first-version &optional second-version) + "Merge changes into current working copy of FILE. +The changes are between FIRST-VERSION and SECOND-VERSION." + (vc-svn-command nil 0 file + "merge" + "-r" (if second-version + (concat first-version ":" second-version) + first-version)) + (vc-file-setprop file 'vc-state 'edited) + (with-current-buffer (get-buffer "*vc*") + (goto-char (point-min)) + (if (looking-at "C ") + 1 ; signal conflict + 0))) ; signal success + +(defun vc-svn-merge-news (file) + "Merge in any new changes made to FILE." + (message "Merging changes into %s..." file) + ;; (vc-file-setprop file 'vc-working-revision nil) + (vc-file-setprop file 'vc-checkout-time 0) + (vc-svn-command nil 0 file "update") + ;; Analyze the merge result reported by SVN, and set + ;; file properties accordingly. + (with-current-buffer (get-buffer "*vc*") + (goto-char (point-min)) + ;; get new working revision + (if (re-search-forward + "^\\(Updated to\\|At\\) revision \\([0-9]+\\)" nil t) + (vc-file-setprop file 'vc-working-revision (match-string 2)) + (vc-file-setprop file 'vc-working-revision nil)) + ;; get file status + (goto-char (point-min)) + (prog1 + (if (looking-at "At revision") + 0 ;; there were no news; indicate success + (if (re-search-forward + ;; Newer SVN clients have 3 columns of chars (one for the + ;; file's contents, then second for its properties, and the + ;; third for lock-grabbing info), before the 2 spaces. + ;; We also used to match the filename in column 0 without any + ;; meta-info before it, but I believe this can never happen. + (concat "^\\(\\([ACGDU]\\)\\(.[B ]\\)? \\)" + (regexp-quote (file-name-nondirectory file))) + nil t) + (cond + ;; Merge successful, we are in sync with repository now + ((string= (match-string 2) "U") + (vc-file-setprop file 'vc-state 'up-to-date) + (vc-file-setprop file 'vc-checkout-time + (nth 5 (file-attributes file))) + 0);; indicate success to the caller + ;; Merge successful, but our own changes are still in the file + ((string= (match-string 2) "G") + (vc-file-setprop file 'vc-state 'edited) + 0);; indicate success to the caller + ;; Conflicts detected! + (t + (vc-file-setprop file 'vc-state 'edited) + 1);; signal the error to the caller + ) + (pop-to-buffer "*vc*") + (error "Couldn't analyze svn update result"))) + (message "Merging changes into %s...done" file)))) + +(defun vc-svn-modify-change-comment (files rev comment) + "Modify the change comments for a specified REV. +You must have ssh access to the repository host, and the directory Emacs +uses locally for temp files must also be writable by you on that host. +This is only supported if the repository access method is either file:// +or svn+ssh://." + (let (tempfile host remotefile directory fileurl-p) + (with-temp-buffer + (vc-do-command (current-buffer) 0 vc-svn-program nil "info") + (goto-char (point-min)) + (unless (re-search-forward "Repository Root: \\(file://\\(/.*\\)\\)\\|\\(svn\\+ssh://\\([^/]+\\)\\(/.*\\)\\)" nil t) + (error "Repository information is unavailable")) + (if (match-string 1) + (progn + (setq fileurl-p t) + (setq directory (match-string 2))) + (setq host (match-string 4)) + (setq directory (match-string 5)) + (setq remotefile (concat host ":" tempfile)))) + (with-temp-file (setq tempfile (make-temp-file user-mail-address)) + (insert comment)) + (if fileurl-p + ;; Repository Root is a local file. + (progn + (unless (vc-do-command + "*vc*" 0 "svnadmin" nil + "setlog" "--bypass-hooks" directory + "-r" rev (format "%s" tempfile)) + (error "Log edit failed")) + (delete-file tempfile)) + + ;; Remote repository, using svn+ssh. + (unless (vc-do-command "*vc*" 0 "scp" nil "-q" tempfile remotefile) + (error "Copy of comment to %s failed" remotefile)) + (unless (vc-do-command + "*vc*" 0 "ssh" nil "-q" host + (format "svnadmin setlog --bypass-hooks %s -r %s %s; rm %s" + directory rev tempfile tempfile)) + (error "Log edit failed"))))) + +;;; +;;; History functions +;;; + +(defvar log-view-per-file-logs) + +(define-derived-mode vc-svn-log-view-mode log-view-mode "SVN-Log-View" + (require 'add-log) + (set (make-local-variable 'log-view-per-file-logs) nil)) + +(defun vc-svn-print-log (files buffer &optional shortlog start-revision limit) + "Get change log(s) associated with FILES." + (save-current-buffer + (vc-setup-buffer buffer) + (let ((inhibit-read-only t)) + (goto-char (point-min)) + (if files + (dolist (file files) + (insert "Working file: " file "\n") + (apply + 'vc-svn-command + buffer + 'async + ;; (if (and (= (length files) 1) (vc-stay-local-p file 'SVN)) 'async 0) + (list file) + "log" + (append + (list + (if start-revision + (format "-r%s" start-revision) + ;; By default Subversion only shows the log up to the + ;; working revision, whereas we also want the log of the + ;; subsequent commits. At least that's what the + ;; vc-cvs.el code does. + "-rHEAD:0")) + (when limit (list "--limit" (format "%s" limit)))))) + ;; Dump log for the entire directory. + (apply 'vc-svn-command buffer 0 nil "log" + (append + (list + (if start-revision (format "-r%s" start-revision) "-rHEAD:0")) + (when limit (list "--limit" (format "%s" limit))))))))) + +(defun vc-svn-diff (files &optional oldvers newvers buffer) + "Get a difference report using SVN between two revisions of fileset FILES." + (and oldvers + (not newvers) + files + (catch 'no + (dolist (f files) + (or (equal oldvers (vc-working-revision f)) + (throw 'no nil))) + t) + ;; Use nil rather than the current revision because svn handles + ;; it better (i.e. locally). Note that if _any_ of the files + ;; has a different revision, we fetch the lot, which is + ;; obviously sub-optimal. + (setq oldvers nil)) + (let* ((switches + (if vc-svn-diff-switches + (vc-switches 'SVN 'diff) + (list "--diff-cmd=diff" "-x" + (mapconcat 'identity (vc-switches nil 'diff) " ")))) + (async (and (not vc-disable-async-diff) + (vc-stay-local-p files 'SVN) + (or oldvers newvers)))) ; Svn diffs those locally. + (apply 'vc-svn-command buffer + (if async 'async 0) + files "diff" + (append + switches + (when oldvers + (list "-r" (if newvers (concat oldvers ":" newvers) + oldvers))))) + (if async 1 ; async diff => pessimistic assumption + ;; For some reason `svn diff' does not return a useful + ;; status w.r.t whether the diff was empty or not. + (buffer-size (get-buffer buffer))))) + +;;; +;;; Tag system +;;; + +(defun vc-svn-create-tag (dir name branchp) + "Assign to DIR's current revision a given NAME. +If BRANCHP is non-nil, the name is created as a branch (and the current +workspace is immediately moved to that new branch). +NAME is assumed to be a URL." + (vc-svn-command nil 0 dir "copy" name) + (when branchp (vc-svn-retrieve-tag dir name nil))) + +(defun vc-svn-retrieve-tag (dir name update) + "Retrieve a tag at and below DIR. +NAME is the name of the tag; if it is empty, do a `svn update'. +If UPDATE is non-nil, then update (resynch) any affected buffers. +NAME is assumed to be a URL." + (vc-svn-command nil 0 dir "switch" name) + ;; FIXME: parse the output and obey `update'. + ) + +;;; +;;; Miscellaneous +;;; + +;; Subversion makes backups for us, so don't bother. +;; (defun vc-svn-make-version-backups-p (file) +;; "Return non-nil if version backups should be made for FILE." +;; (vc-stay-local-p file 'SVN)) + +(defun vc-svn-check-headers () + "Check if the current file has any headers in it." + (save-excursion + (goto-char (point-min)) + (re-search-forward "\\$[A-Za-z\300-\326\330-\366\370-\377]+\ +\\(: [\t -#%-\176\240-\377]*\\)?\\$" nil t))) + + +;;; +;;; Internal functions +;;; + +(defun vc-svn-command (buffer okstatus file-or-list &rest flags) + "A wrapper around `vc-do-command' for use in vc-svn.el. +The difference to vc-do-command is that this function always invokes `svn', +and that it passes `vc-svn-global-switches' to it before FLAGS." + (apply 'vc-do-command (or buffer "*vc*") okstatus vc-svn-program file-or-list + (if (stringp vc-svn-global-switches) + (cons vc-svn-global-switches flags) + (append vc-svn-global-switches + flags)))) + +(defun vc-svn-repository-hostname (dirname) + (with-temp-buffer + (let ((coding-system-for-read + (or file-name-coding-system + default-file-name-coding-system))) + (vc-insert-file (expand-file-name (concat vc-svn-admin-directory + "/entries") + dirname))) + (goto-char (point-min)) + (when (re-search-forward + ;; Old `svn' used name="svn:this_dir", newer use just name="". + (concat "name=\"\\(?:svn:this_dir\\)?\"[\n\t ]*" + "\\(?:[-a-z]+=\"[^\"]*\"[\n\t ]*\\)*?" + "url=\"\\(?1:[^\"]+\\)\"" + ;; Yet newer ones don't use XML any more. + "\\|^\ndir\n[0-9]+\n\\(?1:.*\\)") nil t) + ;; This is not a hostname but a URL. This may actually be considered + ;; as a feature since it allows vc-svn-stay-local to specify different + ;; behavior for different modules on the same server. + (match-string 1)))) + +(defun vc-svn-resolve-when-done () + "Call \"svn resolved\" if the conflict markers have been removed." + (save-excursion + (goto-char (point-min)) + (unless (re-search-forward "^<<<<<<< " nil t) + (vc-svn-command nil 0 buffer-file-name "resolved") + ;; Remove the hook so that it is not called multiple times. + (remove-hook 'after-save-hook 'vc-svn-resolve-when-done t)))) + +;; Inspired by vc-arch-find-file-hook. +(defun vc-svn-find-file-hook () + (when (eq ?C (vc-file-getprop buffer-file-name 'vc-svn-status)) + ;; If the file is marked as "conflicted", then we should try and call + ;; "svn resolved" when applicable. + (if (save-excursion + (goto-char (point-min)) + (re-search-forward "^<<<<<<< " nil t)) + ;; There are conflict markers. + (progn + (smerge-start-session) + (add-hook 'after-save-hook 'vc-svn-resolve-when-done nil t)) + ;; There are no conflict markers. This is problematic: maybe it means + ;; the conflict has been resolved and we should immediately call "svn + ;; resolved", or it means that the file's type does not allow Svn to + ;; use conflict markers in which case we don't really know what to do. + ;; So let's just punt for now. + nil) + (message "There are unresolved conflicts in this file"))) + +(defun vc-svn-parse-status (&optional filename) + "Parse output of \"svn status\" command in the current buffer. +Set file properties accordingly. Unless FILENAME is non-nil, parse only +information about FILENAME and return its status." + (let (file status) + (goto-char (point-min)) + (while (re-search-forward + ;; Ignore the files with status X. + "^\\(?:\\?\\|[ ACDGIMR!~][ MC][ L][ +][ S]..\\([ *]\\) +\\([-0-9]+\\) +\\([0-9?]+\\) +\\([^ ]+\\)\\) +" nil t) + ;; If the username contains spaces, the output format is ambiguous, + ;; so don't trust the output's filename unless we have to. + (setq file (or filename + (expand-file-name + (buffer-substring (point) (line-end-position))))) + (setq status (char-after (line-beginning-position))) + (if (eq status ??) + (vc-file-setprop file 'vc-state 'unregistered) + ;; Use the last-modified revision, so that searching in vc-print-log + ;; output works. + (vc-file-setprop file 'vc-working-revision (match-string 3)) + ;; Remember Svn's own status. + (vc-file-setprop file 'vc-svn-status status) + (vc-file-setprop + file 'vc-state + (cond + ((eq status ?\ ) + (if (eq (char-after (match-beginning 1)) ?*) + 'needs-update + (vc-file-setprop file 'vc-checkout-time + (nth 5 (file-attributes file))) + 'up-to-date)) + ((eq status ?A) + ;; If the file was actually copied, (match-string 2) is "-". + (vc-file-setprop file 'vc-working-revision "0") + (vc-file-setprop file 'vc-checkout-time 0) + 'added) + ((eq status ?C) + (vc-file-setprop file 'vc-state 'conflict)) + ((eq status '?M) + (if (eq (char-after (match-beginning 1)) ?*) + 'needs-merge + 'edited)) + ((eq status ?I) + (vc-file-setprop file 'vc-state 'ignored)) + ((memq status '(?D ?R)) + (vc-file-setprop file 'vc-state 'removed)) + (t 'edited))))) + (when filename (vc-file-getprop filename 'vc-state)))) + +(defun vc-svn-valid-symbolic-tag-name-p (tag) + "Return non-nil if TAG is a valid symbolic tag name." + ;; According to the SVN manual, a valid symbolic tag must start with + ;; an uppercase or lowercase letter and can contain uppercase and + ;; lowercase letters, digits, `-', and `_'. + (and (string-match "^[a-zA-Z]" tag) + (not (string-match "[^a-z0-9A-Z-_]" tag)))) + +(defun vc-svn-valid-revision-number-p (tag) + "Return non-nil if TAG is a valid revision number." + (and (string-match "^[0-9]" tag) + (not (string-match "[^0-9]" tag)))) + +;; Support for `svn annotate' + +(defun vc-svn-annotate-command (file buf &optional rev) + (vc-svn-command buf 'async file "annotate" (if rev (concat "-r" rev)))) + +(defun vc-svn-annotate-time-of-rev (rev) + ;; Arbitrarily assume 10 commmits per day. + (/ (string-to-number rev) 10.0)) + +(defvar vc-annotate-parent-rev) + +(defun vc-svn-annotate-current-time () + (vc-svn-annotate-time-of-rev vc-annotate-parent-rev)) + +(defconst vc-svn-annotate-re "[ \t]*\\([0-9]+\\)[ \t]+[^\t ]+ ") + +(defun vc-svn-annotate-time () + (when (looking-at vc-svn-annotate-re) + (goto-char (match-end 0)) + (vc-svn-annotate-time-of-rev (match-string 1)))) + +(defun vc-svn-annotate-extract-revision-at-line () + (save-excursion + (beginning-of-line) + (if (looking-at vc-svn-annotate-re) (match-string 1)))) + +(defun vc-svn-revision-table (files) + (let ((vc-svn-revisions '())) + (with-current-buffer "*vc*" + (vc-svn-command nil 0 files "log" "-q") + (goto-char (point-min)) + (forward-line) + (let ((start (point-min)) + (loglines (buffer-substring-no-properties (point-min) + (point-max)))) + (while (string-match "^r\\([0-9]+\\) " loglines) + (push (match-string 1 loglines) vc-svn-revisions) + (setq start (+ start (match-end 0))) + (setq loglines (buffer-substring-no-properties start (point-max))))) + vc-svn-revisions))) + +(provide 'vc-svn) + +;; arch-tag: 02f10c68-2b4d-453a-90fc-1eee6cfb268d +;;; vc-svn.el ends here diff -r 681cd08dc0f7 -r e93288477c43 lisp/vc/vc.el --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/lisp/vc/vc.el Sun Jun 13 22:57:55 2010 +0000 @@ -0,0 +1,2702 @@ +;;; vc.el --- drive a version-control system from within Emacs + +;; Copyright (C) 1992, 1993, 1994, 1995, 1996, 1997, 1998, 2000, +;; 2001, 2002, 2003, 2004, 2005, 2006, 2007, 2008, 2009, 2010 +;; Free Software Foundation, Inc. + +;; Author: FSF (see below for full credits) +;; Maintainer: Andre Spiegel +;; Keywords: vc tools + +;; 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 3 of the License, 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. If not, see . + +;;; Credits: + +;; VC was initially designed and implemented by Eric S. Raymond +;; in 1992. Over the years, many other people have +;; contributed substantial amounts of work to VC. These include: +;; +;; Per Cederqvist +;; Paul Eggert +;; Sebastian Kremer +;; Martin Lorentzson +;; Dave Love +;; Stefan Monnier +;; Thien-Thi Nguyen +;; Dan Nicolaescu +;; J.D. Smith +;; Andre Spiegel +;; Richard Stallman +;; +;; In July 2007 ESR returned and redesigned the mode to cope better +;; with modern version-control systems that do commits by fileset +;; rather than per individual file. +;; +;; If you maintain a client of the mode or customize it in your .emacs, +;; note that some backend functions which formerly took single file arguments +;; now take a list of files. These include: register, checkin, print-log, +;; rollback, and diff. + +;;; Commentary: + +;; This mode is fully documented in the Emacs user's manual. +;; +;; Supported version-control systems presently include CVS, RCS, GNU +;; Arch, Subversion, Bzr, Git, Mercurial, Monotone and SCCS +;; (or its free replacement, CSSC). +;; +;; If your site uses the ChangeLog convention supported by Emacs, the +;; function `log-edit-comment-to-change-log' could prove a useful checkin hook, +;; although you might prefer to use C-c C-a (i.e. `log-edit-insert-changelog') +;; from the commit buffer instead or to set `log-edit-setup-invert'. +;; +;; When using SCCS, RCS, CVS: be careful not to do repo surgery, or +;; operations like registrations and deletions and renames, outside VC +;; while VC is running. The support for these systems was designed +;; when disks were much slower, and the code maintains a lot of +;; internal state in order to reduce expensive operations to a +;; minimum. Thus, if you mess with the repo while VC's back is turned, +;; VC may get seriously confused. +;; +;; When using Subversion or a later system, anything you do outside VC +;; *through the VCS tools* should safely interlock with VC +;; operations. Under these VC does little state caching, because local +;; operations are assumed to be fast. The dividing line is +;; +;; ADDING SUPPORT FOR OTHER BACKENDS +;; +;; VC can use arbitrary version control systems as a backend. To add +;; support for a new backend named SYS, write a library vc-sys.el that +;; contains functions of the form `vc-sys-...' (note that SYS is in lower +;; case for the function and library names). VC will use that library if +;; you put the symbol SYS somewhere into the list of +;; `vc-handled-backends'. Then, for example, if `vc-sys-registered' +;; returns non-nil for a file, all SYS-specific versions of VC commands +;; will be available for that file. +;; +;; VC keeps some per-file information in the form of properties (see +;; vc-file-set/getprop in vc-hooks.el). The backend-specific functions +;; do not generally need to be aware of these properties. For example, +;; `vc-sys-working-revision' should compute the working revision and +;; return it; it should not look it up in the property, and it needn't +;; store it there either. However, if a backend-specific function does +;; store a value in a property, that value takes precedence over any +;; value that the generic code might want to set (check for uses of +;; the macro `with-vc-properties' in vc.el). +;; +;; In the list of functions below, each identifier needs to be prepended +;; with `vc-sys-'. Some of the functions are mandatory (marked with a +;; `*'), others are optional (`-'). +;; +;; BACKEND PROPERTIES +;; +;; * revision-granularity +;; +;; Takes no arguments. Returns either 'file or 'repository. Backends +;; that return 'file have per-file revision numbering; backends +;; that return 'repository have per-repository revision numbering, +;; so a revision level implicitly identifies a changeset +;; +;; STATE-QUERYING FUNCTIONS +;; +;; * registered (file) +;; +;; Return non-nil if FILE is registered in this backend. Both this +;; function as well as `state' should be careful to fail gracefully +;; in the event that the backend executable is absent. It is +;; preferable that this function's body is autoloaded, that way only +;; calling vc-registered does not cause the backend to be loaded +;; (all the vc-FOO-registered functions are called to try to find +;; the controlling backend for FILE. +;; +;; * state (file) +;; +;; Return the current version control state of FILE. For a list of +;; possible values, see `vc-state'. This function should do a full and +;; reliable state computation; it is usually called immediately after +;; C-x v v. If you want to use a faster heuristic when visiting a +;; file, put that into `state-heuristic' below. Note that under most +;; VCSes this won't be called at all, dir-status is used instead. +;; +;; - state-heuristic (file) +;; +;; If provided, this function is used to estimate the version control +;; state of FILE at visiting time. It should be considerably faster +;; than the implementation of `state'. For a list of possible values, +;; see the doc string of `vc-state'. +;; +;; - dir-status (dir update-function) +;; +;; Produce RESULT: a list of lists of the form (FILE VC-STATE EXTRA) +;; for the files in DIR. +;; EXTRA can be used for backend specific information about FILE. +;; If a command needs to be run to compute this list, it should be +;; run asynchronously using (current-buffer) as the buffer for the +;; command. When RESULT is computed, it should be passed back by +;; doing: (funcall UPDATE-FUNCTION RESULT nil). +;; If the backend uses a process filter, hence it produces partial results, +;; they can be passed back by doing: +;; (funcall UPDATE-FUNCTION RESULT t) +;; and then do a (funcall UPDATE-FUNCTION RESULT nil) +;; when all the results have been computed. +;; To provide more backend specific functionality for `vc-dir' +;; the following functions might be needed: `dir-extra-headers', +;; `dir-printer', `extra-dir-menu' and `dir-status-files'. +;; +;; - dir-status-files (dir files default-state update-function) +;; +;; This function is identical to dir-status except that it should +;; only report status for the specified FILES. Also it needs to +;; report on all requested files, including up-to-date or ignored +;; files. If not provided, the default is to consider that the files +;; are in DEFAULT-STATE. +;; +;; - dir-extra-headers (dir) +;; +;; Return a string that will be added to the *vc-dir* buffer header. +;; +;; - dir-printer (fileinfo) +;; +;; Pretty print the `vc-dir-fileinfo' FILEINFO. +;; If a backend needs to show more information than the default FILE +;; and STATE in the vc-dir listing, it can store that extra +;; information in `vc-dir-fileinfo->extra'. This function can be +;; used to display that extra information in the *vc-dir* buffer. +;; +;; - status-fileinfo-extra (file) +;; +;; Compute `vc-dir-fileinfo->extra' for FILE. +;; +;; * working-revision (file) +;; +;; Return the working revision of FILE. This is the revision fetched +;; by the last checkout or upate, not necessarily the same thing as the +;; head or tip revision. Should return "0" for a file added but not yet +;; committed. +;; +;; - latest-on-branch-p (file) +;; +;; Return non-nil if the working revision of FILE is the latest revision +;; on its branch (many VCSes call this the 'tip' or 'head' revision). +;; The default implementation always returns t, which means that +;; working with non-current revisions is not supported by default. +;; +;; * checkout-model (files) +;; +;; Indicate whether FILES need to be "checked out" before they can be +;; edited. See `vc-checkout-model' for a list of possible values. +;; +;; - workfile-unchanged-p (file) +;; +;; Return non-nil if FILE is unchanged from the working revision. +;; This function should do a brief comparison of FILE's contents +;; with those of the repository copy of the working revision. If +;; the backend does not have such a brief-comparison feature, the +;; default implementation of this function can be used, which +;; delegates to a full vc-BACKEND-diff. (Note that vc-BACKEND-diff +;; must not run asynchronously in this case, see variable +;; `vc-disable-async-diff'.) +;; +;; - mode-line-string (file) +;; +;; If provided, this function should return the VC-specific mode +;; line string for FILE. The returned string should have a +;; `help-echo' property which is the text to be displayed as a +;; tooltip when the mouse hovers over the VC entry on the mode-line. +;; The default implementation deals well with all states that +;; `vc-state' can return. +;; +;; STATE-CHANGING FUNCTIONS +;; +;; * create-repo (backend) +;; +;; Create an empty repository in the current directory and initialize +;; it so VC mode can add files to it. For file-oriented systems, this +;; need do no more than create a subdirectory with the right name. +;; +;; * register (files &optional rev comment) +;; +;; Register FILES in this backend. Optionally, an initial revision REV +;; and an initial description of the file, COMMENT, may be specified, +;; but it is not guaranteed that the backend will do anything with this. +;; The implementation should pass the value of vc-register-switches +;; to the backend command. (Note: in older versions of VC, this +;; command took a single file argument and not a list.) +;; +;; - init-revision (file) +;; +;; The initial revision to use when registering FILE if one is not +;; specified by the user. If not provided, the variable +;; vc-default-init-revision is used instead. +;; +;; - responsible-p (file) +;; +;; Return non-nil if this backend considers itself "responsible" for +;; FILE, which can also be a directory. This function is used to find +;; out what backend to use for registration of new files and for things +;; like change log generation. The default implementation always +;; returns nil. +;; +;; - could-register (file) +;; +;; Return non-nil if FILE could be registered under this backend. The +;; default implementation always returns t. +;; +;; - receive-file (file rev) +;; +;; Let this backend "receive" a file that is already registered under +;; another backend. The default implementation simply calls `register' +;; for FILE, but it can be overridden to do something more specific, +;; e.g. keep revision numbers consistent or choose editing modes for +;; FILE that resemble those of the other backend. +;; +;; - unregister (file) +;; +;; Unregister FILE from this backend. This is only needed if this +;; backend may be used as a "more local" backend for temporary editing. +;; +;; * checkin (files rev comment) +;; +;; Commit changes in FILES to this backend. REV is a historical artifact +;; and should be ignored. COMMENT is used as a check-in comment. +;; The implementation should pass the value of vc-checkin-switches to +;; the backend command. +;; +;; * find-revision (file rev buffer) +;; +;; Fetch revision REV of file FILE and put it into BUFFER. +;; If REV is the empty string, fetch the head of the trunk. +;; The implementation should pass the value of vc-checkout-switches +;; to the backend command. +;; +;; * checkout (file &optional editable rev) +;; +;; Check out revision REV of FILE into the working area. If EDITABLE +;; is non-nil, FILE should be writable by the user and if locking is +;; used for FILE, a lock should also be set. If REV is non-nil, that +;; is the revision to check out (default is the working revision). +;; If REV is t, that means to check out the head of the current branch; +;; if it is the empty string, check out the head of the trunk. +;; The implementation should pass the value of vc-checkout-switches +;; to the backend command. +;; +;; * revert (file &optional contents-done) +;; +;; Revert FILE back to the working revision. If optional +;; arg CONTENTS-DONE is non-nil, then the contents of FILE have +;; already been reverted from a version backup, and this function +;; only needs to update the status of FILE within the backend. +;; If FILE is in the `added' state it should be returned to the +;; `unregistered' state. +;; +;; - rollback (files) +;; +;; Remove the tip revision of each of FILES from the repository. If +;; this function is not provided, trying to cancel a revision is +;; caught as an error. (Most backends don't provide it.) (Also +;; note that older versions of this backend command were called +;; 'cancel-version' and took a single file arg, not a list of +;; files.) +;; +;; - merge (file rev1 rev2) +;; +;; Merge the changes between REV1 and REV2 into the current working file. +;; +;; - merge-news (file) +;; +;; Merge recent changes from the current branch into FILE. +;; +;; - steal-lock (file &optional revision) +;; +;; Steal any lock on the working revision of FILE, or on REVISION if +;; that is provided. This function is only needed if locking is +;; used for files under this backend, and if files can indeed be +;; locked by other users. +;; +;; - modify-change-comment (files rev comment) +;; +;; Modify the change comments associated with the files at the +;; given revision. This is optional, many backends do not support it. +;; +;; - mark-resolved (files) +;; +;; Mark conflicts as resolved. Some VC systems need to run a +;; command to mark conflicts as resolved. +;; +;; HISTORY FUNCTIONS +;; +;; * print-log (files buffer &optional shortlog start-revision limit) +;; +;; Insert the revision log for FILES into BUFFER. +;; If SHORTLOG is true insert a short version of the log. +;; If LIMIT is true insert only insert LIMIT log entries. If the +;; backend does not support limiting the number of entries to show +;; it should return `limit-unsupported'. +;; If START-REVISION is given, then show the log starting from the +;; revision. At this point START-REVISION is only required to work +;; in conjunction with LIMIT = 1. +;; +;; * log-outgoing (backend remote-location) +;; +;; Insert in BUFFER the revision log for the changes that will be +;; sent when performing a push operation to REMOTE-LOCATION. +;; +;; * log-incoming (backend remote-location) +;; +;; Insert in BUFFER the revision log for the changes that will be +;; received when performing a pull operation from REMOTE-LOCATION. +;; +;; - log-view-mode () +;; +;; Mode to use for the output of print-log. This defaults to +;; `log-view-mode' and is expected to be changed (if at all) to a derived +;; mode of `log-view-mode'. +;; +;; - show-log-entry (revision) +;; +;; If provided, search the log entry for REVISION in the current buffer, +;; and make sure it is displayed in the buffer's window. The default +;; implementation of this function works for RCS-style logs. +;; +;; - comment-history (file) +;; +;; Return a string containing all log entries that were made for FILE. +;; This is used for transferring a file from one backend to another, +;; retaining comment information. +;; +;; - update-changelog (files) +;; +;; Using recent log entries, create ChangeLog entries for FILES, or for +;; all files at or below the default-directory if FILES is nil. The +;; default implementation runs rcs2log, which handles RCS- and +;; CVS-style logs. +;; +;; * diff (files &optional rev1 rev2 buffer) +;; +;; Insert the diff for FILE into BUFFER, or the *vc-diff* buffer if +;; BUFFER is nil. If REV1 and REV2 are non-nil, report differences +;; from REV1 to REV2. If REV1 is nil, use the working revision (as +;; found in the repository) as the older revision; if REV2 is nil, +;; use the current working-copy contents as the newer revision. This +;; function should pass the value of (vc-switches BACKEND 'diff) to +;; the backend command. It should return a status of either 0 (no +;; differences found), or 1 (either non-empty diff or the diff is +;; run asynchronously). +;; +;; - revision-completion-table (files) +;; +;; Return a completion table for existing revisions of FILES. +;; The default is to not use any completion table. +;; +;; - annotate-command (file buf &optional rev) +;; +;; If this function is provided, it should produce an annotated display +;; of FILE in BUF, relative to revision REV. Annotation means each line +;; of FILE displayed is prefixed with version information associated with +;; its addition (deleted lines leave no history) and that the text of the +;; file is fontified according to age. +;; +;; - annotate-time () +;; +;; Only required if `annotate-command' is defined for the backend. +;; Return the time of the next line of annotation at or after point, +;; as a floating point fractional number of days. The helper +;; function `vc-annotate-convert-time' may be useful for converting +;; multi-part times as returned by `current-time' and `encode-time' +;; to this format. Return nil if no more lines of annotation appear +;; in the buffer. You can safely assume that point is placed at the +;; beginning of each line, starting at `point-min'. The buffer that +;; point is placed in is the Annotate output, as defined by the +;; relevant backend. This function also affects how much of the line +;; is fontified; where it leaves point is where fontification begins. +;; +;; - annotate-current-time () +;; +;; Only required if `annotate-command' is defined for the backend, +;; AND you'd like the current time considered to be anything besides +;; (vc-annotate-convert-time (current-time)) -- i.e. the current +;; time with hours, minutes, and seconds included. Probably safe to +;; ignore. Return the current-time, in units of fractional days. +;; +;; - annotate-extract-revision-at-line () +;; +;; Only required if `annotate-command' is defined for the backend. +;; Invoked from a buffer in vc-annotate-mode, return the revision +;; corresponding to the current line, or nil if there is no revision +;; corresponding to the current line. +;; If the backend supports annotating through copies and renames, +;; and displays a file name and a revision, then return a cons +;; (REVISION . FILENAME). +;; +;; TAG SYSTEM +;; +;; - create-tag (dir name branchp) +;; +;; Attach the tag NAME to the state of the working copy. This +;; should make sure that files are up-to-date before proceeding with +;; the action. DIR can also be a file and if BRANCHP is specified, +;; NAME should be created as a branch and DIR should be checked out +;; under this new branch. The default implementation does not +;; support branches but does a sanity check, a tree traversal and +;; assigns the tag to each file. +;; +;; - retrieve-tag (dir name update) +;; +;; Retrieve the version tagged by NAME of all registered files at or below DIR. +;; If UPDATE is non-nil, then update buffers of any files in the +;; tag that are currently visited. The default implementation +;; does a sanity check whether there aren't any uncommitted changes at +;; or below DIR, and then performs a tree walk, using the `checkout' +;; function to retrieve the corresponding revisions. +;; +;; MISCELLANEOUS +;; +;; - make-version-backups-p (file) +;; +;; Return non-nil if unmodified repository revisions of FILE should be +;; backed up locally. If this is done, VC can perform `diff' and +;; `revert' operations itself, without calling the backend system. The +;; default implementation always returns nil. +;; +;; - root (file) +;; Return the root of the VC controlled hierarchy for file. +;; +;; - repository-hostname (dirname) +;; +;; Return the hostname that the backend will have to contact +;; in order to operate on a file in DIRNAME. If the return value +;; is nil, it means that the repository is local. +;; This function is used in `vc-stay-local-p' which backends can use +;; for their convenience. +;; +;; - previous-revision (file rev) +;; +;; Return the revision number that precedes REV for FILE, or nil if no such +;; revision exists. +;; +;; - next-revision (file rev) +;; +;; Return the revision number that follows REV for FILE, or nil if no such +;; revision exists. +;; +;; - log-edit-mode () +;; +;; Turn on the mode used for editing the check in log. This +;; defaults to `log-edit-mode'. If changed, it should use a mode +;; derived from`log-edit-mode'. +;; +;; - check-headers () +;; +;; Return non-nil if the current buffer contains any version headers. +;; +;; - clear-headers () +;; +;; In the current buffer, reset all version headers to their unexpanded +;; form. This function should be provided if the state-querying code +;; for this backend uses the version headers to determine the state of +;; a file. This function will then be called whenever VC changes the +;; version control state in such a way that the headers would give +;; wrong information. +;; +;; - delete-file (file) +;; +;; Delete FILE and mark it as deleted in the repository. If this +;; function is not provided, the command `vc-delete-file' will +;; signal an error. +;; +;; - rename-file (old new) +;; +;; Rename file OLD to NEW, both in the working area and in the +;; repository. If this function is not provided, the renaming +;; will be done by (vc-delete-file old) and (vc-register new). +;; +;; - find-file-hook () +;; +;; Operation called in current buffer when opening a file. This can +;; be used by the backend to setup some local variables it might need. +;; +;; - extra-menu () +;; +;; Return a menu keymap, the items in the keymap will appear at the +;; end of the Version Control menu. The goal is to allow backends +;; to specify extra menu items that appear in the VC menu. This way +;; you can provide menu entries for functionality that is specific +;; to your backend and which does not map to any of the VC generic +;; concepts. +;; +;; - extra-dir-menu () +;; +;; Return a menu keymap, the items in the keymap will appear at the +;; end of the VC Status menu. The goal is to allow backends to +;; specify extra menu items that appear in the VC Status menu. This +;; makes it possible to provide menu entries for functionality that +;; is specific to a backend and which does not map to any of the VC +;; generic concepts. +;; +;; - conflicted-files (dir) +;; +;; Return the list of files where conflict resolution is needed in +;; the project that contains DIR. +;; FIXME: what should it do with non-text conflicts? + +;;; Todo: + +;; - Get rid of the "master file" terminology. + +;; - Add key-binding for vc-delete-file. + +;;;; New Primitives: +;; +;; - deal with push/pull operations. +;; +;; - add a mechanism for editing the underlying VCS's list of files +;; to be ignored, when that's possible. +;; +;;;; Primitives that need changing: +;; +;; - vc-update/vc-merge should deal with VC systems that don't +;; update/merge on a file basis, but on a whole repository basis. +;; vc-update and vc-merge assume the arguments are always files, +;; they don't deal with directories. Make sure the *vc-dir* buffer +;; is updated after these operations. +;; At least bzr, git and hg should benefit from this. +;; +;;;; Improved branch and tag handling: +;; +;; - add a generic mechanism for remembering the current branch names, +;; display the branch name in the mode-line. Replace +;; vc-cvs-sticky-tag with that. +;; +;;;; Internal cleanups: +;; +;; - backends that care about vc-stay-local should try to take it into +;; account for vc-dir. Is this likely to be useful??? YES! +;; +;; - vc-expand-dirs should take a backend parameter and only look for +;; files managed by that backend. +;; +;; - Another important thing: merge all the status-like backend operations. +;; We should remove dir-status, state, and dir-status-files, and +;; replace them with just `status' which takes a fileset and a continuation +;; (like dir-status) and returns a buffer in which the process(es) are run +;; (or nil if it worked synchronously). Hopefully we can define the old +;; 4 operations in term of this one. +;; +;;;; Other +;; +;; - when a file is in `conflict' state, turn on smerge-mode. +;; +;; - figure out what to do with conflicts that are not caused by the +;; file contents, but by metadata or other causes. Example: File A +;; gets renamed to B in one branch and to C in another and you merge +;; the two branches. Or you locally add file FOO and then pull a +;; change that also adds a new file FOO, ... +;; +;; - make it easier to write logs. Maybe C-x 4 a should add to the log +;; buffer, if one is present, instead of adding to the ChangeLog. +;; +;; - When vc-next-action calls vc-checkin it could pre-fill the +;; *VC-log* buffer with some obvious items: the list of files that +;; were added, the list of files that were removed. If the diff is +;; available, maybe it could even call something like +;; `diff-add-change-log-entries-other-window' to create a detailed +;; skeleton for the log... +;; +;; - most vc-dir backends need more work. They might need to +;; provide custom headers, use the `extra' field and deal with all +;; possible VC states. +;; +;; - add a function that calls vc-dir to `find-directory-functions'. +;; +;; - vc-diff, vc-annotate, etc. need to deal better with unregistered +;; files. Now that unregistered and ignored files are shown in +;; vc-dir, it is possible that these commands are called +;; for unregistered/ignored files. +;; +;; - vc-next-action needs work in order to work with multiple +;; backends: `vc-state' returns the state for the default backend, +;; not for the backend in the current *vc-dir* buffer. +;; +;; - vc-dir-kill-dir-status-process should not be specific to dir-status, +;; it should work for other async commands done through vc-do-command +;; as well, +;; +;; - vc-dir toolbar needs more icons. +;; +;; - The backends should avoid using `vc-file-setprop' and `vc-file-getprop'. +;; +;;; Code: + +(require 'vc-hooks) +(require 'vc-dispatcher) + +(eval-when-compile + (require 'cl) + (require 'dired)) + +(unless (assoc 'vc-parent-buffer minor-mode-alist) + (setq minor-mode-alist + (cons '(vc-parent-buffer vc-parent-buffer-name) + minor-mode-alist))) + +;; General customization + +(defgroup vc nil + "Version-control system in Emacs." + :group 'tools) + +(defcustom vc-initial-comment nil + "If non-nil, prompt for initial comment when a file is registered." + :type 'boolean + :group 'vc) + +(defcustom vc-default-init-revision "1.1" + "A string used as the default revision number when a new file is registered. +This can be overridden by giving a prefix argument to \\[vc-register]. This +can also be overridden by a particular VC backend." + :type 'string + :group 'vc + :version "20.3") + +(defcustom vc-checkin-switches nil + "A string or list of strings specifying extra switches for checkin. +These are passed to the checkin program by \\[vc-checkin]." + :type '(choice (const :tag "None" nil) + (string :tag "Argument String") + (repeat :tag "Argument List" + :value ("") + string)) + :group 'vc) + +(defcustom vc-checkout-switches nil + "A string or list of strings specifying extra switches for checkout. +These are passed to the checkout program by \\[vc-checkout]." + :type '(choice (const :tag "None" nil) + (string :tag "Argument String") + (repeat :tag "Argument List" + :value ("") + string)) + :group 'vc) + +(defcustom vc-register-switches nil + "A string or list of strings; extra switches for registering a file. +These are passed to the checkin program by \\[vc-register]." + :type '(choice (const :tag "None" nil) + (string :tag "Argument String") + (repeat :tag "Argument List" + :value ("") + string)) + :group 'vc) + +(defcustom vc-diff-switches nil + "A string or list of strings specifying switches for diff under VC. +When running diff under a given BACKEND, VC uses the first +non-nil value of `vc-BACKEND-diff-switches', `vc-diff-switches', +and `diff-switches', in that order. Since nil means to check the +next variable in the sequence, either of the first two may use +the value t to mean no switches at all. `vc-diff-switches' +should contain switches that are specific to version control, but +not specific to any particular backend." + :type '(choice (const :tag "Unspecified" nil) + (const :tag "None" t) + (string :tag "Argument String") + (repeat :tag "Argument List" :value ("") string)) + :group 'vc + :version "21.1") + +(defcustom vc-diff-knows-L nil + "Indicates whether diff understands the -L option. +The value is either `yes', `no', or nil. If it is nil, VC tries +to use -L and sets this variable to remember whether it worked." + :type '(choice (const :tag "Work out" nil) (const yes) (const no)) + :group 'vc) + +(defcustom vc-log-show-limit 2000 + "Limit the number of items shown by the VC log commands. +Zero means unlimited. +Not all VC backends are able to support this feature." + :type 'integer + :group 'vc) + +(defcustom vc-allow-async-revert nil + "Specifies whether the diff during \\[vc-revert] may be asynchronous. +Enabling this option means that you can confirm a revert operation even +if the local changes in the file have not been found and displayed yet." + :type '(choice (const :tag "No" nil) + (const :tag "Yes" t)) + :group 'vc + :version "22.1") + +;;;###autoload +(defcustom vc-checkout-hook nil + "Normal hook (list of functions) run after checking out a file. +See `run-hooks'." + :type 'hook + :group 'vc + :version "21.1") + +;;;###autoload +(defcustom vc-checkin-hook nil + "Normal hook (list of functions) run after commit or file checkin. +See also `log-edit-done-hook'." + :type 'hook + :options '(log-edit-comment-to-change-log) + :group 'vc) + +;;;###autoload +(defcustom vc-before-checkin-hook nil + "Normal hook (list of functions) run before a commit or a file checkin. +See `run-hooks'." + :type 'hook + :group 'vc) + +;; Header-insertion hair + +(defcustom vc-static-header-alist + '(("\\.c\\'" . + "\n#ifndef lint\nstatic char vcid[] = \"\%s\";\n#endif /* lint */\n")) + "Associate static header string templates with file types. +A \%s in the template is replaced with the first string associated with +the file's version control type in `vc-header-alist'." + :type '(repeat (cons :format "%v" + (regexp :tag "File Type") + (string :tag "Header String"))) + :group 'vc) + +(defcustom vc-comment-alist + '((nroff-mode ".\\\"" "")) + "Special comment delimiters for generating VC headers. +Add an entry in this list if you need to override the normal `comment-start' +and `comment-end' variables. This will only be necessary if the mode language +is sensitive to blank lines." + :type '(repeat (list :format "%v" + (symbol :tag "Mode") + (string :tag "Comment Start") + (string :tag "Comment End"))) + :group 'vc) + +(defcustom vc-checkout-carefully (= (user-uid) 0) + "Non-nil means be extra-careful in checkout. +Verify that the file really is not locked +and that its contents match what the repository version says." + :type 'boolean + :group 'vc) +(make-obsolete-variable 'vc-checkout-carefully + "the corresponding checks are always done now." + "21.1") + + +;; Variables users don't need to see + +(defvar vc-disable-async-diff nil + "VC sets this to t locally to disable some async diff operations. +Backends that offer asynchronous diffs should respect this variable +in their implementation of vc-BACKEND-diff.") + +;; File property caching + +(defun vc-clear-context () + "Clear all cached file properties." + (interactive) + (fillarray vc-file-prop-obarray 0)) + +(defmacro with-vc-properties (files form settings) + "Execute FORM, then maybe set per-file properties for FILES. +If any of FILES is actually a directory, then do the same for all +buffers for files in that directory. +SETTINGS is an association list of property/value pairs. After +executing FORM, set those properties from SETTINGS that have not yet +been updated to their corresponding values." + (declare (debug t)) + `(let ((vc-touched-properties (list t)) + (flist nil)) + (dolist (file ,files) + (if (file-directory-p file) + (dolist (buffer (buffer-list)) + (let ((fname (buffer-file-name buffer))) + (when (and fname (vc-string-prefix-p file fname)) + (push fname flist)))) + (push file flist))) + ,form + (dolist (file flist) + (dolist (setting ,settings) + (let ((property (car setting))) + (unless (memq property vc-touched-properties) + (put (intern file vc-file-prop-obarray) + property (cdr setting)))))))) + +;;; Code for deducing what fileset and backend to assume + +(defun vc-backend-for-registration (file) + "Return a backend that can be used for registering FILE. + +If no backend declares itself responsible for FILE, then FILE +must not be in a version controlled directory, so try to create a +repository, prompting for the directory and the VC backend to +use." + (catch 'found + ;; First try: find a responsible backend, it must be a backend + ;; under which FILE is not yet registered. + (dolist (backend vc-handled-backends) + (and (not (vc-call-backend backend 'registered file)) + (vc-call-backend backend 'responsible-p file) + (throw 'found backend))) + ;; no responsible backend + (let* ((possible-backends + (let (pos) + (dolist (crt vc-handled-backends) + (when (vc-find-backend-function crt 'create-repo) + (push crt pos))) + pos)) + (bk + (intern + ;; Read the VC backend from the user, only + ;; complete with the backends that have the + ;; 'create-repo method. + (completing-read + (format "%s is not in a version controlled directory.\nUse VC backend: " file) + (mapcar 'symbol-name possible-backends) nil t))) + (repo-dir + (let ((def-dir (file-name-directory file))) + ;; read the directory where to create the + ;; repository, make sure it's a parent of + ;; file. + (read-file-name + (format "create %s repository in: " bk) + default-directory def-dir t nil + (lambda (arg) + (message "arg %s" arg) + (and (file-directory-p arg) + (vc-string-prefix-p (expand-file-name arg) def-dir))))))) + (let ((default-directory repo-dir)) + (vc-call-backend bk 'create-repo)) + (throw 'found bk)))) + +(defun vc-responsible-backend (file) + "Return the name of a backend system that is responsible for FILE. + +If FILE is already registered, return the +backend of FILE. If FILE is not registered, then the +first backend in `vc-handled-backends' that declares itself +responsible for FILE is returned." + (or (and (not (file-directory-p file)) (vc-backend file)) + (catch 'found + ;; First try: find a responsible backend. If this is for registration, + ;; it must be a backend under which FILE is not yet registered. + (dolist (backend vc-handled-backends) + (and (vc-call-backend backend 'responsible-p file) + (throw 'found backend)))) + (error "No VC backend is responsible for %s" file))) + +(defun vc-expand-dirs (file-or-dir-list) + "Expands directories in a file list specification. +Within directories, only files already under version control are noticed." + (let ((flattened '())) + (dolist (node file-or-dir-list) + (when (file-directory-p node) + (vc-file-tree-walk + node (lambda (f) (when (vc-backend f) (push f flattened))))) + (unless (file-directory-p node) (push node flattened))) + (nreverse flattened))) + +(defvar vc-dir-backend) + +(declare-function vc-dir-current-file "vc-dir" ()) +(declare-function vc-dir-deduce-fileset "vc-dir" (&optional state-model-only-files)) + +(defun vc-deduce-fileset (&optional observer allow-unregistered + state-model-only-files) + "Deduce a set of files and a backend to which to apply an operation. + +Return (BACKEND FILESET FILESET-ONLY-FILES STATE CHECKOUT-MODEL). +If we're in VC-dir mode, the fileset is the list of marked files. +Otherwise, if we're looking at a buffer visiting a version-controlled file, +the fileset is a singleton containing this file. +If none of these conditions is met, but ALLOW_UNREGISTERED is on and the +visited file is not registered, return a singleton fileset containing it. +Otherwise, throw an error. + +STATE-MODEL-ONLY-FILES if non-nil, means that the caller needs +the FILESET-ONLY-FILES STATE and MODEL info. Otherwise, that +part may be skipped. +BEWARE: this function may change the +current buffer." + ;; FIXME: OBSERVER is unused. The name is not intuitive and is not + ;; documented. It's set to t when called from diff and print-log. + (let (backend) + (cond + ((derived-mode-p 'vc-dir-mode) + (vc-dir-deduce-fileset state-model-only-files)) + ((derived-mode-p 'dired-mode) + (if observer + (vc-dired-deduce-fileset) + (error "State changing VC operations not supported in `dired-mode'"))) + ((setq backend (vc-backend buffer-file-name)) + (if state-model-only-files + (list backend (list buffer-file-name) + (list buffer-file-name) + (vc-state buffer-file-name) + (vc-checkout-model backend buffer-file-name)) + (list backend (list buffer-file-name)))) + ((and (buffer-live-p vc-parent-buffer) + ;; FIXME: Why this test? --Stef + (or (buffer-file-name vc-parent-buffer) + (with-current-buffer vc-parent-buffer + (derived-mode-p 'vc-dir-mode)))) + (progn ;FIXME: Why not `with-current-buffer'? --Stef. + (set-buffer vc-parent-buffer) + (vc-deduce-fileset observer allow-unregistered state-model-only-files))) + ((not buffer-file-name) + (error "Buffer %s is not associated with a file" (buffer-name))) + ((and allow-unregistered (not (vc-registered buffer-file-name))) + (if state-model-only-files + (list (vc-backend-for-registration (buffer-file-name)) + (list buffer-file-name) + (list buffer-file-name) + (when state-model-only-files 'unregistered) + nil) + (list (vc-backend-for-registration (buffer-file-name)) + (list buffer-file-name)))) + (t (error "No fileset is available here"))))) + +(defun vc-dired-deduce-fileset () + (let ((backend (vc-responsible-backend default-directory))) + (unless backend (error "Directory not under VC")) + (list backend + (dired-map-over-marks (dired-get-filename nil t) nil)))) + +(defun vc-ensure-vc-buffer () + "Make sure that the current buffer visits a version-controlled file." + (cond + ((derived-mode-p 'vc-dir-mode) + (set-buffer (find-file-noselect (vc-dir-current-file)))) + (t + (while (and vc-parent-buffer + (buffer-live-p vc-parent-buffer) + ;; Avoid infinite looping when vc-parent-buffer and + ;; current buffer are the same buffer. + (not (eq vc-parent-buffer (current-buffer)))) + (set-buffer vc-parent-buffer)) + (if (not buffer-file-name) + (error "Buffer %s is not associated with a file" (buffer-name)) + (unless (vc-backend buffer-file-name) + (error "File %s is not under version control" buffer-file-name)))))) + +;;; Support for the C-x v v command. +;; This is where all the single-file-oriented code from before the fileset +;; rewrite lives. + +(defsubst vc-editable-p (file) + "Return non-nil if FILE can be edited." + (let ((backend (vc-backend file))) + (and backend + (or (eq (vc-checkout-model backend (list file)) 'implicit) + (memq (vc-state file) '(edited needs-merge conflict)))))) + +(defun vc-compatible-state (p q) + "Controls which states can be in the same commit." + (or + (eq p q) + (and (member p '(edited added removed)) (member q '(edited added removed))))) + +;; Here's the major entry point. + +;;;###autoload +(defun vc-next-action (verbose) + "Do the next logical version control operation on the current fileset. +This requires that all files in the fileset be in the same state. + +For locking systems: + If every file is not already registered, this registers each for version +control. + If every file is registered and not locked by anyone, this checks out +a writable and locked file of each ready for editing. + If every file is checked out and locked by the calling user, this +first checks to see if each file has changed since checkout. If not, +it performs a revert on that file. + If every file has been changed, this pops up a buffer for entry +of a log message; when the message has been entered, it checks in the +resulting changes along with the log message as change commentary. If +the variable `vc-keep-workfiles' is non-nil (which is its default), a +read-only copy of each changed file is left in place afterwards. + If the affected file is registered and locked by someone else, you are +given the option to steal the lock(s). + +For merging systems: + If every file is not already registered, this registers each one for version +control. This does an add, but not a commit. + If every file is added but not committed, each one is committed. + If every working file is changed, but the corresponding repository file is +unchanged, this pops up a buffer for entry of a log message; when the +message has been entered, it checks in the resulting changes along +with the logmessage as change commentary. A writable file is retained. + If the repository file is changed, you are asked if you want to +merge in the changes into your working copy." + (interactive "P") + (let* ((vc-fileset (vc-deduce-fileset nil t 'state-model-only-files)) + (backend (car vc-fileset)) + (files (nth 1 vc-fileset)) + (fileset-only-files (nth 2 vc-fileset)) + ;; FIXME: We used to call `vc-recompute-state' here. + (state (nth 3 vc-fileset)) + ;; The backend should check that the checkout-model is consistent + ;; among all the `files'. + (model (nth 4 vc-fileset))) + + ;; Do the right thing + (cond + ((eq state 'missing) + (error "Fileset files are missing, so cannot be operated on")) + ((eq state 'ignored) + (error "Fileset files are ignored by the version-control system")) + ((or (null state) (eq state 'unregistered)) + (vc-register nil vc-fileset)) + ;; Files are up-to-date, or need a merge and user specified a revision + ((or (eq state 'up-to-date) (and verbose (eq state 'needs-update))) + (cond + (verbose + ;; go to a different revision + (let* ((revision + (read-string "Branch, revision, or backend to move to: ")) + (revision-downcase (downcase revision))) + (if (member + revision-downcase + (mapcar (lambda (arg) (downcase (symbol-name arg))) + vc-handled-backends)) + (let ((vsym (intern-soft revision-downcase))) + (dolist (file files) (vc-transfer-file file vsym))) + (dolist (file files) + (vc-checkout file (eq model 'implicit) revision))))) + ((not (eq model 'implicit)) + ;; check the files out + (dolist (file files) (vc-checkout file t))) + (t + ;; do nothing + (message "Fileset is up-to-date")))) + ;; Files have local changes + ((vc-compatible-state state 'edited) + (let ((ready-for-commit files)) + ;; If files are edited but read-only, give user a chance to correct + (dolist (file files) + (unless (file-writable-p file) + ;; Make the file+buffer read-write. + (unless (y-or-n-p (format "%s is edited but read-only; make it writable and continue?" file)) + (error "Aborted")) + (set-file-modes file (logior (file-modes file) 128)) + (let ((visited (get-file-buffer file))) + (when visited + (with-current-buffer visited + (toggle-read-only -1)))))) + ;; Allow user to revert files with no changes + (save-excursion + (dolist (file files) + (let ((visited (get-file-buffer file))) + ;; For files with locking, if the file does not contain + ;; any changes, just let go of the lock, i.e. revert. + (when (and (not (eq model 'implicit)) + (vc-workfile-unchanged-p file) + ;; If buffer is modified, that means the user just + ;; said no to saving it; in that case, don't revert, + ;; because the user might intend to save after + ;; finishing the log entry and committing. + (not (and visited (buffer-modified-p)))) + (vc-revert-file file) + (setq ready-for-commit (delete file ready-for-commit)))))) + ;; Remaining files need to be committed + (if (not ready-for-commit) + (message "No files remain to be committed") + (if (not verbose) + (vc-checkin ready-for-commit backend) + (let* ((revision (read-string "New revision or backend: ")) + (revision-downcase (downcase revision))) + (if (member + revision-downcase + (mapcar (lambda (arg) (downcase (symbol-name arg))) + vc-handled-backends)) + (let ((vsym (intern revision-downcase))) + (dolist (file files) (vc-transfer-file file vsym))) + (vc-checkin ready-for-commit backend revision))))))) + ;; locked by somebody else (locking VCSes only) + ((stringp state) + ;; In the old days, we computed the revision once and used it on + ;; the single file. Then, for the 2007-2008 fileset rewrite, we + ;; computed the revision once (incorrectly, using a free var) and + ;; used it on all files. To fix the free var bug, we can either + ;; use `(car files)' or do what we do here: distribute the + ;; revision computation among `files'. Although this may be + ;; tedious for those backends where a "revision" is a trans-file + ;; concept, it is nonetheless correct for both those and (more + ;; importantly) for those where "revision" is a per-file concept. + ;; If the intersection of the former group and "locking VCSes" is + ;; non-empty [I vaguely doubt it --ttn], we can reinstate the + ;; pre-computation approach of yore. + (dolist (file files) + (vc-steal-lock + file (if verbose + (read-string (format "%s revision to steal: " file)) + (vc-working-revision file)) + state))) + ;; conflict + ((eq state 'conflict) + ;; FIXME: Is it really the UI we want to provide? + ;; In my experience, the conflicted files should be marked as resolved + ;; one-by-one when saving the file after resolving the conflicts. + ;; I.e. stating explicitly that the conflicts are resolved is done + ;; very rarely. + (vc-mark-resolved backend files)) + ;; needs-update + ((eq state 'needs-update) + (dolist (file files) + (if (yes-or-no-p (format + "%s is not up-to-date. Get latest revision? " + (file-name-nondirectory file))) + (vc-checkout file (eq model 'implicit) t) + (when (and (not (eq model 'implicit)) + (yes-or-no-p "Lock this revision? ")) + (vc-checkout file t))))) + ;; needs-merge + ((eq state 'needs-merge) + (dolist (file files) + (when (yes-or-no-p (format + "%s is not up-to-date. Merge in changes now? " + (file-name-nondirectory file))) + (vc-maybe-resolve-conflicts + file (vc-call-backend backend 'merge-news file))))) + + ;; unlocked-changes + ((eq state 'unlocked-changes) + (dolist (file files) + (when (not (equal buffer-file-name file)) + (find-file-other-window file)) + (if (save-window-excursion + (vc-diff-internal nil + (cons (car vc-fileset) (cons (cadr vc-fileset) (list file))) + (vc-working-revision file) nil) + (goto-char (point-min)) + (let ((inhibit-read-only t)) + (insert + (format "Changes to %s since last lock:\n\n" file))) + (not (beep)) + (yes-or-no-p (concat "File has unlocked changes. " + "Claim lock retaining changes? "))) + (progn (vc-call-backend backend 'steal-lock file) + (clear-visited-file-modtime) + ;; Must clear any headers here because they wouldn't + ;; show that the file is locked now. + (vc-clear-headers file) + (write-file buffer-file-name) + (vc-mode-line file backend)) + (if (not (yes-or-no-p + "Revert to checked-in revision, instead? ")) + (error "Checkout aborted") + (vc-revert-buffer-internal t t) + (vc-checkout file t))))) + ;; Unknown fileset state + (t + (error "Fileset is in an unknown state %s" state))))) + +(defun vc-create-repo (backend) + "Create an empty repository in the current directory." + (interactive + (list + (intern + (upcase + (completing-read + "Create repository for: " + (mapcar (lambda (b) (list (downcase (symbol-name b)))) vc-handled-backends) + nil t))))) + (vc-call-backend backend 'create-repo)) + +(declare-function vc-dir-move-to-goal-column "vc-dir" ()) + +;;;###autoload +(defun vc-register (&optional set-revision vc-fileset comment) + "Register into a version control system. +If VC-FILESET is given, register the files in that fileset. +Otherwise register the current file. +With prefix argument SET-REVISION, allow user to specify initial revision +level. If COMMENT is present, use that as an initial comment. + +The version control system to use is found by cycling through the list +`vc-handled-backends'. The first backend in that list which declares +itself responsible for the file (usually because other files in that +directory are already registered under that backend) will be used to +register the file. If no backend declares itself responsible, the +first backend that could register the file is used." + (interactive "P") + (let* ((fileset-arg (or vc-fileset (vc-deduce-fileset nil t))) + (backend (car fileset-arg)) + (files (nth 1 fileset-arg))) + ;; We used to operate on `only-files', but VC wants to provide the + ;; possibility to register directories rather than files only, since + ;; many VCS allow that as well. + (dolist (fname files) + (let ((bname (get-file-buffer fname))) + (unless fname (setq fname buffer-file-name)) + (when (vc-backend fname) + (if (vc-registered fname) + (error "This file is already registered") + (unless (y-or-n-p "Previous master file has vanished. Make a new one? ") + (error "Aborted")))) + ;; Watch out for new buffers of size 0: the corresponding file + ;; does not exist yet, even though buffer-modified-p is nil. + (when bname + (with-current-buffer bname + (when (and (not (buffer-modified-p)) + (zerop (buffer-size)) + (not (file-exists-p buffer-file-name))) + (set-buffer-modified-p t)) + (vc-buffer-sync))))) + (message "Registering %s... " files) + (mapc 'vc-file-clearprops files) + (vc-call-backend backend 'register files + (if set-revision + (read-string (format "Initial revision level for %s: " files)) + (vc-call-backend backend 'init-revision)) + comment) + (mapc + (lambda (file) + (vc-file-setprop file 'vc-backend backend) + ;; FIXME: This is wrong: it should set `backup-inhibited' in all + ;; the buffers visiting files affected by this `vc-register', not + ;; in the current-buffer. + ;; (unless vc-make-backup-files + ;; (make-local-variable 'backup-inhibited) + ;; (setq backup-inhibited t)) + + (vc-resynch-buffer file vc-keep-workfiles t)) + files) + (when (derived-mode-p 'vc-dir-mode) + (vc-dir-move-to-goal-column)) + (message "Registering %s... done" files))) + +(defun vc-register-with (backend) + "Register the current file with a specified back end." + (interactive "SBackend: ") + (when (not (member backend vc-handled-backends)) + (error "Unknown back end")) + (let ((vc-handled-backends (list backend))) + (call-interactively 'vc-register))) + +(defun vc-checkout (file &optional writable rev) + "Retrieve a copy of the revision REV of FILE. +If WRITABLE is non-nil, make sure the retrieved file is writable. +REV defaults to the latest revision. + +After check-out, runs the normal hook `vc-checkout-hook'." + (and writable + (not rev) + (vc-call make-version-backups-p file) + (vc-up-to-date-p file) + (vc-make-version-backup file)) + (let ((backend (vc-backend file))) + (with-vc-properties (list file) + (condition-case err + (vc-call-backend backend 'checkout file writable rev) + (file-error + ;; Maybe the backend is not installed ;-( + (when writable + (let ((buf (get-file-buffer file))) + (when buf (with-current-buffer buf (toggle-read-only -1))))) + (signal (car err) (cdr err)))) + `((vc-state . ,(if (or (eq (vc-checkout-model backend (list file)) 'implicit) + (not writable)) + (if (vc-call-backend backend 'latest-on-branch-p file) + 'up-to-date + 'needs-update) + 'edited)) + (vc-checkout-time . ,(nth 5 (file-attributes file)))))) + (vc-resynch-buffer file t t) + (run-hooks 'vc-checkout-hook)) + +(defun vc-mark-resolved (backend files) + (prog1 (with-vc-properties + files + (vc-call-backend backend 'mark-resolved files) + ;; FIXME: Is this TRTD? Might not be. + `((vc-state . edited))) + (message + (substitute-command-keys + "Conflicts have been resolved in %s. \ +Type \\[vc-next-action] to check in changes.") + (if (> (length files) 1) + (format "%d files" (length files)) + "this file")))) + +(defun vc-steal-lock (file rev owner) + "Steal the lock on FILE." + (let (file-description) + (if rev + (setq file-description (format "%s:%s" file rev)) + (setq file-description file)) + (when (not (yes-or-no-p (format "Steal the lock on %s from %s? " + file-description owner))) + (error "Steal canceled")) + (message "Stealing lock on %s..." file) + (with-vc-properties + (list file) + (vc-call steal-lock file rev) + `((vc-state . edited))) + (vc-resynch-buffer file t t) + (message "Stealing lock on %s...done" file) + ;; Write mail after actually stealing, because if the stealing + ;; goes wrong, we don't want to send any mail. + (compose-mail owner (format "Stolen lock on %s" file-description)) + (setq default-directory (expand-file-name "~/")) + (goto-char (point-max)) + (insert + (format "I stole the lock on %s, " file-description) + (current-time-string) + ".\n") + (message "Please explain why you stole the lock. Type C-c C-c when done."))) + +(defun vc-checkin (files backend &optional rev comment initial-contents) + "Check in FILES. +The optional argument REV may be a string specifying the new revision +level (strongly deprecated). COMMENT is a comment +string; if omitted, a buffer is popped up to accept a comment. If +INITIAL-CONTENTS is non-nil, then COMMENT is used as the initial contents +of the log entry buffer. + +If `vc-keep-workfiles' is nil, FILE is deleted afterwards, provided +that the version control system supports this mode of operation. + +Runs the normal hooks `vc-before-checkin-hook' and `vc-checkin-hook'." + (when vc-before-checkin-hook + (run-hooks 'vc-before-checkin-hook)) + (lexical-let + ((backend backend)) + (vc-start-logentry + files comment initial-contents + "Enter a change comment." + "*VC-log*" + (lambda () + (vc-call-backend backend 'log-edit-mode)) + (lexical-let ((rev rev)) + (lambda (files comment) + (message "Checking in %s..." (vc-delistify files)) + ;; "This log message intentionally left almost blank". + ;; RCS 5.7 gripes about white-space-only comments too. + (or (and comment (string-match "[^\t\n ]" comment)) + (setq comment "*** empty log message ***")) + (with-vc-properties + files + ;; We used to change buffers to get local value of + ;; vc-checkin-switches, but 'the' local buffer is + ;; not a well-defined concept for filesets. + (progn + (vc-call-backend backend 'checkin files rev comment) + (mapc 'vc-delete-automatic-version-backups files)) + `((vc-state . up-to-date) + (vc-checkout-time . ,(nth 5 (file-attributes file))) + (vc-working-revision . nil))) + (message "Checking in %s...done" (vc-delistify files)))) + 'vc-checkin-hook))) + +;;; Additional entry points for examining version histories + +;; (defun vc-default-diff-tree (backend dir rev1 rev2) +;; "List differences for all registered files at and below DIR. +;; The meaning of REV1 and REV2 is the same as for `vc-revision-diff'." +;; ;; This implementation does an explicit tree walk, and calls +;; ;; vc-BACKEND-diff directly for each file. An optimization +;; ;; would be to use `vc-diff-internal', so that diffs can be local, +;; ;; and to call it only for files that are actually changed. +;; ;; However, this is expensive for some backends, and so it is left +;; ;; to backend-specific implementations. +;; (setq default-directory dir) +;; (vc-file-tree-walk +;; default-directory +;; (lambda (f) +;; (vc-exec-after +;; `(let ((coding-system-for-read (vc-coding-system-for-diff ',f))) +;; (message "Looking at %s" ',f) +;; (vc-call-backend ',(vc-backend f) +;; 'diff (list ',f) ',rev1 ',rev2)))))) + +(defun vc-coding-system-for-diff (file) + "Return the coding system for reading diff output for FILE." + (or coding-system-for-read + ;; if we already have this file open, + ;; use the buffer's coding system + (let ((buf (find-buffer-visiting file))) + (when buf (with-current-buffer buf + buffer-file-coding-system))) + ;; otherwise, try to find one based on the file name + (car (find-operation-coding-system 'insert-file-contents file)) + ;; and a final fallback + 'undecided)) + +(defun vc-switches (backend op) + "Return a list of vc-BACKEND switches for operation OP. +BACKEND is a symbol such as `CVS', which will be downcased. +OP is a symbol such as `diff'. + +In decreasing order of preference, return the value of: +vc-BACKEND-OP-switches (e.g. `vc-cvs-diff-switches'); +vc-OP-switches (e.g. `vc-diff-switches'); or, in the case of +diff only, `diff-switches'. + +If the chosen value is not a string or a list, return nil. +This is so that you may set, e.g. `vc-svn-diff-switches' to t in order +to override the value of `vc-diff-switches' and `diff-switches'." + (let ((switches + (or (when backend + (let ((sym (vc-make-backend-sym + backend (intern (concat (symbol-name op) + "-switches"))))) + (when (boundp sym) (symbol-value sym)))) + (let ((sym (intern (format "vc-%s-switches" (symbol-name op))))) + (when (boundp sym) (symbol-value sym))) + (cond + ((eq op 'diff) diff-switches))))) + (if (stringp switches) (list switches) + ;; If not a list, return nil. + ;; This is so we can set vc-diff-switches to t to override + ;; any switches in diff-switches. + (when (listp switches) switches)))) + +;; Old def for compatibility with Emacs-21.[123]. +(defmacro vc-diff-switches-list (backend) `(vc-switches ',backend 'diff)) +(make-obsolete 'vc-diff-switches-list 'vc-switches "22.1") + +(defun vc-diff-finish (buffer messages) + ;; The empty sync output case has already been handled, so the only + ;; possibility of an empty output is for an async process. + (when (buffer-live-p buffer) + (let ((window (get-buffer-window buffer t)) + (emptyp (zerop (buffer-size buffer)))) + (with-current-buffer buffer + (and messages emptyp + (let ((inhibit-read-only t)) + (insert (cdr messages) ".\n") + (message "%s" (cdr messages)))) + (goto-char (point-min)) + (when window + (shrink-window-if-larger-than-buffer window))) + (when (and messages (not emptyp)) + (message "%sdone" (car messages)))))) + +(defvar vc-diff-added-files nil + "If non-nil, diff added files by comparing them to /dev/null.") + +(defun vc-diff-internal (async vc-fileset rev1 rev2 &optional verbose) + "Report diffs between two revisions of a fileset. +Diff output goes to the *vc-diff* buffer. The function +returns t if the buffer had changes, nil otherwise." + (let* ((files (cadr vc-fileset)) + (messages (cons (format "Finding changes in %s..." + (vc-delistify files)) + (format "No changes between %s and %s" + (or rev1 "working revision") + (or rev2 "workfile")))) + ;; Set coding system based on the first file. It's a kluge, + ;; but the only way to set it for each file included would + ;; be to call the back end separately for each file. + (coding-system-for-read + (if files (vc-coding-system-for-diff (car files)) 'undecided))) + (vc-setup-buffer "*vc-diff*") + (message "%s" (car messages)) + ;; Many backends don't handle well the case of a file that has been + ;; added but not yet committed to the repo (notably CVS and Subversion). + ;; Do that work here so the backends don't have to futz with it. --ESR + ;; + ;; Actually most backends (including CVS) have options to control the + ;; behavior since which one is better depends on the user and on the + ;; situation). Worse yet: this code does not handle the case where + ;; `file' is a directory which contains added files. + ;; I made it conditional on vc-diff-added-files but it should probably + ;; just be removed (or copied/moved to specific backends). --Stef. + (when vc-diff-added-files + (let ((filtered '()) + process-file-side-effects) + (dolist (file files) + (if (or (file-directory-p file) + (not (string= (vc-working-revision file) "0"))) + (push file filtered) + ;; This file is added but not yet committed; + ;; there is no repository version to diff against. + (if (or rev1 rev2) + (error "No revisions of %s exist" file) + ;; We regard this as "changed". + ;; Diff it against /dev/null. + (apply 'vc-do-command "*vc-diff*" + 1 "diff" file + (append (vc-switches nil 'diff) '("/dev/null")))))) + (setq files (nreverse filtered)))) + (let ((vc-disable-async-diff (not async))) + (vc-call-backend (car vc-fileset) 'diff files rev1 rev2 "*vc-diff*")) + (set-buffer "*vc-diff*") + (if (and (zerop (buffer-size)) + (not (get-buffer-process (current-buffer)))) + ;; Treat this case specially so as not to pop the buffer. + (progn + (message "%s" (cdr messages)) + nil) + (diff-mode) + ;; Make the *vc-diff* buffer read only, the diff-mode key + ;; bindings are nicer for read only buffers. pcl-cvs does the + ;; same thing. + (setq buffer-read-only t) + (vc-exec-after `(vc-diff-finish ,(current-buffer) ',(when verbose + messages))) + ;; Display the buffer, but at the end because it can change point. + (pop-to-buffer (current-buffer)) + ;; In the async case, we return t even if there are no differences + ;; because we don't know that yet. + t))) + +(defun vc-read-revision (prompt &optional files backend default initial-input) + (cond + ((null files) + (let ((vc-fileset (vc-deduce-fileset t))) ;FIXME: why t? --Stef + (setq files (cadr vc-fileset)) + (setq backend (car vc-fileset)))) + ((null backend) (setq backend (vc-backend (car files))))) + (let ((completion-table + (vc-call-backend backend 'revision-completion-table files))) + (if completion-table + (completing-read prompt completion-table + nil nil initial-input nil default) + (read-string prompt initial-input nil default)))) + +;;;###autoload +(defun vc-version-diff (files rev1 rev2) + "Report diffs between revisions of the fileset in the repository history." + (interactive + (let* ((vc-fileset (vc-deduce-fileset t)) ;FIXME: why t? --Stef + (files (cadr vc-fileset)) + (backend (car vc-fileset)) + (first (car files)) + (rev1-default nil) + (rev2-default nil)) + (cond + ;; someday we may be able to do revision completion on non-singleton + ;; filesets, but not yet. + ((/= (length files) 1) + nil) + ;; if it's a directory, don't supply any revision default + ((file-directory-p first) + nil) + ;; if the file is not up-to-date, use working revision as older revision + ((not (vc-up-to-date-p first)) + (setq rev1-default (vc-working-revision first))) + ;; if the file is not locked, use last and previous revisions as defaults + (t + (setq rev1-default (vc-call-backend backend 'previous-revision first + (vc-working-revision first))) + (when (string= rev1-default "") (setq rev1-default nil)) + (setq rev2-default (vc-working-revision first)))) + ;; construct argument list + (let* ((rev1-prompt (if rev1-default + (concat "Older revision (default " + rev1-default "): ") + "Older revision: ")) + (rev2-prompt (concat "Newer revision (default " + (or rev2-default "current source") "): ")) + (rev1 (vc-read-revision rev1-prompt files backend rev1-default)) + (rev2 (vc-read-revision rev2-prompt files backend rev2-default))) + (when (string= rev1 "") (setq rev1 nil)) + (when (string= rev2 "") (setq rev2 nil)) + (list files rev1 rev2)))) + ;; All that was just so we could do argument completion! + (when (and (not rev1) rev2) + (error "Not a valid revision range")) + ;; Yes, it's painful to call (vc-deduce-fileset) again. Alas, the + ;; placement rules for (interactive) don't actually leave us a choice. + (vc-diff-internal t (vc-deduce-fileset t) rev1 rev2 + (called-interactively-p 'interactive))) + +;;;###autoload +(defun vc-diff (historic &optional not-urgent) + "Display diffs between file revisions. +Normally this compares the currently selected fileset with their +working revisions. With a prefix argument HISTORIC, it reads two revision +designators specifying which revisions to compare. + +The optional argument NOT-URGENT non-nil means it is ok to say no to +saving the buffer." + (interactive (list current-prefix-arg t)) + (if historic + (call-interactively 'vc-version-diff) + (when buffer-file-name (vc-buffer-sync not-urgent)) + (vc-diff-internal t (vc-deduce-fileset t) nil nil + (called-interactively-p 'interactive)))) + +;;;###autoload +(defun vc-root-diff (historic &optional not-urgent) + "Display diffs between VC-controlled whole tree revisions. +Normally, this compares the tree corresponding to the current +fileset with the working revision. +With a prefix argument HISTORIC, prompt for two revision +designators specifying which revisions to compare. + +The optional argument NOT-URGENT non-nil means it is ok to say no to +saving the buffer." + (interactive (list current-prefix-arg t)) + (if historic + ;; FIXME: this does not work right, `vc-version-diff' ends up + ;; calling `vc-deduce-fileset' to find the files to diff, and + ;; that's not what we want here, we want the diff for the VC root dir. + (call-interactively 'vc-version-diff) + (when buffer-file-name (vc-buffer-sync not-urgent)) + (let ((backend + (cond ((derived-mode-p 'vc-dir-mode) vc-dir-backend) + ((derived-mode-p 'dired-mode) (vc-responsible-backend default-directory)) + (vc-mode (vc-backend buffer-file-name)))) + rootdir working-revision) + (unless backend + (error "Buffer is not version controlled")) + (setq rootdir (vc-call-backend backend 'root default-directory)) + (setq working-revision (vc-working-revision rootdir)) + ;; VC diff for the root directory produces output that is + ;; relative to it. Bind default-directory to the root directory + ;; here, this way the *vc-diff* buffer is setup correctly, so + ;; relative file names work. + (let ((default-directory rootdir)) + (vc-diff-internal + t (list backend (list rootdir) working-revision) nil nil + (called-interactively-p 'interactive)))))) + +;;;###autoload +(defun vc-revision-other-window (rev) + "Visit revision REV of the current file in another window. +If the current file is named `F', the revision is named `F.~REV~'. +If `F.~REV~' already exists, use it instead of checking it out again." + (interactive + (save-current-buffer + (vc-ensure-vc-buffer) + (list + (vc-read-revision "Revision to visit (default is working revision): " + (list buffer-file-name))))) + (vc-ensure-vc-buffer) + (let* ((file buffer-file-name) + (revision (if (string-equal rev "") + (vc-working-revision file) + rev))) + (switch-to-buffer-other-window (vc-find-revision file revision)))) + +(defun vc-find-revision (file revision) + "Read REVISION of FILE into a buffer and return the buffer." + (let ((automatic-backup (vc-version-backup-file-name file revision)) + (filebuf (or (get-file-buffer file) (current-buffer))) + (filename (vc-version-backup-file-name file revision 'manual))) + (unless (file-exists-p filename) + (if (file-exists-p automatic-backup) + (rename-file automatic-backup filename nil) + (message "Checking out %s..." filename) + (with-current-buffer filebuf + (let ((failed t)) + (unwind-protect + (let ((coding-system-for-read 'no-conversion) + (coding-system-for-write 'no-conversion)) + (with-temp-file filename + (let ((outbuf (current-buffer))) + ;; Change buffer to get local value of + ;; vc-checkout-switches. + (with-current-buffer filebuf + (vc-call find-revision file revision outbuf)))) + (setq failed nil)) + (when (and failed (file-exists-p filename)) + (delete-file filename)))) + (vc-mode-line file)) + (message "Checking out %s...done" filename))) + (let ((result-buf (find-file-noselect filename))) + (with-current-buffer result-buf + ;; Set the parent buffer so that things like + ;; C-x v g, C-x v l, ... etc work. + (set (make-local-variable 'vc-parent-buffer) filebuf)) + result-buf))) + +;; Header-insertion code + +;;;###autoload +(defun vc-insert-headers () + "Insert headers into a file for use with a version control system. +Headers desired are inserted at point, and are pulled from +the variable `vc-BACKEND-header'." + (interactive) + (vc-ensure-vc-buffer) + (save-excursion + (save-restriction + (widen) + (when (or (not (vc-check-headers)) + (y-or-n-p "Version headers already exist. Insert another set? ")) + (let* ((delims (cdr (assq major-mode vc-comment-alist))) + (comment-start-vc (or (car delims) comment-start "#")) + (comment-end-vc (or (car (cdr delims)) comment-end "")) + (hdsym (vc-make-backend-sym (vc-backend buffer-file-name) + 'header)) + (hdstrings (and (boundp hdsym) (symbol-value hdsym)))) + (dolist (s hdstrings) + (insert comment-start-vc "\t" s "\t" + comment-end-vc "\n")) + (when vc-static-header-alist + (dolist (f vc-static-header-alist) + (when (string-match (car f) buffer-file-name) + (insert (format (cdr f) (car hdstrings))))))))))) + +(defun vc-clear-headers (&optional file) + "Clear all version headers in the current buffer (or FILE). +The headers are reset to their non-expanded form." + (let* ((filename (or file buffer-file-name)) + (visited (find-buffer-visiting filename)) + (backend (vc-backend filename))) + (when (vc-find-backend-function backend 'clear-headers) + (if visited + (let ((context (vc-buffer-context))) + ;; save-excursion may be able to relocate point and mark + ;; properly. If it fails, vc-restore-buffer-context + ;; will give it a second try. + (save-excursion + (vc-call-backend backend 'clear-headers)) + (vc-restore-buffer-context context)) + (set-buffer (find-file-noselect filename)) + (vc-call-backend backend 'clear-headers) + (kill-buffer filename))))) + +(defun vc-modify-change-comment (files rev oldcomment) + "Edit the comment associated with the given files and revision." + ;; Less of a kluge than it looks like; log-view mode only passes + ;; this function a singleton list. Arguments left in this form in + ;; case the more general operation ever becomes meaningful. + (let ((backend (vc-responsible-backend (car files)))) + (vc-start-logentry + files oldcomment t + "Enter a replacement change comment." + "*VC-log*" + (lambda () (vc-call-backend backend 'log-edit-mode)) + (lexical-let ((rev rev)) + (lambda (files comment) + (vc-call-backend backend + 'modify-change-comment files rev comment)))))) + +;;;###autoload +(defun vc-merge () + "Merge changes between two revisions into the current buffer's file. +This asks for two revisions to merge from in the minibuffer. If the +first revision is a branch number, then merge all changes from that +branch. If the first revision is empty, merge news, i.e. recent changes +from the current branch. + +See Info node `Merging'." + (interactive) + (vc-ensure-vc-buffer) + (vc-buffer-sync) + (let* ((file buffer-file-name) + (backend (vc-backend file)) + (state (vc-state file)) + first-revision second-revision status) + (cond + ((stringp state) ;; Locking VCses only + (error "File is locked by %s" state)) + ((not (vc-editable-p file)) + (if (y-or-n-p + "File must be checked out for merging. Check out now? ") + (vc-checkout file t) + (error "Merge aborted")))) + (setq first-revision + (vc-read-revision + (concat "Branch or revision to merge from " + "(default news on current branch): ") + (list file) + backend)) + (if (string= first-revision "") + (setq status (vc-call-backend backend 'merge-news file)) + (if (not (vc-find-backend-function backend 'merge)) + (error "Sorry, merging is not implemented for %s" backend) + (if (not (vc-branch-p first-revision)) + (setq second-revision + (vc-read-revision + "Second revision: " + (list file) backend nil + ;; FIXME: This is CVS/RCS/SCCS specific. + (concat (vc-branch-part first-revision) "."))) + ;; We want to merge an entire branch. Set revisions + ;; accordingly, so that vc-BACKEND-merge understands us. + (setq second-revision first-revision) + ;; first-revision must be the starting point of the branch + (setq first-revision (vc-branch-part first-revision))) + (setq status (vc-call-backend backend 'merge file + first-revision second-revision)))) + (vc-maybe-resolve-conflicts file status "WORKFILE" "MERGE SOURCE"))) + +(defun vc-maybe-resolve-conflicts (file status &optional name-A name-B) + (vc-resynch-buffer file t (not (buffer-modified-p))) + (if (zerop status) (message "Merge successful") + (smerge-mode 1) + (message "File contains conflicts."))) + +;;;###autoload +(defalias 'vc-resolve-conflicts 'smerge-ediff) + +;; TODO: This is OK but maybe we could integrate it better. +;; E.g. it could be run semi-automatically (via a prompt?) when saving a file +;; that was conflicted (i.e. upon mark-resolved). +;; FIXME: should we add an "other-window" version? Or maybe we should +;; hook it inside find-file so it automatically works for +;; find-file-other-window as well. E.g. find-file could use a new +;; `default-next-file' variable for its default file (M-n), and +;; we could then set it upon mark-resolve, so C-x C-s C-x C-f M-n would +;; automatically offer the next conflicted file. +(defun vc-find-conflicted-file () + "Visit the next conflicted file in the current project." + (interactive) + (let* ((backend (or (if buffer-file-name (vc-backend buffer-file-name)) + (vc-responsible-backend default-directory) + (error "No VC backend"))) + (files (vc-call-backend backend + 'conflicted-files default-directory))) + ;; Don't try and visit the current file. + (if (equal (car files) buffer-file-name) (pop files)) + (if (null files) + (message "No more conflicted files") + (find-file (pop files)) + (message "%s more conflicted files after this one" + (if files (length files) "No"))))) + +;; Named-configuration entry points + +(defun vc-tag-precondition (dir) + "Scan the tree below DIR, looking for files not up-to-date. +If any file is not up-to-date, return the name of the first such file. +\(This means, neither tag creation nor retrieval is allowed.\) +If one or more of the files are currently visited, return `visited'. +Otherwise, return nil." + (let ((status nil)) + (catch 'vc-locked-example + (vc-file-tree-walk + dir + (lambda (f) + (if (not (vc-up-to-date-p f)) (throw 'vc-locked-example f) + (when (get-file-buffer f) (setq status 'visited))))) + status))) + +;;;###autoload +(defun vc-create-tag (dir name branchp) + "Descending recursively from DIR, make a tag called NAME. +For each registered file, the working revision becomes part of +the named configuration. If the prefix argument BRANCHP is +given, the tag is made as a new branch and the files are +checked out in that new branch." + (interactive + (let ((granularity + (vc-call-backend (vc-responsible-backend default-directory) + 'revision-granularity))) + (list + (if (eq granularity 'repository) + ;; For VC's that do not work at file level, it's pointless + ;; to ask for a directory, branches are created at repository level. + default-directory + (read-file-name "Directory: " default-directory default-directory t)) + (read-string (if current-prefix-arg "New branch name: " "New tag name: ")) + current-prefix-arg))) + (message "Making %s... " (if branchp "branch" "tag")) + (when (file-directory-p dir) (setq dir (file-name-as-directory dir))) + (vc-call-backend (vc-responsible-backend dir) + 'create-tag dir name branchp) + (vc-resynch-buffer dir t t t) + (message "Making %s... done" (if branchp "branch" "tag"))) + +;;;###autoload +(defun vc-retrieve-tag (dir name) + "Descending recursively from DIR, retrieve the tag called NAME. +If NAME is empty, it refers to the latest revisions. +If locking is used for the files in DIR, then there must not be any +locked files at or below DIR (but if NAME is empty, locked files are +allowed and simply skipped)." + (interactive + (let ((granularity + (vc-call-backend (vc-responsible-backend default-directory) + 'revision-granularity))) + (list + (if (eq granularity 'repository) + ;; For VC's that do not work at file level, it's pointless + ;; to ask for a directory, branches are created at repository level. + default-directory + (read-file-name "Directory: " default-directory default-directory t)) + (read-string "Tag name to retrieve (default latest revisions): ")))) + (let ((update (yes-or-no-p "Update any affected buffers? ")) + (msg (if (or (not name) (string= name "")) + (format "Updating %s... " (abbreviate-file-name dir)) + (format "Retrieving tag into %s... " + (abbreviate-file-name dir))))) + (message "%s" msg) + (vc-call-backend (vc-responsible-backend dir) + 'retrieve-tag dir name update) + (vc-resynch-buffer dir t t t) + (message "%s" (concat msg "done")))) + + +;; Miscellaneous other entry points + +;; FIXME: this should be a defcustom +;; FIXME: maybe add another choice: +;; `root-directory' (or somesuch), which would mean show a short log +;; for the root directory. +(defvar vc-log-short-style '(directory) + "Whether or not to show a short log. +If it contains `directory' then if the fileset contains a directory show a short log. +If it contains `file' then show short logs for files. +Not all VC backends support short logs!") + +(defvar log-view-vc-backend) +(defvar log-view-vc-fileset) + +(defun vc-print-log-setup-buttons (working-revision is-start-revision limit pl-return) + (when (and limit (not (eq 'limit-unsupported pl-return)) + (not is-start-revision)) + (goto-char (point-max)) + (lexical-let ((working-revision working-revision) + (limit limit)) + (widget-create 'push-button + :notify (lambda (&rest ignore) + (vc-print-log-internal + log-view-vc-backend log-view-vc-fileset + working-revision nil (* 2 limit))) + :help-echo "Show the log again, and double the number of log entries shown" + "Show 2X entries") + (widget-insert " ") + (widget-create 'push-button + :notify (lambda (&rest ignore) + (vc-print-log-internal + log-view-vc-backend log-view-vc-fileset + working-revision nil nil)) + :help-echo "Show the log again, showing all entries" + "Show unlimited entries")) + (widget-setup))) + +(defun vc-print-log-internal (backend files working-revision + &optional is-start-revision limit) + ;; Don't switch to the output buffer before running the command, + ;; so that any buffer-local settings in the vc-controlled + ;; buffer can be accessed by the command. + (let ((dir-present nil) + (vc-short-log nil) + (buffer-name "*vc-change-log*") + type + pl-return) + (dolist (file files) + (when (file-directory-p file) + (setq dir-present t))) + (setq vc-short-log + (not (null (if dir-present + (memq 'directory vc-log-short-style) + (memq 'file vc-log-short-style))))) + (setq type (if vc-short-log 'short 'long)) + (lexical-let + ((working-revision working-revision) + (limit limit) + (shortlog vc-short-log) + (is-start-revision is-start-revision)) + (vc-log-internal-common + backend buffer-name files type + (lambda (bk buf type-arg files-arg) + (vc-call-backend bk 'print-log files-arg buf + shortlog (when is-start-revision working-revision) limit)) + (lambda (bk files-arg ret) + (vc-print-log-setup-buttons working-revision + is-start-revision limit ret)) + (lambda (bk) + (vc-call-backend bk 'show-log-entry working-revision)))))) + +(defvar vc-log-view-type nil + "Set this to differentiate the different types of logs.") +(put 'vc-log-view-type 'permanent-local t) + +(defun vc-log-internal-common (backend + buffer-name + files + type + backend-func + setup-buttons-func + goto-location-func) + (let (retval) + (with-current-buffer (get-buffer-create buffer-name) + (set (make-local-variable 'vc-log-view-type) type)) + (setq retval (funcall backend-func backend buffer-name type files)) + (pop-to-buffer buffer-name) + (let ((inhibit-read-only t)) + ;; log-view-mode used to be called with inhibit-read-only bound + ;; to t, so let's keep doing it, just in case. + (vc-call-backend backend 'log-view-mode) + (set (make-local-variable 'log-view-vc-backend) backend) + (set (make-local-variable 'log-view-vc-fileset) files)) + (vc-exec-after + `(let ((inhibit-read-only t)) + (funcall ',setup-buttons-func ',backend ',files ',retval) + (shrink-window-if-larger-than-buffer) + (funcall ',goto-location-func ',backend) + (setq vc-sentinel-movepoint (point)) + (set-buffer-modified-p nil))))) + +(defun vc-incoming-outgoing-internal (backend remote-location buffer-name type) + (vc-log-internal-common + backend buffer-name nil type + (lexical-let + ((remote-location remote-location)) + (lambda (bk buf type-arg files) + (vc-call-backend bk type-arg buf remote-location))) + (lambda (bk files-arg ret)) + (lambda (bk) + (goto-char (point-min))))) + +;;;###autoload +(defun vc-print-log (&optional working-revision limit) + "List the change log of the current fileset in a window. +If WORKING-REVISION is non-nil, leave point at that revision. +If LIMIT is non-nil, it should be a number specifying the maximum +number of revisions to show; the default is `vc-log-show-limit'. + +When called interactively with a prefix argument, prompt for +WORKING-REVISION and LIMIT." + (interactive + (cond + (current-prefix-arg + (let ((rev (read-from-minibuffer "Log from revision (default: last revision): " nil + nil nil nil)) + (lim (string-to-number + (read-from-minibuffer + "Limit display (unlimited: 0): " + (format "%s" vc-log-show-limit) + nil nil nil)))) + (when (string= rev "") (setq rev nil)) + (when (<= lim 0) (setq lim nil)) + (list rev lim))) + (t + (list nil (when (> vc-log-show-limit 0) vc-log-show-limit))))) + (let* ((vc-fileset (vc-deduce-fileset t)) ;FIXME: Why t? --Stef + (backend (car vc-fileset)) + (files (cadr vc-fileset)) + (working-revision (or working-revision (vc-working-revision (car files))))) + (vc-print-log-internal backend files working-revision nil limit))) + +;;;###autoload +(defun vc-print-root-log (&optional limit) + "List the change log for the current VC controlled tree in a window. +If LIMIT is non-nil, it should be a number specifying the maximum +number of revisions to show; the default is `vc-log-show-limit'. +When called interactively with a prefix argument, prompt for LIMIT." + (interactive + (cond + (current-prefix-arg + (let ((lim (string-to-number + (read-from-minibuffer + "Limit display (unlimited: 0): " + (format "%s" vc-log-show-limit) + nil nil nil)))) + (when (<= lim 0) (setq lim nil)) + (list lim))) + (t + (list (when (> vc-log-show-limit 0) vc-log-show-limit))))) + (let ((backend + (cond ((derived-mode-p 'vc-dir-mode) vc-dir-backend) + ((derived-mode-p 'dired-mode) (vc-responsible-backend default-directory)) + (vc-mode (vc-backend buffer-file-name)))) + rootdir working-revision) + (unless backend + (error "Buffer is not version controlled")) + (setq rootdir (vc-call-backend backend 'root default-directory)) + (setq working-revision (vc-working-revision rootdir)) + (vc-print-log-internal backend (list rootdir) working-revision nil limit))) + +;;;###autoload +(defun vc-log-incoming (&optional remote-location) + "Show a log of changes that will be received with a pull operation from REMOTE-LOCATION." + (interactive "sRemote location (empty for default): ") + (let ((backend + (cond ((derived-mode-p 'vc-dir-mode) vc-dir-backend) + ((derived-mode-p 'dired-mode) (vc-responsible-backend default-directory)) + (vc-mode (vc-backend buffer-file-name)))) + rootdir working-revision) + (unless backend + (error "Buffer is not version controlled")) + (vc-incoming-outgoing-internal backend remote-location "*vc-incoming*" 'log-incoming))) + +;;;###autoload +(defun vc-log-outgoing (&optional remote-location) + "Show a log of changes that will be sent with a push operation to REMOTE-LOCATION." + (interactive "sRemote location (empty for default): ") + (let ((backend + (cond ((derived-mode-p 'vc-dir-mode) vc-dir-backend) + ((derived-mode-p 'dired-mode) (vc-responsible-backend default-directory)) + (vc-mode (vc-backend buffer-file-name)))) + rootdir working-revision) + (unless backend + (error "Buffer is not version controlled")) + (vc-incoming-outgoing-internal backend remote-location "*vc-outgoing*" 'log-outgoing))) + +;;;###autoload +(defun vc-revert () + "Revert working copies of the selected fileset to their repository contents. +This asks for confirmation if the buffer contents are not identical +to the working revision (except for keyword expansion)." + (interactive) + (let* ((vc-fileset (vc-deduce-fileset)) + (files (cadr vc-fileset))) + ;; If any of the files is visited by the current buffer, make + ;; sure buffer is saved. If the user says `no', abort since + ;; we cannot show the changes and ask for confirmation to + ;; discard them. + (when (or (not files) (memq (buffer-file-name) files)) + (vc-buffer-sync nil)) + (dolist (file files) + (let ((buf (get-file-buffer file))) + (when (and buf (buffer-modified-p buf)) + (error "Please kill or save all modified buffers before reverting"))) + (when (vc-up-to-date-p file) + (unless (yes-or-no-p (format "%s seems up-to-date. Revert anyway? " file)) + (error "Revert canceled")))) + (when (vc-diff-internal vc-allow-async-revert vc-fileset nil nil) + (unless (yes-or-no-p + (format "Discard changes in %s? " + (let ((str (vc-delistify files)) + (nfiles (length files))) + (if (< (length str) 50) + str + (format "%d file%s" nfiles + (if (= nfiles 1) "" "s")))))) + (error "Revert canceled")) + (delete-windows-on "*vc-diff*") + (kill-buffer "*vc-diff*")) + (dolist (file files) + (message "Reverting %s..." (vc-delistify files)) + (vc-revert-file file) + (message "Reverting %s...done" (vc-delistify files))))) + +;;;###autoload +(defun vc-rollback () + "Roll back (remove) the most recent changeset committed to the repository. +This may be either a file-level or a repository-level operation, +depending on the underlying version-control system." + (interactive) + (let* ((vc-fileset (vc-deduce-fileset)) + (backend (car vc-fileset)) + (files (cadr vc-fileset)) + (granularity (vc-call-backend backend 'revision-granularity))) + (unless (vc-find-backend-function backend 'rollback) + (error "Rollback is not supported in %s" backend)) + (when (and (not (eq granularity 'repository)) (/= (length files) 1)) + (error "Rollback requires a singleton fileset or repository versioning")) + ;; FIXME: latest-on-branch-p should take the fileset. + (when (not (vc-call-backend backend 'latest-on-branch-p (car files))) + (error "Rollback is only possible at the tip revision")) + ;; If any of the files is visited by the current buffer, make + ;; sure buffer is saved. If the user says `no', abort since + ;; we cannot show the changes and ask for confirmation to + ;; discard them. + (when (or (not files) (memq (buffer-file-name) files)) + (vc-buffer-sync nil)) + (dolist (file files) + (when (buffer-modified-p (get-file-buffer file)) + (error "Please kill or save all modified buffers before rollback")) + (when (not (vc-up-to-date-p file)) + (error "Please revert all modified workfiles before rollback"))) + ;; Accumulate changes associated with the fileset + (vc-setup-buffer "*vc-diff*") + (not-modified) + (message "Finding changes...") + (let* ((tip (vc-working-revision (car files))) + ;; FIXME: `previous-revision' should take the fileset. + (previous (vc-call-backend backend 'previous-revision + (car files) tip))) + (vc-diff-internal nil vc-fileset previous tip)) + ;; Display changes + (unless (yes-or-no-p "Discard these revisions? ") + (error "Rollback canceled")) + (delete-windows-on "*vc-diff*") + (kill-buffer"*vc-diff*") + ;; Do the actual reversions + (message "Rolling back %s..." (vc-delistify files)) + (with-vc-properties + files + (vc-call-backend backend 'rollback files) + `((vc-state . ,'up-to-date) + (vc-checkout-time . , (nth 5 (file-attributes file))) + (vc-working-revision . nil))) + (dolist (f files) (vc-resynch-buffer f t t)) + (message "Rolling back %s...done" (vc-delistify files)))) + +;;;###autoload +(define-obsolete-function-alias 'vc-revert-buffer 'vc-revert "23.1") + +;;;###autoload +(defun vc-update () + "Update the current fileset's files to their tip revisions. +For each one that contains no changes, and is not locked, then this simply +replaces the work file with the latest revision on its branch. If the file +contains changes, and the backend supports merging news, then any recent +changes from the current branch are merged into the working file." + (interactive) + (let* ((vc-fileset (vc-deduce-fileset)) + (backend (car vc-fileset)) + (files (cadr vc-fileset))) + (save-some-buffers ; save buffers visiting files + nil (lambda () + (and (buffer-modified-p) + (let ((file (buffer-file-name))) + (and file (member file files)))))) + (dolist (file files) + (if (vc-up-to-date-p file) + (vc-checkout file nil t) + (if (eq (vc-checkout-model backend (list file)) 'locking) + (if (eq (vc-state file) 'edited) + (error "%s" + (substitute-command-keys + "File is locked--type \\[vc-revert] to discard changes")) + (error "Unexpected file state (%s) -- type %s" + (vc-state file) + (substitute-command-keys + "\\[vc-next-action] to correct"))) + (vc-maybe-resolve-conflicts + file (vc-call-backend backend 'merge-news file))))))) + +(defun vc-version-backup-file (file &optional rev) + "Return name of backup file for revision REV of FILE. +If version backups should be used for FILE, and there exists +such a backup for REV or the working revision of file, return +its name; otherwise return nil." + (when (vc-call make-version-backups-p file) + (let ((backup-file (vc-version-backup-file-name file rev))) + (if (file-exists-p backup-file) + backup-file + ;; there is no automatic backup, but maybe the user made one manually + (setq backup-file (vc-version-backup-file-name file rev 'manual)) + (when (file-exists-p backup-file) + backup-file))))) + +(defun vc-revert-file (file) + "Revert FILE back to the repository working revision it was based on." + (with-vc-properties + (list file) + (let ((backup-file (vc-version-backup-file file))) + (when backup-file + (copy-file backup-file file 'ok-if-already-exists 'keep-date) + (vc-delete-automatic-version-backups file)) + (vc-call revert file backup-file)) + `((vc-state . up-to-date) + (vc-checkout-time . ,(nth 5 (file-attributes file))))) + (vc-resynch-buffer file t t)) + +;;;###autoload +(defun vc-switch-backend (file backend) + "Make BACKEND the current version control system for FILE. +FILE must already be registered in BACKEND. The change is not +permanent, only for the current session. This function only changes +VC's perspective on FILE, it does not register or unregister it. +By default, this command cycles through the registered backends. +To get a prompt, use a prefix argument." + (interactive + (list + (or buffer-file-name + (error "There is no version-controlled file in this buffer")) + (let ((crt-bk (vc-backend buffer-file-name)) + (backends nil)) + (unless crt-bk + (error "File %s is not under version control" buffer-file-name)) + ;; Find the registered backends. + (dolist (crt vc-handled-backends) + (when (and (vc-call-backend crt 'registered buffer-file-name) + (not (eq crt-bk crt))) + (push crt backends))) + ;; Find the next backend. + (let ((def (car backends)) + (others backends)) + (cond + ((null others) (error "No other backend to switch to")) + (current-prefix-arg + (intern + (upcase + (completing-read + (format "Switch to backend [%s]: " def) + (mapcar (lambda (b) (list (downcase (symbol-name b)))) backends) + nil t nil nil (downcase (symbol-name def)))))) + (t def)))))) + (unless (eq backend (vc-backend file)) + (vc-file-clearprops file) + (vc-file-setprop file 'vc-backend backend) + ;; Force recomputation of the state + (unless (vc-call-backend backend 'registered file) + (vc-file-clearprops file) + (error "%s is not registered in %s" file backend)) + (vc-mode-line file))) + +;;;###autoload +(defun vc-transfer-file (file new-backend) + "Transfer FILE to another version control system NEW-BACKEND. +If NEW-BACKEND has a higher precedence than FILE's current backend +\(i.e. it comes earlier in `vc-handled-backends'), then register FILE in +NEW-BACKEND, using the revision number from the current backend as the +base level. If NEW-BACKEND has a lower precedence than the current +backend, then commit all changes that were made under the current +backend to NEW-BACKEND, and unregister FILE from the current backend. +\(If FILE is not yet registered under NEW-BACKEND, register it.)" + (let* ((old-backend (vc-backend file)) + (edited (memq (vc-state file) '(edited needs-merge))) + (registered (vc-call-backend new-backend 'registered file)) + (move + (and registered ; Never move if not registered in new-backend yet. + ;; move if new-backend comes later in vc-handled-backends + (or (memq new-backend (memq old-backend vc-handled-backends)) + (y-or-n-p "Final transfer? ")))) + (comment nil)) + (when (eq old-backend new-backend) + (error "%s is the current backend of %s" new-backend file)) + (if registered + (set-file-modes file (logior (file-modes file) 128)) + ;; `registered' might have switched under us. + (vc-switch-backend file old-backend) + (let* ((rev (vc-working-revision file)) + (modified-file (and edited (make-temp-file file))) + (unmodified-file (and modified-file (vc-version-backup-file file)))) + ;; Go back to the base unmodified file. + (unwind-protect + (progn + (when modified-file + (copy-file file modified-file 'ok-if-already-exists) + ;; If we have a local copy of the unmodified file, handle that + ;; here and not in vc-revert-file because we don't want to + ;; delete that copy -- it is still useful for OLD-BACKEND. + (if unmodified-file + (copy-file unmodified-file file + 'ok-if-already-exists 'keep-date) + (when (y-or-n-p "Get base revision from repository? ") + (vc-revert-file file)))) + (vc-call-backend new-backend 'receive-file file rev)) + (when modified-file + (vc-switch-backend file new-backend) + (unless (eq (vc-checkout-model new-backend (list file)) 'implicit) + (vc-checkout file t nil)) + (rename-file modified-file file 'ok-if-already-exists) + (vc-file-setprop file 'vc-checkout-time nil))))) + (when move + (vc-switch-backend file old-backend) + (setq comment (vc-call-backend old-backend 'comment-history file)) + (vc-call-backend old-backend 'unregister file)) + (vc-switch-backend file new-backend) + (when (or move edited) + (vc-file-setprop file 'vc-state 'edited) + (vc-mode-line file new-backend) + (vc-checkin file new-backend nil comment (stringp comment))))) + +(defun vc-rename-master (oldmaster newfile templates) + "Rename OLDMASTER to be the master file for NEWFILE based on TEMPLATES." + (let* ((dir (file-name-directory (expand-file-name oldmaster))) + (newdir (or (file-name-directory newfile) "")) + (newbase (file-name-nondirectory newfile)) + (masters + ;; List of potential master files for `newfile' + (mapcar + (lambda (s) (vc-possible-master s newdir newbase)) + templates))) + (when (or (file-symlink-p oldmaster) + (file-symlink-p (file-name-directory oldmaster))) + (error "This is unsafe in the presence of symbolic links")) + (rename-file + oldmaster + (catch 'found + ;; If possible, keep the master file in the same directory. + (dolist (f masters) + (when (and f (string= (file-name-directory (expand-file-name f)) dir)) + (throw 'found f))) + ;; If not, just use the first possible place. + (dolist (f masters) + (and f (or (not (setq dir (file-name-directory f))) + (file-directory-p dir)) + (throw 'found f))) + (error "New file lacks a version control directory"))))) + +;;;###autoload +(defun vc-delete-file (file) + "Delete file and mark it as such in the version control system." + (interactive "fVC delete file: ") + (setq file (expand-file-name file)) + (let ((buf (get-file-buffer file)) + (backend (vc-backend file))) + (unless backend + (error "File %s is not under version control" + (file-name-nondirectory file))) + (unless (vc-find-backend-function backend 'delete-file) + (error "Deleting files under %s is not supported in VC" backend)) + (when (and buf (buffer-modified-p buf)) + (error "Please save or undo your changes before deleting %s" file)) + (let ((state (vc-state file))) + (when (eq state 'edited) + (error "Please commit or undo your changes before deleting %s" file)) + (when (eq state 'conflict) + (error "Please resolve the conflicts before deleting %s" file))) + (unless (y-or-n-p (format "Really want to delete %s? " + (file-name-nondirectory file))) + (error "Abort!")) + (unless (or (file-directory-p file) (null make-backup-files) + (not (file-exists-p file))) + (with-current-buffer (or buf (find-file-noselect file)) + (let ((backup-inhibited nil)) + (backup-buffer)))) + ;; Bind `default-directory' so that the command that the backend + ;; runs to remove the file is invoked in the correct context. + (let ((default-directory (file-name-directory file))) + (vc-call-backend backend 'delete-file file)) + ;; If the backend hasn't deleted the file itself, let's do it for him. + (when (file-exists-p file) (delete-file file)) + ;; Forget what VC knew about the file. + (vc-file-clearprops file) + ;; Make sure the buffer is deleted and the *vc-dir* buffers are + ;; updated after this. + (vc-resynch-buffer file nil t))) + +;;;###autoload +(defun vc-rename-file (old new) + "Rename file OLD to NEW in both work area and repository." + (interactive "fVC rename file: \nFRename to: ") + ;; in CL I would have said (setq new (merge-pathnames new old)) + (let ((old-base (file-name-nondirectory old))) + (when (and (not (string= "" old-base)) + (string= "" (file-name-nondirectory new))) + (setq new (concat new old-base)))) + (let ((oldbuf (get-file-buffer old))) + (when (and oldbuf (buffer-modified-p oldbuf)) + (error "Please save files before moving them")) + (when (get-file-buffer new) + (error "Already editing new file name")) + (when (file-exists-p new) + (error "New file already exists")) + (let ((state (vc-state old))) + (unless (memq state '(up-to-date edited)) + (error "Please %s files before moving them" + (if (stringp state) "check in" "update")))) + (vc-call rename-file old new) + (vc-file-clearprops old) + ;; Move the actual file (unless the backend did it already) + (when (file-exists-p old) (rename-file old new)) + ;; ?? Renaming a file might change its contents due to keyword expansion. + ;; We should really check out a new copy if the old copy was precisely equal + ;; to some checked-in revision. However, testing for this is tricky.... + (when oldbuf + (with-current-buffer oldbuf + (let ((buffer-read-only buffer-read-only)) + (set-visited-file-name new)) + (vc-mode-line new (vc-backend new)) + (set-buffer-modified-p nil))))) + +;;;###autoload +(defun vc-update-change-log (&rest args) + "Find change log file and add entries from recent version control logs. +Normally, find log entries for all registered files in the default +directory. + +With prefix arg of \\[universal-argument], only find log entries for the current buffer's file. + +With any numeric prefix arg, find log entries for all currently visited +files that are under version control. This puts all the entries in the +log for the default directory, which may not be appropriate. + +From a program, any ARGS are assumed to be filenames for which +log entries should be gathered." + (interactive + (cond ((consp current-prefix-arg) ;C-u + (list buffer-file-name)) + (current-prefix-arg ;Numeric argument. + (let ((files nil) + (buffers (buffer-list)) + file) + (while buffers + (setq file (buffer-file-name (car buffers))) + (and file (vc-backend file) + (setq files (cons file files))) + (setq buffers (cdr buffers))) + files)) + (t + ;; Don't supply any filenames to backend; this means + ;; it should find all relevant files relative to + ;; the default-directory. + nil))) + (vc-call-backend (vc-responsible-backend default-directory) + 'update-changelog args)) + +;; functions that operate on RCS revision numbers. This code should +;; also be moved into the backends. It stays for now, however, since +;; it is used in code below. +(defun vc-branch-p (rev) + "Return t if REV is a branch revision." + (not (eq nil (string-match "\\`[0-9]+\\(\\.[0-9]+\\.[0-9]+\\)*\\'" rev)))) + +;;;###autoload +(defun vc-branch-part (rev) + "Return the branch part of a revision number REV." + (let ((index (string-match "\\.[0-9]+\\'" rev))) + (when index + (substring rev 0 index)))) + +(define-obsolete-function-alias + 'vc-default-previous-version 'vc-default-previous-revision "23.1") + +(defun vc-default-responsible-p (backend file) + "Indicate whether BACKEND is reponsible for FILE. +The default is to return nil always." + nil) + +(defun vc-default-could-register (backend file) + "Return non-nil if BACKEND could be used to register FILE. +The default implementation returns t for all files." + t) + +(defun vc-default-latest-on-branch-p (backend file) + "Return non-nil if FILE is the latest on its branch. +This default implementation always returns non-nil, which means that +editing non-current revisions is not supported by default." + t) + +(defun vc-default-init-revision (backend) vc-default-init-revision) + +(defun vc-default-find-revision (backend file rev buffer) + "Provide the new `find-revision' op based on the old `checkout' op. +This is only for compatibility with old backends. They should be updated +to provide the `find-revision' operation instead." + (let ((tmpfile (make-temp-file (expand-file-name file)))) + (unwind-protect + (progn + (vc-call-backend backend 'checkout file nil rev tmpfile) + (with-current-buffer buffer + (insert-file-contents-literally tmpfile))) + (delete-file tmpfile)))) + +(defun vc-default-rename-file (backend old new) + (condition-case nil + (add-name-to-file old new) + (error (rename-file old new))) + (vc-delete-file old) + (with-current-buffer (find-file-noselect new) + (vc-register))) + +(defalias 'vc-default-check-headers 'ignore) + +(declare-function log-edit-mode "log-edit" ()) + +(defun vc-default-log-edit-mode (backend) (log-edit-mode)) + +(defun vc-default-log-view-mode (backend) (log-view-mode)) + +(defun vc-default-show-log-entry (backend rev) + (with-no-warnings + (log-view-goto-rev rev))) + +(defun vc-default-comment-history (backend file) + "Return a string with all log entries stored in BACKEND for FILE." + (when (vc-find-backend-function backend 'print-log) + (with-current-buffer "*vc*" + (vc-call-backend backend 'print-log (list file)) + (buffer-string)))) + +(defun vc-default-receive-file (backend file rev) + "Let BACKEND receive FILE from another version control system." + (vc-call-backend backend 'register (list file) rev "")) + +(defun vc-default-retrieve-tag (backend dir name update) + (if (string= name "") + (progn + (vc-file-tree-walk + dir + (lambda (f) (and + (vc-up-to-date-p f) + (vc-error-occurred + (vc-call-backend backend 'checkout f nil "") + (when update (vc-resynch-buffer f t t))))))) + (let ((result (vc-tag-precondition dir))) + (if (stringp result) + (error "File %s is locked" result) + (setq update (and (eq result 'visited) update)) + (vc-file-tree-walk + dir + (lambda (f) (vc-error-occurred + (vc-call-backend backend 'checkout f nil name) + (when update (vc-resynch-buffer f t t))))))))) + +(defun vc-default-revert (backend file contents-done) + (unless contents-done + (let ((rev (vc-working-revision file)) + (file-buffer (or (get-file-buffer file) (current-buffer)))) + (message "Checking out %s..." file) + (let ((failed t) + (backup-name (car (find-backup-file-name file)))) + (when backup-name + (copy-file file backup-name 'ok-if-already-exists 'keep-date) + (unless (file-writable-p file) + (set-file-modes file (logior (file-modes file) 128)))) + (unwind-protect + (let ((coding-system-for-read 'no-conversion) + (coding-system-for-write 'no-conversion)) + (with-temp-file file + (let ((outbuf (current-buffer))) + ;; Change buffer to get local value of vc-checkout-switches. + (with-current-buffer file-buffer + (let ((default-directory (file-name-directory file))) + (vc-call-backend backend 'find-revision + file rev outbuf))))) + (setq failed nil)) + (when backup-name + (if failed + (rename-file backup-name file 'ok-if-already-exists) + (and (not vc-make-backup-files) (delete-file backup-name)))))) + (message "Checking out %s...done" file)))) + +(defalias 'vc-default-revision-completion-table 'ignore) +(defalias 'vc-default-mark-resolved 'ignore) + +(defun vc-default-dir-status-files (backend dir files default-state update-function) + (funcall update-function + (mapcar (lambda (file) (list file default-state)) files))) + +(defun vc-check-headers () + "Check if the current file has any headers in it." + (interactive) + (vc-call-backend (vc-backend buffer-file-name) 'check-headers)) + + + +;; These things should probably be generally available + +(defun vc-string-prefix-p (prefix string) + (let ((lpref (length prefix))) + (and (>= (length string) lpref) + (eq t (compare-strings prefix nil nil string nil lpref))))) + +(defun vc-file-tree-walk (dirname func &rest args) + "Walk recursively through DIRNAME. +Invoke FUNC f ARGS on each VC-managed file f underneath it." + (vc-file-tree-walk-internal (expand-file-name dirname) func args) + (message "Traversing directory %s...done" dirname)) + +(defun vc-file-tree-walk-internal (file func args) + (if (not (file-directory-p file)) + (when (vc-backend file) (apply func file args)) + (message "Traversing directory %s..." (abbreviate-file-name file)) + (let ((dir (file-name-as-directory file))) + (mapcar + (lambda (f) (or + (string-equal f ".") + (string-equal f "..") + (member f vc-directory-exclusion-list) + (let ((dirf (expand-file-name f dir))) + (or + (file-symlink-p dirf) ;; Avoid possible loops. + (vc-file-tree-walk-internal dirf func args))))) + (directory-files dir))))) + +(provide 'vc) + +;; arch-tag: ca82c1de-3091-4e26-af92-460abc6213a6 +;;; vc.el ends here diff -r 681cd08dc0f7 -r e93288477c43 src/ChangeLog --- a/src/ChangeLog Thu Jun 10 22:43:47 2010 +0000 +++ b/src/ChangeLog Sun Jun 13 22:57:55 2010 +0000 @@ -1,3 +1,27 @@ +2010-06-12 Eli Zaretskii + + * makefile.w32-in ($(BLD)/bidi.$(O)): Depend on biditype.h and + bidimirror.h. + + * deps.mk (bidi.o): Depend on biditype.h and bidimirror.h. + + * bidi.c (bidi_initialize): Remove explicit initialization of + bidi_type_table; include biditype.h instead. Don't support + entries whose second codepoint is zero. Initialize + bidi_mirror_table. + (bidi_mirror_char): Use bidi_mirror_table. + + * biditype.h: New file. + + * bidimirror.h: New file. + + * window.c (syms_of_window): Doc fix (bug#6409). + +2010-06-12 Romain Francoise + + * Makefile.in (lisp, shortlisp): Use new location of vc-hooks and + ediff-hook. + 2010-06-10 Glenn Morris * editfns.c (Fbyte_to_string): Pacify compiler. diff -r 681cd08dc0f7 -r e93288477c43 src/Makefile.in --- a/src/Makefile.in Thu Jun 10 22:43:47 2010 +0000 +++ b/src/Makefile.in Sun Jun 13 22:57:55 2010 +0000 @@ -458,8 +458,8 @@ ${lispsource}textmodes/text-mode.elc \ ${lispsource}emacs-lisp/timer.elc \ ${lispsource}jka-cmpr-hook.elc \ - ${lispsource}vc-hooks.elc \ - ${lispsource}ediff-hook.elc \ + ${lispsource}vc/vc-hooks.elc \ + ${lispsource}vc/ediff-hook.elc \ ${lispsource}epa-hook.elc \ ${TOOLTIP_SUPPORT} \ ${MSDOS_SUPPORT} \ @@ -549,9 +549,9 @@ ../lisp/textmodes/paragraphs.elc \ ../lisp/textmodes/text-mode.elc \ ../lisp/emacs-lisp/timer.elc \ - ../lisp/vc-hooks.elc \ + ../lisp/vc/vc-hooks.elc \ + ../lisp/vc/ediff-hook.elc \ ../lisp/jka-cmpr-hook.elc \ - ../lisp/ediff-hook.elc \ ../lisp/epa-hook.elc \ ../lisp/widget.elc \ ../lisp/window.elc \ diff -r 681cd08dc0f7 -r e93288477c43 src/bidi.c --- a/src/bidi.c Thu Jun 10 22:43:47 2010 +0000 +++ b/src/bidi.c Sun Jun 13 22:57:55 2010 +0000 @@ -68,7 +68,7 @@ static int bidi_initialized = 0; -static Lisp_Object bidi_type_table; +static Lisp_Object bidi_type_table, bidi_mirror_table; /* FIXME: Remove these when bidi_explicit_dir_char uses a lookup table. */ #define LRM_CHAR 0x200E @@ -108,297 +108,26 @@ static void bidi_initialize () { - /* FIXME: This should come from the Unicode Database. */ - struct { - int from, to; - bidi_type_t type; - } bidi_type[] = - { { 0x0000, 0x0008, WEAK_BN }, - { 0x0009, 0x0000, NEUTRAL_S }, - { 0x000A, 0x0000, NEUTRAL_B }, - { 0x000B, 0x0000, NEUTRAL_S }, - { 0x000C, 0x0000, NEUTRAL_WS }, - { 0x000D, 0x0000, NEUTRAL_B }, - { 0x000E, 0x001B, WEAK_BN }, - { 0x001C, 0x001E, NEUTRAL_B }, - { 0x001F, 0x0000, NEUTRAL_S }, - { 0x0020, 0x0000, NEUTRAL_WS }, - { 0x0021, 0x0022, NEUTRAL_ON }, - { 0x0023, 0x0025, WEAK_ET }, - { 0x0026, 0x002A, NEUTRAL_ON }, - { 0x002B, 0x0000, WEAK_ES }, - { 0x002C, 0x0000, WEAK_CS }, - { 0x002D, 0x0000, WEAK_ES }, - { 0x002E, 0x002F, WEAK_CS }, - { 0x0030, 0x0039, WEAK_EN }, - { 0x003A, 0x0000, WEAK_CS }, - { 0x003B, 0x0040, NEUTRAL_ON }, - { 0x005B, 0x0060, NEUTRAL_ON }, - { 0x007B, 0x007E, NEUTRAL_ON }, - { 0x007F, 0x0084, WEAK_BN }, - { 0x0085, 0x0000, NEUTRAL_B }, - { 0x0086, 0x009F, WEAK_BN }, - { 0x00A0, 0x0000, WEAK_CS }, - { 0x00A1, 0x0000, NEUTRAL_ON }, - { 0x00A2, 0x00A5, WEAK_ET }, - { 0x00A6, 0x00A9, NEUTRAL_ON }, - { 0x00AB, 0x00AC, NEUTRAL_ON }, - { 0x00AD, 0x0000, WEAK_BN }, - { 0x00AE, 0x00Af, NEUTRAL_ON }, - { 0x00B0, 0x00B1, WEAK_ET }, - { 0x00B2, 0x00B3, WEAK_EN }, - { 0x00B4, 0x0000, NEUTRAL_ON }, - { 0x00B6, 0x00B8, NEUTRAL_ON }, - { 0x00B9, 0x0000, WEAK_EN }, - { 0x00BB, 0x00BF, NEUTRAL_ON }, - { 0x00D7, 0x0000, NEUTRAL_ON }, - { 0x00F7, 0x0000, NEUTRAL_ON }, - { 0x02B9, 0x02BA, NEUTRAL_ON }, - { 0x02C2, 0x02CF, NEUTRAL_ON }, - { 0x02D2, 0x02DF, NEUTRAL_ON }, - { 0x02E5, 0x02ED, NEUTRAL_ON }, - { 0x0300, 0x036F, WEAK_NSM }, - { 0x0374, 0x0375, NEUTRAL_ON }, - { 0x037E, 0x0385, NEUTRAL_ON }, - { 0x0387, 0x0000, NEUTRAL_ON }, - { 0x03F6, 0x0000, NEUTRAL_ON }, - { 0x0483, 0x0489, WEAK_NSM }, - { 0x058A, 0x0000, NEUTRAL_ON }, - { 0x0591, 0x05BD, WEAK_NSM }, - { 0x05BE, 0x0000, STRONG_R }, - { 0x05BF, 0x0000, WEAK_NSM }, - { 0x05C0, 0x0000, STRONG_R }, - { 0x05C1, 0x05C2, WEAK_NSM }, - { 0x05C3, 0x0000, STRONG_R }, - { 0x05C4, 0x05C5, WEAK_NSM }, - { 0x05C6, 0x0000, STRONG_R }, - { 0x05C7, 0x0000, WEAK_NSM }, - { 0x05D0, 0x05F4, STRONG_R }, - { 0x060C, 0x0000, WEAK_CS }, - { 0x061B, 0x064A, STRONG_AL }, - { 0x064B, 0x0655, WEAK_NSM }, - { 0x0660, 0x0669, WEAK_AN }, - { 0x066A, 0x0000, WEAK_ET }, - { 0x066B, 0x066C, WEAK_AN }, - { 0x066D, 0x066F, STRONG_AL }, - { 0x0670, 0x0000, WEAK_NSM }, - { 0x0671, 0x06D5, STRONG_AL }, - { 0x06D6, 0x06DC, WEAK_NSM }, - { 0x06DD, 0x0000, STRONG_AL }, - { 0x06DE, 0x06E4, WEAK_NSM }, - { 0x06E5, 0x06E6, STRONG_AL }, - { 0x06E7, 0x06E8, WEAK_NSM }, - { 0x06E9, 0x0000, NEUTRAL_ON }, - { 0x06EA, 0x06ED, WEAK_NSM }, - { 0x06F0, 0x06F9, WEAK_EN }, - { 0x06FA, 0x070D, STRONG_AL }, - { 0x070F, 0x0000, WEAK_BN }, - { 0x0710, 0x0000, STRONG_AL }, - { 0x0711, 0x0000, WEAK_NSM }, - { 0x0712, 0x072C, STRONG_AL }, - { 0x0730, 0x074A, WEAK_NSM }, - { 0x0780, 0x07A5, STRONG_AL }, - { 0x07A6, 0x07B0, WEAK_NSM }, - { 0x07B1, 0x0000, STRONG_AL }, - { 0x0901, 0x0902, WEAK_NSM }, - { 0x093C, 0x0000, WEAK_NSM }, - { 0x0941, 0x0948, WEAK_NSM }, - { 0x094D, 0x0000, WEAK_NSM }, - { 0x0951, 0x0954, WEAK_NSM }, - { 0x0962, 0x0963, WEAK_NSM }, - { 0x0981, 0x0000, WEAK_NSM }, - { 0x09BC, 0x0000, WEAK_NSM }, - { 0x09C1, 0x09C4, WEAK_NSM }, - { 0x09CD, 0x0000, WEAK_NSM }, - { 0x09E2, 0x09E3, WEAK_NSM }, - { 0x09F2, 0x09F3, WEAK_ET }, - { 0x0A02, 0x0000, WEAK_NSM }, - { 0x0A3C, 0x0000, WEAK_NSM }, - { 0x0A41, 0x0A4D, WEAK_NSM }, - { 0x0A70, 0x0A71, WEAK_NSM }, - { 0x0A81, 0x0A82, WEAK_NSM }, - { 0x0ABC, 0x0000, WEAK_NSM }, - { 0x0AC1, 0x0AC8, WEAK_NSM }, - { 0x0ACD, 0x0000, WEAK_NSM }, - { 0x0B01, 0x0000, WEAK_NSM }, - { 0x0B3C, 0x0000, WEAK_NSM }, - { 0x0B3F, 0x0000, WEAK_NSM }, - { 0x0B41, 0x0B43, WEAK_NSM }, - { 0x0B4D, 0x0B56, WEAK_NSM }, - { 0x0B82, 0x0000, WEAK_NSM }, - { 0x0BC0, 0x0000, WEAK_NSM }, - { 0x0BCD, 0x0000, WEAK_NSM }, - { 0x0C3E, 0x0C40, WEAK_NSM }, - { 0x0C46, 0x0C56, WEAK_NSM }, - { 0x0CBF, 0x0000, WEAK_NSM }, - { 0x0CC6, 0x0000, WEAK_NSM }, - { 0x0CCC, 0x0CCD, WEAK_NSM }, - { 0x0D41, 0x0D43, WEAK_NSM }, - { 0x0D4D, 0x0000, WEAK_NSM }, - { 0x0DCA, 0x0000, WEAK_NSM }, - { 0x0DD2, 0x0DD6, WEAK_NSM }, - { 0x0E31, 0x0000, WEAK_NSM }, - { 0x0E34, 0x0E3A, WEAK_NSM }, - { 0x0E3F, 0x0000, WEAK_ET }, - { 0x0E47, 0x0E4E, WEAK_NSM }, - { 0x0EB1, 0x0000, WEAK_NSM }, - { 0x0EB4, 0x0EBC, WEAK_NSM }, - { 0x0EC8, 0x0ECD, WEAK_NSM }, - { 0x0F18, 0x0F19, WEAK_NSM }, - { 0x0F35, 0x0000, WEAK_NSM }, - { 0x0F37, 0x0000, WEAK_NSM }, - { 0x0F39, 0x0000, WEAK_NSM }, - { 0x0F3A, 0x0F3D, NEUTRAL_ON }, - { 0x0F71, 0x0F7E, WEAK_NSM }, - { 0x0F80, 0x0F84, WEAK_NSM }, - { 0x0F86, 0x0F87, WEAK_NSM }, - { 0x0F90, 0x0FBC, WEAK_NSM }, - { 0x0FC6, 0x0000, WEAK_NSM }, - { 0x102D, 0x1030, WEAK_NSM }, - { 0x1032, 0x1037, WEAK_NSM }, - { 0x1039, 0x0000, WEAK_NSM }, - { 0x1058, 0x1059, WEAK_NSM }, - { 0x1680, 0x0000, NEUTRAL_WS }, - { 0x169B, 0x169C, NEUTRAL_ON }, - { 0x1712, 0x1714, WEAK_NSM }, - { 0x1732, 0x1734, WEAK_NSM }, - { 0x1752, 0x1753, WEAK_NSM }, - { 0x1772, 0x1773, WEAK_NSM }, - { 0x17B7, 0x17BD, WEAK_NSM }, - { 0x17C6, 0x0000, WEAK_NSM }, - { 0x17C9, 0x17D3, WEAK_NSM }, - { 0x17DB, 0x0000, WEAK_ET }, - { 0x1800, 0x180A, NEUTRAL_ON }, - { 0x180B, 0x180D, WEAK_NSM }, - { 0x180E, 0x0000, WEAK_BN }, - { 0x18A9, 0x0000, WEAK_NSM }, - { 0x1FBD, 0x0000, NEUTRAL_ON }, - { 0x1FBF, 0x1FC1, NEUTRAL_ON }, - { 0x1FCD, 0x1FCF, NEUTRAL_ON }, - { 0x1FDD, 0x1FDF, NEUTRAL_ON }, - { 0x1FED, 0x1FEF, NEUTRAL_ON }, - { 0x1FFD, 0x1FFE, NEUTRAL_ON }, - { 0x2000, 0x200A, NEUTRAL_WS }, - { 0x200B, 0x200D, WEAK_BN }, - { 0x200F, 0x0000, STRONG_R }, - { 0x2010, 0x2027, NEUTRAL_ON }, - { 0x2028, 0x0000, NEUTRAL_WS }, - { 0x2029, 0x0000, NEUTRAL_B }, - { 0x202A, 0x0000, LRE }, - { 0x202B, 0x0000, RLE }, - { 0x202C, 0x0000, PDF }, - { 0x202D, 0x0000, LRO }, - { 0x202E, 0x0000, RLO }, - { 0x202F, 0x0000, NEUTRAL_WS }, - { 0x2030, 0x2034, WEAK_ET }, - { 0x2035, 0x2057, NEUTRAL_ON }, - { 0x205F, 0x0000, NEUTRAL_WS }, - { 0x2060, 0x206F, WEAK_BN }, - { 0x2070, 0x0000, WEAK_EN }, - { 0x2074, 0x2079, WEAK_EN }, - { 0x207A, 0x207B, WEAK_ET }, - { 0x207C, 0x207E, NEUTRAL_ON }, - { 0x2080, 0x2089, WEAK_EN }, - { 0x208A, 0x208B, WEAK_ET }, - { 0x208C, 0x208E, NEUTRAL_ON }, - { 0x20A0, 0x20B1, WEAK_ET }, - { 0x20D0, 0x20EA, WEAK_NSM }, - { 0x2100, 0x2101, NEUTRAL_ON }, - { 0x2103, 0x2106, NEUTRAL_ON }, - { 0x2108, 0x2109, NEUTRAL_ON }, - { 0x2114, 0x0000, NEUTRAL_ON }, - { 0x2116, 0x2118, NEUTRAL_ON }, - { 0x211E, 0x2123, NEUTRAL_ON }, - { 0x2125, 0x0000, NEUTRAL_ON }, - { 0x2127, 0x0000, NEUTRAL_ON }, - { 0x2129, 0x0000, NEUTRAL_ON }, - { 0x212E, 0x0000, WEAK_ET }, - { 0x2132, 0x0000, NEUTRAL_ON }, - { 0x213A, 0x0000, NEUTRAL_ON }, - { 0x2140, 0x2144, NEUTRAL_ON }, - { 0x214A, 0x215F, NEUTRAL_ON }, - { 0x2190, 0x2211, NEUTRAL_ON }, - { 0x2212, 0x2213, WEAK_ET }, - { 0x2214, 0x2335, NEUTRAL_ON }, - { 0x237B, 0x2394, NEUTRAL_ON }, - { 0x2396, 0x244A, NEUTRAL_ON }, - { 0x2460, 0x249B, WEAK_EN }, - { 0x24EA, 0x0000, WEAK_EN }, - { 0x24EB, 0x2FFB, NEUTRAL_ON }, - { 0x3000, 0x0000, NEUTRAL_WS }, - { 0x3001, 0x3004, NEUTRAL_ON }, - { 0x3008, 0x3020, NEUTRAL_ON }, - { 0x302A, 0x302F, WEAK_NSM }, - { 0x3030, 0x0000, NEUTRAL_ON }, - { 0x3036, 0x3037, NEUTRAL_ON }, - { 0x303D, 0x303F, NEUTRAL_ON }, - { 0x3099, 0x309A, WEAK_NSM }, - { 0x309B, 0x309C, NEUTRAL_ON }, - { 0x30A0, 0x0000, NEUTRAL_ON }, - { 0x30FB, 0x0000, NEUTRAL_ON }, - { 0x3251, 0x325F, NEUTRAL_ON }, - { 0x32B1, 0x32BF, NEUTRAL_ON }, - { 0xA490, 0xA4C6, NEUTRAL_ON }, - { 0xFB1D, 0x0000, STRONG_R }, - { 0xFB1E, 0x0000, WEAK_NSM }, - { 0xFB1F, 0xFB28, STRONG_R }, - { 0xFB29, 0x0000, WEAK_ET }, - { 0xFB2A, 0xFB4F, STRONG_R }, - { 0xFB50, 0xFD3D, STRONG_AL }, - { 0xFD3E, 0xFD3F, NEUTRAL_ON }, - { 0xFD50, 0xFDFC, STRONG_AL }, - { 0xFE00, 0xFE23, WEAK_NSM }, - { 0xFE30, 0xFE4F, NEUTRAL_ON }, - { 0xFE50, 0x0000, WEAK_CS }, - { 0xFE51, 0x0000, NEUTRAL_ON }, - { 0xFE52, 0x0000, WEAK_CS }, - { 0xFE54, 0x0000, NEUTRAL_ON }, - { 0xFE55, 0x0000, WEAK_CS }, - { 0xFE56, 0xFE5E, NEUTRAL_ON }, - { 0xFE5F, 0x0000, WEAK_ET }, - { 0xFE60, 0xFE61, NEUTRAL_ON }, - { 0xFE62, 0xFE63, WEAK_ET }, - { 0xFE64, 0xFE68, NEUTRAL_ON }, - { 0xFE69, 0xFE6A, WEAK_ET }, - { 0xFE6B, 0x0000, NEUTRAL_ON }, - { 0xFE70, 0xFEFC, STRONG_AL }, - { 0xFEFF, 0x0000, WEAK_BN }, - { 0xFF01, 0xFF02, NEUTRAL_ON }, - { 0xFF03, 0xFF05, WEAK_ET }, - { 0xFF06, 0xFF0A, NEUTRAL_ON }, - { 0xFF0B, 0x0000, WEAK_ET }, - { 0xFF0C, 0x0000, WEAK_CS }, - { 0xFF0D, 0x0000, WEAK_ET }, - { 0xFF0E, 0x0000, WEAK_CS }, - { 0xFF0F, 0x0000, WEAK_ES }, - { 0xFF10, 0xFF19, WEAK_EN }, - { 0xFF1A, 0x0000, WEAK_CS }, - { 0xFF1B, 0xFF20, NEUTRAL_ON }, - { 0xFF3B, 0xFF40, NEUTRAL_ON }, - { 0xFF5B, 0xFF65, NEUTRAL_ON }, - { 0xFFE0, 0xFFE1, WEAK_ET }, - { 0xFFE2, 0xFFE4, NEUTRAL_ON }, - { 0xFFE5, 0xFFE6, WEAK_ET }, - { 0xFFE8, 0xFFEE, NEUTRAL_ON }, - { 0xFFF9, 0xFFFB, WEAK_BN }, - { 0xFFFC, 0xFFFD, NEUTRAL_ON }, - { 0x1D167, 0x1D169, WEAK_NSM }, - { 0x1D173, 0x1D17A, WEAK_BN }, - { 0x1D17B, 0x1D182, WEAK_NSM }, - { 0x1D185, 0x1D18B, WEAK_NSM }, - { 0x1D1AA, 0x1D1AD, WEAK_NSM }, - { 0x1D7CE, 0x1D7FF, WEAK_EN }, - { 0xE0001, 0xE007F, WEAK_BN } }; + +#include "biditype.h" +#include "bidimirror.h" + int i; bidi_type_table = Fmake_char_table (Qnil, make_number (STRONG_L)); staticpro (&bidi_type_table); for (i = 0; i < sizeof bidi_type / sizeof bidi_type[0]; i++) - char_table_set_range (bidi_type_table, bidi_type[i].from, - bidi_type[i].to ? bidi_type[i].to : bidi_type[i].from, + char_table_set_range (bidi_type_table, bidi_type[i].from, bidi_type[i].to, make_number (bidi_type[i].type)); + bidi_mirror_table = Fmake_char_table (Qnil, Qnil); + staticpro (&bidi_mirror_table); + + for (i = 0; i < sizeof bidi_mirror / sizeof bidi_mirror[0]; i++) + char_table_set (bidi_mirror_table, bidi_mirror[i].from, + make_number (bidi_mirror[i].to)); + Qparagraph_start = intern ("paragraph-start"); staticpro (&Qparagraph_start); paragraph_start_re = Fsymbol_value (Qparagraph_start); @@ -501,24 +230,31 @@ } } -/* Return the mirrored character of C, if any. - - Note: The conditions in UAX#9 clause L4 must be tested by the - caller. */ -/* FIXME: exceedingly temporary! Should consult the Unicode database - of character properties. */ +/* Return the mirrored character of C, if it has one. If C has no + mirrored counterpart, return C. + Note: The conditions in UAX#9 clause L4 regarding the surrounding + context must be tested by the caller. */ int bidi_mirror_char (int c) { - static const char mirrored_pairs[] = "()<>[]{}"; - const char *p = c > 0 && c < 128 ? strchr (mirrored_pairs, c) : NULL; + Lisp_Object val; - if (p) + if (c == BIDI_EOB) + return c; + if (c < 0 || c > MAX_CHAR) + abort (); + + val = CHAR_TABLE_REF (bidi_mirror_table, c); + if (INTEGERP (val)) { - size_t i = p - mirrored_pairs; + int v = XINT (val); - return mirrored_pairs [(i ^ 1)]; + if (v < 0 || v > MAX_CHAR) + abort (); + + return v; } + return c; } diff -r 681cd08dc0f7 -r e93288477c43 src/bidimirror.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/bidimirror.h Sun Jun 13 22:57:55 2010 +0000 @@ -0,0 +1,365 @@ + struct { + int from, to; + } bidi_mirror[] = { + { 0x0028, 0x0029 }, + { 0x0029, 0x0028 }, + { 0x003C, 0x003E }, + { 0x003E, 0x003C }, + { 0x005B, 0x005D }, + { 0x005D, 0x005B }, + { 0x007B, 0x007D }, + { 0x007D, 0x007B }, + { 0x00AB, 0x00BB }, + { 0x00BB, 0x00AB }, + { 0x0F3A, 0x0F3B }, + { 0x0F3B, 0x0F3A }, + { 0x0F3C, 0x0F3D }, + { 0x0F3D, 0x0F3C }, + { 0x169B, 0x169C }, + { 0x169C, 0x169B }, + { 0x2039, 0x203A }, + { 0x203A, 0x2039 }, + { 0x2045, 0x2046 }, + { 0x2046, 0x2045 }, + { 0x207D, 0x207E }, + { 0x207E, 0x207D }, + { 0x208D, 0x208E }, + { 0x208E, 0x208D }, + { 0x2208, 0x220B }, + { 0x2209, 0x220C }, + { 0x220A, 0x220D }, + { 0x220B, 0x2208 }, + { 0x220C, 0x2209 }, + { 0x220D, 0x220A }, + { 0x2215, 0x29F5 }, + { 0x223C, 0x223D }, + { 0x223D, 0x223C }, + { 0x2243, 0x22CD }, + { 0x2252, 0x2253 }, + { 0x2253, 0x2252 }, + { 0x2254, 0x2255 }, + { 0x2255, 0x2254 }, + { 0x2264, 0x2265 }, + { 0x2265, 0x2264 }, + { 0x2266, 0x2267 }, + { 0x2267, 0x2266 }, + { 0x2268, 0x2269 }, + { 0x2269, 0x2268 }, + { 0x226A, 0x226B }, + { 0x226B, 0x226A }, + { 0x226E, 0x226F }, + { 0x226F, 0x226E }, + { 0x2270, 0x2271 }, + { 0x2271, 0x2270 }, + { 0x2272, 0x2273 }, + { 0x2273, 0x2272 }, + { 0x2274, 0x2275 }, + { 0x2275, 0x2274 }, + { 0x2276, 0x2277 }, + { 0x2277, 0x2276 }, + { 0x2278, 0x2279 }, + { 0x2279, 0x2278 }, + { 0x227A, 0x227B }, + { 0x227B, 0x227A }, + { 0x227C, 0x227D }, + { 0x227D, 0x227C }, + { 0x227E, 0x227F }, + { 0x227F, 0x227E }, + { 0x2280, 0x2281 }, + { 0x2281, 0x2280 }, + { 0x2282, 0x2283 }, + { 0x2283, 0x2282 }, + { 0x2284, 0x2285 }, + { 0x2285, 0x2284 }, + { 0x2286, 0x2287 }, + { 0x2287, 0x2286 }, + { 0x2288, 0x2289 }, + { 0x2289, 0x2288 }, + { 0x228A, 0x228B }, + { 0x228B, 0x228A }, + { 0x228F, 0x2290 }, + { 0x2290, 0x228F }, + { 0x2291, 0x2292 }, + { 0x2292, 0x2291 }, + { 0x2298, 0x29B8 }, + { 0x22A2, 0x22A3 }, + { 0x22A3, 0x22A2 }, + { 0x22A6, 0x2ADE }, + { 0x22A8, 0x2AE4 }, + { 0x22A9, 0x2AE3 }, + { 0x22AB, 0x2AE5 }, + { 0x22B0, 0x22B1 }, + { 0x22B1, 0x22B0 }, + { 0x22B2, 0x22B3 }, + { 0x22B3, 0x22B2 }, + { 0x22B4, 0x22B5 }, + { 0x22B5, 0x22B4 }, + { 0x22B6, 0x22B7 }, + { 0x22B7, 0x22B6 }, + { 0x22C9, 0x22CA }, + { 0x22CA, 0x22C9 }, + { 0x22CB, 0x22CC }, + { 0x22CC, 0x22CB }, + { 0x22CD, 0x2243 }, + { 0x22D0, 0x22D1 }, + { 0x22D1, 0x22D0 }, + { 0x22D6, 0x22D7 }, + { 0x22D7, 0x22D6 }, + { 0x22D8, 0x22D9 }, + { 0x22D9, 0x22D8 }, + { 0x22DA, 0x22DB }, + { 0x22DB, 0x22DA }, + { 0x22DC, 0x22DD }, + { 0x22DD, 0x22DC }, + { 0x22DE, 0x22DF }, + { 0x22DF, 0x22DE }, + { 0x22E0, 0x22E1 }, + { 0x22E1, 0x22E0 }, + { 0x22E2, 0x22E3 }, + { 0x22E3, 0x22E2 }, + { 0x22E4, 0x22E5 }, + { 0x22E5, 0x22E4 }, + { 0x22E6, 0x22E7 }, + { 0x22E7, 0x22E6 }, + { 0x22E8, 0x22E9 }, + { 0x22E9, 0x22E8 }, + { 0x22EA, 0x22EB }, + { 0x22EB, 0x22EA }, + { 0x22EC, 0x22ED }, + { 0x22ED, 0x22EC }, + { 0x22F0, 0x22F1 }, + { 0x22F1, 0x22F0 }, + { 0x22F2, 0x22FA }, + { 0x22F3, 0x22FB }, + { 0x22F4, 0x22FC }, + { 0x22F6, 0x22FD }, + { 0x22F7, 0x22FE }, + { 0x22FA, 0x22F2 }, + { 0x22FB, 0x22F3 }, + { 0x22FC, 0x22F4 }, + { 0x22FD, 0x22F6 }, + { 0x22FE, 0x22F7 }, + { 0x2308, 0x2309 }, + { 0x2309, 0x2308 }, + { 0x230A, 0x230B }, + { 0x230B, 0x230A }, + { 0x2329, 0x232A }, + { 0x232A, 0x2329 }, + { 0x2768, 0x2769 }, + { 0x2769, 0x2768 }, + { 0x276A, 0x276B }, + { 0x276B, 0x276A }, + { 0x276C, 0x276D }, + { 0x276D, 0x276C }, + { 0x276E, 0x276F }, + { 0x276F, 0x276E }, + { 0x2770, 0x2771 }, + { 0x2771, 0x2770 }, + { 0x2772, 0x2773 }, + { 0x2773, 0x2772 }, + { 0x2774, 0x2775 }, + { 0x2775, 0x2774 }, + { 0x27C3, 0x27C4 }, + { 0x27C4, 0x27C3 }, + { 0x27C5, 0x27C6 }, + { 0x27C6, 0x27C5 }, + { 0x27C8, 0x27C9 }, + { 0x27C9, 0x27C8 }, + { 0x27D5, 0x27D6 }, + { 0x27D6, 0x27D5 }, + { 0x27DD, 0x27DE }, + { 0x27DE, 0x27DD }, + { 0x27E2, 0x27E3 }, + { 0x27E3, 0x27E2 }, + { 0x27E4, 0x27E5 }, + { 0x27E5, 0x27E4 }, + { 0x27E6, 0x27E7 }, + { 0x27E7, 0x27E6 }, + { 0x27E8, 0x27E9 }, + { 0x27E9, 0x27E8 }, + { 0x27EA, 0x27EB }, + { 0x27EB, 0x27EA }, + { 0x27EC, 0x27ED }, + { 0x27ED, 0x27EC }, + { 0x27EE, 0x27EF }, + { 0x27EF, 0x27EE }, + { 0x2983, 0x2984 }, + { 0x2984, 0x2983 }, + { 0x2985, 0x2986 }, + { 0x2986, 0x2985 }, + { 0x2987, 0x2988 }, + { 0x2988, 0x2987 }, + { 0x2989, 0x298A }, + { 0x298A, 0x2989 }, + { 0x298B, 0x298C }, + { 0x298C, 0x298B }, + { 0x298D, 0x2990 }, + { 0x298E, 0x298F }, + { 0x298F, 0x298E }, + { 0x2990, 0x298D }, + { 0x2991, 0x2992 }, + { 0x2992, 0x2991 }, + { 0x2993, 0x2994 }, + { 0x2994, 0x2993 }, + { 0x2995, 0x2996 }, + { 0x2996, 0x2995 }, + { 0x2997, 0x2998 }, + { 0x2998, 0x2997 }, + { 0x29B8, 0x2298 }, + { 0x29C0, 0x29C1 }, + { 0x29C1, 0x29C0 }, + { 0x29C4, 0x29C5 }, + { 0x29C5, 0x29C4 }, + { 0x29CF, 0x29D0 }, + { 0x29D0, 0x29CF }, + { 0x29D1, 0x29D2 }, + { 0x29D2, 0x29D1 }, + { 0x29D4, 0x29D5 }, + { 0x29D5, 0x29D4 }, + { 0x29D8, 0x29D9 }, + { 0x29D9, 0x29D8 }, + { 0x29DA, 0x29DB }, + { 0x29DB, 0x29DA }, + { 0x29F5, 0x2215 }, + { 0x29F8, 0x29F9 }, + { 0x29F9, 0x29F8 }, + { 0x29FC, 0x29FD }, + { 0x29FD, 0x29FC }, + { 0x2A2B, 0x2A2C }, + { 0x2A2C, 0x2A2B }, + { 0x2A2D, 0x2A2E }, + { 0x2A2E, 0x2A2D }, + { 0x2A34, 0x2A35 }, + { 0x2A35, 0x2A34 }, + { 0x2A3C, 0x2A3D }, + { 0x2A3D, 0x2A3C }, + { 0x2A64, 0x2A65 }, + { 0x2A65, 0x2A64 }, + { 0x2A79, 0x2A7A }, + { 0x2A7A, 0x2A79 }, + { 0x2A7D, 0x2A7E }, + { 0x2A7E, 0x2A7D }, + { 0x2A7F, 0x2A80 }, + { 0x2A80, 0x2A7F }, + { 0x2A81, 0x2A82 }, + { 0x2A82, 0x2A81 }, + { 0x2A83, 0x2A84 }, + { 0x2A84, 0x2A83 }, + { 0x2A8B, 0x2A8C }, + { 0x2A8C, 0x2A8B }, + { 0x2A91, 0x2A92 }, + { 0x2A92, 0x2A91 }, + { 0x2A93, 0x2A94 }, + { 0x2A94, 0x2A93 }, + { 0x2A95, 0x2A96 }, + { 0x2A96, 0x2A95 }, + { 0x2A97, 0x2A98 }, + { 0x2A98, 0x2A97 }, + { 0x2A99, 0x2A9A }, + { 0x2A9A, 0x2A99 }, + { 0x2A9B, 0x2A9C }, + { 0x2A9C, 0x2A9B }, + { 0x2AA1, 0x2AA2 }, + { 0x2AA2, 0x2AA1 }, + { 0x2AA6, 0x2AA7 }, + { 0x2AA7, 0x2AA6 }, + { 0x2AA8, 0x2AA9 }, + { 0x2AA9, 0x2AA8 }, + { 0x2AAA, 0x2AAB }, + { 0x2AAB, 0x2AAA }, + { 0x2AAC, 0x2AAD }, + { 0x2AAD, 0x2AAC }, + { 0x2AAF, 0x2AB0 }, + { 0x2AB0, 0x2AAF }, + { 0x2AB3, 0x2AB4 }, + { 0x2AB4, 0x2AB3 }, + { 0x2ABB, 0x2ABC }, + { 0x2ABC, 0x2ABB }, + { 0x2ABD, 0x2ABE }, + { 0x2ABE, 0x2ABD }, + { 0x2ABF, 0x2AC0 }, + { 0x2AC0, 0x2ABF }, + { 0x2AC1, 0x2AC2 }, + { 0x2AC2, 0x2AC1 }, + { 0x2AC3, 0x2AC4 }, + { 0x2AC4, 0x2AC3 }, + { 0x2AC5, 0x2AC6 }, + { 0x2AC6, 0x2AC5 }, + { 0x2ACD, 0x2ACE }, + { 0x2ACE, 0x2ACD }, + { 0x2ACF, 0x2AD0 }, + { 0x2AD0, 0x2ACF }, + { 0x2AD1, 0x2AD2 }, + { 0x2AD2, 0x2AD1 }, + { 0x2AD3, 0x2AD4 }, + { 0x2AD4, 0x2AD3 }, + { 0x2AD5, 0x2AD6 }, + { 0x2AD6, 0x2AD5 }, + { 0x2ADE, 0x22A6 }, + { 0x2AE3, 0x22A9 }, + { 0x2AE4, 0x22A8 }, + { 0x2AE5, 0x22AB }, + { 0x2AEC, 0x2AED }, + { 0x2AED, 0x2AEC }, + { 0x2AF7, 0x2AF8 }, + { 0x2AF8, 0x2AF7 }, + { 0x2AF9, 0x2AFA }, + { 0x2AFA, 0x2AF9 }, + { 0x2E02, 0x2E03 }, + { 0x2E03, 0x2E02 }, + { 0x2E04, 0x2E05 }, + { 0x2E05, 0x2E04 }, + { 0x2E09, 0x2E0A }, + { 0x2E0A, 0x2E09 }, + { 0x2E0C, 0x2E0D }, + { 0x2E0D, 0x2E0C }, + { 0x2E1C, 0x2E1D }, + { 0x2E1D, 0x2E1C }, + { 0x2E20, 0x2E21 }, + { 0x2E21, 0x2E20 }, + { 0x2E22, 0x2E23 }, + { 0x2E23, 0x2E22 }, + { 0x2E24, 0x2E25 }, + { 0x2E25, 0x2E24 }, + { 0x2E26, 0x2E27 }, + { 0x2E27, 0x2E26 }, + { 0x2E28, 0x2E29 }, + { 0x2E29, 0x2E28 }, + { 0x3008, 0x3009 }, + { 0x3009, 0x3008 }, + { 0x300A, 0x300B }, + { 0x300B, 0x300A }, + { 0x300C, 0x300D }, + { 0x300D, 0x300C }, + { 0x300E, 0x300F }, + { 0x300F, 0x300E }, + { 0x3010, 0x3011 }, + { 0x3011, 0x3010 }, + { 0x3014, 0x3015 }, + { 0x3015, 0x3014 }, + { 0x3016, 0x3017 }, + { 0x3017, 0x3016 }, + { 0x3018, 0x3019 }, + { 0x3019, 0x3018 }, + { 0x301A, 0x301B }, + { 0x301B, 0x301A }, + { 0xFE59, 0xFE5A }, + { 0xFE5A, 0xFE59 }, + { 0xFE5B, 0xFE5C }, + { 0xFE5C, 0xFE5B }, + { 0xFE5D, 0xFE5E }, + { 0xFE5E, 0xFE5D }, + { 0xFE64, 0xFE65 }, + { 0xFE65, 0xFE64 }, + { 0xFF08, 0xFF09 }, + { 0xFF09, 0xFF08 }, + { 0xFF1C, 0xFF1E }, + { 0xFF1E, 0xFF1C }, + { 0xFF3B, 0xFF3D }, + { 0xFF3D, 0xFF3B }, + { 0xFF5B, 0xFF5D }, + { 0xFF5D, 0xFF5B }, + { 0xFF5F, 0xFF60 }, + { 0xFF60, 0xFF5F }, + { 0xFF62, 0xFF63 }, + { 0xFF63, 0xFF62 } }; diff -r 681cd08dc0f7 -r e93288477c43 src/biditype.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/biditype.h Sun Jun 13 22:57:55 2010 +0000 @@ -0,0 +1,446 @@ + struct { + int from, to; + bidi_type_t type; + } bidi_type[] = { + { 0x0000, 0x0008, WEAK_BN }, + { 0x0009, 0x0009, NEUTRAL_S }, + { 0x000A, 0x000A, NEUTRAL_B }, + { 0x000B, 0x000B, NEUTRAL_S }, + { 0x000C, 0x000C, NEUTRAL_WS }, + { 0x000D, 0x000D, NEUTRAL_B }, + { 0x000E, 0x001B, WEAK_BN }, + { 0x001C, 0x001E, NEUTRAL_B }, + { 0x001F, 0x001F, NEUTRAL_S }, + { 0x0020, 0x0020, NEUTRAL_WS }, + { 0x0021, 0x0022, NEUTRAL_ON }, + { 0x0023, 0x0025, WEAK_ET }, + { 0x0026, 0x002A, NEUTRAL_ON }, + { 0x002B, 0x002B, WEAK_ES }, + { 0x002C, 0x002C, WEAK_CS }, + { 0x002D, 0x002D, WEAK_ES }, + { 0x002E, 0x002F, WEAK_CS }, + { 0x0030, 0x0039, WEAK_EN }, + { 0x003A, 0x003A, WEAK_CS }, + { 0x003B, 0x0040, NEUTRAL_ON }, + { 0x005B, 0x0060, NEUTRAL_ON }, + { 0x007B, 0x007E, NEUTRAL_ON }, + { 0x007F, 0x0084, WEAK_BN }, + { 0x0085, 0x0085, NEUTRAL_B }, + { 0x0086, 0x009F, WEAK_BN }, + { 0x00A0, 0x00A0, WEAK_CS }, + { 0x00A1, 0x00A1, NEUTRAL_ON }, + { 0x00A2, 0x00A5, WEAK_ET }, + { 0x00A6, 0x00A9, NEUTRAL_ON }, + { 0x00AB, 0x00AC, NEUTRAL_ON }, + { 0x00AD, 0x00AD, WEAK_BN }, + { 0x00AE, 0x00AF, NEUTRAL_ON }, + { 0x00B0, 0x00B1, WEAK_ET }, + { 0x00B2, 0x00B3, WEAK_EN }, + { 0x00B4, 0x00B4, NEUTRAL_ON }, + { 0x00B6, 0x00B8, NEUTRAL_ON }, + { 0x00B9, 0x00B9, WEAK_EN }, + { 0x00BB, 0x00BF, NEUTRAL_ON }, + { 0x00D7, 0x00D7, NEUTRAL_ON }, + { 0x00F7, 0x00F7, NEUTRAL_ON }, + { 0x02B9, 0x02BA, NEUTRAL_ON }, + { 0x02C2, 0x02CF, NEUTRAL_ON }, + { 0x02D2, 0x02DF, NEUTRAL_ON }, + { 0x02E5, 0x02ED, NEUTRAL_ON }, + { 0x02EF, 0x02FF, NEUTRAL_ON }, + { 0x0300, 0x036F, WEAK_NSM }, + { 0x0374, 0x0375, NEUTRAL_ON }, + { 0x037E, 0x0385, NEUTRAL_ON }, + { 0x0387, 0x0387, NEUTRAL_ON }, + { 0x03F6, 0x03F6, NEUTRAL_ON }, + { 0x0483, 0x0489, WEAK_NSM }, + { 0x058A, 0x058A, NEUTRAL_ON }, + { 0x0591, 0x05BD, WEAK_NSM }, + { 0x05BE, 0x05BE, STRONG_R }, + { 0x05BF, 0x05BF, WEAK_NSM }, + { 0x05C0, 0x05C0, STRONG_R }, + { 0x05C1, 0x05C2, WEAK_NSM }, + { 0x05C3, 0x05C3, STRONG_R }, + { 0x05C4, 0x05C5, WEAK_NSM }, + { 0x05C6, 0x05C6, STRONG_R }, + { 0x05C7, 0x05C7, WEAK_NSM }, + { 0x05D0, 0x05F4, STRONG_R }, + { 0x0600, 0x0603, WEAK_AN }, + { 0x0606, 0x0607, NEUTRAL_ON }, + { 0x0608, 0x0608, STRONG_AL }, + { 0x0609, 0x060A, WEAK_ET }, + { 0x060B, 0x060B, STRONG_AL }, + { 0x060C, 0x060C, WEAK_CS }, + { 0x060D, 0x060D, STRONG_AL }, + { 0x060E, 0x060F, NEUTRAL_ON }, + { 0x0610, 0x061A, WEAK_NSM }, + { 0x061B, 0x064A, STRONG_AL }, + { 0x064B, 0x065F, WEAK_NSM }, + { 0x0660, 0x0669, WEAK_AN }, + { 0x066A, 0x066A, WEAK_ET }, + { 0x066B, 0x066C, WEAK_AN }, + { 0x066D, 0x066F, STRONG_AL }, + { 0x0670, 0x0670, WEAK_NSM }, + { 0x0671, 0x06D5, STRONG_AL }, + { 0x06D6, 0x06DC, WEAK_NSM }, + { 0x06DD, 0x06DD, WEAK_AN }, + { 0x06DE, 0x06E4, WEAK_NSM }, + { 0x06E5, 0x06E6, STRONG_AL }, + { 0x06E7, 0x06E8, WEAK_NSM }, + { 0x06E9, 0x06E9, NEUTRAL_ON }, + { 0x06EA, 0x06ED, WEAK_NSM }, + { 0x06EE, 0x06EF, STRONG_AL }, + { 0x06F0, 0x06F9, WEAK_EN }, + { 0x06FA, 0x070D, STRONG_AL }, + { 0x070F, 0x070F, WEAK_AN }, + { 0x0710, 0x0710, STRONG_AL }, + { 0x0711, 0x0711, WEAK_NSM }, + { 0x0712, 0x072F, STRONG_AL }, + { 0x0730, 0x074A, WEAK_NSM }, + { 0x074D, 0x07A5, STRONG_AL }, + { 0x07A6, 0x07B0, WEAK_NSM }, + { 0x07B1, 0x07B1, STRONG_AL }, + { 0x07C0, 0x07EA, STRONG_R }, + { 0x07EB, 0x07F3, WEAK_NSM }, + { 0x07F4, 0x07F5, STRONG_R }, + { 0x07F6, 0x07F9, NEUTRAL_ON }, + { 0x07FA, 0x0815, STRONG_R }, + { 0x0816, 0x0819, WEAK_NSM }, + { 0x081A, 0x081A, STRONG_R }, + { 0x081B, 0x0823, WEAK_NSM }, + { 0x0824, 0x0824, STRONG_R }, + { 0x0825, 0x0827, WEAK_NSM }, + { 0x0828, 0x0828, STRONG_R }, + { 0x0829, 0x082D, WEAK_NSM }, + { 0x0830, 0x0858, STRONG_R }, + { 0x0859, 0x085B, WEAK_NSM }, + { 0x085E, 0x085E, STRONG_R }, + { 0x0900, 0x0902, WEAK_NSM }, + { 0x093A, 0x093A, WEAK_NSM }, + { 0x093C, 0x093C, WEAK_NSM }, + { 0x0941, 0x0948, WEAK_NSM }, + { 0x094D, 0x094D, WEAK_NSM }, + { 0x0951, 0x0957, WEAK_NSM }, + { 0x0962, 0x0963, WEAK_NSM }, + { 0x0981, 0x0981, WEAK_NSM }, + { 0x09BC, 0x09BC, WEAK_NSM }, + { 0x09C1, 0x09C4, WEAK_NSM }, + { 0x09CD, 0x09CD, WEAK_NSM }, + { 0x09E2, 0x09E3, WEAK_NSM }, + { 0x09F2, 0x09F3, WEAK_ET }, + { 0x09FB, 0x09FB, WEAK_ET }, + { 0x0A01, 0x0A02, WEAK_NSM }, + { 0x0A3C, 0x0A3C, WEAK_NSM }, + { 0x0A41, 0x0A51, WEAK_NSM }, + { 0x0A70, 0x0A71, WEAK_NSM }, + { 0x0A75, 0x0A82, WEAK_NSM }, + { 0x0ABC, 0x0ABC, WEAK_NSM }, + { 0x0AC1, 0x0AC8, WEAK_NSM }, + { 0x0ACD, 0x0ACD, WEAK_NSM }, + { 0x0AE2, 0x0AE3, WEAK_NSM }, + { 0x0AF1, 0x0AF1, WEAK_ET }, + { 0x0B01, 0x0B01, WEAK_NSM }, + { 0x0B3C, 0x0B3C, WEAK_NSM }, + { 0x0B3F, 0x0B3F, WEAK_NSM }, + { 0x0B41, 0x0B44, WEAK_NSM }, + { 0x0B4D, 0x0B56, WEAK_NSM }, + { 0x0B62, 0x0B63, WEAK_NSM }, + { 0x0B82, 0x0B82, WEAK_NSM }, + { 0x0BC0, 0x0BC0, WEAK_NSM }, + { 0x0BCD, 0x0BCD, WEAK_NSM }, + { 0x0BF3, 0x0BF8, NEUTRAL_ON }, + { 0x0BF9, 0x0BF9, WEAK_ET }, + { 0x0BFA, 0x0BFA, NEUTRAL_ON }, + { 0x0C3E, 0x0C40, WEAK_NSM }, + { 0x0C46, 0x0C56, WEAK_NSM }, + { 0x0C62, 0x0C63, WEAK_NSM }, + { 0x0C78, 0x0C7E, NEUTRAL_ON }, + { 0x0CBC, 0x0CBC, WEAK_NSM }, + { 0x0CCC, 0x0CCD, WEAK_NSM }, + { 0x0CE2, 0x0CE3, WEAK_NSM }, + { 0x0D41, 0x0D44, WEAK_NSM }, + { 0x0D4D, 0x0D4D, WEAK_NSM }, + { 0x0D62, 0x0D63, WEAK_NSM }, + { 0x0DCA, 0x0DCA, WEAK_NSM }, + { 0x0DD2, 0x0DD6, WEAK_NSM }, + { 0x0E31, 0x0E31, WEAK_NSM }, + { 0x0E34, 0x0E3A, WEAK_NSM }, + { 0x0E3F, 0x0E3F, WEAK_ET }, + { 0x0E47, 0x0E4E, WEAK_NSM }, + { 0x0EB1, 0x0EB1, WEAK_NSM }, + { 0x0EB4, 0x0EBC, WEAK_NSM }, + { 0x0EC8, 0x0ECD, WEAK_NSM }, + { 0x0F18, 0x0F19, WEAK_NSM }, + { 0x0F35, 0x0F35, WEAK_NSM }, + { 0x0F37, 0x0F37, WEAK_NSM }, + { 0x0F39, 0x0F39, WEAK_NSM }, + { 0x0F3A, 0x0F3D, NEUTRAL_ON }, + { 0x0F71, 0x0F7E, WEAK_NSM }, + { 0x0F80, 0x0F84, WEAK_NSM }, + { 0x0F86, 0x0F87, WEAK_NSM }, + { 0x0F8D, 0x0FBC, WEAK_NSM }, + { 0x0FC6, 0x0FC6, WEAK_NSM }, + { 0x102D, 0x1030, WEAK_NSM }, + { 0x1032, 0x1037, WEAK_NSM }, + { 0x1039, 0x103A, WEAK_NSM }, + { 0x103D, 0x103E, WEAK_NSM }, + { 0x1058, 0x1059, WEAK_NSM }, + { 0x105E, 0x1060, WEAK_NSM }, + { 0x1071, 0x1074, WEAK_NSM }, + { 0x1082, 0x1082, WEAK_NSM }, + { 0x1085, 0x1086, WEAK_NSM }, + { 0x108D, 0x108D, WEAK_NSM }, + { 0x109D, 0x109D, WEAK_NSM }, + { 0x135D, 0x135F, WEAK_NSM }, + { 0x1390, 0x1399, NEUTRAL_ON }, + { 0x1400, 0x1400, NEUTRAL_ON }, + { 0x1680, 0x1680, NEUTRAL_WS }, + { 0x169B, 0x169C, NEUTRAL_ON }, + { 0x1712, 0x1714, WEAK_NSM }, + { 0x1732, 0x1734, WEAK_NSM }, + { 0x1752, 0x1753, WEAK_NSM }, + { 0x1772, 0x1773, WEAK_NSM }, + { 0x17B7, 0x17BD, WEAK_NSM }, + { 0x17C6, 0x17C6, WEAK_NSM }, + { 0x17C9, 0x17D3, WEAK_NSM }, + { 0x17DB, 0x17DB, WEAK_ET }, + { 0x17DD, 0x17DD, WEAK_NSM }, + { 0x17F0, 0x180A, NEUTRAL_ON }, + { 0x180B, 0x180D, WEAK_NSM }, + { 0x180E, 0x180E, NEUTRAL_WS }, + { 0x18A9, 0x18A9, WEAK_NSM }, + { 0x1920, 0x1922, WEAK_NSM }, + { 0x1927, 0x1928, WEAK_NSM }, + { 0x1932, 0x1932, WEAK_NSM }, + { 0x1939, 0x193B, WEAK_NSM }, + { 0x1940, 0x1945, NEUTRAL_ON }, + { 0x19DE, 0x19FF, NEUTRAL_ON }, + { 0x1A17, 0x1A18, WEAK_NSM }, + { 0x1A56, 0x1A56, WEAK_NSM }, + { 0x1A58, 0x1A60, WEAK_NSM }, + { 0x1A62, 0x1A62, WEAK_NSM }, + { 0x1A65, 0x1A6C, WEAK_NSM }, + { 0x1A73, 0x1A7F, WEAK_NSM }, + { 0x1B00, 0x1B03, WEAK_NSM }, + { 0x1B34, 0x1B34, WEAK_NSM }, + { 0x1B36, 0x1B3A, WEAK_NSM }, + { 0x1B3C, 0x1B3C, WEAK_NSM }, + { 0x1B42, 0x1B42, WEAK_NSM }, + { 0x1B6B, 0x1B73, WEAK_NSM }, + { 0x1B80, 0x1B81, WEAK_NSM }, + { 0x1BA2, 0x1BA5, WEAK_NSM }, + { 0x1BA8, 0x1BA9, WEAK_NSM }, + { 0x1BE6, 0x1BE6, WEAK_NSM }, + { 0x1BE8, 0x1BE9, WEAK_NSM }, + { 0x1BED, 0x1BED, WEAK_NSM }, + { 0x1BEF, 0x1BF1, WEAK_NSM }, + { 0x1C2C, 0x1C33, WEAK_NSM }, + { 0x1C36, 0x1C37, WEAK_NSM }, + { 0x1CD0, 0x1CD2, WEAK_NSM }, + { 0x1CD4, 0x1CE0, WEAK_NSM }, + { 0x1CE2, 0x1CE8, WEAK_NSM }, + { 0x1CED, 0x1CED, WEAK_NSM }, + { 0x1DC0, 0x1DFF, WEAK_NSM }, + { 0x1FBD, 0x1FBD, NEUTRAL_ON }, + { 0x1FBF, 0x1FC1, NEUTRAL_ON }, + { 0x1FCD, 0x1FCF, NEUTRAL_ON }, + { 0x1FDD, 0x1FDF, NEUTRAL_ON }, + { 0x1FED, 0x1FEF, NEUTRAL_ON }, + { 0x1FFD, 0x1FFE, NEUTRAL_ON }, + { 0x2000, 0x200A, NEUTRAL_WS }, + { 0x200B, 0x200D, WEAK_BN }, + { 0x200F, 0x200F, STRONG_R }, + { 0x2010, 0x2027, NEUTRAL_ON }, + { 0x2028, 0x2028, NEUTRAL_WS }, + { 0x2029, 0x2029, NEUTRAL_B }, + { 0x202A, 0x202A, LRE }, + { 0x202B, 0x202B, RLE }, + { 0x202C, 0x202C, PDF }, + { 0x202D, 0x202D, LRO }, + { 0x202E, 0x202E, RLO }, + { 0x202F, 0x202F, WEAK_CS }, + { 0x2030, 0x2034, WEAK_ET }, + { 0x2035, 0x2043, NEUTRAL_ON }, + { 0x2044, 0x2044, WEAK_CS }, + { 0x2045, 0x205E, NEUTRAL_ON }, + { 0x205F, 0x205F, NEUTRAL_WS }, + { 0x2060, 0x206F, WEAK_BN }, + { 0x2070, 0x2070, WEAK_EN }, + { 0x2074, 0x2079, WEAK_EN }, + { 0x207A, 0x207B, WEAK_ES }, + { 0x207C, 0x207E, NEUTRAL_ON }, + { 0x2080, 0x2089, WEAK_EN }, + { 0x208A, 0x208B, WEAK_ES }, + { 0x208C, 0x208E, NEUTRAL_ON }, + { 0x20A0, 0x20B8, WEAK_ET }, + { 0x20D0, 0x20F0, WEAK_NSM }, + { 0x2100, 0x2101, NEUTRAL_ON }, + { 0x2103, 0x2106, NEUTRAL_ON }, + { 0x2108, 0x2109, NEUTRAL_ON }, + { 0x2114, 0x2114, NEUTRAL_ON }, + { 0x2116, 0x2118, NEUTRAL_ON }, + { 0x211E, 0x2123, NEUTRAL_ON }, + { 0x2125, 0x2125, NEUTRAL_ON }, + { 0x2127, 0x2127, NEUTRAL_ON }, + { 0x2129, 0x2129, NEUTRAL_ON }, + { 0x212E, 0x212E, WEAK_ET }, + { 0x213A, 0x213B, NEUTRAL_ON }, + { 0x2140, 0x2144, NEUTRAL_ON }, + { 0x214A, 0x214D, NEUTRAL_ON }, + { 0x2150, 0x215F, NEUTRAL_ON }, + { 0x2189, 0x2211, NEUTRAL_ON }, + { 0x2212, 0x2212, WEAK_ES }, + { 0x2213, 0x2213, WEAK_ET }, + { 0x2214, 0x2335, NEUTRAL_ON }, + { 0x237B, 0x2394, NEUTRAL_ON }, + { 0x2396, 0x2487, NEUTRAL_ON }, + { 0x2488, 0x249B, WEAK_EN }, + { 0x24EA, 0x26AB, NEUTRAL_ON }, + { 0x26AD, 0x27FF, NEUTRAL_ON }, + { 0x2900, 0x2B59, NEUTRAL_ON }, + { 0x2CE5, 0x2CEA, NEUTRAL_ON }, + { 0x2CEF, 0x2CF1, WEAK_NSM }, + { 0x2CF9, 0x2CFF, NEUTRAL_ON }, + { 0x2D7F, 0x2D7F, WEAK_NSM }, + { 0x2DE0, 0x2DFF, WEAK_NSM }, + { 0x2E00, 0x2FFB, NEUTRAL_ON }, + { 0x3000, 0x3000, NEUTRAL_WS }, + { 0x3001, 0x3004, NEUTRAL_ON }, + { 0x3008, 0x3020, NEUTRAL_ON }, + { 0x302A, 0x302F, WEAK_NSM }, + { 0x3030, 0x3030, NEUTRAL_ON }, + { 0x3036, 0x3037, NEUTRAL_ON }, + { 0x303D, 0x303F, NEUTRAL_ON }, + { 0x3099, 0x309A, WEAK_NSM }, + { 0x309B, 0x309C, NEUTRAL_ON }, + { 0x30A0, 0x30A0, NEUTRAL_ON }, + { 0x30FB, 0x30FB, NEUTRAL_ON }, + { 0x31C0, 0x31E3, NEUTRAL_ON }, + { 0x321D, 0x321E, NEUTRAL_ON }, + { 0x3250, 0x325F, NEUTRAL_ON }, + { 0x327C, 0x327E, NEUTRAL_ON }, + { 0x32B1, 0x32BF, NEUTRAL_ON }, + { 0x32CC, 0x32CF, NEUTRAL_ON }, + { 0x3377, 0x337A, NEUTRAL_ON }, + { 0x33DE, 0x33DF, NEUTRAL_ON }, + { 0x33FF, 0x33FF, NEUTRAL_ON }, + { 0x4DC0, 0x4DFF, NEUTRAL_ON }, + { 0xA490, 0xA4C6, NEUTRAL_ON }, + { 0xA60D, 0xA60F, NEUTRAL_ON }, + { 0xA66F, 0xA672, WEAK_NSM }, + { 0xA673, 0xA673, NEUTRAL_ON }, + { 0xA67C, 0xA67D, WEAK_NSM }, + { 0xA67E, 0xA67F, NEUTRAL_ON }, + { 0xA6F0, 0xA6F1, WEAK_NSM }, + { 0xA700, 0xA721, NEUTRAL_ON }, + { 0xA788, 0xA788, NEUTRAL_ON }, + { 0xA802, 0xA802, WEAK_NSM }, + { 0xA806, 0xA806, WEAK_NSM }, + { 0xA80B, 0xA80B, WEAK_NSM }, + { 0xA825, 0xA826, WEAK_NSM }, + { 0xA828, 0xA82B, NEUTRAL_ON }, + { 0xA838, 0xA839, WEAK_ET }, + { 0xA874, 0xA877, NEUTRAL_ON }, + { 0xA8C4, 0xA8C4, WEAK_NSM }, + { 0xA8E0, 0xA8F1, WEAK_NSM }, + { 0xA926, 0xA92D, WEAK_NSM }, + { 0xA947, 0xA951, WEAK_NSM }, + { 0xA980, 0xA982, WEAK_NSM }, + { 0xA9B3, 0xA9B3, WEAK_NSM }, + { 0xA9B6, 0xA9B9, WEAK_NSM }, + { 0xA9BC, 0xA9BC, WEAK_NSM }, + { 0xAA29, 0xAA2E, WEAK_NSM }, + { 0xAA31, 0xAA32, WEAK_NSM }, + { 0xAA35, 0xAA36, WEAK_NSM }, + { 0xAA43, 0xAA43, WEAK_NSM }, + { 0xAA4C, 0xAA4C, WEAK_NSM }, + { 0xAAB0, 0xAAB0, WEAK_NSM }, + { 0xAAB2, 0xAAB4, WEAK_NSM }, + { 0xAAB7, 0xAAB8, WEAK_NSM }, + { 0xAABE, 0xAABF, WEAK_NSM }, + { 0xAAC1, 0xAAC1, WEAK_NSM }, + { 0xABE5, 0xABE5, WEAK_NSM }, + { 0xABE8, 0xABE8, WEAK_NSM }, + { 0xABED, 0xABED, WEAK_NSM }, + { 0xFB1D, 0xFB1D, STRONG_R }, + { 0xFB1E, 0xFB1E, WEAK_NSM }, + { 0xFB1F, 0xFB28, STRONG_R }, + { 0xFB29, 0xFB29, WEAK_ES }, + { 0xFB2A, 0xFB4F, STRONG_R }, + { 0xFB50, 0xFD3D, STRONG_AL }, + { 0xFD3E, 0xFD3F, NEUTRAL_ON }, + { 0xFD50, 0xFDFC, STRONG_AL }, + { 0xFDFD, 0xFDFD, NEUTRAL_ON }, + { 0xFE00, 0xFE0F, WEAK_NSM }, + { 0xFE10, 0xFE19, NEUTRAL_ON }, + { 0xFE20, 0xFE26, WEAK_NSM }, + { 0xFE30, 0xFE4F, NEUTRAL_ON }, + { 0xFE50, 0xFE50, WEAK_CS }, + { 0xFE51, 0xFE51, NEUTRAL_ON }, + { 0xFE52, 0xFE52, WEAK_CS }, + { 0xFE54, 0xFE54, NEUTRAL_ON }, + { 0xFE55, 0xFE55, WEAK_CS }, + { 0xFE56, 0xFE5E, NEUTRAL_ON }, + { 0xFE5F, 0xFE5F, WEAK_ET }, + { 0xFE60, 0xFE61, NEUTRAL_ON }, + { 0xFE62, 0xFE63, WEAK_ES }, + { 0xFE64, 0xFE68, NEUTRAL_ON }, + { 0xFE69, 0xFE6A, WEAK_ET }, + { 0xFE6B, 0xFE6B, NEUTRAL_ON }, + { 0xFE70, 0xFEFC, STRONG_AL }, + { 0xFEFF, 0xFEFF, WEAK_BN }, + { 0xFF01, 0xFF02, NEUTRAL_ON }, + { 0xFF03, 0xFF05, WEAK_ET }, + { 0xFF06, 0xFF0A, NEUTRAL_ON }, + { 0xFF0B, 0xFF0B, WEAK_ES }, + { 0xFF0C, 0xFF0C, WEAK_CS }, + { 0xFF0D, 0xFF0D, WEAK_ES }, + { 0xFF0E, 0xFF0F, WEAK_CS }, + { 0xFF10, 0xFF19, WEAK_EN }, + { 0xFF1A, 0xFF1A, WEAK_CS }, + { 0xFF1B, 0xFF20, NEUTRAL_ON }, + { 0xFF3B, 0xFF40, NEUTRAL_ON }, + { 0xFF5B, 0xFF65, NEUTRAL_ON }, + { 0xFFE0, 0xFFE1, WEAK_ET }, + { 0xFFE2, 0xFFE4, NEUTRAL_ON }, + { 0xFFE5, 0xFFE6, WEAK_ET }, + { 0xFFE8, 0xFFFD, NEUTRAL_ON }, + { 0x10101, 0x10101, NEUTRAL_ON }, + { 0x10140, 0x1019B, NEUTRAL_ON }, + { 0x101FD, 0x101FD, WEAK_NSM }, + { 0x10800, 0x1091B, STRONG_R }, + { 0x1091F, 0x1091F, NEUTRAL_ON }, + { 0x10920, 0x10A00, STRONG_R }, + { 0x10A01, 0x10A0F, WEAK_NSM }, + { 0x10A10, 0x10A33, STRONG_R }, + { 0x10A38, 0x10A3F, WEAK_NSM }, + { 0x10A40, 0x10B35, STRONG_R }, + { 0x10B39, 0x10B3F, NEUTRAL_ON }, + { 0x10B40, 0x10C48, STRONG_R }, + { 0x10E60, 0x10E7E, WEAK_AN }, + { 0x11001, 0x11001, WEAK_NSM }, + { 0x11038, 0x11046, WEAK_NSM }, + { 0x11052, 0x11065, NEUTRAL_ON }, + { 0x11080, 0x11081, WEAK_NSM }, + { 0x110B3, 0x110B6, WEAK_NSM }, + { 0x110B9, 0x110BA, WEAK_NSM }, + { 0x1D167, 0x1D169, WEAK_NSM }, + { 0x1D173, 0x1D17A, WEAK_BN }, + { 0x1D17B, 0x1D182, WEAK_NSM }, + { 0x1D185, 0x1D18B, WEAK_NSM }, + { 0x1D1AA, 0x1D1AD, WEAK_NSM }, + { 0x1D200, 0x1D241, NEUTRAL_ON }, + { 0x1D242, 0x1D244, WEAK_NSM }, + { 0x1D245, 0x1D356, NEUTRAL_ON }, + { 0x1D6DB, 0x1D6DB, NEUTRAL_ON }, + { 0x1D715, 0x1D715, NEUTRAL_ON }, + { 0x1D74F, 0x1D74F, NEUTRAL_ON }, + { 0x1D789, 0x1D789, NEUTRAL_ON }, + { 0x1D7C3, 0x1D7C3, NEUTRAL_ON }, + { 0x1D7CE, 0x1D7FF, WEAK_EN }, + { 0x1F000, 0x1F0DF, NEUTRAL_ON }, + { 0x1F100, 0x1F10A, WEAK_EN }, + { 0x1F300, 0x1F48B, NEUTRAL_ON }, + { 0x1F48D, 0x1F523, NEUTRAL_ON }, + { 0x1F525, 0x1F773, NEUTRAL_ON }, + { 0xE0001, 0xE007F, WEAK_BN }, + { 0xE0100, 0xE01EF, WEAK_NSM } }; diff -r 681cd08dc0f7 -r e93288477c43 src/deps.mk --- a/src/deps.mk Thu Jun 10 22:43:47 2010 +0000 +++ b/src/deps.mk Sun Jun 13 22:57:55 2010 +0000 @@ -39,7 +39,8 @@ atimer.o: atimer.c atimer.h syssignal.h systime.h lisp.h blockinput.h \ $(config_h) -bidi.o: bidi.c buffer.h character.h dispextern.h lisp.h $(config_h) +bidi.o: bidi.c buffer.h character.h dispextern.h lisp.h \ + biditype.h bidimirror.h $(config_h) buffer.o: buffer.c buffer.h region-cache.h commands.h window.h \ $(INTERVALS_H) blockinput.h atimer.h systime.h character.h \ indent.h keyboard.h coding.h keymap.h frame.h lisp.h $(config_h) diff -r 681cd08dc0f7 -r e93288477c43 src/makefile.w32-in --- a/src/makefile.w32-in Thu Jun 10 22:43:47 2010 +0000 +++ b/src/makefile.w32-in Sun Jun 13 22:57:55 2010 +0000 @@ -344,6 +344,8 @@ $(SRC)/buffer.h \ $(SRC)/character.h \ $(SRC)/dispextern.h \ + $(SRC)/biditype.h \ + $(SRC)/bidimirror.h \ $(SRC)/w32gui.h $(BLD)/buffer.$(O) : \ diff -r 681cd08dc0f7 -r e93288477c43 src/window.c --- a/src/window.c Thu Jun 10 22:43:47 2010 +0000 +++ b/src/window.c Sun Jun 13 22:57:55 2010 +0000 @@ -7290,7 +7290,7 @@ DEFVAR_LISP ("recenter-redisplay", &Vrecenter_redisplay, doc: /* If non-nil, then the `recenter' command with a nil argument -the entire frame to be redrawn; the special value `tty' causes the +will redraw the entire frame; the special value `tty' causes the frame to be redrawn only if it is a tty frame. */); Vrecenter_redisplay = Qtty; diff -r 681cd08dc0f7 -r e93288477c43 test/ChangeLog --- a/test/ChangeLog Thu Jun 10 22:43:47 2010 +0000 +++ b/test/ChangeLog Sun Jun 13 22:57:55 2010 +0000 @@ -1,3 +1,7 @@ +2010-06-11 Chong Yidong + + * comint-testsuite.el: New file. + 2010-06-02 Stefan Monnier * indent: New dir. diff -r 681cd08dc0f7 -r e93288477c43 test/comint-testsuite.el --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/test/comint-testsuite.el Sun Jun 13 22:57:55 2010 +0000 @@ -0,0 +1,59 @@ +;;; bytecomp-testsuite.el + +;; Copyright (C) 2010 Free Software Foundation, Inc. + +;; 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 3 of the License, 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. If not, see . + +;;; Commentary: + +;; Tests for comint and related modes. + +;;; Code: + +(require 'comint) + +(defun comint-testsuite-run () + (interactive) + (with-output-to-temp-buffer "*comint test*" + (comint-testsuite--test-comint-password-prompt-regexp))) + +(defun comint-testsuite--test-comint-password-prompt-regexp () + (interactive) + (let ((password-strings + '("foo@example.net's password: " ;ssh + "Password for foo@example.org: " ; knit + "Kerberos password for devnull/root GNU.ORG: " ; ksu + "Enter passphrase: " ; ssh-add + "Enter passphrase (empty for no passphrase): " ; ssh-keygen + "Enter same passphrase again: " ; ssh-keygen + "Passphrase for key root@GNU.ORG: " ; plink + "[sudo] password for user:" ; Ubuntu sudo + "Password (again):" + "Enter password:")) + fail) + (dolist (str password-strings) + (unless (string-match comint-password-prompt-regexp str) + (setq fail t) + (princ (format " ERROR: comint-password-prompt-regexp did not match %s\n" + str)))) + (if fail + (princ "FAILED: comint-password-prompt-regexp test\n") + (princ "PASSED: comint-password-prompt-regexp test\n")))) + +(provide 'comint-testsuite) + +;;; comint-testsuite.el ends here +