comparison contrib/mercurial.el @ 999:bb391518bc28

Emacs: first cut at commit support.
author Bryan O'Sullivan <bos@serpentine.com>
date Mon, 22 Aug 2005 03:16:32 -0700
parents 5ed566574486
children 3362b410c219
comparison
equal deleted inserted replaced
998:c37dd58a444a 999:bb391518bc28
86 (defcustom hg-global-prefix "\C-ch" 86 (defcustom hg-global-prefix "\C-ch"
87 "The global prefix for Mercurial keymap bindings." 87 "The global prefix for Mercurial keymap bindings."
88 :type 'sexp 88 :type 'sexp
89 :group 'mercurial) 89 :group 'mercurial)
90 90
91 (defcustom hg-commit-allow-empty-message nil
92 "Whether to allow changes to be committed with empty descriptions."
93 :type 'boolean
94 :group 'mercurial)
95
96 (defcustom hg-commit-allow-empty-file-list nil
97 "Whether to allow changes to be committed without any modified files."
98 :type 'boolean
99 :group 'mercurial)
100
91 (defcustom hg-rev-completion-limit 100 101 (defcustom hg-rev-completion-limit 100
92 "The maximum number of revisions that hg-read-rev will offer to complete. 102 "The maximum number of revisions that hg-read-rev will offer to complete.
93 This affects memory usage and performance when prompting for revisions 103 This affects memory usage and performance when prompting for revisions
94 in a repository with a lot of history." 104 in a repository with a lot of history."
95 :type 'integer 105 :type 'integer
124 (defvar hg-output-buffer-name "*Hg*" 134 (defvar hg-output-buffer-name "*Hg*"
125 "The name to use for Mercurial output buffers.") 135 "The name to use for Mercurial output buffers.")
126 136
127 (defvar hg-file-history nil) 137 (defvar hg-file-history nil)
128 (defvar hg-rev-history nil) 138 (defvar hg-rev-history nil)
139
140
141 ;;; Random constants.
142
143 (defconst hg-commit-message-start
144 "--- Enter your commit message. Type `C-c C-c' to commit. ---\n")
145
146 (defconst hg-commit-message-end
147 "--- Files in bold will be committed. Click to toggle selection. ---\n")
129 148
130 149
131 ;;; hg-mode keymap. 150 ;;; hg-mode keymap.
132 151
133 (defvar hg-prefix-map 152 (defvar hg-prefix-map
191 (define-key hg-view-mode-map 210 (define-key hg-view-mode-map
192 (if hg-running-xemacs [button2] [mouse-2]) 211 (if hg-running-xemacs [button2] [mouse-2])
193 'hg-buffer-mouse-clicked) 212 'hg-buffer-mouse-clicked)
194 213
195 214
215 ;;; Commit mode keymaps.
216
217 (defvar hg-commit-mode-map (make-sparse-keymap))
218 (define-key hg-commit-mode-map "\C-c\C-c" 'hg-commit-finish)
219 (define-key hg-commit-mode-map "\C-c\C-k" 'hg-commit-abort)
220
221 (defvar hg-commit-mode-file-map (make-sparse-keymap))
222 (define-key hg-commit-mode-file-map
223 (if hg-running-xemacs [button2] [mouse-2])
224 'hg-commit-mouse-clicked)
225 (define-key hg-commit-mode-file-map " " 'hg-commit-toggle-file)
226 (define-key hg-commit-mode-file-map "\r" 'hg-commit-toggle-file)
227
228
196 ;;; Convenience functions. 229 ;;; Convenience functions.
197 230
198 (defsubst hg-binary () 231 (defsubst hg-binary ()
199 (if hg-binary 232 (if hg-binary
200 hg-binary 233 hg-binary
208 This function bridges yet another pointless impedance gap between 241 This function bridges yet another pointless impedance gap between
209 XEmacs and GNU Emacs." 242 XEmacs and GNU Emacs."
210 (if (fboundp 'replace-in-string) 243 (if (fboundp 'replace-in-string)
211 (replace-in-string str regexp newtext literal) 244 (replace-in-string str regexp newtext literal)
212 (replace-regexp-in-string regexp newtext str nil literal))) 245 (replace-regexp-in-string regexp newtext str nil literal)))
246
247 (defsubst hg-strip (str)
248 "Strip leading and trailing white space from a string."
249 (hg-replace-in-string (hg-replace-in-string str "[ \t\r\n]+$" "")
250 "^[ \t\r\n]+" ""))
213 251
214 (defsubst hg-chomp (str) 252 (defsubst hg-chomp (str)
215 "Strip trailing newlines from a string." 253 "Strip trailing newlines from a string."
216 (hg-replace-in-string str "[\r\n]+$" "")) 254 (hg-replace-in-string str "[\r\n]+$" ""))
217 255
313 nil 351 nil
314 nil 352 nil
315 'hg-rev-history 353 'hg-rev-history
316 (or default "tip"))) 354 (or default "tip")))
317 rev))) 355 rev)))
356
357 (defmacro hg-do-across-repo (path &rest body)
358 (let ((root-name (gensym "root-"))
359 (buf-name (gensym "buf-")))
360 `(let ((,root-name (hg-root ,path)))
361 (save-excursion
362 (dolist (,buf-name (buffer-list))
363 (set-buffer ,buf-name)
364 (when (and hg-status (equal (hg-root buffer-file-name) ,root-name))
365 ,@body))))))
366
367 (put 'hg-do-across-repo 'lisp-indent-function 1)
368
318 369
319 ;;; View mode bits. 370 ;;; View mode bits.
320 371
321 (defun hg-exit-view-mode (buf) 372 (defun hg-exit-view-mode (buf)
322 "Exit from hg-view-mode. 373 "Exit from hg-view-mode.
531 582
532 (defun hg-annotate () 583 (defun hg-annotate ()
533 (interactive) 584 (interactive)
534 (error "not implemented")) 585 (error "not implemented"))
535 586
587 (defun hg-commit-toggle-file (pos)
588 "Toggle whether or not the file at POS will be committed."
589 (interactive "d")
590 (save-excursion
591 (goto-char pos)
592 (let ((face (get-text-property pos 'face))
593 bol)
594 (beginning-of-line)
595 (setq bol (+ (point) 4))
596 (end-of-line)
597 (if (eq face 'bold)
598 (progn
599 (remove-text-properties bol (point) '(face nil))
600 (message "%s will not be committed"
601 (buffer-substring bol (point))))
602 (add-text-properties bol (point) '(face bold))
603 (message "%s will be committed"
604 (buffer-substring bol (point)))))))
605
606 (defun hg-commit-mouse-clicked (event)
607 "Toggle whether or not the file at POS will be committed."
608 (interactive "@e")
609 (hg-commit-toggle-file (event-point event)))
610
611 (defun hg-commit-abort ()
612 (interactive)
613 (error "not implemented"))
614
615 (defun hg-commit-finish ()
616 (interactive)
617 (goto-char (point-min))
618 (search-forward hg-commit-message-start)
619 (let (message files)
620 (let ((start (point)))
621 (goto-char (point-max))
622 (search-backward hg-commit-message-end)
623 (setq message (hg-strip (buffer-substring start (point)))))
624 (when (and (= (length message) 0)
625 (not hg-commit-allow-empty-message))
626 (error "Cannot proceed - commit message is empty"))
627 (forward-line 1)
628 (beginning-of-line)
629 (while (< (point) (point-max))
630 (let ((pos (+ (point) 4)))
631 (end-of-line)
632 (when (eq (get-text-property pos 'face) 'bold)
633 (end-of-line)
634 (setq files (cons (buffer-substring pos (point)) files))))
635 (forward-line 1))
636 (when (and (= (length files) 0)
637 (not hg-commit-allow-empty-file-list))
638 (error "Cannot proceed - no files to commit"))
639 (setq message (concat message "\n"))
640 (apply 'hg-run0 "--cwd" hg-root "commit" "-m" message files)))
641
642 (defun hg-commit-mode ()
643 "Mode for describing a commit of changes to a Mercurial repository.
644 This involves two actions: describing the changes with a commit
645 message, and choosing the files to commit.
646
647 To describe the commit, simply type some text in the designated area.
648
649 By default, all modified, added and removed files are selected for
650 committing. Files that will be committed are displayed in bold face\;
651 those that will not are displayed in normal face.
652
653 To toggle whether a file will be committed, move the cursor over a
654 particular file and hit space or return. Alternatively, middle click
655 on the file.
656
657 When you are finished with preparations, type \\[hg-commit-finish] to
658 proceed with the commit."
659 (interactive)
660 (use-local-map hg-commit-mode-map)
661 (set-syntax-table text-mode-syntax-table)
662 (setq local-abbrev-table text-mode-abbrev-table
663 major-mode 'hg-commit-mode
664 mode-name "Hg-Commit")
665 (set-buffer-modified-p nil)
666 (setq buffer-undo-list nil)
667 (run-hooks 'text-mode-hook 'hg-commit-mode-hook))
668
536 (defun hg-commit () 669 (defun hg-commit ()
537 (interactive) 670 (interactive)
538 (error "not implemented")) 671 (let ((root (hg-root))
672 (prev-buffer (current-buffer)))
673 (unless root
674 (error "Cannot commit outside a repository!"))
675 (hg-do-across-repo
676 (vc-buffer-sync))
677 (let* ((buf-name (format "*Mercurial: Commit %s*" root)))
678 (pop-to-buffer (get-buffer-create buf-name))
679 (when (= (point-min) (point-max))
680 (set (make-local-variable 'hg-root) root)
681 (set (make-local-variable 'hg-prev-buffer) prev-buffer)
682 (insert "\n")
683 (let ((bol (point)))
684 (insert hg-commit-message-end)
685 (add-text-properties bol (point) '(read-only t face bold-italic)))
686 (let ((file-area (point)))
687 (insert (hg-chomp (hg-run0 "--cwd" root "status" "-arm")))
688 (goto-char file-area)
689 (while (< (point) (point-max))
690 (let ((bol (point)))
691 (forward-char 1)
692 (insert " ")
693 (end-of-line)
694 (add-text-properties (+ bol 4) (point)
695 '(face bold mouse-face highlight)))
696 (forward-line 1))
697 (goto-char file-area)
698 (add-text-properties (point) (point-max)
699 `(read-only t keymap ,hg-commit-mode-file-map))
700 (goto-char (point-min))
701 (insert hg-commit-message-start)
702 (add-text-properties (point-min) (point)
703 '(read-only t face bold-italic))
704 (insert "\n\n")
705 (forward-line -1)
706 (hg-commit-mode))))))
539 707
540 (defun hg-diff (path &optional rev1 rev2) 708 (defun hg-diff (path &optional rev1 rev2)
541 "Show the differences between REV1 and REV2 of PATH. 709 "Show the differences between REV1 and REV2 of PATH.
542 When called interactively, the default behaviour is to treat REV1 as 710 When called interactively, the default behaviour is to treat REV1 as
543 the tip revision, REV2 as the current edited version of the file, and 711 the tip revision, REV2 as the current edited version of the file, and
649 If the path is outside a repository, return nil. 817 If the path is outside a repository, return nil.
650 When called interactively, the root is printed. A prefix argument 818 When called interactively, the root is printed. A prefix argument
651 prompts for a path to check." 819 prompts for a path to check."
652 (interactive (list (hg-read-file-name))) 820 (interactive (list (hg-read-file-name)))
653 (let ((root (do ((prev nil dir) 821 (let ((root (do ((prev nil dir)
654 (dir (file-name-directory (or path (buffer-file-name))) 822 (dir (file-name-directory (or path buffer-file-name ""))
655 (file-name-directory (directory-file-name dir)))) 823 (file-name-directory (directory-file-name dir))))
656 ((equal prev dir)) 824 ((equal prev dir))
657 (when (file-directory-p (concat dir ".hg")) 825 (when (file-directory-p (concat dir ".hg"))
658 (return dir))))) 826 (return dir)))))
659 (when (interactive-p) 827 (when (interactive-p)