Show More
This diff has been collapsed as it changes many lines, (590 lines changed) Show them Hide them | |||||
@@ -0,0 +1,590 b'' | |||||
|
1 | ;;; mercurial.el --- Emacs support for the Mercurial distributed SCM | |||
|
2 | ||||
|
3 | ;; Copyright (C) 2005 Bryan O'Sullivan | |||
|
4 | ||||
|
5 | ;; Author: Bryan O'Sullivan <bos@serpentine.com> | |||
|
6 | ||||
|
7 | ;; $Id$ | |||
|
8 | ||||
|
9 | ;; mercurial.el is free software; you can redistribute it and/or | |||
|
10 | ;; modify it under the terms of version 2 of the GNU General Public | |||
|
11 | ;; License as published by the Free Software Foundation. | |||
|
12 | ||||
|
13 | ;; mercurial.el is distributed in the hope that it will be useful, but | |||
|
14 | ;; WITHOUT ANY WARRANTY; without even the implied warranty of | |||
|
15 | ;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU | |||
|
16 | ;; General Public License for more details. | |||
|
17 | ||||
|
18 | ;; You should have received a copy of the GNU General Public License | |||
|
19 | ;; along with mercurial.el, GNU Emacs, or XEmacs; see the file COPYING | |||
|
20 | ;; (`C-h C-l'). If not, write to the Free Software Foundation, Inc., | |||
|
21 | ;; 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. | |||
|
22 | ||||
|
23 | ;;; Commentary: | |||
|
24 | ||||
|
25 | ;; This mode builds upon Emacs's VC mode to provide flexible | |||
|
26 | ;; integration with the Mercurial distributed SCM tool. | |||
|
27 | ||||
|
28 | ;; To get going as quickly as possible, load mercurial.el into Emacs and | |||
|
29 | ;; type `C-c h h'; this runs hg-help-overview, which prints a helpful | |||
|
30 | ;; usage overview. | |||
|
31 | ||||
|
32 | ;; Much of the inspiration for mercurial.el comes from Rajesh | |||
|
33 | ;; Vaidheeswarran's excellent p4.el, which does an admirably thorough | |||
|
34 | ;; job for the commercial Perforce SCM product. In fact, substantial | |||
|
35 | ;; chunks of code are adapted from p4.el. | |||
|
36 | ||||
|
37 | ;; This code has been developed under XEmacs 21.5, and may will not | |||
|
38 | ;; work as well under GNU Emacs (albeit tested under 21.2). Patches | |||
|
39 | ;; to enhance the portability of this code, fix bugs, and add features | |||
|
40 | ;; are most welcome. You can clone a Mercurial repository for this | |||
|
41 | ;; package from http://www.serpentine.com/hg/hg-emacs | |||
|
42 | ||||
|
43 | ;; Please send problem reports and suggestions to bos@serpentine.com. | |||
|
44 | ||||
|
45 | ||||
|
46 | ;;; Code: | |||
|
47 | ||||
|
48 | (require 'advice) | |||
|
49 | (require 'cl) | |||
|
50 | (require 'diff-mode) | |||
|
51 | (require 'easymenu) | |||
|
52 | (require 'vc) | |||
|
53 | ||||
|
54 | ||||
|
55 | ;;; XEmacs has view-less, while GNU Emacs has view. Joy. | |||
|
56 | ||||
|
57 | (condition-case nil | |||
|
58 | (require 'view-less) | |||
|
59 | (error nil)) | |||
|
60 | (condition-case nil | |||
|
61 | (require 'view) | |||
|
62 | (error nil)) | |||
|
63 | ||||
|
64 | ||||
|
65 | ;;; Variables accessible through the custom system. | |||
|
66 | ||||
|
67 | (defgroup mercurial nil | |||
|
68 | "Mercurial distributed SCM." | |||
|
69 | :group 'tools) | |||
|
70 | ||||
|
71 | (defcustom hg-binary | |||
|
72 | (dolist (path '("~/bin/hg" | |||
|
73 | "/usr/bin/hg" | |||
|
74 | "/usr/local/bin/hg")) | |||
|
75 | (when (file-executable-p path) | |||
|
76 | (return path))) | |||
|
77 | "The path to Mercurial's hg executable." | |||
|
78 | :type '(file :must-match t) | |||
|
79 | :group 'mercurial) | |||
|
80 | ||||
|
81 | (defcustom hg-mode-hook nil | |||
|
82 | "Hook run when a buffer enters hg-mode." | |||
|
83 | :type 'sexp | |||
|
84 | :group 'mercurial) | |||
|
85 | ||||
|
86 | (defcustom hg-global-prefix "\C-ch" | |||
|
87 | "The global prefix for Mercurial keymap bindings." | |||
|
88 | :type 'sexp | |||
|
89 | :group 'mercurial) | |||
|
90 | ||||
|
91 | (defcustom hg-rev-completion-limit 100 | |||
|
92 | "The maximum number of revisions that hg-read-rev will offer to complete. | |||
|
93 | This affects memory usage and performance when prompting for revisions | |||
|
94 | in a repository with a lot of history." | |||
|
95 | :type 'integer | |||
|
96 | :group 'mercurial) | |||
|
97 | ||||
|
98 | (defcustom hg-log-limit 50 | |||
|
99 | "The maximum number of revisions that hg-log will display." | |||
|
100 | :type 'integer | |||
|
101 | :group 'mercurial) | |||
|
102 | ||||
|
103 | ||||
|
104 | ;;; Other variables. | |||
|
105 | ||||
|
106 | (defconst hg-running-xemacs (string-match "XEmacs" emacs-version) | |||
|
107 | "Is mercurial.el running under XEmacs?") | |||
|
108 | ||||
|
109 | (defvar hg-mode nil | |||
|
110 | "Is this file managed by Mercurial?") | |||
|
111 | (make-variable-buffer-local 'hg-mode) | |||
|
112 | (put 'hg-mode 'permanent-local t) | |||
|
113 | ||||
|
114 | (defvar hg-status nil) | |||
|
115 | (make-variable-buffer-local 'hg-status) | |||
|
116 | (put 'hg-status 'permanent-local t) | |||
|
117 | ||||
|
118 | (defvar hg-output-buffer-name "*Hg*" | |||
|
119 | "The name to use for Mercurial output buffers.") | |||
|
120 | ||||
|
121 | (defvar hg-file-history nil) | |||
|
122 | (defvar hg-rev-history nil) | |||
|
123 | ||||
|
124 | ||||
|
125 | ;;; hg-mode keymap. | |||
|
126 | ||||
|
127 | (defvar hg-prefix-map | |||
|
128 | (let ((map (copy-keymap vc-prefix-map))) | |||
|
129 | (set-keymap-name map 'hg-prefix-map) | |||
|
130 | map) | |||
|
131 | "This keymap overrides some default vc-mode bindings.") | |||
|
132 | (fset 'hg-prefix-map hg-prefix-map) | |||
|
133 | (define-key hg-prefix-map "=" 'hg-diff) | |||
|
134 | (define-key hg-prefix-map "c" 'hg-undo) | |||
|
135 | (define-key hg-prefix-map "g" 'hg-annotate) | |||
|
136 | (define-key hg-prefix-map "l" 'hg-log) | |||
|
137 | (define-key hg-prefix-map "n" 'hg-commit-file) | |||
|
138 | ;; (define-key hg-prefix-map "r" 'hg-update) | |||
|
139 | (define-key hg-prefix-map "u" 'hg-revert-file) | |||
|
140 | (define-key hg-prefix-map "~" 'hg-version-other-window) | |||
|
141 | ||||
|
142 | (defvar hg-mode-map (make-sparse-keymap)) | |||
|
143 | (define-key hg-mode-map "\C-xv" 'hg-prefix-map) | |||
|
144 | ||||
|
145 | (add-minor-mode 'hg-mode 'hg-mode hg-mode-map) | |||
|
146 | ||||
|
147 | ||||
|
148 | ;;; Global keymap. | |||
|
149 | ||||
|
150 | (global-set-key "\C-xvi" 'hg-add) | |||
|
151 | ||||
|
152 | (defvar hg-global-map (make-sparse-keymap)) | |||
|
153 | (fset 'hg-global-map hg-global-map) | |||
|
154 | (global-set-key hg-global-prefix 'hg-global-map) | |||
|
155 | (define-key hg-global-map "," 'hg-incoming) | |||
|
156 | (define-key hg-global-map "." 'hg-outgoing) | |||
|
157 | (define-key hg-global-map "<" 'hg-pull) | |||
|
158 | (define-key hg-global-map "=" 'hg-diff) | |||
|
159 | (define-key hg-global-map ">" 'hg-push) | |||
|
160 | (define-key hg-global-map "?" 'hg-help-overview) | |||
|
161 | (define-key hg-global-map "A" 'hg-addremove) | |||
|
162 | (define-key hg-global-map "U" 'hg-revert) | |||
|
163 | (define-key hg-global-map "a" 'hg-add) | |||
|
164 | (define-key hg-global-map "c" 'hg-commit) | |||
|
165 | (define-key hg-global-map "f" 'hg-forget) | |||
|
166 | (define-key hg-global-map "h" 'hg-help-overview) | |||
|
167 | (define-key hg-global-map "i" 'hg-init) | |||
|
168 | (define-key hg-global-map "l" 'hg-log) | |||
|
169 | (define-key hg-global-map "r" 'hg-root) | |||
|
170 | (define-key hg-global-map "s" 'hg-status) | |||
|
171 | (define-key hg-global-map "u" 'hg-update) | |||
|
172 | ||||
|
173 | ||||
|
174 | ;;; View mode keymap. | |||
|
175 | ||||
|
176 | (defvar hg-view-mode-map | |||
|
177 | (let ((map (copy-keymap (if (boundp 'view-minor-mode-map) | |||
|
178 | view-minor-mode-map | |||
|
179 | view-mode-map)))) | |||
|
180 | (set-keymap-name map 'hg-view-mode-map) | |||
|
181 | map)) | |||
|
182 | (fset 'hg-view-mode-map hg-view-mode-map) | |||
|
183 | (define-key hg-view-mode-map | |||
|
184 | (if hg-running-xemacs [button2] [mouse-2]) | |||
|
185 | 'hg-buffer-mouse-clicked) | |||
|
186 | ||||
|
187 | ||||
|
188 | ;;; Convenience functions. | |||
|
189 | ||||
|
190 | (defun hg-binary () | |||
|
191 | (if hg-binary | |||
|
192 | hg-binary | |||
|
193 | (error "No `hg' executable found!"))) | |||
|
194 | ||||
|
195 | (defun hg-replace-in-string (str regexp newtext &optional literal) | |||
|
196 | "Replace all matches in STR for REGEXP with NEWTEXT string. | |||
|
197 | Return the new string. Optional LITERAL non-nil means do a literal | |||
|
198 | replacement. | |||
|
199 | ||||
|
200 | This function bridges yet another pointless impedance gap between | |||
|
201 | XEmacs and GNU Emacs." | |||
|
202 | (if (fboundp 'replace-in-string) | |||
|
203 | (replace-in-string str regexp newtext literal) | |||
|
204 | (replace-regexp-in-string regexp newtext str nil literal))) | |||
|
205 | ||||
|
206 | (defun hg-chomp (str) | |||
|
207 | "Strip trailing newlines from a string." | |||
|
208 | (hg-replace-in-string str "[\r\n]+$" "")) | |||
|
209 | ||||
|
210 | (defun hg-run-command (command &rest args) | |||
|
211 | "Run the shell command COMMAND, returning (EXIT-CODE . COMMAND-OUTPUT). | |||
|
212 | The list ARGS contains a list of arguments to pass to the command." | |||
|
213 | (let* (exit-code | |||
|
214 | (output | |||
|
215 | (with-output-to-string | |||
|
216 | (with-current-buffer | |||
|
217 | standard-output | |||
|
218 | (setq exit-code | |||
|
219 | (apply 'call-process command nil t nil args)))))) | |||
|
220 | (cons exit-code output))) | |||
|
221 | ||||
|
222 | (defun hg-run (command &rest args) | |||
|
223 | "Run the Mercurial command COMMAND, returning (EXIT-CODE . COMMAND-OUTPUT)." | |||
|
224 | (apply 'hg-run-command (hg-binary) command args)) | |||
|
225 | ||||
|
226 | (defun hg-run0 (command &rest args) | |||
|
227 | "Run the Mercurial command COMMAND, returning its output. | |||
|
228 | If the command does not exit with a zero status code, raise an error." | |||
|
229 | (let ((res (apply 'hg-run-command (hg-binary) command args))) | |||
|
230 | (if (not (eq (car res) 0)) | |||
|
231 | (error "Mercurial command failed %s - exit code %s" | |||
|
232 | (cons command args) | |||
|
233 | (car res)) | |||
|
234 | (cdr res)))) | |||
|
235 | ||||
|
236 | (defun hg-buffer-commands (pnt) | |||
|
237 | "Use the properties of a character to do something sensible." | |||
|
238 | (interactive "d") | |||
|
239 | (let ((rev (get-char-property pnt 'rev)) | |||
|
240 | (file (get-char-property pnt 'file)) | |||
|
241 | (date (get-char-property pnt 'date)) | |||
|
242 | (user (get-char-property pnt 'user)) | |||
|
243 | (host (get-char-property pnt 'host)) | |||
|
244 | (prev-buf (current-buffer))) | |||
|
245 | (cond | |||
|
246 | (file | |||
|
247 | (find-file-other-window file)) | |||
|
248 | (rev | |||
|
249 | (hg-diff hg-view-file-name rev rev prev-buf)) | |||
|
250 | ((message "I don't know how to do that yet"))))) | |||
|
251 | ||||
|
252 | (defun hg-buffer-mouse-clicked (event) | |||
|
253 | "Translate the mouse clicks in a HG log buffer to character events. | |||
|
254 | These are then handed off to `hg-buffer-commands'. | |||
|
255 | ||||
|
256 | Handle frickin' frackin' gratuitous event-related incompatibilities." | |||
|
257 | (interactive "e") | |||
|
258 | (if hg-running-xemacs | |||
|
259 | (progn | |||
|
260 | (select-window (event-window event)) | |||
|
261 | (hg-buffer-commands (event-point event))) | |||
|
262 | (select-window (posn-window (event-end event))) | |||
|
263 | (hg-buffer-commands (posn-point (event-start event))))) | |||
|
264 | ||||
|
265 | (unless (fboundp 'view-minor-mode) | |||
|
266 | (defun view-minor-mode (prev-buffer exit-func) | |||
|
267 | (view-mode))) | |||
|
268 | ||||
|
269 | (defun hg-abbrev-file-name (file) | |||
|
270 | (if hg-running-xemacs | |||
|
271 | (abbreviate-file-name file t) | |||
|
272 | (abbreviate-file-name file))) | |||
|
273 | ||||
|
274 | (defun hg-read-file-name (&optional prompt default) | |||
|
275 | "Read a file or directory name, or a pattern, to use with a command." | |||
|
276 | (let ((path (or default (buffer-file-name)))) | |||
|
277 | (if (or (not path) current-prefix-arg) | |||
|
278 | (expand-file-name | |||
|
279 | (read-file-name (format "File, directory or pattern%s: " | |||
|
280 | (or prompt "")) | |||
|
281 | (and path (file-name-directory path)) | |||
|
282 | nil nil | |||
|
283 | (and path (file-name-nondirectory path)) | |||
|
284 | 'hg-file-history)) | |||
|
285 | path))) | |||
|
286 | ||||
|
287 | (defun hg-read-rev (&optional prompt default) | |||
|
288 | "Read a revision or tag, offering completions." | |||
|
289 | (let ((rev (or default "tip"))) | |||
|
290 | (if (or (not rev) current-prefix-arg) | |||
|
291 | (let ((revs (split-string (hg-chomp | |||
|
292 | (hg-run0 "-q" "log" "-r" | |||
|
293 | (format "-%d" | |||
|
294 | hg-rev-completion-limit) | |||
|
295 | "-r" "tip")) | |||
|
296 | "[\n:]"))) | |||
|
297 | (dolist (line (split-string (hg-chomp (hg-run0 "tags")) "\n")) | |||
|
298 | (setq revs (cons (car (split-string line "\\s-")) revs))) | |||
|
299 | (completing-read (format "Revision%s (%s): " | |||
|
300 | (or prompt "") | |||
|
301 | (or default "tip")) | |||
|
302 | (map 'list 'cons revs revs) | |||
|
303 | nil | |||
|
304 | nil | |||
|
305 | nil | |||
|
306 | 'hg-rev-history | |||
|
307 | (or default "tip"))) | |||
|
308 | rev))) | |||
|
309 | ||||
|
310 | ;;; View mode bits. | |||
|
311 | ||||
|
312 | (defun hg-exit-view-mode (buf) | |||
|
313 | "Exit from hg-view-mode. | |||
|
314 | We delete the current window if entering hg-view-mode split the | |||
|
315 | current frame." | |||
|
316 | (when (and (eq buf (current-buffer)) | |||
|
317 | (> (length (window-list)) 1)) | |||
|
318 | (delete-window)) | |||
|
319 | (when (buffer-live-p buf) | |||
|
320 | (kill-buffer buf))) | |||
|
321 | ||||
|
322 | (defun hg-view-mode (prev-buffer &optional file-name) | |||
|
323 | (goto-char (point-min)) | |||
|
324 | (set-buffer-modified-p nil) | |||
|
325 | (toggle-read-only t) | |||
|
326 | (view-minor-mode prev-buffer 'hg-exit-view-mode) | |||
|
327 | (use-local-map hg-view-mode-map) | |||
|
328 | (setq truncate-lines t) | |||
|
329 | (when file-name | |||
|
330 | (set (make-local-variable 'hg-view-file-name) | |||
|
331 | (hg-abbrev-file-name file-name)))) | |||
|
332 | ||||
|
333 | (defun hg-file-status (file) | |||
|
334 | "Return status of FILE, or nil if FILE does not exist or is unmanaged." | |||
|
335 | (let* ((s (hg-run "status" file)) | |||
|
336 | (exit (car s)) | |||
|
337 | (output (cdr s))) | |||
|
338 | (if (= exit 0) | |||
|
339 | (let ((state (assoc (substring output 0 (min (length output) 2)) | |||
|
340 | '(("M " . modified) | |||
|
341 | ("A " . added) | |||
|
342 | ("R " . removed))))) | |||
|
343 | (if state | |||
|
344 | (cdr state) | |||
|
345 | 'normal))))) | |||
|
346 | ||||
|
347 | (defun hg-tip () | |||
|
348 | (split-string (hg-chomp (hg-run0 "-q" "tip")) ":")) | |||
|
349 | ||||
|
350 | (defmacro hg-view-output (args &rest body) | |||
|
351 | "Execute BODY in a clean buffer, then quickly display that buffer. | |||
|
352 | If the buffer contains one line, its contents are displayed in the | |||
|
353 | minibuffer. Otherwise, the buffer is displayed in view-mode. | |||
|
354 | ARGS is of the form (BUFFER-NAME &optional FILE), where BUFFER-NAME is | |||
|
355 | the name of the buffer to create, and FILE is the name of the file | |||
|
356 | being viewed." | |||
|
357 | (let ((prev-buf (gensym "prev-buf-")) | |||
|
358 | (v-b-name (car args)) | |||
|
359 | (v-m-rest (cdr args))) | |||
|
360 | `(let ((view-buf-name ,v-b-name) | |||
|
361 | (,prev-buf (current-buffer))) | |||
|
362 | (get-buffer-create view-buf-name) | |||
|
363 | (kill-buffer view-buf-name) | |||
|
364 | (get-buffer-create view-buf-name) | |||
|
365 | (set-buffer view-buf-name) | |||
|
366 | (save-excursion | |||
|
367 | ,@body) | |||
|
368 | (case (count-lines (point-min) (point-max)) | |||
|
369 | ((0) | |||
|
370 | (kill-buffer view-buf-name) | |||
|
371 | (message "(No output)")) | |||
|
372 | ((1) | |||
|
373 | (let ((msg (hg-chomp (buffer-substring (point-min) (point-max))))) | |||
|
374 | (kill-buffer view-buf-name) | |||
|
375 | (message "%s" msg))) | |||
|
376 | (t | |||
|
377 | (pop-to-buffer view-buf-name) | |||
|
378 | (hg-view-mode ,prev-buf ,@v-m-rest)))))) | |||
|
379 | ||||
|
380 | (put 'hg-view-output 'lisp-indent-function 1) | |||
|
381 | ||||
|
382 | ;;; Hooks. | |||
|
383 | ||||
|
384 | (defun hg-mode-line () | |||
|
385 | (when (hg-root) | |||
|
386 | (let ((status (hg-file-status buffer-file-name))) | |||
|
387 | (setq hg-status status | |||
|
388 | hg-mode (and status (concat " Hg:" | |||
|
389 | (car (hg-tip)) | |||
|
390 | (cdr (assq status | |||
|
391 | '((normal . "") | |||
|
392 | (removed . "r") | |||
|
393 | (added . "a") | |||
|
394 | (modified . "m"))))))) | |||
|
395 | status))) | |||
|
396 | ||||
|
397 | (defun hg-find-file-hook () | |||
|
398 | (when (hg-mode-line) | |||
|
399 | (run-hooks 'hg-mode-hook))) | |||
|
400 | ||||
|
401 | (add-hook 'find-file-hooks 'hg-find-file-hook) | |||
|
402 | ||||
|
403 | (defun hg-after-save-hook () | |||
|
404 | (let ((old-status hg-status)) | |||
|
405 | (hg-mode-line) | |||
|
406 | (if (and (not old-status) hg-status) | |||
|
407 | (run-hooks 'hg-mode-hook)))) | |||
|
408 | ||||
|
409 | (add-hook 'after-save-hook 'hg-after-save-hook) | |||
|
410 | ||||
|
411 | ||||
|
412 | ;;; User interface functions. | |||
|
413 | ||||
|
414 | (defun hg-help-overview () | |||
|
415 | "This is an overview of the Mercurial SCM mode for Emacs. | |||
|
416 | ||||
|
417 | You can find the source code, license (GPL v2), and credits for this | |||
|
418 | code by typing `M-x find-library mercurial RET'. | |||
|
419 | ||||
|
420 | The Mercurial mode user interface is based on that of the older VC | |||
|
421 | mode, so if you're already familiar with VC, the same keybindings and | |||
|
422 | functions will generally work. | |||
|
423 | ||||
|
424 | Below is a list of common SCM tasks, with the key bindings needed to | |||
|
425 | perform them, and the command names. This list is not exhaustive. | |||
|
426 | ||||
|
427 | In the list below, `G/L' indicates whether a key binding is global (G) | |||
|
428 | or local (L). Global keybindings work on any file inside a Mercurial | |||
|
429 | repository. Local keybindings only apply to files under the control | |||
|
430 | of Mercurial. Many commands take a prefix argument. | |||
|
431 | ||||
|
432 | ||||
|
433 | SCM Task G/L Key Binding Command Name | |||
|
434 | -------- --- ----------- ------------ | |||
|
435 | Help overview (what you are reading) G C-c h h hg-help-overview | |||
|
436 | ||||
|
437 | Tell Mercurial to manage a file G C-c h a hg-add | |||
|
438 | Commit changes to current file only L C-x v n hg-commit | |||
|
439 | Undo changes to file since commit L C-x v u hg-revert-file | |||
|
440 | ||||
|
441 | Diff file vs last checkin L C-x v = hg-diff | |||
|
442 | ||||
|
443 | View file change history L C-x v l hg-log | |||
|
444 | View annotated file L C-x v a hg-annotate | |||
|
445 | ||||
|
446 | Diff repo vs last checkin G C-c h = hg-diff | |||
|
447 | View status of files in repo G C-c h s hg-status | |||
|
448 | Commit all changes G C-c h c hg-commit | |||
|
449 | ||||
|
450 | Undo all changes since last commit G C-c h U hg-revert | |||
|
451 | View repo change history G C-c h l hg-log | |||
|
452 | ||||
|
453 | See changes that can be pulled G C-c h , hg-incoming | |||
|
454 | Pull changes G C-c h < hg-pull | |||
|
455 | Update working directory after pull G C-c h u hg-update | |||
|
456 | See changes that can be pushed G C-c h . hg-outgoing | |||
|
457 | Push changes G C-c h > hg-push" | |||
|
458 | (interactive) | |||
|
459 | (hg-view-output ("Mercurial Help Overview") | |||
|
460 | (insert (documentation 'hg-help-overview)))) | |||
|
461 | ||||
|
462 | (defun hg-add (path) | |||
|
463 | (interactive (list (hg-read-file-name " to add"))) | |||
|
464 | (let ((buf (current-buffer)) | |||
|
465 | (update (equal buffer-file-name path))) | |||
|
466 | (hg-view-output (hg-output-buffer-name) | |||
|
467 | (apply 'call-process (hg-binary) nil t nil (list "add" path))) | |||
|
468 | (when update | |||
|
469 | (with-current-buffer buf | |||
|
470 | (hg-mode-line))))) | |||
|
471 | ||||
|
472 | (defun hg-addremove () | |||
|
473 | (interactive) | |||
|
474 | (error "not implemented")) | |||
|
475 | ||||
|
476 | (defun hg-annotate () | |||
|
477 | (interactive) | |||
|
478 | (error "not implemented")) | |||
|
479 | ||||
|
480 | (defun hg-commit () | |||
|
481 | (interactive) | |||
|
482 | (error "not implemented")) | |||
|
483 | ||||
|
484 | (defun hg-diff (path &optional rev1 rev2) | |||
|
485 | (interactive (list (hg-read-file-name " to diff") | |||
|
486 | (hg-read-rev " to start with") | |||
|
487 | (let ((rev2 (hg-read-rev " to end with" 'working-dir))) | |||
|
488 | (and (not (eq rev2 'working-dir)) rev2)))) | |||
|
489 | (let ((a-path (hg-abbrev-file-name path))) | |||
|
490 | (hg-view-output ((if (equal rev1 rev2) | |||
|
491 | (format "Mercurial: Rev %s of %s" rev1 a-path) | |||
|
492 | (format "Mercurial: Rev %s to %s of %s" | |||
|
493 | rev1 (or rev2 "Current") a-path))) | |||
|
494 | (if rev2 | |||
|
495 | (call-process (hg-binary) nil t nil "diff" "-r" rev1 "-r" rev2 path) | |||
|
496 | (call-process (hg-binary) nil t nil "diff" "-r" rev1 path)) | |||
|
497 | (diff-mode) | |||
|
498 | (font-lock-fontify-buffer)))) | |||
|
499 | ||||
|
500 | (defun hg-forget (path) | |||
|
501 | (interactive (list (hg-read-file-name " to forget"))) | |||
|
502 | (let ((buf (current-buffer)) | |||
|
503 | (update (equal buffer-file-name path))) | |||
|
504 | (hg-view-output (hg-output-buffer-name) | |||
|
505 | (apply 'call-process (hg-binary) nil t nil (list "forget" path))) | |||
|
506 | (when update | |||
|
507 | (with-current-buffer buf | |||
|
508 | (hg-mode-line))))) | |||
|
509 | ||||
|
510 | (defun hg-incoming () | |||
|
511 | (interactive) | |||
|
512 | (error "not implemented")) | |||
|
513 | ||||
|
514 | (defun hg-init () | |||
|
515 | (interactive) | |||
|
516 | (error "not implemented")) | |||
|
517 | ||||
|
518 | (defun hg-log (path &optional rev1 rev2) | |||
|
519 | (interactive (list (hg-read-file-name " to log") | |||
|
520 | (hg-read-rev " to start with" "-1") | |||
|
521 | (hg-read-rev " to end with" (format "-%d" hg-log-limit)))) | |||
|
522 | (message "log %s %s" rev1 rev2) | |||
|
523 | (sit-for 1) | |||
|
524 | (let ((a-path (hg-abbrev-file-name path))) | |||
|
525 | (hg-view-output ((if (equal rev1 rev2) | |||
|
526 | (format "Mercurial: Rev %s of %s" rev1 a-path) | |||
|
527 | (format "Mercurial: Rev %s to %s of %s" | |||
|
528 | rev1 (or rev2 "Current") a-path))) | |||
|
529 | (call-process (hg-binary) nil t nil "log" "-r" rev1 "-r" rev2 path) | |||
|
530 | (diff-mode) | |||
|
531 | (font-lock-fontify-buffer)))) | |||
|
532 | ||||
|
533 | (defun hg-outgoing () | |||
|
534 | (interactive) | |||
|
535 | (error "not implemented")) | |||
|
536 | ||||
|
537 | (defun hg-pull () | |||
|
538 | (interactive) | |||
|
539 | (error "not implemented")) | |||
|
540 | ||||
|
541 | (defun hg-push () | |||
|
542 | (interactive) | |||
|
543 | (error "not implemented")) | |||
|
544 | ||||
|
545 | (defun hg-revert () | |||
|
546 | (interactive) | |||
|
547 | (error "not implemented")) | |||
|
548 | ||||
|
549 | (defun hg-revert-file () | |||
|
550 | (interactive) | |||
|
551 | (error "not implemented")) | |||
|
552 | ||||
|
553 | (defun hg-root (&optional path) | |||
|
554 | (interactive (list (hg-read-file-name))) | |||
|
555 | (let ((root (do ((prev nil dir) | |||
|
556 | (dir (file-name-directory (or path (buffer-file-name))) | |||
|
557 | (file-name-directory (directory-file-name dir)))) | |||
|
558 | ((equal prev dir)) | |||
|
559 | (when (file-directory-p (concat dir ".hg")) | |||
|
560 | (return dir))))) | |||
|
561 | (when (interactive-p) | |||
|
562 | (if root | |||
|
563 | (message "The root of this repository is `%s'." root) | |||
|
564 | (message "The path `%s' is not in a Mercurial repository." | |||
|
565 | (abbreviate-file-name path t)))) | |||
|
566 | root)) | |||
|
567 | ||||
|
568 | (defun hg-status (path) | |||
|
569 | (interactive (list (hg-read-file-name " for status" (hg-root)))) | |||
|
570 | (let ((root (hg-root))) | |||
|
571 | (hg-view-output (hg-output-buffer-name) | |||
|
572 | (apply 'call-process (hg-binary) nil t nil | |||
|
573 | (list "-C" root "status" path))))) | |||
|
574 | ||||
|
575 | (defun hg-undo () | |||
|
576 | (interactive) | |||
|
577 | (error "not implemented")) | |||
|
578 | ||||
|
579 | (defun hg-version-other-window () | |||
|
580 | (interactive) | |||
|
581 | (error "not implemented")) | |||
|
582 | ||||
|
583 | ||||
|
584 | (provide 'mercurial) | |||
|
585 | ||||
|
586 | ||||
|
587 | ;;; Local Variables: | |||
|
588 | ;;; mode: emacs-lisp | |||
|
589 | ;;; prompt-to-byte-compile: nil | |||
|
590 | ;;; end: |
@@ -42,9 +42,10 b' def matchpats(repo, cwd, pats = [], opts' | |||||
42 | def makewalk(repo, pats, opts, head = ''): |
|
42 | def makewalk(repo, pats, opts, head = ''): | |
43 | cwd = repo.getcwd() |
|
43 | cwd = repo.getcwd() | |
44 | files, matchfn = matchpats(repo, cwd, pats, opts, head) |
|
44 | files, matchfn = matchpats(repo, cwd, pats, opts, head) | |
|
45 | exact = dict(zip(files, files)) | |||
45 | def walk(): |
|
46 | def walk(): | |
46 | for src, fn in repo.walk(files = files, match = matchfn): |
|
47 | for src, fn in repo.walk(files = files, match = matchfn): | |
47 | yield src, fn, util.pathto(cwd, fn) |
|
48 | yield src, fn, util.pathto(cwd, fn), fn in exact | |
48 | return files, matchfn, walk() |
|
49 | return files, matchfn, walk() | |
49 |
|
50 | |||
50 | def walk(repo, pats, opts, head = ''): |
|
51 | def walk(repo, pats, opts, head = ''): | |
@@ -375,9 +376,8 b' def help_(ui, cmd=None):' | |||||
375 | def add(ui, repo, *pats, **opts): |
|
376 | def add(ui, repo, *pats, **opts): | |
376 | '''add the specified files on the next commit''' |
|
377 | '''add the specified files on the next commit''' | |
377 | names = [] |
|
378 | names = [] | |
378 | q = dict(zip(pats, pats)) |
|
379 | for src, abs, rel, exact in walk(repo, pats, opts): | |
379 | for src, abs, rel in walk(repo, pats, opts): |
|
380 | if exact: | |
380 | if rel in q or abs in q: |
|
|||
381 | names.append(abs) |
|
381 | names.append(abs) | |
382 | elif repo.dirstate.state(abs) == '?': |
|
382 | elif repo.dirstate.state(abs) == '?': | |
383 | ui.status('adding %s\n' % rel) |
|
383 | ui.status('adding %s\n' % rel) | |
@@ -386,15 +386,14 b' def add(ui, repo, *pats, **opts):' | |||||
386 |
|
386 | |||
387 | def addremove(ui, repo, *pats, **opts): |
|
387 | def addremove(ui, repo, *pats, **opts): | |
388 | """add all new files, delete all missing files""" |
|
388 | """add all new files, delete all missing files""" | |
389 | q = dict(zip(pats, pats)) |
|
|||
390 | add, remove = [], [] |
|
389 | add, remove = [], [] | |
391 | for src, abs, rel in walk(repo, pats, opts): |
|
390 | for src, abs, rel, exact in walk(repo, pats, opts): | |
392 | if src == 'f' and repo.dirstate.state(abs) == '?': |
|
391 | if src == 'f' and repo.dirstate.state(abs) == '?': | |
393 | add.append(abs) |
|
392 | add.append(abs) | |
394 |
if |
|
393 | if not exact: ui.status('adding ', rel, '\n') | |
395 | if repo.dirstate.state(abs) != 'r' and not os.path.exists(rel): |
|
394 | if repo.dirstate.state(abs) != 'r' and not os.path.exists(rel): | |
396 | remove.append(abs) |
|
395 | remove.append(abs) | |
397 |
if |
|
396 | if not exact: ui.status('removing ', rel, '\n') | |
398 | repo.add(add) |
|
397 | repo.add(add) | |
399 | repo.remove(remove) |
|
398 | repo.remove(remove) | |
400 |
|
399 | |||
@@ -432,7 +431,7 b' def annotate(ui, repo, *pats, **opts):' | |||||
432 | node = repo.dirstate.parents()[0] |
|
431 | node = repo.dirstate.parents()[0] | |
433 | change = repo.changelog.read(node) |
|
432 | change = repo.changelog.read(node) | |
434 | mmap = repo.manifest.read(change[0]) |
|
433 | mmap = repo.manifest.read(change[0]) | |
435 | for src, abs, rel in walk(repo, pats, opts): |
|
434 | for src, abs, rel, exact in walk(repo, pats, opts): | |
436 | if abs not in mmap: |
|
435 | if abs not in mmap: | |
437 | ui.warn("warning: %s is not in the repository!\n" % rel) |
|
436 | ui.warn("warning: %s is not in the repository!\n" % rel) | |
438 | continue |
|
437 | continue | |
@@ -629,8 +628,12 b' def debugindexdot(ui, file_):' | |||||
629 | def debugwalk(ui, repo, *pats, **opts): |
|
628 | def debugwalk(ui, repo, *pats, **opts): | |
630 | items = list(walk(repo, pats, opts)) |
|
629 | items = list(walk(repo, pats, opts)) | |
631 | if not items: return |
|
630 | if not items: return | |
632 | fmt = '%%s %%-%ds %%s' % max([len(abs) for (src, abs, rel) in items]) |
|
631 | fmt = '%%s %%-%ds %%-%ds %%s' % ( | |
633 | for i in items: print fmt % i |
|
632 | max([len(abs) for (src, abs, rel, exact) in items]), | |
|
633 | max([len(rel) for (src, abs, rel, exact) in items])) | |||
|
634 | exactly = {True: 'exact', False: ''} | |||
|
635 | for src, abs, rel, exact in items: | |||
|
636 | print fmt % (src, abs, rel, exactly[exact]) | |||
634 |
|
637 | |||
635 | def diff(ui, repo, *pats, **opts): |
|
638 | def diff(ui, repo, *pats, **opts): | |
636 | """diff working directory (or selected files)""" |
|
639 | """diff working directory (or selected files)""" | |
@@ -645,7 +648,7 b' def diff(ui, repo, *pats, **opts):' | |||||
645 | match = util.always |
|
648 | match = util.always | |
646 | if pats: |
|
649 | if pats: | |
647 | roots, match, results = makewalk(repo, pats, opts) |
|
650 | roots, match, results = makewalk(repo, pats, opts) | |
648 | for src, abs, rel in results: |
|
651 | for src, abs, rel, exact in results: | |
649 | files.append(abs) |
|
652 | files.append(abs) | |
650 | dodiff(sys.stdout, ui, repo, files, *revs, **{'match': match}) |
|
653 | dodiff(sys.stdout, ui, repo, files, *revs, **{'match': match}) | |
651 |
|
654 | |||
@@ -687,12 +690,11 b' def export(ui, repo, *changesets, **opts' | |||||
687 |
|
690 | |||
688 | def forget(ui, repo, *pats, **opts): |
|
691 | def forget(ui, repo, *pats, **opts): | |
689 | """don't add the specified files on the next commit""" |
|
692 | """don't add the specified files on the next commit""" | |
690 | q = dict(zip(pats, pats)) |
|
|||
691 | forget = [] |
|
693 | forget = [] | |
692 | for src, abs, rel in walk(repo, pats, opts): |
|
694 | for src, abs, rel, exact in walk(repo, pats, opts): | |
693 | if repo.dirstate.state(abs) == 'a': |
|
695 | if repo.dirstate.state(abs) == 'a': | |
694 | forget.append(abs) |
|
696 | forget.append(abs) | |
695 |
if |
|
697 | if not exact: ui.status('forgetting ', rel, '\n') | |
696 | repo.forget(forget) |
|
698 | repo.forget(forget) | |
697 |
|
699 | |||
698 | def heads(ui, repo, **opts): |
|
700 | def heads(ui, repo, **opts): | |
@@ -809,7 +811,7 b' def locate(ui, repo, *pats, **opts):' | |||||
809 | end = '\n' |
|
811 | end = '\n' | |
810 | if opts['print0']: end = '\0' |
|
812 | if opts['print0']: end = '\0' | |
811 |
|
813 | |||
812 | for src, abs, rel in walk(repo, pats, opts, '(?:.*/|)'): |
|
814 | for src, abs, rel, exact in walk(repo, pats, opts, '(?:.*/|)'): | |
813 | if repo.dirstate.state(abs) == '?': continue |
|
815 | if repo.dirstate.state(abs) == '?': continue | |
814 | if opts['fullpath']: |
|
816 | if opts['fullpath']: | |
815 | ui.write(os.path.join(repo.root, abs), end) |
|
817 | ui.write(os.path.join(repo.root, abs), end) | |
@@ -1402,6 +1404,7 b" globalopts = [('v', 'verbose', None, 've" | |||||
1402 | ('', 'debug', None, 'debug mode'), |
|
1404 | ('', 'debug', None, 'debug mode'), | |
1403 | ('q', 'quiet', None, 'quiet mode'), |
|
1405 | ('q', 'quiet', None, 'quiet mode'), | |
1404 | ('', 'profile', None, 'profile'), |
|
1406 | ('', 'profile', None, 'profile'), | |
|
1407 | ('C', 'cwd', '', 'change working directory'), | |||
1405 | ('R', 'repository', "", 'repository root directory'), |
|
1408 | ('R', 'repository', "", 'repository root directory'), | |
1406 | ('', 'traceback', None, 'print traceback on exception'), |
|
1409 | ('', 'traceback', None, 'print traceback on exception'), | |
1407 | ('y', 'noninteractive', None, 'run non-interactively'), |
|
1410 | ('y', 'noninteractive', None, 'run non-interactively'), | |
@@ -1490,6 +1493,14 b' def dispatch(args):' | |||||
1490 | help_(u, 'shortlist') |
|
1493 | help_(u, 'shortlist') | |
1491 | sys.exit(1) |
|
1494 | sys.exit(1) | |
1492 |
|
1495 | |||
|
1496 | if options['cwd']: | |||
|
1497 | try: | |||
|
1498 | os.chdir(options['cwd']) | |||
|
1499 | except OSError, inst: | |||
|
1500 | u = ui.ui() | |||
|
1501 | u.warn('abort: %s: %s\n' % (options['cwd'], inst.strerror)) | |||
|
1502 | sys.exit(1) | |||
|
1503 | ||||
1493 | if options["time"]: |
|
1504 | if options["time"]: | |
1494 | def get_times(): |
|
1505 | def get_times(): | |
1495 | t = os.times() |
|
1506 | t = os.times() |
@@ -12,76 +12,76 b' adding mammals/skunk' | |||||
12 | adding mammals/Procyonidae/cacomistle |
|
12 | adding mammals/Procyonidae/cacomistle | |
13 | adding mammals/Procyonidae/coatimundi |
|
13 | adding mammals/Procyonidae/coatimundi | |
14 | adding mammals/Procyonidae/raccoon |
|
14 | adding mammals/Procyonidae/raccoon | |
15 | f fennel fennel |
|
15 | f fennel fennel | |
16 | f fenugreek fenugreek |
|
16 | f fenugreek fenugreek | |
17 | f fiddlehead fiddlehead |
|
17 | f fiddlehead fiddlehead | |
18 | f glob:glob glob:glob |
|
18 | f glob:glob glob:glob | |
19 | f beans/black beans/black |
|
19 | f beans/black beans/black | |
20 | f beans/borlotti beans/borlotti |
|
20 | f beans/borlotti beans/borlotti | |
21 | f beans/kidney beans/kidney |
|
21 | f beans/kidney beans/kidney | |
22 | f beans/navy beans/navy |
|
22 | f beans/navy beans/navy | |
23 | f beans/pinto beans/pinto |
|
23 | f beans/pinto beans/pinto | |
24 | f beans/turtle beans/turtle |
|
24 | f beans/turtle beans/turtle | |
25 | f mammals/skunk mammals/skunk |
|
25 | f mammals/skunk mammals/skunk | |
26 | f mammals/Procyonidae/cacomistle mammals/Procyonidae/cacomistle |
|
26 | f mammals/Procyonidae/cacomistle mammals/Procyonidae/cacomistle | |
27 | f mammals/Procyonidae/coatimundi mammals/Procyonidae/coatimundi |
|
27 | f mammals/Procyonidae/coatimundi mammals/Procyonidae/coatimundi | |
28 | f mammals/Procyonidae/raccoon mammals/Procyonidae/raccoon |
|
28 | f mammals/Procyonidae/raccoon mammals/Procyonidae/raccoon | |
29 | f mammals/skunk skunk |
|
29 | f mammals/skunk skunk | |
30 | f mammals/Procyonidae/cacomistle Procyonidae/cacomistle |
|
30 | f mammals/Procyonidae/cacomistle Procyonidae/cacomistle | |
31 | f mammals/Procyonidae/coatimundi Procyonidae/coatimundi |
|
31 | f mammals/Procyonidae/coatimundi Procyonidae/coatimundi | |
32 | f mammals/Procyonidae/raccoon Procyonidae/raccoon |
|
32 | f mammals/Procyonidae/raccoon Procyonidae/raccoon | |
33 | f mammals/Procyonidae/cacomistle Procyonidae/cacomistle |
|
33 | f mammals/Procyonidae/cacomistle Procyonidae/cacomistle | |
34 | f mammals/Procyonidae/coatimundi Procyonidae/coatimundi |
|
34 | f mammals/Procyonidae/coatimundi Procyonidae/coatimundi | |
35 | f mammals/Procyonidae/raccoon Procyonidae/raccoon |
|
35 | f mammals/Procyonidae/raccoon Procyonidae/raccoon | |
36 | f mammals/Procyonidae/cacomistle cacomistle |
|
36 | f mammals/Procyonidae/cacomistle cacomistle | |
37 | f mammals/Procyonidae/coatimundi coatimundi |
|
37 | f mammals/Procyonidae/coatimundi coatimundi | |
38 | f mammals/Procyonidae/raccoon raccoon |
|
38 | f mammals/Procyonidae/raccoon raccoon | |
39 | f mammals/skunk ../skunk |
|
39 | f mammals/skunk ../skunk | |
40 | f mammals/Procyonidae/cacomistle cacomistle |
|
40 | f mammals/Procyonidae/cacomistle cacomistle | |
41 | f mammals/Procyonidae/coatimundi coatimundi |
|
41 | f mammals/Procyonidae/coatimundi coatimundi | |
42 | f mammals/Procyonidae/raccoon raccoon |
|
42 | f mammals/Procyonidae/raccoon raccoon | |
43 | f beans/black ../beans/black |
|
43 | f beans/black ../beans/black | |
44 | f beans/borlotti ../beans/borlotti |
|
44 | f beans/borlotti ../beans/borlotti | |
45 | f beans/kidney ../beans/kidney |
|
45 | f beans/kidney ../beans/kidney | |
46 | f beans/navy ../beans/navy |
|
46 | f beans/navy ../beans/navy | |
47 | f beans/pinto ../beans/pinto |
|
47 | f beans/pinto ../beans/pinto | |
48 | f beans/turtle ../beans/turtle |
|
48 | f beans/turtle ../beans/turtle | |
49 | f mammals/skunk skunk |
|
49 | f mammals/skunk skunk | |
50 | f mammals/Procyonidae/cacomistle Procyonidae/cacomistle |
|
50 | f mammals/Procyonidae/cacomistle Procyonidae/cacomistle | |
51 | f mammals/Procyonidae/coatimundi Procyonidae/coatimundi |
|
51 | f mammals/Procyonidae/coatimundi Procyonidae/coatimundi | |
52 | f mammals/Procyonidae/raccoon Procyonidae/raccoon |
|
52 | f mammals/Procyonidae/raccoon Procyonidae/raccoon | |
53 | f beans/black beans/black |
|
53 | f beans/black beans/black | |
54 | f beans/borlotti beans/borlotti |
|
54 | f beans/borlotti beans/borlotti | |
55 | f beans/kidney beans/kidney |
|
55 | f beans/kidney beans/kidney | |
56 | f beans/navy beans/navy |
|
56 | f beans/navy beans/navy | |
57 | f beans/pinto beans/pinto |
|
57 | f beans/pinto beans/pinto | |
58 | f beans/turtle beans/turtle |
|
58 | f beans/turtle beans/turtle | |
59 | f beans/black beans/black |
|
59 | f beans/black beans/black | |
60 | f beans/borlotti beans/borlotti |
|
60 | f beans/borlotti beans/borlotti | |
61 | f mammals/skunk mammals/skunk |
|
61 | f mammals/skunk mammals/skunk | |
62 | f mammals/skunk mammals/skunk |
|
62 | f mammals/skunk mammals/skunk | |
63 | f mammals/Procyonidae/cacomistle mammals/Procyonidae/cacomistle |
|
63 | f mammals/Procyonidae/cacomistle mammals/Procyonidae/cacomistle | |
64 | f mammals/Procyonidae/coatimundi mammals/Procyonidae/coatimundi |
|
64 | f mammals/Procyonidae/coatimundi mammals/Procyonidae/coatimundi | |
65 | f mammals/Procyonidae/raccoon mammals/Procyonidae/raccoon |
|
65 | f mammals/Procyonidae/raccoon mammals/Procyonidae/raccoon | |
66 | abort: .. not under repository root |
|
66 | abort: .. not under repository root | |
67 | abort: beans/../.. not under repository root |
|
67 | abort: beans/../.. not under repository root | |
68 | f fennel fennel |
|
68 | f fennel fennel | |
69 | f fenugreek fenugreek |
|
69 | f fenugreek fenugreek | |
70 | f fiddlehead fiddlehead |
|
70 | f fiddlehead fiddlehead | |
71 | f glob:glob glob:glob |
|
71 | f glob:glob glob:glob | |
72 | f fenugreek fenugreek |
|
72 | f fenugreek fenugreek | |
73 | f glob:glob glob:glob |
|
73 | f glob:glob glob:glob | |
74 | f beans/black beans/black |
|
74 | f beans/black beans/black | |
75 | f mammals/skunk mammals/skunk |
|
75 | f mammals/skunk mammals/skunk | |
76 | f beans/black beans/black |
|
76 | f beans/black beans/black | |
77 | f beans/black beans/black |
|
77 | f beans/black beans/black | |
78 | f beans/borlotti beans/borlotti |
|
78 | f beans/borlotti beans/borlotti | |
79 | f beans/kidney beans/kidney |
|
79 | f beans/kidney beans/kidney | |
80 | f beans/navy beans/navy |
|
80 | f beans/navy beans/navy | |
81 | f beans/pinto beans/pinto |
|
81 | f beans/pinto beans/pinto | |
82 | f beans/turtle beans/turtle |
|
82 | f beans/turtle beans/turtle | |
83 | NOEXIST: No such file or directory |
|
83 | NOEXIST: No such file or directory | |
84 | fifo: unsupported file type (type is fifo) |
|
84 | fifo: unsupported file type (type is fifo) | |
85 | m fenugreek fenugreek |
|
85 | m fenugreek fenugreek exact | |
86 | m fenugreek fenugreek |
|
86 | m fenugreek fenugreek exact | |
87 | f new new |
|
87 | f new new exact |
General Comments 0
You need to be logged in to leave comments.
Login now