contrib/mercurial.el
changeset 999 bb391518bc28
parent 996 5ed566574486
child 1000 3362b410c219
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)