-
Notifications
You must be signed in to change notification settings - Fork 0
/
pcvs.el
2440 lines (2187 loc) · 91.5 KB
/
pcvs.el
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
;;; 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 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 <http://www.gnu.org/licenses/>.
;;; 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 <dir> 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 (set (make-local-variable '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) "<log message>")
;; 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) "<log message>"))
;; 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.