summaryrefslogtreecommitdiffstats
path: root/gnu/usr.bin/cvs/contrib/pcl-cvs/pcl-cvs.el
blob: 99da3695ad7d0301011df64bb9258c8848259930 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
1001
1002
1003
1004
1005
1006
1007
1008
1009
1010
1011
1012
1013
1014
1015
1016
1017
1018
1019
1020
1021
1022
1023
1024
1025
1026
1027
1028
1029
1030
1031
1032
1033
1034
1035
1036
1037
1038
1039
1040
1041
1042
1043
1044
1045
1046
1047
1048
1049
1050
1051
1052
1053
1054
1055
1056
1057
1058
1059
1060
1061
1062
1063
1064
1065
1066
1067
1068
1069
1070
1071
1072
1073
1074
1075
1076
1077
1078
1079
1080
1081
1082
1083
1084
1085
1086
1087
1088
1089
1090
1091
1092
1093
1094
1095
1096
1097
1098
1099
1100
1101
1102
1103
1104
1105
1106
1107
1108
1109
1110
1111
1112
1113
1114
1115
1116
1117
1118
1119
1120
1121
1122
1123
1124
1125
1126
1127
1128
1129
1130
1131
1132
1133
1134
1135
1136
1137
1138
1139
1140
1141
1142
1143
1144
1145
1146
1147
1148
1149
1150
1151
1152
1153
1154
1155
1156
1157
1158
1159
1160
1161
1162
1163
1164
1165
1166
1167
1168
1169
1170
1171
1172
1173
1174
1175
1176
1177
1178
1179
1180
1181
1182
1183
1184
1185
1186
1187
1188
1189
1190
1191
1192
1193
1194
1195
1196
1197
1198
1199
1200
1201
1202
1203
1204
1205
1206
1207
1208
1209
1210
1211
1212
1213
1214
1215
1216
1217
1218
1219
1220
1221
1222
1223
1224
1225
1226
1227
1228
1229
1230
1231
1232
1233
1234
1235
1236
1237
1238
1239
1240
1241
1242
1243
1244
1245
1246
1247
1248
1249
1250
1251
1252
1253
1254
1255
1256
1257
1258
1259
1260
1261
1262
1263
1264
1265
1266
1267
1268
1269
1270
1271
1272
1273
1274
1275
1276
1277
1278
1279
1280
1281
1282
1283
1284
1285
1286
1287
1288
1289
1290
1291
1292
1293
1294
1295
1296
1297
1298
1299
1300
1301
1302
1303
1304
1305
1306
1307
1308
1309
1310
1311
1312
1313
1314
1315
1316
1317
1318
1319
1320
1321
1322
1323
1324
1325
1326
1327
1328
1329
1330
1331
1332
1333
1334
1335
1336
1337
1338
1339
1340
1341
1342
1343
1344
1345
1346
1347
1348
1349
1350
1351
1352
1353
1354
1355
1356
1357
1358
1359
1360
1361
1362
1363
1364
1365
1366
1367
1368
1369
1370
1371
1372
1373
1374
1375
1376
1377
1378
1379
1380
1381
1382
1383
1384
1385
1386
1387
1388
1389
1390
1391
1392
1393
1394
1395
1396
1397
1398
1399
1400
1401
1402
1403
1404
1405
1406
1407
1408
1409
1410
1411
1412
1413
1414
1415
1416
1417
1418
1419
1420
1421
1422
1423
1424
1425
1426
1427
1428
1429
1430
1431
1432
1433
1434
1435
1436
1437
1438
1439
1440
1441
1442
1443
1444
1445
1446
1447
1448
1449
1450
1451
1452
1453
1454
1455
1456
1457
1458
1459
1460
1461
1462
1463
1464
1465
1466
1467
1468
1469
1470
1471
1472
1473
1474
1475
1476
;;; pcl-cvs.el,v 1.2 1992/04/07 20:49:19 berliner Exp
;;; pcl-cvs.el -- A Front-end to CVS 1.3 or later.  Release 1.02.
;;; Copyright (C) 1991, 1992  Per Cederqvist
;;;
;;; This program is free software; you can redistribute it and/or modify
;;; it under the terms of the GNU General Public License as published by
;;; the Free Software Foundation; either version 2 of the License, or
;;; (at your option) any later version.
;;;
;;; This program 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 this program; if not, write to the Free Software
;;; Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.

;;;; See below for installation instructions.
;;;;
;;;; There is an TeXinfo file that describes this package.  The GNU
;;;; General Public License is included in that file.  You should read
;;;; it to get the most from this package.

;;; Don't try to use this with CVS 1.2 or earlier. It won't work. Get
;;; CVS 1.3.

;;; Mail questions and bug reports to ceder@lysator.liu.se.

(require 'cookie)
(provide 'pcl-cvs)

;;; -------------------------------------------------------
;;;	    START OF THINGS TO CHECK WHEN INSTALLING

(defvar cvs-program "/usr/gnu/bin/cvs"
  "*Full path to the cvs executable.")

(defvar cvs-diff-program "/usr/gnu/bin/diff"
  "*Full path to the diff program.")

(defvar cvs-rm-program "/usr/gnu/bin/rm"
  "*Full path to the rm program. Typically /bin/rm.")

;; Uncomment the following line if you are running on 18.57 or earlier.
;(setq delete-exited-processes nil)
;; Emacs version 18.57 and earlier is likely to crash if
;; delete-exited-processes is t, since the sentinel uses lots of
;; memory, and 18.57 forgets to GCPROT a variable if
;; delete-exited-processes is t.

;;;	     END OF THINGS TO CHECK WHEN INSTALLING
;;; --------------------------------------------------------

(defvar cvs-bakprefix ".#"
  "The prefix that CVS prepends to files when rcsmerge'ing.")

(defvar cvs-erase-input-buffer nil
  "*Non-nil if input buffers should be cleared before asking for new info.")

(defvar cvs-auto-remove-handled nil
  "*Non-nil if cvs-remove-handled should be called automatically.
If this is set to any non-nil value entries that does not need to be
checked in will be removed from the *cvs* buffer after every cvs-commit
command.")

(defconst cvs-cursor-column 14
  "Column to position cursor in in cvs-mode.
Column 0 is left-most column.")

(defvar cvs-mode-map nil
  "Keymap for the cvs mode.")

(defvar cvs-edit-mode-map nil
  "Keymap for the cvs edit mode (used when editing cvs log messages).")

(defvar cvs-buffer-name "*cvs*"
  "Name of the cvs buffer.")

(defvar cvs-commit-prompt-buffer "*cvs-commit-message*"
  "Name of buffer in which the user is prompted for a log message when
committing files.")

(defvar cvs-temp-buffer-name "*cvs-tmp*"
  "*Name of the cvs temporary buffer.
Output from cvs is placed here by synchronous commands.")

(defvar cvs-cvs-diff-flags nil
  "*List of strings to use as flags to pass to ``cvs diff''.
Used by cvs-diff-cvs.
Set this to '("-u") to get a Unidiff format, or '("-c") to get context diffs.")

(defvar cvs-status-flags nil
  "*List of strings to pass to ``cvs status''.")

(defvar cvs-log-flags nil
  "*List of strings to pass to ``cvs log''.")

(defvar cvs-diff-flags nil
  "*List of strings to use as flags to pass to ``diff''.
Do not confuse with cvs-cvs-diff-flags.  Used by cvs-diff-backup.")

(defvar cvs-buffers-to-delete nil
  "List of temporary buffers that should be discarded as soon as possible.
Due to a bug in emacs 18.57 the sentinel can't discard them reliably.")

;; You are NOT allowed to disable this message by default.  However, you
;; are encouraged to inform your users that by adding
;;	(setq cvs-inhibit-copyright-message t)
;; to their .emacs they can get rid of it.  Just don't add that line
;; to your default.el!
(defvar cvs-inhibit-copyright-message nil
  "*Don't display a Copyright message in the ``*cvs*'' buffer.")

(defvar cvs-startup-message
  (if cvs-inhibit-copyright-message
      "PCL-CVS release 1.02"
    "PCL-CVS release 1.02.  Copyright (C) 1992 Per Cederqvist
Pcl-cvs comes with absolutely no warranty; for details consult the manual.
This is free software, and you are welcome to redistribute it under certain
conditions; again, consult the TeXinfo manual for details.")
  "*Startup message for CVS.")

(defvar cvs-cvs-buffer nil
  "Internal to pcl-cvs.el.
This variable exists in the *cvs-commit-message* buffer and names
the *cvs* buffer.")

;;; The cvs data structure:
;;;
;;; When the `cvs update' is ready we parse the output. Every file
;;; that is affected in some way is added as a cookie of fileinfo
;;; (as defined below).
;;;

;;; cvs-fileinfo
;;;
;;;  marked		 t/nil
;;;  type		 One of
;;;			   UPDATED    - file copied from repository
;;;			   MODIFIED   - modified by you, unchanged in
;;;					repository
;;;			   ADDED      - added by you, not yet committed
;;;			   REMOVED    - removed by you, not yet committed
;;;			   CVS-REMOVED- removed, since file no longer exists
;;;					in the repository.
;;;			   MERGED     - successful merge
;;;			   CONFLICT   - conflict when merging
;;;			   REM-CONFLICT-removed in repository, changed locally.
;;;			   MOD-CONFLICT-removed locally, changed in repository.
;;;			   DIRCHANGE  - A change of directory.
;;;			   UNKNOWN    - An unknown file.
;;;			   MOVE-AWAY  - A file that is in the way.
;;;			   REPOS-MISSING- The directory is removed from the
;;;					  repository. Go fetch a backup.
;;;  dir		 Directory the file resides in. Should not end with
;;;			 slash.
;;;  file-name		 The file name.
;;;  backup-file	 Name of the backup file if MERGED or CONFLICT.
;;;  cvs-diff-buffer	 A buffer that contains a 'cvs diff file'.
;;;  backup-diff-buffer	 A buffer that contains a 'diff file backup-file'.
;;;  full-log		 The output from cvs, unparsed.
;;;  mod-time		 Modification time of file used for *-diff-buffer.
;;;  handled		 True if this file doesn't require further action.
;;; 
;;; Constructor:

;;; cvs-fileinfo

;;; Constructor:

(defun cvs-create-fileinfo (type
			    dir
			    file-name
			    full-log)
  "Create a fileinfo from all parameters.
Arguments: TYPE DIR FILE-NAME FULL-LOG.
A fileinfo has the following fields:

  marked	    t/nil
  type		      One of
			UPDATED	   - file copied from repository
			MODIFIED   - modified by you, unchanged in
				     repository
			ADDED	   - added by you, not yet committed
			REMOVED	   - removed by you, not yet committed
			CVS-REMOVED- removed, since file no longer exists
				     in the repository.
			MERGED	   - successful merge
			CONFLICT   - conflict when merging
			REM-CONFLICT-removed in repository, but altered
				     locally.
			MOD-CONFLICT-removed locally, changed in repository.
			DIRCHANGE  - A change of directory.
			UNKNOWN	   - An unknown file.
			MOVE-AWAY  - A file that is in the way.
			REPOS-MISSING- The directory has vanished from the
				       repository.
  dir		      Directory the file resides in. Should not end with slash.
  file-name	      The file name.
  backup-file	      Name of the backup file if MERGED or CONFLICT.
  cvs-diff-buffer     A buffer that contains a 'cvs diff file'.
  backup-diff-buffer  A buffer that contains a 'diff file backup-file'.
  full-log	      The output from cvs, unparsed.
  mod-time	      Modification time of file used for *-diff-buffer.
  handled	      True if this file doesn't require further action."
  (cons
   'CVS-FILEINFO
   (vector nil nil type dir file-name nil nil nil full-log nil)))


;;; Selectors:

(defun cvs-fileinfo->handled (cvs-fileinfo)
  "Get the  `handled' field from CVS-FILEINFO."
  (elt (cdr cvs-fileinfo) 0))

(defun cvs-fileinfo->marked (cvs-fileinfo)
  "Check if CVS-FILEINFO is marked."
  (elt (cdr cvs-fileinfo) 1))

(defun cvs-fileinfo->type (cvs-fileinfo)
  "Get type from CVS-FILEINFO.
Type is one of UPDATED, MODIFIED, ADDED, REMOVED, CVS-REMOVED, MERGED,
CONFLICT, REM-CONFLICT, MOD-CONFLICT, DIRCHANGE, UNKNOWN, MOVE-AWAY
or REPOS-MISSING."
  (elt (cdr cvs-fileinfo) 2))

(defun cvs-fileinfo->dir (cvs-fileinfo)
  "Get dir from CVS-FILEINFO.
The directory name does not end with a slash. "
  (elt (cdr cvs-fileinfo) 3))

(defun cvs-fileinfo->file-name (cvs-fileinfo)
  "Get file-name from CVS-FILEINFO."
  (elt (cdr cvs-fileinfo) 4))

(defun cvs-fileinfo->backup-file (cvs-fileinfo)
  "Get backup-file from CVS-FILEINFO."
  (elt (cdr cvs-fileinfo) 5))

(defun cvs-fileinfo->cvs-diff-buffer (cvs-fileinfo)
  "Get cvs-diff-buffer from CVS-FILEINFO."
  (elt (cdr cvs-fileinfo) 6))

(defun cvs-fileinfo->backup-diff-buffer (cvs-fileinfo)
  "Get backup-diff-buffer from CVS-FILEINFO."
  (elt (cdr cvs-fileinfo) 7))

(defun cvs-fileinfo->full-log (cvs-fileinfo)
  "Get full-log from CVS-FILEINFO."
  (elt (cdr cvs-fileinfo) 8))

(defun cvs-fileinfo->mod-time (cvs-fileinfo)
  "Get mod-time from CVS-FILEINFO."
  (elt (cdr cvs-fileinfo) 9))

;;; Modifiers:

(defun cvs-set-fileinfo->handled (cvs-fileinfo newval)
  "Set handled in CVS-FILEINFO to NEWVAL."
  (aset (cdr cvs-fileinfo) 0 newval))

(defun cvs-set-fileinfo->marked (cvs-fileinfo newval)
  "Set marked in CVS-FILEINFO to NEWVAL."
  (aset (cdr cvs-fileinfo) 1 newval))

(defun cvs-set-fileinfo->type (cvs-fileinfo newval)
  "Set type in CVS-FILEINFO to NEWVAL."
  (aset (cdr cvs-fileinfo) 2 newval))

(defun cvs-set-fileinfo->dir (cvs-fileinfo newval)
  "Set dir in CVS-FILEINFO to NEWVAL.
The directory should now end with a slash."
  (aset (cdr cvs-fileinfo) 3 newval))

(defun cvs-set-fileinfo->file-name (cvs-fileinfo newval)
  "Set file-name in CVS-FILEINFO to NEWVAL."
  (aset (cdr cvs-fileinfo) 4 newval))

(defun cvs-set-fileinfo->backup-file (cvs-fileinfo newval)
  "Set backup-file in CVS-FILEINFO to NEWVAL."
  (aset (cdr cvs-fileinfo) 5 newval))

(defun cvs-set-fileinfo->cvs-diff-buffer (cvs-fileinfo newval)
  "Set cvs-diff-buffer in CVS-FILEINFO to NEWVAL."
  (aset (cdr cvs-fileinfo) 6 newval))

(defun cvs-set-fileinfo->backup-diff-buffer (cvs-fileinfo newval)
  "Set backup-diff-buffer in CVS-FILEINFO to NEWVAL."
  (aset (cdr cvs-fileinfo) 7 newval))

(defun cvs-set-fileinfo->full-log (cvs-fileinfo newval)
  "Set full-log in CVS-FILEINFO to NEWVAL."
  (aset (cdr cvs-fileinfo) 8 newval))

(defun cvs-set-fileinfo->mod-time (cvs-fileinfo newval)
  "Set full-log in CVS-FILEINFO to NEWVAL."
  (aset (cdr cvs-fileinfo) 9 newval))



;;; Predicate:

(defun cvs-fileinfo-p (object)
  "Return t if OBJECT is a cvs-fileinfo."
  (eq (car-safe object) 'CVS-FILEINFO))

;;;; End of types.

(defun cvs-use-temp-buffer ()
  "Display a temporary buffer in another window and select it.
The selected window will not be changed.  The temporary buffer will
be erased and writable."

  (display-buffer (get-buffer-create cvs-temp-buffer-name))
  (set-buffer cvs-temp-buffer-name)
  (setq buffer-read-only nil)
  (erase-buffer))

; Too complicated to handle all the cases that are generated.
; Maybe later.
;(defun cvs-examine (directory &optional local)
;  "Run a 'cvs -n update' in the current working 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.
;If optional prefix argument LOCAL is non-nil, 'cvs update -l' is run."
;  (interactive (list (read-file-name "CVS Update (directory): "
;				     nil default-directory nil)
;		     current-prefix-arg))
;  (cvs-do-update directory local 'noupdate))

(defun cvs-update (directory &optional local)
  "Run a 'cvs update' in the current working directory. Feed the
output to a *cvs* buffer and run cvs-mode on it.
If optional prefix argument LOCAL is non-nil, 'cvs update -l' is run."
  (interactive (list (read-file-name "CVS Update (directory): "
				     nil default-directory nil)
		     current-prefix-arg))
  (cvs-do-update directory local nil))

(defun cvs-filter (predicate list &rest extra-args)
  "Apply PREDICATE to each element on LIST.
Args: PREDICATE LIST &rest EXTRA-ARGS.
Return a new list consisting of those elements that PREDICATE
returns non-nil for.

If more than two arguments are given the remaining args are
passed to PREDICATE."
  ;; Avoid recursion - this should work for LONG lists also!
  (let* ((head (cons 'dummy-header nil))
	 (tail head))
    (while list
      (if (apply predicate (car list) extra-args)
	  (setq tail (setcdr tail (list (car list)))))
      (setq list (cdr list)))
    (cdr head)))

(defun cvs-update-no-prompt ()
  "Run cvs update in current directory."
  (interactive)
  (cvs-do-update default-directory nil nil))

(defun cvs-do-update (directory local dont-change-disc)
  "Do a 'cvs update' in DIRECTORY.
If LOCAL is non-nil 'cvs update -l' is executed.
If DONT-CHANGE-DISC is non-nil 'cvs -n update' is executed.
Both LOCAL and DONT-CHANGE-DISC may be non-nil simultaneously.

*Note*: DONT-CHANGE-DISC does not yet work. The parser gets confused."
  (save-some-buffers)
  (let* ((this-dir (file-name-as-directory (expand-file-name directory)))
	 (use-this-window (equal (buffer-name (current-buffer))
				 cvs-buffer-name))
	 (update-buffer (generate-new-buffer
			 (concat (file-name-nondirectory
				  (substring this-dir 0 -1))
				 "-update")))
	 cvs-process args)

    ;; The *cvs* buffer is killed to avoid confusion - is the update ready
    ;; or not?
    (if (get-buffer cvs-buffer-name)
	(kill-buffer cvs-buffer-name))

    ;; Generate "-n update -l".
    (if local (setq args (list "-l")))
    (setq args (cons "update" args))
    (if dont-change-disc (setq args (cons "-n" args)))

    ;; Set up the buffer that receives the output from "cvs update".
    (if use-this-window
	(switch-to-buffer update-buffer)
      (set-buffer update-buffer)
      (display-buffer update-buffer))

    (setq default-directory this-dir)
    (setq cvs-process
	  (let ((process-connection-type nil)) ; Use a pipe, not a pty.
	    (apply 'start-process "cvs" update-buffer cvs-program args)))

    (setq mode-line-process
	  (concat ": "
		  (symbol-name (process-status cvs-process))))
    (set-buffer-modified-p (buffer-modified-p))	; Update the mode line.
    (set-process-sentinel cvs-process 'cvs-sentinel)

    ;; Work around a bug in emacs 18.57 and earlier.
    (setq cvs-buffers-to-delete
	  (cvs-delete-unused-temporary-buffers cvs-buffers-to-delete))))

(defun cvs-delete-unused-temporary-buffers (list)
  "Delete all buffers on LIST that is not visible.
Return a list of all buffers that still is alive."

  (cond
   ((null list) nil)
   ((get-buffer-window (car list))
    (cons (car list)
	  (cvs-delete-unused-temporary-buffers (cdr list))))
   (t
    (kill-buffer (car list))
    (cvs-delete-unused-temporary-buffers (cdr list)))))


(put 'cvs-mode 'mode-class 'special)

(defun cvs-mode ()
  "\\<cvs-mode-map>Mode used for pcl-cvs, a frontend to CVS.

To get the *cvs* buffer you should use ``\\[cvs-update]''.

Full documentation is in the TeXinfo file.  These are the most useful commands:

\\[cookie-previous-cookie] Move up.                    \\[cookie-next-cookie] Move down.
\\[cvs-commit]   Commit file.                \\[cvs-update-no-prompt]   Reupdate directory.
\\[cvs-mark]   Mark file/dir.              \\[cvs-unmark]   Unmark file/dir.
\\[cvs-mark-all-files]   Mark all files.             \\[cvs-unmark-all-files]   Unmark all files.
\\[cvs-find-file]   Edit file/run Dired.        \\[cvs-find-file-other-window]   Find file or run Dired in other window.
\\[cvs-remove-handled]   Remove processed entries.   \\[cvs-add-change-log-entry-other-window]   Write ChangeLog in other window.
\\[cvs-add]   Add to repository.          \\[cvs-remove-file]   Remove file.
\\[cvs-diff-cvs]   Diff between base revision. \\[cvs-diff-backup]   Diff backup file.
\\[cvs-acknowledge] Delete line from buffer.    \\[cvs-ignore]   Add file to the .cvsignore file.
\\[cvs-log]   Run ``cvs log''.            \\[cvs-status]   Run ``cvs status''.

Entry to this mode runs cvs-mode-hook.
This description is updated for release 1.02 of pcl-cvs.
All bindings:
\\{cvs-mode-map}"
  (interactive)
  (setq major-mode 'cvs-mode)
  (setq mode-name "CVS")
  (setq buffer-read-only nil)
  (buffer-flush-undo (current-buffer))
  (make-local-variable 'goal-column)
  (setq goal-column cvs-cursor-column)
  (use-local-map cvs-mode-map)
  (run-hooks 'cvs-mode-hook))

(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."
  (cond
   ((null (buffer-name (process-buffer proc)))
    ;; buffer killed
    (set-process-buffer proc nil))
   ((memq (process-status proc) '(signal exit))
    (let* ((obuf (current-buffer))
	   (omax (point-max))
	   (opoint (point)))
      ;; save-excursion isn't the right thing if
      ;;  process-buffer is current-buffer
      (unwind-protect
	  (progn
	    (set-buffer (process-buffer proc))
	    (setq mode-line-process
		  (concat ": "
			  (symbol-name (process-status proc))))
	    (cvs-parse-buffer)
	    (setq cvs-buffers-to-delete
		  (cons (process-buffer proc) cvs-buffers-to-delete)))
	(set-buffer-modified-p (buffer-modified-p)))
      (if (equal obuf (process-buffer proc))
	  nil
	(set-buffer (process-buffer proc))
	(if (< opoint omax)
	    (goto-char opoint))
	(set-buffer obuf))))))

(defun cvs-skip-line (regexp errormsg &optional arg)
  "Like forward-line, but check that the skipped line matches REGEXP.
If it doesn't match REGEXP (error ERRORMSG) is called.
If optional ARG, a number, is given the ARGth parenthesized expression
in the REGEXP is returned as a string.
Point should be in column 1 when this function is called."
  (cond
   ((looking-at regexp)
    (forward-line 1)
    (if arg
	(buffer-substring (match-beginning arg)
			  (match-end arg))))
   (t
    (error errormsg))))

(defun cvs-get-current-dir (dirname)
  "Return current working directory, suitable for cvs-parse-buffer.
Args: DIRNAME.
Concatenates default-directory and DIRNAME to form an absolute path."
  (if (string= "." dirname)
      (substring default-directory 0 -1)
    (concat default-directory dirname)))


(defun cvs-parse-buffer ()
  "Parse the current buffer and select a *cvs* buffer.
Signals an error if unexpected output was detected in the buffer."
  (goto-char (point-min))
  (let ((buf (get-buffer-create cvs-buffer-name))
	(current-dir default-directory)
	(root-dir default-directory)
	(parse-buf (current-buffer)))

    (cookie-create
     buf 'cvs-pp cvs-startup-message	;Se comment above cvs-startup-message.
     "---------- End -----")

    (cookie-enter-first
     buf
     (cvs-create-fileinfo
      'DIRCHANGE current-dir
      nil ""))

    (while (< (point) (point-max))
      (cond

       ;; CVS is descending a subdirectory.

       ((looking-at "cvs update: Updating \\(.*\\)$")
	(setq current-dir
	      (cvs-get-current-dir
	       (buffer-substring (match-beginning 1) (match-end 1))))

	;; Omit empty directories.
	(if (eq (cvs-fileinfo->type (cookie-last buf))
		'DIRCHANGE)
	    (cookie-delete-last buf))

	(cookie-enter-last
	 buf
	 (cvs-create-fileinfo
	  'DIRCHANGE current-dir
	  nil (buffer-substring (match-beginning 0)
				(match-end 0))))
	(forward-line 1))

       ;; File removed, since it is removed (by third party) in repository.
       
       ((or (looking-at "cvs update: warning: \\(.*\\) is not (any longer) \
pertinent")
	    (looking-at "cvs update: \\(.*\\) is no longer in the repository"))
	(cookie-enter-last
	 buf
	 (cvs-create-fileinfo
	  'CVS-REMOVED current-dir
	  (file-name-nondirectory
	   (buffer-substring (match-beginning 1) (match-end 1)))
	  (buffer-substring (match-beginning 0)
			    (match-end 0))))
	(forward-line 1))

       ;; File removed by you, but recreated by cvs. Ignored.

       ((looking-at "cvs update: warning: .* was lost$")
	(forward-line 1))

       ;; A file that has been created by you, but added to the cvs
       ;; repository by another.

       ((looking-at "^cvs update: move away \\(.*\\); it is in the way$")
	(cookie-enter-last
	 buf
	 (cvs-create-fileinfo
	  'MOVE-AWAY current-dir
	  (file-name-nondirectory
	   (buffer-substring (match-beginning 1) (match-end 1)))
	  (buffer-substring (match-beginning 0)
			    (match-end 0))))
	(forward-line 1))

       ;; Empty line. Probably inserted by mistake by user (or developer :-)
       ;; Ignore.

       ((looking-at "^$")
	(forward-line 1))

       ;; Cvs waits for a lock. Ignore.

       ((looking-at
	 "^cvs update: \\[..:..:..\\] waiting for .*lock in ")
	(forward-line 1))

       ;; File removed in repository, but edited by you.

       ((looking-at
	 "cvs update: conflict: \\(.*\\) is modified but no longer \
in the repository$")
	(cookie-enter-last
	 buf
	 (cvs-create-fileinfo
	  'REM-CONFLICT current-dir
	  (file-name-nondirectory
	   (buffer-substring (match-beginning 1) (match-end 1)))
	  (buffer-substring (match-beginning 0)
			    (match-end 0))))
	(forward-line 1))

       ((looking-at
	 "cvs update: conflict: removed \\(.*\\) was modified by second party")
	(cvs-create-fileinfo
	 'MOD-CONFLICT current-dir
	 (buffer-substring (match-beginning 1) (match-end 1))
	 (buffer-substring (match-beginning 0) (match-end 0)))
	(forward-line 1))

       ((looking-at "cvs update: in directory ")
	(let ((start (point)))
	  (forward-line 1)
	  (cvs-skip-line
	   (regexp-quote "cvs [update aborted]: there is no repository ")
	   "Unexpected cvs output.")
	  (cookie-enter-last
	   buf
	   (cvs-create-fileinfo
	    'REPOS-MISSING current-dir
	    nil
	  (buffer-substring start (point))))))

       ;; The file is copied from the repository.

       ((looking-at "U \\(.*\\)$")
	(cookie-enter-last
	 buf
	 (let ((fileinfo
		(cvs-create-fileinfo
		 'UPDATED current-dir
		 (file-name-nondirectory
		  (buffer-substring (match-beginning 1) (match-end 1)))
		 (buffer-substring (match-beginning 0) (match-end 0)))))
	   (cvs-set-fileinfo->handled fileinfo t)
	   fileinfo))
	(forward-line 1))

       ;; The file is modified by the user, and untouched in the repository.

       ((looking-at "M \\(.*\\)$")
	(cookie-enter-last
	 buf
	 (cvs-create-fileinfo
	  'MODIFIED current-dir
	  (file-name-nondirectory
	   (buffer-substring (match-beginning 1) (match-end 1)))
	  (buffer-substring (match-beginning 0) (match-end 0))))
	(forward-line 1))

       ;; The file is "cvs add"ed, but not "cvs ci"ed.

       ((looking-at "A \\(.*\\)$")
	(cookie-enter-last
	 buf
	 (cvs-create-fileinfo
	  'ADDED current-dir
	  (file-name-nondirectory
	   (buffer-substring (match-beginning 1) (match-end 1)))
	  (buffer-substring (match-beginning 0) (match-end 0))))
	(forward-line 1))

       ;; The file is "cvs remove"ed, but not "cvs ci"ed.

       ((looking-at "R \\(.*\\)$")
	(cookie-enter-last
	 buf
	 (cvs-create-fileinfo
	  'REMOVED current-dir
	  (file-name-nondirectory
	   (buffer-substring (match-beginning 1) (match-end 1)))
	  (buffer-substring (match-beginning 0) (match-end 0))))
	(forward-line 1))

       ;; Unknown file.

       ((looking-at "? \\(.*\\)$")
	(cookie-enter-last
	 buf
	 (cvs-create-fileinfo
	  'UNKNOWN current-dir
	  (file-name-nondirectory
	   (buffer-substring (match-beginning 1) (match-end 1)))
	  (buffer-substring (match-beginning 0) (match-end 0))))
	(forward-line 1))
       (t

	;; CVS has decided to merge someone elses changes into this
	;; document. This leads to a lot of garbage being printed.
	;; First there is two lines that contains no information
	;; that we skip (but we check that we recognize them).

	(let ((complex-start (point))
	      initial-revision filename)

	  (cvs-skip-line "^RCS file: .*$" "Parse error.")
	  (setq initial-revision
		(cvs-skip-line "^retrieving revision \\(.*\\)$"
			       "Unexpected output from cvs." 1))
	  (cvs-skip-line "^retrieving revision .*$"
			 "Unexpected output from cvs.")

	  ;; Get the file name from the next line.

	  (setq
	   filename
	   (cvs-skip-line
	    "^Merging differences between [0-9.]+ and [0-9.]+ into \\(.*\\)$"
	    "Unexpected output from cvs."
	    1))

	  (cond

	   ;; The file was successfully merged.

	   ((looking-at "^M ")
	    (forward-line 1)
	    (let ((fileinfo
		   (cvs-create-fileinfo
		    'MERGED current-dir
		    filename
		    (buffer-substring complex-start (point)))))
	      (cvs-set-fileinfo->backup-file
	       fileinfo
	       (concat cvs-bakprefix filename "." initial-revision))
	      (cookie-enter-last
	       buf fileinfo)))

	   ;; A conflicting merge.

	   (t
	    (cvs-skip-line "^merge: overlaps during merge$"
			   "Unexpected output from cvs.")
	    (cvs-skip-line "^cvs update: conflicts found in "
			   "Unexpected output from cvs.")
	    (cvs-skip-line "^C " "Unexpected cvs output.")
	    (let ((fileinfo
		   (cvs-create-fileinfo
		    'CONFLICT current-dir
		    filename
		    (buffer-substring complex-start (point)))))

	      (cvs-set-fileinfo->backup-file
	       fileinfo
	       (concat cvs-bakprefix filename "." initial-revision))

	      (cookie-enter-last buf fileinfo))))))))

    ;; All parsing is done.

    ;; If the last entry is a directory, remove it.
    (if (eq (cvs-fileinfo->type (cookie-last buf))
	    'DIRCHANGE)
	(cookie-delete-last buf))

    (set-buffer buf)
    (cvs-mode)
    (setq cookie-last-tin (cookie-nth buf 0))
    (goto-char (point-min))
    (cookie-previous-cookie buf (point-min) 1)
    (setq default-directory root-dir)
    (if (get-buffer-window parse-buf)
	(set-window-buffer (get-buffer-window parse-buf) buf)
      (display-buffer buf))))


(defun cvs-pp (fileinfo)
  "Pretty print FILEINFO into a string."

  (let ((a (cvs-fileinfo->type fileinfo))
        (s (if (cvs-fileinfo->marked fileinfo)
               "*" " "))
        (f (cvs-fileinfo->file-name fileinfo))
        (ci (if (cvs-fileinfo->handled fileinfo)
                "  " "ci")))
    (cond
     ((eq a 'UPDATED)
      (format "%s Updated     %s" s f))
     ((eq a 'MODIFIED)
      (format "%s Modified %s %s" s ci f))
     ((eq a 'MERGED)
      (format "%s Merged   %s %s" s ci f))
     ((eq a 'CONFLICT)
      (format "%s Conflict    %s" s f))
     ((eq a 'ADDED)
      (format "%s Added    %s %s" s ci f))
     ((eq a 'REMOVED)
      (format "%s Removed  %s %s" s ci f))
     ((eq a 'UNKNOWN)
      (format "%s Unknown     %s" s f))
     ((eq a 'CVS-REMOVED)
      (format "%s Removed from repository:  %s" s f))
     ((eq a 'REM-CONFLICT)
      (format "%s Conflict: Removed from repository, changed by you: %s" s f))
     ((eq a 'MOD-CONFLICT)
      (format "%s Conflict: Removed by you, changed in repository: %s" s f))
     ((eq a 'DIRCHANGE)
      (format "\nIn directory %s:"
              (cvs-fileinfo->dir fileinfo)))
     ((eq a 'MOVE-AWAY)
      (format "%s Move away %s - it is in the way" s f))
     ((eq a 'REPOS-MISSING)
      (format "  This repository is missing! Remove this dir manually."))
     (t
      (format "%s Internal error! %s" s f)))))


;;; You can define your own keymap in .emacs. pcl-cvs.el won't overwrite it.

(if cvs-mode-map
    nil
  (setq cvs-mode-map (make-keymap))
  (suppress-keymap cvs-mode-map)
  (define-key cvs-mode-map " "	'cookie-next-cookie)
  (define-key cvs-mode-map "?"	'describe-mode)
  (define-key cvs-mode-map "A"	'cvs-add-change-log-entry-other-window)
  (define-key cvs-mode-map "M"	'cvs-mark-all-files)
  (define-key cvs-mode-map "U"	'cvs-unmark-all-files)
  (define-key cvs-mode-map "\C-?"  'cvs-unmark-up)
  (define-key cvs-mode-map "\C-n"  'cookie-next-cookie)
  (define-key cvs-mode-map "\C-p"  'cookie-previous-cookie)
  (define-key cvs-mode-map "a"	'cvs-add)
  (define-key cvs-mode-map "b"	'cvs-diff-backup)
  (define-key cvs-mode-map "c"	'cvs-commit)
  (define-key cvs-mode-map "d"	'cvs-diff-cvs)
  (define-key cvs-mode-map "f"	'cvs-find-file)
  (define-key cvs-mode-map "g"	'cvs-update-no-prompt)
  (define-key cvs-mode-map "i"	'cvs-ignore)
  (define-key cvs-mode-map "l"	'cvs-log)
  (define-key cvs-mode-map "m"	'cvs-mark)
  (define-key cvs-mode-map "n"	'cookie-next-cookie)
  (define-key cvs-mode-map "o"	'cvs-find-file-other-window)
  (define-key cvs-mode-map "p"	'cookie-previous-cookie)
  (define-key cvs-mode-map "r"	'cvs-remove-file)
  (define-key cvs-mode-map "s"	'cvs-status)
  (define-key cvs-mode-map "\C-k"  'cvs-acknowledge)
  (define-key cvs-mode-map "x"	'cvs-remove-handled)
  (define-key cvs-mode-map "u"	'cvs-unmark))


(defun cvs-get-marked ()
  "Return a list of all selected tins.
If there are any marked tins, return them.
Otherwise, if the cursor selects a directory, return all files in it.
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."
    
  (cond
   ;; Any marked cookies?
   ((cookie-collect-tins (current-buffer)
			 'cvs-fileinfo->marked))
   ;; Nope.
   (t
    (let ((sel (cookie-get-selection
		(current-buffer) (point) cookie-last-tin)))
      (cond
       ;; If a directory is selected, all it members are returned.
       ((and sel (eq (cvs-fileinfo->type
			(cookie-cookie (current-buffer) sel))
		       'DIRCHANGE))
	(cookie-collect-tins
	 (current-buffer) 'cvs-dir-member-p
	 (cvs-fileinfo->dir (cookie-cookie (current-buffer) sel))))
       (t
	(list sel)))))))


(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))
       (string= (cvs-fileinfo->dir fileinfo) dir)))

(defun cvs-dir-empty-p (cvs-buf tin)
  "Return non-nil if TIN is a directory that is empty.
Args: CVS-BUF TIN."
  (and (eq (cvs-fileinfo->type (cookie-cookie cvs-buf tin)) 'DIRCHANGE)
       (or (not (cookie-next cvs-buf tin))
	   (eq (cvs-fileinfo->type (cookie-cookie cvs-buf
						  (cookie-next cvs-buf tin)))
	       'DIRCHANGE))))

(defun cvs-remove-handled ()
  "Remove all lines that are handled.
Empty directories are removed."
  (interactive)
  ;; Pass one: remove files that are handled.
  (cookie-filter (current-buffer)
		 (function
		  (lambda (fileinfo) (not (cvs-fileinfo->handled fileinfo)))))
  ;; Pass two: remove empty directories.
  (cookie-filter-tins (current-buffer)
		      (function
		       (lambda (tin)
			 (not (cvs-dir-empty-p (current-buffer) tin))))))

(defun cvs-mark (pos)
  "Mark a fileinfo. Args: POS.
If the fileinfo is a directory, all the contents of that directory are
marked instead. A directory can never be marked.
POS is a buffer position."

  (interactive "d")

  (let* ((tin (cookie-get-selection
	       (current-buffer) pos cookie-last-tin))
	 (sel (cookie-cookie (current-buffer) tin)))

    (cond
     ;; Does POS point to a directory? If so, mark all files in that directory.
     ((eq (cvs-fileinfo->type sel) 'DIRCHANGE)
      (cookie-map
       (function (lambda (f dir)
		   (cond
		    ((cvs-dir-member-p f dir)
		     (cvs-set-fileinfo->marked f t)
		     t))))		;Tell cookie to redisplay this cookie.
       (current-buffer)
       (cvs-fileinfo->dir sel)))
     (t
      (cvs-set-fileinfo->marked sel t)
      (cookie-invalidate-tins (current-buffer) tin)
      (cookie-next-cookie (current-buffer) pos 1)))))
  

(defun cvs-committable (tin cvs-buf)
  "Check if the TIN is committable.
It is committable if it
   a) is not handled and
   b) is either MODIFIED, ADDED, REMOVED, MERGED or CONFLICT."
  (let* ((fileinfo (cookie-cookie cvs-buf tin))
	 (type (cvs-fileinfo->type fileinfo)))
    (and (not (cvs-fileinfo->handled fileinfo))
	 (or (eq type 'MODIFIED)
	     (eq type 'ADDED)
	     (eq type 'REMOVED)
	     (eq type 'MERGED)
	     (eq type 'CONFLICT)))))

(defun cvs-commit ()

  "Check in all marked files, or the current file.
The user will be asked for a log message in a buffer.
If cvs-erase-input-buffer is non-nil that buffer will be erased.
Otherwise 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]."

  (interactive)

  (let* ((cvs-buf (current-buffer))
	 (marked (cvs-filter (function cvs-committable)
			     (cvs-get-marked)
			     cvs-buf)))
    (if (null marked)
	(error "Nothing to commit!")
      (pop-to-buffer (get-buffer-create cvs-commit-prompt-buffer))
      (goto-char (point-min))

      (if cvs-erase-input-buffer
	  (erase-buffer)
	(push-mark (point-max)))
      (cvs-edit-mode)
      (make-local-variable 'cvs-commit-list)
      (setq cvs-commit-list marked)
      (make-local-variable 'cvs-cvs-buffer)
      (setq cvs-cvs-buffer cvs-buf)
      (message "Press C-c C-c when you are done editing."))))


(defun cvs-edit-done ()
  "Commit the files to the repository."
  (interactive)
  (save-some-buffers)
  (let ((cc-list cvs-commit-list)
	(cc-buffer cvs-cvs-buffer)
	(msg-buffer (current-buffer))
	(msg (buffer-substring (point-min) (point-max))))
    (pop-to-buffer cc-buffer)
    (bury-buffer msg-buffer)
    (cvs-use-temp-buffer)
    (message "Committing...")
    (cvs-execute-list cc-list cvs-program (list "commit" "-m" msg))
    (mapcar (function
	     (lambda (tin)
	       (cvs-set-fileinfo->handled (cookie-cookie cc-buffer tin) t)))
	    cc-list)
    (apply 'cookie-invalidate-tins cc-buffer cc-list)
    (set-buffer cc-buffer)
    (if cvs-auto-remove-handled
	(cvs-remove-handled)))
  
  (message "Committing... Done."))


(defun cvs-execute-list (tin-list program constant-args)
  "Run PROGRAM on all elements on TIN-LIST.
Args: TIN-LIST PROGRAM CONSTANT-ARGS
The PROGRAM will be called with pwd set to the directory the
files reside in. CONSTANT-ARGS should be a list of strings. The
arguments given to the program will be CONSTANT-ARGS followed by all
the files (from TIN-LIST) that resides in that directory. If the files
in TIN-LIST resides in different directories the PROGRAM will be run
once for each directory (if all files in the same directory appears
after each other."
			 
    (while tin-list
      (let ((current-dir (cvs-fileinfo->dir
			  (cookie-cookie cvs-buffer-name
					 (car tin-list))))
	    arg-list arg-str)

	;; Collect all marked files in this directory.

	(while (and tin-list
		    (string=
		     current-dir
		     (cvs-fileinfo->dir
		      (cookie-cookie cvs-buffer-name (car tin-list)))))
	  (setq arg-list
		(cons (cvs-fileinfo->file-name
		       (cookie-cookie cvs-buffer-name (car tin-list)))
		      arg-list))
	  (setq tin-list (cdr tin-list)))

	(setq arg-list (nreverse arg-list))

	;; Execute the command on all the files that were collected.

	(setq default-directory (file-name-as-directory current-dir))
	(insert (format "=== cd %s\n" default-directory))
	(insert (format "=== %s %s\n\n"
			program
			(mapconcat '(lambda (foo) foo)
				   (nconc (copy-sequence constant-args)
					  arg-list)
				   " ")))
	(apply 'call-process program nil t t
	       (nconc (copy-sequence constant-args) arg-list))
	(goto-char (point-max)))))


(defun cvs-execute-single-file-list (tin-list extractor program constant-args)
  "Run PROGRAM on all elements on TIN-LIST.

Args: TIN-LIST EXTRACTOR PROGRAM CONSTANT-ARGS

The PROGRAM will be called with pwd set to the directory the files
reside in.  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 TIN-LIST.  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."

    (while tin-list
      (let ((default-directory (file-name-as-directory
				(cvs-fileinfo->dir
				 (cookie-cookie cvs-buffer-name
					       (car tin-list)))))
	    (arg-list
	     (funcall extractor
		      (cookie-cookie cvs-buffer-name (car tin-list)))))

	;; Execute the command unless extractor returned t.

	(if (eq arg-list t)
	    nil
	  (insert (format "=== cd %s\n" default-directory))
	  (insert (format "=== %s %s\n\n"
			  program
			  (mapconcat '(lambda (foo) foo)
				     (nconc (copy-sequence constant-args)
					    arg-list)
				     " ")))
	  (apply 'call-process program nil t t
		 (nconc (copy-sequence constant-args) arg-list))
	  (goto-char (point-max))))
      (setq tin-list (cdr tin-list))))


(defun cvs-edit-mode ()
  "\\<cvs-edit-mode-map>Mode for editing cvs log messages.
Commands:
\\[cvs-edit-done] checks in the file when you are ready.
This mode is based on fundamental mode."
  (interactive)
  (use-local-map cvs-edit-mode-map)
  (setq major-mode 'cvs-edit-mode)
  (setq mode-name "CVS Log")
  (auto-fill-mode 1))


(if cvs-edit-mode-map
    nil
  (setq cvs-edit-mode-map (make-sparse-keymap))
  (define-prefix-command 'cvs-control-c-prefix)
  (define-key cvs-edit-mode-map "\C-c" 'cvs-control-c-prefix)
  (define-key cvs-edit-mode-map "\C-c\C-c" 'cvs-edit-done))


(defun cvs-diff-cvs ()
  "Diff the selected files against the repository.
The flags the variable cvs-cvs-diff-flags will be passed to ``cvs diff''."
  (interactive)

  (save-some-buffers)
  (let ((marked (cvs-get-marked)))
    (cvs-use-temp-buffer)
    (message "cvsdiffing...")
    (cvs-execute-list marked cvs-program (cons "diff" cvs-cvs-diff-flags)))
  (message "cvsdiffing... Done."))


(defun cvs-backup-diffable (tin cvs-buf)
  "Check if the TIN is backup-diffable.
It must have a backup file to be diffable."
  (cvs-fileinfo->backup-file (cookie-cookie cvs-buf tin)))

(defun cvs-diff-backup ()
  "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.

The flags in cvs-diff-flags will be passed to ``diff''."

  (interactive)
  (save-some-buffers)
  (let ((marked (cvs-filter (function cvs-backup-diffable)
			    (cvs-get-marked)
			    (current-buffer))))
    (if (null marked)
	(error "No ``Conflict'' or ``Merged'' file selected!"))
    (cvs-use-temp-buffer)
    (message "diffing...")
    (cvs-execute-single-file-list
     marked 'cvs-diff-backup-extractor cvs-diff-program cvs-diff-flags))
  (message "diffing... 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."
  (if (null (cvs-fileinfo->backup-file fileinfo))
      (error "%s has no backup file."
	     (concat
	      (file-name-as-directory (cvs-fileinfo->dir fileinfo))
	      (cvs-fileinfo->file-name fileinfo))))
  (list (cvs-fileinfo->file-name fileinfo)
	(cvs-fileinfo->backup-file fileinfo)))

(defun cvs-find-file-other-window (pos)
  "Select a buffer containing the file in another window.
Args: POS"
  (interactive "d")
  (save-some-buffers)
  (let* ((cookie-last-tin
	  (cookie-get-selection (current-buffer) pos cookie-last-tin))
	 (type (cvs-fileinfo->type (cookie-cookie (current-buffer)
						  cookie-last-tin))))
    (cond
     ((or (eq type 'REMOVED)
	  (eq type 'CVS-REMOVED))
      (error "Can't visit a removed file."))
     ((eq type 'DIRCHANGE)
      (let ((obuf (current-buffer))
	    (odir default-directory))
	(setq default-directory
	      (file-name-as-directory
	       (cvs-fileinfo->dir
		(cookie-cookie (current-buffer) cookie-last-tin))))
	(dired-other-window default-directory)
	(set-buffer obuf)
	(setq default-directory odir))) 
     (t
      (find-file-other-window (cvs-full-path (current-buffer)
					     cookie-last-tin))))))

(defun cvs-full-path (buffer tin)
  "Return the full path for the file that is described in TIN.
Args: BUFFER TIN."
  (concat
   (file-name-as-directory
    (cvs-fileinfo->dir (cookie-cookie buffer tin)))
   (cvs-fileinfo->file-name (cookie-cookie buffer tin))))

(defun cvs-find-file (pos)
  "Select a buffer containing the file in another window.
Args: POS"
  (interactive "d")
  (let* ((cvs-buf (current-buffer))
	 (cookie-last-tin (cookie-get-selection cvs-buf pos cookie-last-tin))
	 (fileinfo (cookie-cookie cvs-buf cookie-last-tin))
	 (type (cvs-fileinfo->type fileinfo)))
    (cond
     ((or (eq type 'REMOVED)
	  (eq type 'CVS-REMOVED))
      (error "Can't visit a removed file."))
     ((eq type 'DIRCHANGE)
      (let ((odir default-directory))
	(setq default-directory
	      (file-name-as-directory (cvs-fileinfo->dir fileinfo)))
	(dired default-directory)
	(set-buffer cvs-buf)
	(setq default-directory odir))) 
     (t
      (find-file (cvs-full-path cvs-buf cookie-last-tin))))))

(defun cvs-mark-all-files ()
  "Mark all files.
Directories are not marked."
  (interactive)
  (cookie-map (function (lambda (cookie)
			  (cond
			   ((not (eq (cvs-fileinfo->type cookie) 'DIRCHANGE))
			    (cvs-set-fileinfo->marked cookie t)
			    t))))
	      (current-buffer)))


(defun cvs-unmark (pos)
  "Unmark a fileinfo. Args: POS."
  (interactive "d")

  (let* ((tin (cookie-get-selection
	       (current-buffer) pos cookie-last-tin))
	 (sel (cookie-cookie (current-buffer) tin)))

    (cond
     ((eq (cvs-fileinfo->type sel) 'DIRCHANGE)
      (cookie-map
       (function (lambda (f dir)
		   (cond
		    ((cvs-dir-member-p f dir)
		     (cvs-set-fileinfo->marked f nil)
		     t))))
       (current-buffer)
       (cvs-fileinfo->dir sel)))
     (t
      (cvs-set-fileinfo->marked sel nil)
      (cookie-invalidate-tins (current-buffer) tin)
      (cookie-next-cookie (current-buffer) pos 1)))))

(defun cvs-unmark-all-files ()
  "Unmark all files.
Directories are also unmarked, but that doesn't matter, since
they should always be unmarked."
  (interactive)
  (cookie-map (function (lambda (cookie)
			  (cvs-set-fileinfo->marked cookie nil)
			  t))
	      (current-buffer)))


(defun cvs-do-removal (cvs-buf tins)
  "Remove files.
Args: CVS-BUF TINS.
CVS-BUF is the cvs buffer. TINS is a list of tins that the
user wants to delete. The files are deleted. If the type of
the tin is 'UNKNOWN the tin is removed from the buffer. If it
is anything else the file is added to a list that should be
`cvs remove'd and the tin is changed to be of type 'REMOVED.

Returns a list of tins files that should be `cvs remove'd."
  (cvs-use-temp-buffer)
  (mapcar 'cvs-insert-full-path tins)
  (cond
   ((and tins (yes-or-no-p (format "Delete %d files? " (length tins))))
    (let (files-to-remove)
      (while tins
	(let* ((tin (car tins))
	       (fileinfo (cookie-cookie cvs-buf tin))
	       (type (cvs-fileinfo->type fileinfo)))
	  (if (not (or (eq type 'REMOVED) (eq type 'CVS-REMOVED)))
	      (progn
		(delete-file (cvs-full-path cvs-buf tin))
		(cond
		 ((or (eq type 'UNKNOWN) (eq type 'MOVE-AWAY))
		  (cookie-delete cvs-buf tin))
		 (t
		  (setq files-to-remove (cons tin files-to-remove))
		  (cvs-set-fileinfo->type fileinfo 'REMOVED)
		  (cvs-set-fileinfo->handled fileinfo nil)
		  (cookie-invalidate-tins cvs-buf tin))))))
	(setq tins (cdr tins)))
      files-to-remove))
   (t nil)))



(defun cvs-remove-file ()
  "Remove all marked files."
  (interactive)
  (let ((files-to-remove (cvs-do-removal (current-buffer) (cvs-get-marked))))
    (if (null files-to-remove)
	nil
      (cvs-use-temp-buffer)
      (message "removing from repository...")
      (cvs-execute-list files-to-remove cvs-program '("remove"))
      (message "removing from repository... done."))))

(defun cvs-acknowledge ()
  "Remove all marked files from the buffer."
  (interactive)

  (mapcar (function (lambda (tin)
		       (cookie-delete (current-buffer) tin)))
	  (cvs-get-marked))
  (setq cookie-last-tin nil))


(defun cvs-unmark-up (pos)
  "Unmark the file on the previous line.
Takes one argument POS, a buffer position."
  (interactive "d")
  (cookie-previous-cookie (current-buffer) pos 1)
  (cvs-set-fileinfo->marked (cookie-cookie (current-buffer) cookie-last-tin)
			    nil)
  (cookie-invalidate-tins (current-buffer) cookie-last-tin))

(defun cvs-add-file-update-buffer (cvs-buf tin)
  "Subfunction to cvs-add. Internal use only.
Update the display. Return non-nil if `cvs add' should be called on this
file. Args: CVS-BUF TIN.
Returns 'ADD or 'RESURRECT."
  (let ((fileinfo (cookie-cookie cvs-buf tin)))
    (cond
     ((eq (cvs-fileinfo->type fileinfo) 'UNKNOWN)
      (cvs-set-fileinfo->type fileinfo 'ADDED)
      (cookie-invalidate-tins cvs-buf tin)
      'ADD)
     ((eq (cvs-fileinfo->type fileinfo) 'REMOVED)
      (cvs-set-fileinfo->type fileinfo 'UPDATED)
      (cvs-set-fileinfo->handled fileinfo t)
      (cookie-invalidate-tins cvs-buf tin)
      'RESURRECT))))

(defun cvs-add-sub (cvs-buf candidates)
  "Internal use only.
Args: CVS-BUF CANDIDATES.
CANDIDATES is a list of tins. Updates the CVS-BUF and returns a pair of lists.
The first list is unknown tins that shall be `cvs add -m msg'ed.
The second list is removed files that shall be `cvs add'ed (resurrected)."
  (let (add resurrect)
    (while candidates
      (let ((type (cvs-add-file-update-buffer cvs-buf (car candidates))))
	(cond ((eq type 'ADD)
	       (setq add (cons (car candidates) add)))
	      ((eq type 'RESURRECT)
	       (setq resurrect (cons (car candidates) resurrect)))))
      (setq candidates (cdr candidates)))
    (cons add resurrect)))

(defun cvs-add ()
  "Add marked files to the cvs repository."
  (interactive)

  (let* ((buf (current-buffer))
	 (result (cvs-add-sub buf (cvs-get-marked)))
	 (added (car result))
	 (resurrect (cdr result))
	 (msg (if added (read-from-minibuffer "Enter description: "))))

    (if (or resurrect added)
	(cvs-use-temp-buffer))

    (cond (resurrect
	   (message "Resurrecting files from repository...")
	   (cvs-execute-list resurrect cvs-program '("add"))
	   (message "Done.")))

    (cond (added
	   (message "Adding new files to repository...")
	   (cvs-execute-list added cvs-program (list "add" "-m" msg))
	   (message "Done.")))))

(defun cvs-ignore ()
  "Arrange so that CVS ignores the selected files.
This command ignores files that are not flagged as `Unknown'."
  (interactive)

  (mapcar (function (lambda (tin)
		      (cond
		       ((eq (cvs-fileinfo->type
			     (cookie-cookie (current-buffer) tin)) 'UNKNOWN)
			(cvs-append-to-ignore
			 (cookie-cookie (current-buffer) tin))
			(cookie-delete (current-buffer) tin)))))
	  (cvs-get-marked))
  (setq cookie-last-tin nil))

(defun cvs-append-to-ignore (fileinfo)
  "Append the file in fileinfo to the .cvsignore file"
  (save-window-excursion
    (set-buffer (find-file-noselect (concat (file-name-as-directory
					     (cvs-fileinfo->dir fileinfo))
					    ".cvsignore")))
    (goto-char (point-max))
    (if (not (zerop (current-column)))
	(insert "\n"))
    (insert (cvs-fileinfo->file-name fileinfo) "\n")
    (save-buffer)))

(defun cvs-status ()
  "Show cvs status for all marked files."
  (interactive)

  (save-some-buffers)
  (let ((marked (cvs-get-marked)))
    (cvs-use-temp-buffer)
    (message "Running cvs status ...")
    (cvs-execute-list marked cvs-program (cons "status" cvs-status-flags)))
  (message "Running cvs status ... Done."))

(defun cvs-log ()
  "Display the cvs log of all selected files."
  (interactive)

  (let ((marked (cvs-get-marked)))
    (cvs-use-temp-buffer)
    (message "Running cvs log ...")
    (cvs-execute-list marked cvs-program (cons "log" cvs-log-flags)))
  (message "Running cvs log ... Done."))


(defun cvs-insert-full-path (tin)
  "Insert full path to the file described in TIN."
  (insert (format "%s\n" (cvs-full-path cvs-buffer-name tin))))


(defun cvs-add-change-log-entry-other-window (pos)
  "Add a ChangeLog entry in the ChangeLog of the current directory.
Args: POS."
  (interactive "d")
  (let* ((cvs-buf (current-buffer))
	 (odir default-directory))
    (setq default-directory
	  (file-name-as-directory
	   (cvs-fileinfo->dir
	    (cookie-cookie
	     cvs-buf
	     (cookie-get-selection cvs-buf pos cookie-last-tin)))))
    (if (not default-directory)		;In case there was no entries.
	(setq default-directory odir))
    (add-change-log-entry-other-window)
    (set-buffer cvs-buf)
    (setq default-directory odir)))


(defun print-cvs-tin (foo)
  "Debug utility."
  (let ((cookie (cookie-cookie (current-buffer) foo))
	(stream (get-buffer-create "debug")))
    (princ "==============\n" stream)
    (princ (cvs-fileinfo->file-name cookie) stream)
    (princ "\n" stream)
    (princ (cvs-fileinfo->dir cookie) stream)
    (princ "\n" stream)
    (princ (cvs-fileinfo->full-log cookie) stream)
    (princ "\n" stream)
    (princ (cvs-fileinfo->marked cookie) stream)
    (princ "\n" stream)))
OpenPOWER on IntegriCloud